Completed
Branch master (03f360)
by
unknown
13:33 queued 11:19
created
core/libraries/form_sections/form_handlers/SequentialStepFormManager.php 1 patch
Indentation   +579 added lines, -579 removed lines patch added patch discarded remove patch
@@ -31,583 +31,583 @@
 block discarded – undo
31 31
 abstract class SequentialStepFormManager
32 32
 {
33 33
 
34
-    /**
35
-     * a simplified URL with no form related parameters
36
-     * that will be used to build the form's redirect URLs
37
-     *
38
-     * @var string $base_url
39
-     */
40
-    private $base_url = '';
41
-
42
-    /**
43
-     * the key used for the URL param that denotes the current form step
44
-     * defaults to 'ee-form-step'
45
-     *
46
-     * @var string $form_step_url_key
47
-     */
48
-    private $form_step_url_key = '';
49
-
50
-    /**
51
-     * @var string $default_form_step
52
-     */
53
-    private $default_form_step = '';
54
-
55
-    /**
56
-     * @var string $form_action
57
-     */
58
-    private $form_action;
59
-
60
-    /**
61
-     * value of one of the string constant above
62
-     *
63
-     * @var string $form_config
64
-     */
65
-    private $form_config;
66
-
67
-    /**
68
-     * @var string $progress_step_style
69
-     */
70
-    private $progress_step_style = '';
71
-
72
-    /**
73
-     * @var RequestInterface $request
74
-     */
75
-    private $request;
76
-
77
-    /**
78
-     * @var Collection $form_steps
79
-     */
80
-    protected $form_steps;
81
-
82
-    /**
83
-     * @var ProgressStepManager $progress_step_manager
84
-     */
85
-    protected $progress_step_manager;
86
-
87
-
88
-    /**
89
-     * @return Collection|null
90
-     */
91
-    abstract protected function getFormStepsCollection();
92
-
93
-    // phpcs:disable PEAR.Functions.ValidDefaultValue.NotAtEnd
94
-    /**
95
-     * StepsManager constructor
96
-     *
97
-     * @param string                           $base_url
98
-     * @param string                           $default_form_step
99
-     * @param string                           $form_action
100
-     * @param string                           $form_config
101
-     * @param EE_Request|RequestInterface|null $request
102
-     * @param string                           $progress_step_style
103
-     * @throws InvalidDataTypeException
104
-     * @throws InvalidArgumentException
105
-     */
106
-    public function __construct(
107
-        $base_url,
108
-        $default_form_step,
109
-        $form_action = '',
110
-        $form_config = FormHandler::ADD_FORM_TAGS_AND_SUBMIT,
111
-        $progress_step_style = 'number_bubbles',
112
-        $request = null
113
-    ) {
114
-        $this->setBaseUrl($base_url);
115
-        $this->setDefaultFormStep($default_form_step);
116
-        $this->setFormAction($form_action);
117
-        $this->setFormConfig($form_config);
118
-        $this->setProgressStepStyle($progress_step_style);
119
-        $this->request = $request instanceof RequestInterface
120
-            ? $request
121
-            : LoaderFactory::getLoader()->getShared('EventEspresso\core\services\request\RequestInterface');
122
-    }
123
-
124
-
125
-    /**
126
-     * @return string
127
-     * @throws InvalidFormHandlerException
128
-     */
129
-    public function baseUrl()
130
-    {
131
-        if (strpos($this->base_url, $this->getCurrentStep()->slug()) === false) {
132
-            add_query_arg(
133
-                array($this->form_step_url_key => $this->getCurrentStep()->slug()),
134
-                $this->base_url
135
-            );
136
-        }
137
-        return $this->base_url;
138
-    }
139
-
140
-
141
-    /**
142
-     * @param string $base_url
143
-     * @throws InvalidDataTypeException
144
-     * @throws InvalidArgumentException
145
-     */
146
-    protected function setBaseUrl($base_url)
147
-    {
148
-        if (! is_string($base_url)) {
149
-            throw new InvalidDataTypeException('$base_url', $base_url, 'string');
150
-        }
151
-        if (empty($base_url)) {
152
-            throw new InvalidArgumentException(
153
-                esc_html__('The base URL can not be an empty string.', 'event_espresso')
154
-            );
155
-        }
156
-        $this->base_url = $base_url;
157
-    }
158
-
159
-
160
-    /**
161
-     * @return string
162
-     * @throws InvalidDataTypeException
163
-     */
164
-    public function formStepUrlKey()
165
-    {
166
-        if (empty($this->form_step_url_key)) {
167
-            $this->setFormStepUrlKey();
168
-        }
169
-        return $this->form_step_url_key;
170
-    }
171
-
172
-
173
-    /**
174
-     * @param string $form_step_url_key
175
-     * @throws InvalidDataTypeException
176
-     */
177
-    public function setFormStepUrlKey($form_step_url_key = 'ee-form-step')
178
-    {
179
-        if (! is_string($form_step_url_key)) {
180
-            throw new InvalidDataTypeException('$form_step_key', $form_step_url_key, 'string');
181
-        }
182
-        $this->form_step_url_key = ! empty($form_step_url_key) ? $form_step_url_key : 'ee-form-step';
183
-    }
184
-
185
-
186
-    /**
187
-     * @return string
188
-     */
189
-    public function defaultFormStep()
190
-    {
191
-        return $this->default_form_step;
192
-    }
193
-
194
-
195
-    /**
196
-     * @param $default_form_step
197
-     * @throws InvalidDataTypeException
198
-     */
199
-    protected function setDefaultFormStep($default_form_step)
200
-    {
201
-        if (! is_string($default_form_step)) {
202
-            throw new InvalidDataTypeException('$default_form_step', $default_form_step, 'string');
203
-        }
204
-        $this->default_form_step = $default_form_step;
205
-    }
206
-
207
-
208
-    /**
209
-     * @return void
210
-     * @throws InvalidIdentifierException
211
-     * @throws InvalidDataTypeException
212
-     */
213
-    protected function setCurrentStepFromRequest()
214
-    {
215
-        $current_step_slug = $this->request()->getRequestParam($this->formStepUrlKey(), $this->defaultFormStep());
216
-        if (! $this->form_steps->setCurrent($current_step_slug)) {
217
-            throw new InvalidIdentifierException(
218
-                $current_step_slug,
219
-                $this->defaultFormStep(),
220
-                sprintf(
221
-                    esc_html__('The "%1$s" form step could not be set.', 'event_espresso'),
222
-                    $current_step_slug
223
-                )
224
-            );
225
-        }
226
-    }
227
-
228
-
229
-    /**
230
-     * @return SequentialStepFormInterface|object
231
-     * @throws InvalidFormHandlerException
232
-     */
233
-    public function getCurrentStep()
234
-    {
235
-        if (! $this->form_steps->current() instanceof SequentialStepForm) {
236
-            throw new InvalidFormHandlerException($this->form_steps->current());
237
-        }
238
-        return $this->form_steps->current();
239
-    }
240
-
241
-
242
-    /**
243
-     * @return string
244
-     * @throws InvalidFormHandlerException
245
-     */
246
-    public function formAction()
247
-    {
248
-        if (! is_string($this->form_action) || empty($this->form_action)) {
249
-            $this->form_action = $this->baseUrl();
250
-        }
251
-        return $this->form_action;
252
-    }
253
-
254
-
255
-    /**
256
-     * @param string $form_action
257
-     * @throws InvalidDataTypeException
258
-     */
259
-    public function setFormAction($form_action)
260
-    {
261
-        if (! is_string($form_action)) {
262
-            throw new InvalidDataTypeException('$form_action', $form_action, 'string');
263
-        }
264
-        $this->form_action = $form_action;
265
-    }
266
-
267
-
268
-    /**
269
-     * @param array $form_action_args
270
-     * @throws InvalidDataTypeException
271
-     * @throws InvalidFormHandlerException
272
-     */
273
-    public function addFormActionArgs($form_action_args = array())
274
-    {
275
-        if (! is_array($form_action_args)) {
276
-            throw new InvalidDataTypeException('$form_action_args', $form_action_args, 'array');
277
-        }
278
-        $form_action_args = ! empty($form_action_args)
279
-            ? $form_action_args
280
-            : array($this->formStepUrlKey() => $this->form_steps->current()->slug());
281
-        $this->getCurrentStep()->setFormAction(
282
-            add_query_arg($form_action_args, $this->formAction())
283
-        );
284
-        $this->form_action = $this->getCurrentStep()->formAction();
285
-    }
286
-
287
-
288
-    /**
289
-     * @return string
290
-     */
291
-    public function formConfig()
292
-    {
293
-        return $this->form_config;
294
-    }
295
-
296
-
297
-    /**
298
-     * @param string $form_config
299
-     */
300
-    public function setFormConfig($form_config)
301
-    {
302
-        $this->form_config = $form_config;
303
-    }
304
-
305
-
306
-    /**
307
-     * @return string
308
-     */
309
-    public function progressStepStyle()
310
-    {
311
-        return $this->progress_step_style;
312
-    }
313
-
314
-
315
-    /**
316
-     * @param string $progress_step_style
317
-     */
318
-    public function setProgressStepStyle($progress_step_style)
319
-    {
320
-        $this->progress_step_style = $progress_step_style;
321
-    }
322
-
323
-
324
-    /**
325
-     * @return RequestInterface
326
-     */
327
-    public function request()
328
-    {
329
-        return $this->request;
330
-    }
331
-
332
-
333
-    /**
334
-     * @return Collection|null
335
-     * @throws InvalidInterfaceException
336
-     */
337
-    protected function getProgressStepsCollection()
338
-    {
339
-        static $collection = null;
340
-        if (! $collection instanceof ProgressStepCollection) {
341
-            $collection = new ProgressStepCollection();
342
-        }
343
-        return $collection;
344
-    }
345
-
346
-
347
-    /**
348
-     * @param Collection $progress_steps_collection
349
-     * @return ProgressStepManager
350
-     * @throws InvalidInterfaceException
351
-     * @throws InvalidClassException
352
-     * @throws InvalidDataTypeException
353
-     * @throws InvalidEntityException
354
-     * @throws InvalidFormHandlerException
355
-     */
356
-    protected function generateProgressSteps(Collection $progress_steps_collection)
357
-    {
358
-        $current_step = $this->getCurrentStep();
359
-        /** @var SequentialStepForm $form_step */
360
-        foreach ($this->form_steps as $form_step) {
361
-            // is this step active ?
362
-            if (! $form_step->initialize()) {
363
-                continue;
364
-            }
365
-            $progress_steps_collection->add(
366
-                new ProgressStep(
367
-                    $form_step->order(),
368
-                    $form_step->slug(),
369
-                    $form_step->slug(),
370
-                    $form_step->formName()
371
-                ),
372
-                $form_step->slug()
373
-            );
374
-        }
375
-        // set collection pointer back to current step
376
-        $this->form_steps->setCurrentUsingObject($current_step);
377
-        return new ProgressStepManager(
378
-            $this->progressStepStyle(),
379
-            $this->defaultFormStep(),
380
-            $this->formStepUrlKey(),
381
-            $progress_steps_collection
382
-        );
383
-    }
384
-
385
-
386
-    /**
387
-     * @throws InvalidClassException
388
-     * @throws InvalidDataTypeException
389
-     * @throws InvalidEntityException
390
-     * @throws InvalidIdentifierException
391
-     * @throws InvalidInterfaceException
392
-     * @throws InvalidArgumentException
393
-     * @throws InvalidFormHandlerException
394
-     */
395
-    public function buildForm()
396
-    {
397
-        $this->buildCurrentStepFormForDisplay();
398
-    }
399
-
400
-
401
-    /**
402
-     * @param array $form_data
403
-     * @throws InvalidArgumentException
404
-     * @throws InvalidClassException
405
-     * @throws InvalidDataTypeException
406
-     * @throws InvalidEntityException
407
-     * @throws InvalidFormHandlerException
408
-     * @throws InvalidIdentifierException
409
-     * @throws InvalidInterfaceException
410
-     */
411
-    public function processForm($form_data = array())
412
-    {
413
-        $this->buildCurrentStepFormForProcessing();
414
-        $this->processCurrentStepForm($form_data);
415
-    }
416
-
417
-
418
-    /**
419
-     * @throws InvalidClassException
420
-     * @throws InvalidDataTypeException
421
-     * @throws InvalidEntityException
422
-     * @throws InvalidInterfaceException
423
-     * @throws InvalidIdentifierException
424
-     * @throws InvalidArgumentException
425
-     * @throws InvalidFormHandlerException
426
-     */
427
-    public function buildCurrentStepFormForDisplay()
428
-    {
429
-        $form_step = $this->buildCurrentStepForm();
430
-        // no displayable content ? then skip straight to processing
431
-        if (! $form_step->displayable()) {
432
-            $this->addFormActionArgs();
433
-            $form_step->setFormAction($this->formAction());
434
-            wp_safe_redirect($form_step->formAction());
435
-        }
436
-    }
437
-
438
-
439
-    /**
440
-     * @throws InvalidClassException
441
-     * @throws InvalidDataTypeException
442
-     * @throws InvalidEntityException
443
-     * @throws InvalidInterfaceException
444
-     * @throws InvalidIdentifierException
445
-     * @throws InvalidArgumentException
446
-     * @throws InvalidFormHandlerException
447
-     */
448
-    public function buildCurrentStepFormForProcessing()
449
-    {
450
-        $this->buildCurrentStepForm(false);
451
-    }
452
-
453
-
454
-    /**
455
-     * @param bool $for_display
456
-     * @return SequentialStepFormInterface
457
-     * @throws InvalidArgumentException
458
-     * @throws InvalidClassException
459
-     * @throws InvalidDataTypeException
460
-     * @throws InvalidEntityException
461
-     * @throws InvalidFormHandlerException
462
-     * @throws InvalidIdentifierException
463
-     * @throws InvalidInterfaceException
464
-     */
465
-    private function buildCurrentStepForm($for_display = true)
466
-    {
467
-        $this->form_steps = $this->getFormStepsCollection();
468
-        $this->setCurrentStepFromRequest();
469
-        $form_step = $this->getCurrentStep();
470
-        if ($form_step->submitBtnText() === esc_html__('Submit', 'event_espresso')) {
471
-            $form_step->setSubmitBtnText(esc_html__('Next Step', 'event_espresso'));
472
-        }
473
-        if ($for_display && $form_step->displayable()) {
474
-            $this->progress_step_manager = $this->generateProgressSteps(
475
-                $this->getProgressStepsCollection()
476
-            );
477
-            $this->progress_step_manager->setCurrentStep(
478
-                $form_step->slug()
479
-            );
480
-            // mark all previous progress steps as completed
481
-            $this->progress_step_manager->setPreviousStepsCompleted();
482
-            $this->progress_step_manager->enqueueStylesAndScripts();
483
-            $this->addFormActionArgs();
484
-            $form_step->setFormAction($this->formAction());
485
-        } else {
486
-            $form_step->setRedirectUrl($this->baseUrl());
487
-            $form_step->addRedirectArgs(
488
-                array($this->formStepUrlKey() => $this->form_steps->current()->slug())
489
-            );
490
-        }
491
-        $form_step->generate();
492
-        if ($for_display) {
493
-            $form_step->enqueueStylesAndScripts();
494
-        }
495
-        return $form_step;
496
-    }
497
-
498
-
499
-    /**
500
-     * @param bool $return_as_string
501
-     * @return string
502
-     * @throws InvalidFormHandlerException
503
-     */
504
-    public function displayProgressSteps($return_as_string = true)
505
-    {
506
-        $form_step = $this->getCurrentStep();
507
-        if (! $form_step->displayable()) {
508
-            return '';
509
-        }
510
-        $progress_steps = apply_filters(
511
-            'FHEE__EventEspresso_core_libraries_form_sections_form_handlers_SequentialStepFormManager__displayProgressSteps__before_steps',
512
-            ''
513
-        );
514
-        $progress_steps .= $this->progress_step_manager->displaySteps();
515
-        $progress_steps .= apply_filters(
516
-            'FHEE__EventEspresso_core_libraries_form_sections_form_handlers_SequentialStepFormManager__displayProgressSteps__after_steps',
517
-            ''
518
-        );
519
-        if ($return_as_string) {
520
-            return $progress_steps;
521
-        }
522
-        echo wp_kses($progress_steps, AllowedTags::getWithFormTags());
523
-        return '';
524
-    }
525
-
526
-
527
-    /**
528
-     * @param bool $return_as_string
529
-     * @return string
530
-     * @throws InvalidFormHandlerException
531
-     */
532
-    public function displayCurrentStepForm($return_as_string = true)
533
-    {
534
-        if ($return_as_string) {
535
-            return $this->getCurrentStep()->display();
536
-        }
537
-        echo wp_kses($this->getCurrentStep()->display(), AllowedTags::getWithFormTags());
538
-        return '';
539
-    }
540
-
541
-
542
-    /**
543
-     * @param array $form_data
544
-     * @return void
545
-     * @throws InvalidArgumentException
546
-     * @throws InvalidDataTypeException
547
-     * @throws InvalidFormHandlerException
548
-     */
549
-    public function processCurrentStepForm($form_data = array())
550
-    {
551
-        // grab instance of current step because after calling next() below,
552
-        // any calls to getCurrentStep() will return the "next" step because we advanced
553
-        $current_step = $this->getCurrentStep();
554
-        try {
555
-            // form processing should either throw exceptions or return true
556
-            $current_step->process($form_data);
557
-        } catch (Exception $e) {
558
-            // something went wrong, convert the Exception to an EE_Error
559
-            EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
560
-            // prevent redirect to next step or other if exception was thrown
561
-            if (
562
-                $current_step->redirectTo() === SequentialStepForm::REDIRECT_TO_NEXT_STEP
563
-                || $current_step->redirectTo() === SequentialStepForm::REDIRECT_TO_OTHER
564
-            ) {
565
-                $current_step->setRedirectTo(SequentialStepForm::REDIRECT_TO_CURRENT_STEP);
566
-            }
567
-        }
568
-        // save notices to a transient so that when we redirect back
569
-        // to the display portion for this step
570
-        // those notices can be displayed
571
-        EE_Error::get_notices(false, true);
572
-        $this->redirectForm($current_step);
573
-    }
574
-
575
-
576
-    /**
577
-     * handles where to go to next
578
-     *
579
-     * @param SequentialStepFormInterface $current_step
580
-     * @throws InvalidArgumentException
581
-     * @throws InvalidDataTypeException
582
-     * @throws InvalidFormHandlerException
583
-     */
584
-    public function redirectForm(SequentialStepFormInterface $current_step)
585
-    {
586
-        $redirect_step = $current_step;
587
-        switch ($current_step->redirectTo()) {
588
-            case SequentialStepForm::REDIRECT_TO_OTHER:
589
-                // going somewhere else, so just check out now
590
-                wp_safe_redirect($redirect_step->redirectUrl());
591
-                exit();
592
-            case SequentialStepForm::REDIRECT_TO_PREV_STEP:
593
-                $redirect_step = $this->form_steps->previous();
594
-                break;
595
-            case SequentialStepForm::REDIRECT_TO_NEXT_STEP:
596
-                $this->form_steps->next();
597
-                if ($this->form_steps->valid()) {
598
-                    $redirect_step = $this->form_steps->current();
599
-                }
600
-                break;
601
-            case SequentialStepForm::REDIRECT_TO_CURRENT_STEP:
602
-            default:
603
-                // $redirect_step is already set
604
-        }
605
-        $current_step->setRedirectUrl($this->baseUrl());
606
-        $current_step->addRedirectArgs(
607
-            // use the slug for whatever step we are redirecting too
608
-            array($this->formStepUrlKey() => $redirect_step->slug())
609
-        );
610
-        wp_safe_redirect($current_step->redirectUrl());
611
-        exit();
612
-    }
34
+	/**
35
+	 * a simplified URL with no form related parameters
36
+	 * that will be used to build the form's redirect URLs
37
+	 *
38
+	 * @var string $base_url
39
+	 */
40
+	private $base_url = '';
41
+
42
+	/**
43
+	 * the key used for the URL param that denotes the current form step
44
+	 * defaults to 'ee-form-step'
45
+	 *
46
+	 * @var string $form_step_url_key
47
+	 */
48
+	private $form_step_url_key = '';
49
+
50
+	/**
51
+	 * @var string $default_form_step
52
+	 */
53
+	private $default_form_step = '';
54
+
55
+	/**
56
+	 * @var string $form_action
57
+	 */
58
+	private $form_action;
59
+
60
+	/**
61
+	 * value of one of the string constant above
62
+	 *
63
+	 * @var string $form_config
64
+	 */
65
+	private $form_config;
66
+
67
+	/**
68
+	 * @var string $progress_step_style
69
+	 */
70
+	private $progress_step_style = '';
71
+
72
+	/**
73
+	 * @var RequestInterface $request
74
+	 */
75
+	private $request;
76
+
77
+	/**
78
+	 * @var Collection $form_steps
79
+	 */
80
+	protected $form_steps;
81
+
82
+	/**
83
+	 * @var ProgressStepManager $progress_step_manager
84
+	 */
85
+	protected $progress_step_manager;
86
+
87
+
88
+	/**
89
+	 * @return Collection|null
90
+	 */
91
+	abstract protected function getFormStepsCollection();
92
+
93
+	// phpcs:disable PEAR.Functions.ValidDefaultValue.NotAtEnd
94
+	/**
95
+	 * StepsManager constructor
96
+	 *
97
+	 * @param string                           $base_url
98
+	 * @param string                           $default_form_step
99
+	 * @param string                           $form_action
100
+	 * @param string                           $form_config
101
+	 * @param EE_Request|RequestInterface|null $request
102
+	 * @param string                           $progress_step_style
103
+	 * @throws InvalidDataTypeException
104
+	 * @throws InvalidArgumentException
105
+	 */
106
+	public function __construct(
107
+		$base_url,
108
+		$default_form_step,
109
+		$form_action = '',
110
+		$form_config = FormHandler::ADD_FORM_TAGS_AND_SUBMIT,
111
+		$progress_step_style = 'number_bubbles',
112
+		$request = null
113
+	) {
114
+		$this->setBaseUrl($base_url);
115
+		$this->setDefaultFormStep($default_form_step);
116
+		$this->setFormAction($form_action);
117
+		$this->setFormConfig($form_config);
118
+		$this->setProgressStepStyle($progress_step_style);
119
+		$this->request = $request instanceof RequestInterface
120
+			? $request
121
+			: LoaderFactory::getLoader()->getShared('EventEspresso\core\services\request\RequestInterface');
122
+	}
123
+
124
+
125
+	/**
126
+	 * @return string
127
+	 * @throws InvalidFormHandlerException
128
+	 */
129
+	public function baseUrl()
130
+	{
131
+		if (strpos($this->base_url, $this->getCurrentStep()->slug()) === false) {
132
+			add_query_arg(
133
+				array($this->form_step_url_key => $this->getCurrentStep()->slug()),
134
+				$this->base_url
135
+			);
136
+		}
137
+		return $this->base_url;
138
+	}
139
+
140
+
141
+	/**
142
+	 * @param string $base_url
143
+	 * @throws InvalidDataTypeException
144
+	 * @throws InvalidArgumentException
145
+	 */
146
+	protected function setBaseUrl($base_url)
147
+	{
148
+		if (! is_string($base_url)) {
149
+			throw new InvalidDataTypeException('$base_url', $base_url, 'string');
150
+		}
151
+		if (empty($base_url)) {
152
+			throw new InvalidArgumentException(
153
+				esc_html__('The base URL can not be an empty string.', 'event_espresso')
154
+			);
155
+		}
156
+		$this->base_url = $base_url;
157
+	}
158
+
159
+
160
+	/**
161
+	 * @return string
162
+	 * @throws InvalidDataTypeException
163
+	 */
164
+	public function formStepUrlKey()
165
+	{
166
+		if (empty($this->form_step_url_key)) {
167
+			$this->setFormStepUrlKey();
168
+		}
169
+		return $this->form_step_url_key;
170
+	}
171
+
172
+
173
+	/**
174
+	 * @param string $form_step_url_key
175
+	 * @throws InvalidDataTypeException
176
+	 */
177
+	public function setFormStepUrlKey($form_step_url_key = 'ee-form-step')
178
+	{
179
+		if (! is_string($form_step_url_key)) {
180
+			throw new InvalidDataTypeException('$form_step_key', $form_step_url_key, 'string');
181
+		}
182
+		$this->form_step_url_key = ! empty($form_step_url_key) ? $form_step_url_key : 'ee-form-step';
183
+	}
184
+
185
+
186
+	/**
187
+	 * @return string
188
+	 */
189
+	public function defaultFormStep()
190
+	{
191
+		return $this->default_form_step;
192
+	}
193
+
194
+
195
+	/**
196
+	 * @param $default_form_step
197
+	 * @throws InvalidDataTypeException
198
+	 */
199
+	protected function setDefaultFormStep($default_form_step)
200
+	{
201
+		if (! is_string($default_form_step)) {
202
+			throw new InvalidDataTypeException('$default_form_step', $default_form_step, 'string');
203
+		}
204
+		$this->default_form_step = $default_form_step;
205
+	}
206
+
207
+
208
+	/**
209
+	 * @return void
210
+	 * @throws InvalidIdentifierException
211
+	 * @throws InvalidDataTypeException
212
+	 */
213
+	protected function setCurrentStepFromRequest()
214
+	{
215
+		$current_step_slug = $this->request()->getRequestParam($this->formStepUrlKey(), $this->defaultFormStep());
216
+		if (! $this->form_steps->setCurrent($current_step_slug)) {
217
+			throw new InvalidIdentifierException(
218
+				$current_step_slug,
219
+				$this->defaultFormStep(),
220
+				sprintf(
221
+					esc_html__('The "%1$s" form step could not be set.', 'event_espresso'),
222
+					$current_step_slug
223
+				)
224
+			);
225
+		}
226
+	}
227
+
228
+
229
+	/**
230
+	 * @return SequentialStepFormInterface|object
231
+	 * @throws InvalidFormHandlerException
232
+	 */
233
+	public function getCurrentStep()
234
+	{
235
+		if (! $this->form_steps->current() instanceof SequentialStepForm) {
236
+			throw new InvalidFormHandlerException($this->form_steps->current());
237
+		}
238
+		return $this->form_steps->current();
239
+	}
240
+
241
+
242
+	/**
243
+	 * @return string
244
+	 * @throws InvalidFormHandlerException
245
+	 */
246
+	public function formAction()
247
+	{
248
+		if (! is_string($this->form_action) || empty($this->form_action)) {
249
+			$this->form_action = $this->baseUrl();
250
+		}
251
+		return $this->form_action;
252
+	}
253
+
254
+
255
+	/**
256
+	 * @param string $form_action
257
+	 * @throws InvalidDataTypeException
258
+	 */
259
+	public function setFormAction($form_action)
260
+	{
261
+		if (! is_string($form_action)) {
262
+			throw new InvalidDataTypeException('$form_action', $form_action, 'string');
263
+		}
264
+		$this->form_action = $form_action;
265
+	}
266
+
267
+
268
+	/**
269
+	 * @param array $form_action_args
270
+	 * @throws InvalidDataTypeException
271
+	 * @throws InvalidFormHandlerException
272
+	 */
273
+	public function addFormActionArgs($form_action_args = array())
274
+	{
275
+		if (! is_array($form_action_args)) {
276
+			throw new InvalidDataTypeException('$form_action_args', $form_action_args, 'array');
277
+		}
278
+		$form_action_args = ! empty($form_action_args)
279
+			? $form_action_args
280
+			: array($this->formStepUrlKey() => $this->form_steps->current()->slug());
281
+		$this->getCurrentStep()->setFormAction(
282
+			add_query_arg($form_action_args, $this->formAction())
283
+		);
284
+		$this->form_action = $this->getCurrentStep()->formAction();
285
+	}
286
+
287
+
288
+	/**
289
+	 * @return string
290
+	 */
291
+	public function formConfig()
292
+	{
293
+		return $this->form_config;
294
+	}
295
+
296
+
297
+	/**
298
+	 * @param string $form_config
299
+	 */
300
+	public function setFormConfig($form_config)
301
+	{
302
+		$this->form_config = $form_config;
303
+	}
304
+
305
+
306
+	/**
307
+	 * @return string
308
+	 */
309
+	public function progressStepStyle()
310
+	{
311
+		return $this->progress_step_style;
312
+	}
313
+
314
+
315
+	/**
316
+	 * @param string $progress_step_style
317
+	 */
318
+	public function setProgressStepStyle($progress_step_style)
319
+	{
320
+		$this->progress_step_style = $progress_step_style;
321
+	}
322
+
323
+
324
+	/**
325
+	 * @return RequestInterface
326
+	 */
327
+	public function request()
328
+	{
329
+		return $this->request;
330
+	}
331
+
332
+
333
+	/**
334
+	 * @return Collection|null
335
+	 * @throws InvalidInterfaceException
336
+	 */
337
+	protected function getProgressStepsCollection()
338
+	{
339
+		static $collection = null;
340
+		if (! $collection instanceof ProgressStepCollection) {
341
+			$collection = new ProgressStepCollection();
342
+		}
343
+		return $collection;
344
+	}
345
+
346
+
347
+	/**
348
+	 * @param Collection $progress_steps_collection
349
+	 * @return ProgressStepManager
350
+	 * @throws InvalidInterfaceException
351
+	 * @throws InvalidClassException
352
+	 * @throws InvalidDataTypeException
353
+	 * @throws InvalidEntityException
354
+	 * @throws InvalidFormHandlerException
355
+	 */
356
+	protected function generateProgressSteps(Collection $progress_steps_collection)
357
+	{
358
+		$current_step = $this->getCurrentStep();
359
+		/** @var SequentialStepForm $form_step */
360
+		foreach ($this->form_steps as $form_step) {
361
+			// is this step active ?
362
+			if (! $form_step->initialize()) {
363
+				continue;
364
+			}
365
+			$progress_steps_collection->add(
366
+				new ProgressStep(
367
+					$form_step->order(),
368
+					$form_step->slug(),
369
+					$form_step->slug(),
370
+					$form_step->formName()
371
+				),
372
+				$form_step->slug()
373
+			);
374
+		}
375
+		// set collection pointer back to current step
376
+		$this->form_steps->setCurrentUsingObject($current_step);
377
+		return new ProgressStepManager(
378
+			$this->progressStepStyle(),
379
+			$this->defaultFormStep(),
380
+			$this->formStepUrlKey(),
381
+			$progress_steps_collection
382
+		);
383
+	}
384
+
385
+
386
+	/**
387
+	 * @throws InvalidClassException
388
+	 * @throws InvalidDataTypeException
389
+	 * @throws InvalidEntityException
390
+	 * @throws InvalidIdentifierException
391
+	 * @throws InvalidInterfaceException
392
+	 * @throws InvalidArgumentException
393
+	 * @throws InvalidFormHandlerException
394
+	 */
395
+	public function buildForm()
396
+	{
397
+		$this->buildCurrentStepFormForDisplay();
398
+	}
399
+
400
+
401
+	/**
402
+	 * @param array $form_data
403
+	 * @throws InvalidArgumentException
404
+	 * @throws InvalidClassException
405
+	 * @throws InvalidDataTypeException
406
+	 * @throws InvalidEntityException
407
+	 * @throws InvalidFormHandlerException
408
+	 * @throws InvalidIdentifierException
409
+	 * @throws InvalidInterfaceException
410
+	 */
411
+	public function processForm($form_data = array())
412
+	{
413
+		$this->buildCurrentStepFormForProcessing();
414
+		$this->processCurrentStepForm($form_data);
415
+	}
416
+
417
+
418
+	/**
419
+	 * @throws InvalidClassException
420
+	 * @throws InvalidDataTypeException
421
+	 * @throws InvalidEntityException
422
+	 * @throws InvalidInterfaceException
423
+	 * @throws InvalidIdentifierException
424
+	 * @throws InvalidArgumentException
425
+	 * @throws InvalidFormHandlerException
426
+	 */
427
+	public function buildCurrentStepFormForDisplay()
428
+	{
429
+		$form_step = $this->buildCurrentStepForm();
430
+		// no displayable content ? then skip straight to processing
431
+		if (! $form_step->displayable()) {
432
+			$this->addFormActionArgs();
433
+			$form_step->setFormAction($this->formAction());
434
+			wp_safe_redirect($form_step->formAction());
435
+		}
436
+	}
437
+
438
+
439
+	/**
440
+	 * @throws InvalidClassException
441
+	 * @throws InvalidDataTypeException
442
+	 * @throws InvalidEntityException
443
+	 * @throws InvalidInterfaceException
444
+	 * @throws InvalidIdentifierException
445
+	 * @throws InvalidArgumentException
446
+	 * @throws InvalidFormHandlerException
447
+	 */
448
+	public function buildCurrentStepFormForProcessing()
449
+	{
450
+		$this->buildCurrentStepForm(false);
451
+	}
452
+
453
+
454
+	/**
455
+	 * @param bool $for_display
456
+	 * @return SequentialStepFormInterface
457
+	 * @throws InvalidArgumentException
458
+	 * @throws InvalidClassException
459
+	 * @throws InvalidDataTypeException
460
+	 * @throws InvalidEntityException
461
+	 * @throws InvalidFormHandlerException
462
+	 * @throws InvalidIdentifierException
463
+	 * @throws InvalidInterfaceException
464
+	 */
465
+	private function buildCurrentStepForm($for_display = true)
466
+	{
467
+		$this->form_steps = $this->getFormStepsCollection();
468
+		$this->setCurrentStepFromRequest();
469
+		$form_step = $this->getCurrentStep();
470
+		if ($form_step->submitBtnText() === esc_html__('Submit', 'event_espresso')) {
471
+			$form_step->setSubmitBtnText(esc_html__('Next Step', 'event_espresso'));
472
+		}
473
+		if ($for_display && $form_step->displayable()) {
474
+			$this->progress_step_manager = $this->generateProgressSteps(
475
+				$this->getProgressStepsCollection()
476
+			);
477
+			$this->progress_step_manager->setCurrentStep(
478
+				$form_step->slug()
479
+			);
480
+			// mark all previous progress steps as completed
481
+			$this->progress_step_manager->setPreviousStepsCompleted();
482
+			$this->progress_step_manager->enqueueStylesAndScripts();
483
+			$this->addFormActionArgs();
484
+			$form_step->setFormAction($this->formAction());
485
+		} else {
486
+			$form_step->setRedirectUrl($this->baseUrl());
487
+			$form_step->addRedirectArgs(
488
+				array($this->formStepUrlKey() => $this->form_steps->current()->slug())
489
+			);
490
+		}
491
+		$form_step->generate();
492
+		if ($for_display) {
493
+			$form_step->enqueueStylesAndScripts();
494
+		}
495
+		return $form_step;
496
+	}
497
+
498
+
499
+	/**
500
+	 * @param bool $return_as_string
501
+	 * @return string
502
+	 * @throws InvalidFormHandlerException
503
+	 */
504
+	public function displayProgressSteps($return_as_string = true)
505
+	{
506
+		$form_step = $this->getCurrentStep();
507
+		if (! $form_step->displayable()) {
508
+			return '';
509
+		}
510
+		$progress_steps = apply_filters(
511
+			'FHEE__EventEspresso_core_libraries_form_sections_form_handlers_SequentialStepFormManager__displayProgressSteps__before_steps',
512
+			''
513
+		);
514
+		$progress_steps .= $this->progress_step_manager->displaySteps();
515
+		$progress_steps .= apply_filters(
516
+			'FHEE__EventEspresso_core_libraries_form_sections_form_handlers_SequentialStepFormManager__displayProgressSteps__after_steps',
517
+			''
518
+		);
519
+		if ($return_as_string) {
520
+			return $progress_steps;
521
+		}
522
+		echo wp_kses($progress_steps, AllowedTags::getWithFormTags());
523
+		return '';
524
+	}
525
+
526
+
527
+	/**
528
+	 * @param bool $return_as_string
529
+	 * @return string
530
+	 * @throws InvalidFormHandlerException
531
+	 */
532
+	public function displayCurrentStepForm($return_as_string = true)
533
+	{
534
+		if ($return_as_string) {
535
+			return $this->getCurrentStep()->display();
536
+		}
537
+		echo wp_kses($this->getCurrentStep()->display(), AllowedTags::getWithFormTags());
538
+		return '';
539
+	}
540
+
541
+
542
+	/**
543
+	 * @param array $form_data
544
+	 * @return void
545
+	 * @throws InvalidArgumentException
546
+	 * @throws InvalidDataTypeException
547
+	 * @throws InvalidFormHandlerException
548
+	 */
549
+	public function processCurrentStepForm($form_data = array())
550
+	{
551
+		// grab instance of current step because after calling next() below,
552
+		// any calls to getCurrentStep() will return the "next" step because we advanced
553
+		$current_step = $this->getCurrentStep();
554
+		try {
555
+			// form processing should either throw exceptions or return true
556
+			$current_step->process($form_data);
557
+		} catch (Exception $e) {
558
+			// something went wrong, convert the Exception to an EE_Error
559
+			EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
560
+			// prevent redirect to next step or other if exception was thrown
561
+			if (
562
+				$current_step->redirectTo() === SequentialStepForm::REDIRECT_TO_NEXT_STEP
563
+				|| $current_step->redirectTo() === SequentialStepForm::REDIRECT_TO_OTHER
564
+			) {
565
+				$current_step->setRedirectTo(SequentialStepForm::REDIRECT_TO_CURRENT_STEP);
566
+			}
567
+		}
568
+		// save notices to a transient so that when we redirect back
569
+		// to the display portion for this step
570
+		// those notices can be displayed
571
+		EE_Error::get_notices(false, true);
572
+		$this->redirectForm($current_step);
573
+	}
574
+
575
+
576
+	/**
577
+	 * handles where to go to next
578
+	 *
579
+	 * @param SequentialStepFormInterface $current_step
580
+	 * @throws InvalidArgumentException
581
+	 * @throws InvalidDataTypeException
582
+	 * @throws InvalidFormHandlerException
583
+	 */
584
+	public function redirectForm(SequentialStepFormInterface $current_step)
585
+	{
586
+		$redirect_step = $current_step;
587
+		switch ($current_step->redirectTo()) {
588
+			case SequentialStepForm::REDIRECT_TO_OTHER:
589
+				// going somewhere else, so just check out now
590
+				wp_safe_redirect($redirect_step->redirectUrl());
591
+				exit();
592
+			case SequentialStepForm::REDIRECT_TO_PREV_STEP:
593
+				$redirect_step = $this->form_steps->previous();
594
+				break;
595
+			case SequentialStepForm::REDIRECT_TO_NEXT_STEP:
596
+				$this->form_steps->next();
597
+				if ($this->form_steps->valid()) {
598
+					$redirect_step = $this->form_steps->current();
599
+				}
600
+				break;
601
+			case SequentialStepForm::REDIRECT_TO_CURRENT_STEP:
602
+			default:
603
+				// $redirect_step is already set
604
+		}
605
+		$current_step->setRedirectUrl($this->baseUrl());
606
+		$current_step->addRedirectArgs(
607
+			// use the slug for whatever step we are redirecting too
608
+			array($this->formStepUrlKey() => $redirect_step->slug())
609
+		);
610
+		wp_safe_redirect($current_step->redirectUrl());
611
+		exit();
612
+	}
613 613
 }
Please login to merge, or discard this patch.
messages/messenger/admin_templates/event_switcher_row.template.php 1 patch
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -25,6 +25,6 @@
 block discarded – undo
25 25
     <td><?php echo esc_html($mt_name); ?></td>
26 26
     <td><?php echo wp_kses($selector, AllowedTags::getWithFormTags()); ?></td>
27 27
     <td class="message-selector-action-column">
28
-        <?php echo wp_kses($create_button . $edit_button, AllowedTags::getWithFormTags()); ?>
28
+        <?php echo wp_kses($create_button.$edit_button, AllowedTags::getWithFormTags()); ?>
29 29
     </td>
30 30
 </tr>
Please login to merge, or discard this patch.
core/db_classes/EE_Transaction.class.php 1 patch
Indentation   +1701 added lines, -1701 removed lines patch added patch discarded remove patch
@@ -14,1705 +14,1705 @@
 block discarded – undo
14 14
 class EE_Transaction extends EE_Base_Class implements EEI_Transaction
15 15
 {
16 16
 
17
-    /**
18
-     * The length of time in seconds that a lock is applied before being considered expired.
19
-     * It is not long because a transaction should only be locked for the duration of the request that locked it
20
-     */
21
-    const LOCK_EXPIRATION = 2;
22
-
23
-    /**
24
-     * txn status upon initial construction.
25
-     *
26
-     * @var string
27
-     */
28
-    protected $_old_txn_status;
29
-
30
-
31
-    /**
32
-     * @param array  $props_n_values          incoming values
33
-     * @param string $timezone                incoming timezone
34
-     *                                        (if not set the timezone set for the website will be used.)
35
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
36
-     *                                        date_format and the second value is the time format
37
-     * @return EE_Transaction
38
-     * @throws EE_Error
39
-     * @throws InvalidArgumentException
40
-     * @throws InvalidDataTypeException
41
-     * @throws InvalidInterfaceException
42
-     * @throws ReflectionException
43
-     */
44
-    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
45
-    {
46
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
47
-        $txn = $has_object
48
-            ? $has_object
49
-            : new self($props_n_values, false, $timezone, $date_formats);
50
-        if (! $has_object) {
51
-            $txn->set_old_txn_status($txn->status_ID());
52
-        }
53
-        return $txn;
54
-    }
55
-
56
-
57
-    /**
58
-     * @param array  $props_n_values  incoming values from the database
59
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
60
-     *                                the website will be used.
61
-     * @return EE_Transaction
62
-     * @throws EE_Error
63
-     * @throws InvalidArgumentException
64
-     * @throws InvalidDataTypeException
65
-     * @throws InvalidInterfaceException
66
-     * @throws ReflectionException
67
-     */
68
-    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
69
-    {
70
-        $txn = new self($props_n_values, true, $timezone);
71
-        $txn->set_old_txn_status($txn->status_ID());
72
-        return $txn;
73
-    }
74
-
75
-
76
-    /**
77
-     * Sets a meta field indicating that this TXN is locked and should not be updated in the db.
78
-     * If a lock has already been set, then we will attempt to remove it in case it has expired.
79
-     * If that also fails, then an exception is thrown.
80
-     *
81
-     * @throws EE_Error
82
-     * @throws InvalidArgumentException
83
-     * @throws InvalidDataTypeException
84
-     * @throws InvalidInterfaceException
85
-     * @throws ReflectionException
86
-     */
87
-    public function lock()
88
-    {
89
-        // attempt to set lock, but if that fails...
90
-        if (! $this->add_extra_meta('lock', time(), true)) {
91
-            // then attempt to remove the lock in case it is expired
92
-            if ($this->_remove_expired_lock()) {
93
-                // if removal was successful, then try setting lock again
94
-                $this->lock();
95
-            } else {
96
-                // but if the lock can not be removed, then throw an exception
97
-                throw new EE_Error(
98
-                    sprintf(
99
-                        esc_html__(
100
-                            '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.',
101
-                            'event_espresso'
102
-                        ),
103
-                        $this->ID()
104
-                    )
105
-                );
106
-            }
107
-        }
108
-    }
109
-
110
-
111
-    /**
112
-     * removes transaction lock applied in EE_Transaction::lock()
113
-     *
114
-     * @return int
115
-     * @throws EE_Error
116
-     * @throws InvalidArgumentException
117
-     * @throws InvalidDataTypeException
118
-     * @throws InvalidInterfaceException
119
-     * @throws ReflectionException
120
-     */
121
-    public function unlock()
122
-    {
123
-        return $this->delete_extra_meta('lock');
124
-    }
125
-
126
-
127
-    /**
128
-     * Decides whether or not now is the right time to update the transaction.
129
-     * This is useful because we don't always know if it is safe to update the transaction
130
-     * and its related data. why?
131
-     * because it's possible that the transaction is being used in another
132
-     * request and could overwrite anything we save.
133
-     * So we want to only update the txn once we know that won't happen.
134
-     * We also check that the lock isn't expired, and remove it if it is
135
-     *
136
-     * @return boolean
137
-     * @throws EE_Error
138
-     * @throws InvalidArgumentException
139
-     * @throws InvalidDataTypeException
140
-     * @throws InvalidInterfaceException
141
-     * @throws ReflectionException
142
-     */
143
-    public function is_locked()
144
-    {
145
-        // if TXN is not locked, then return false immediately
146
-        if (! $this->_get_lock()) {
147
-            return false;
148
-        }
149
-        // if not, then let's try and remove the lock in case it's expired...
150
-        // _remove_expired_lock() returns 0 when lock is valid (ie: removed = false)
151
-        // and a positive number if the lock was removed (ie: number of locks deleted),
152
-        // so we need to return the opposite
153
-        return ! $this->_remove_expired_lock() ? true : false;
154
-    }
155
-
156
-
157
-    /**
158
-     * Gets the meta field indicating that this TXN is locked
159
-     *
160
-     * @return int
161
-     * @throws EE_Error
162
-     * @throws InvalidArgumentException
163
-     * @throws InvalidDataTypeException
164
-     * @throws InvalidInterfaceException
165
-     * @throws ReflectionException
166
-     */
167
-    protected function _get_lock()
168
-    {
169
-        return (int) $this->get_extra_meta('lock', true, 0);
170
-    }
171
-
172
-
173
-    /**
174
-     * If the lock on this transaction is expired, then we want to remove it so that the transaction can be updated
175
-     *
176
-     * @return int
177
-     * @throws EE_Error
178
-     * @throws InvalidArgumentException
179
-     * @throws InvalidDataTypeException
180
-     * @throws InvalidInterfaceException
181
-     * @throws ReflectionException
182
-     */
183
-    protected function _remove_expired_lock()
184
-    {
185
-        $locked = $this->_get_lock();
186
-        if ($locked && time() - EE_Transaction::LOCK_EXPIRATION > $locked) {
187
-            return $this->unlock();
188
-        }
189
-        return 0;
190
-    }
191
-
192
-
193
-    /**
194
-     * Set transaction total
195
-     *
196
-     * @param float $total total value of transaction
197
-     * @throws EE_Error
198
-     * @throws InvalidArgumentException
199
-     * @throws InvalidDataTypeException
200
-     * @throws InvalidInterfaceException
201
-     * @throws ReflectionException
202
-     */
203
-    public function set_total($total = 0.00)
204
-    {
205
-        $this->set('TXN_total', (float) $total);
206
-    }
207
-
208
-
209
-    /**
210
-     * Set Total Amount Paid to Date
211
-     *
212
-     * @param float $total_paid total amount paid to date (sum of all payments)
213
-     * @throws EE_Error
214
-     * @throws InvalidArgumentException
215
-     * @throws InvalidDataTypeException
216
-     * @throws InvalidInterfaceException
217
-     * @throws ReflectionException
218
-     */
219
-    public function set_paid($total_paid = 0.00)
220
-    {
221
-        $this->set('TXN_paid', (float) $total_paid);
222
-    }
223
-
224
-
225
-    /**
226
-     * Set transaction status
227
-     *
228
-     * @param string $status        whether the transaction is open, declined, accepted,
229
-     *                              or any number of custom values that can be set
230
-     * @throws EE_Error
231
-     * @throws InvalidArgumentException
232
-     * @throws InvalidDataTypeException
233
-     * @throws InvalidInterfaceException
234
-     * @throws ReflectionException
235
-     */
236
-    public function set_status($status = '')
237
-    {
238
-        $this->set('STS_ID', $status);
239
-    }
240
-
241
-
242
-    /**
243
-     * Set hash salt
244
-     *
245
-     * @param string $hash_salt required for some payment gateways
246
-     * @throws EE_Error
247
-     * @throws InvalidArgumentException
248
-     * @throws InvalidDataTypeException
249
-     * @throws InvalidInterfaceException
250
-     * @throws ReflectionException
251
-     */
252
-    public function set_hash_salt($hash_salt = '')
253
-    {
254
-        $this->set('TXN_hash_salt', $hash_salt);
255
-    }
256
-
257
-
258
-    /**
259
-     * Sets TXN_reg_steps array
260
-     *
261
-     * @param array $txn_reg_steps
262
-     * @throws EE_Error
263
-     * @throws InvalidArgumentException
264
-     * @throws InvalidDataTypeException
265
-     * @throws InvalidInterfaceException
266
-     * @throws ReflectionException
267
-     */
268
-    public function set_reg_steps(array $txn_reg_steps)
269
-    {
270
-        $this->set('TXN_reg_steps', $txn_reg_steps);
271
-    }
272
-
273
-
274
-    /**
275
-     * Gets TXN_reg_steps
276
-     *
277
-     * @return array
278
-     * @throws EE_Error
279
-     * @throws InvalidArgumentException
280
-     * @throws InvalidDataTypeException
281
-     * @throws InvalidInterfaceException
282
-     * @throws ReflectionException
283
-     */
284
-    public function reg_steps()
285
-    {
286
-        $TXN_reg_steps = $this->get('TXN_reg_steps');
287
-        return is_array($TXN_reg_steps) ? (array) $TXN_reg_steps : array();
288
-    }
289
-
290
-
291
-    /**
292
-     * @return string of transaction's total cost, with currency symbol and decimal
293
-     * @throws EE_Error
294
-     * @throws InvalidArgumentException
295
-     * @throws InvalidDataTypeException
296
-     * @throws InvalidInterfaceException
297
-     * @throws ReflectionException
298
-     */
299
-    public function pretty_total()
300
-    {
301
-        return $this->get_pretty('TXN_total');
302
-    }
303
-
304
-
305
-    /**
306
-     * Gets the amount paid in a pretty string (formatted and with currency symbol)
307
-     *
308
-     * @return string
309
-     * @throws EE_Error
310
-     * @throws InvalidArgumentException
311
-     * @throws InvalidDataTypeException
312
-     * @throws InvalidInterfaceException
313
-     * @throws ReflectionException
314
-     */
315
-    public function pretty_paid()
316
-    {
317
-        return $this->get_pretty('TXN_paid');
318
-    }
319
-
320
-
321
-    /**
322
-     * calculate the amount remaining for this transaction and return;
323
-     *
324
-     * @return float amount remaining
325
-     * @throws EE_Error
326
-     * @throws InvalidArgumentException
327
-     * @throws InvalidDataTypeException
328
-     * @throws InvalidInterfaceException
329
-     * @throws ReflectionException
330
-     */
331
-    public function remaining()
332
-    {
333
-        return $this->total() - $this->paid();
334
-    }
335
-
336
-
337
-    /**
338
-     * get Transaction Total
339
-     *
340
-     * @return float
341
-     * @throws EE_Error
342
-     * @throws InvalidArgumentException
343
-     * @throws InvalidDataTypeException
344
-     * @throws InvalidInterfaceException
345
-     * @throws ReflectionException
346
-     */
347
-    public function total()
348
-    {
349
-        return (float) $this->get('TXN_total');
350
-    }
351
-
352
-
353
-    /**
354
-     * get Total Amount Paid to Date
355
-     *
356
-     * @return float
357
-     * @throws EE_Error
358
-     * @throws InvalidArgumentException
359
-     * @throws InvalidDataTypeException
360
-     * @throws InvalidInterfaceException
361
-     * @throws ReflectionException
362
-     */
363
-    public function paid()
364
-    {
365
-        return (float) $this->get('TXN_paid');
366
-    }
367
-
368
-
369
-    /**
370
-     * @return mixed|null
371
-     * @throws EE_Error
372
-     * @throws InvalidArgumentException
373
-     * @throws InvalidDataTypeException
374
-     * @throws InvalidInterfaceException
375
-     * @throws ReflectionException
376
-     */
377
-    public function get_cart_session()
378
-    {
379
-        $session_data = (array) $this->get('TXN_session_data');
380
-        return isset($session_data['cart']) && $session_data['cart'] instanceof EE_Cart
381
-            ? $session_data['cart']
382
-            : null;
383
-    }
384
-
385
-
386
-    /**
387
-     * get Transaction session data
388
-     *
389
-     * @return array|mixed
390
-     * @throws EE_Error
391
-     * @throws InvalidArgumentException
392
-     * @throws InvalidDataTypeException
393
-     * @throws InvalidInterfaceException
394
-     * @throws ReflectionException
395
-     */
396
-    public function session_data()
397
-    {
398
-        $session_data = $this->get('TXN_session_data');
399
-        if (empty($session_data)) {
400
-            $session_data = array(
401
-                'id'            => null,
402
-                'user_id'       => null,
403
-                'ip_address'    => null,
404
-                'user_agent'    => null,
405
-                'init_access'   => null,
406
-                'last_access'   => null,
407
-                'pages_visited' => array(),
408
-            );
409
-        }
410
-        return $session_data;
411
-    }
412
-
413
-
414
-    /**
415
-     * Set session data within the TXN object
416
-     *
417
-     * @param EE_Session|array $session_data
418
-     * @throws EE_Error
419
-     * @throws InvalidArgumentException
420
-     * @throws InvalidDataTypeException
421
-     * @throws InvalidInterfaceException
422
-     * @throws ReflectionException
423
-     */
424
-    public function set_txn_session_data($session_data)
425
-    {
426
-        if ($session_data instanceof EE_Session) {
427
-            $this->set('TXN_session_data', $session_data->get_session_data(null, true));
428
-        } else {
429
-            $this->set('TXN_session_data', $session_data);
430
-        }
431
-    }
432
-
433
-
434
-    /**
435
-     * get Transaction hash salt
436
-     *
437
-     * @return mixed
438
-     * @throws EE_Error
439
-     * @throws InvalidArgumentException
440
-     * @throws InvalidDataTypeException
441
-     * @throws InvalidInterfaceException
442
-     * @throws ReflectionException
443
-     */
444
-    public function hash_salt_()
445
-    {
446
-        return $this->get('TXN_hash_salt');
447
-    }
448
-
449
-
450
-    /**
451
-     * Returns the transaction datetime as either:
452
-     *            - unix timestamp format ($format = false, $gmt = true)
453
-     *            - formatted date string including the UTC (timezone) offset ($format = true ($gmt
454
-     *              has no affect with this option)), this also may include a timezone abbreviation if the
455
-     *              set timezone in this class differs from what the timezone is on the blog.
456
-     *            - formatted date string including the UTC (timezone) offset (default).
457
-     *
458
-     * @param boolean $format   - whether to return a unix timestamp (default) or formatted date string
459
-     * @param boolean $gmt      - whether to return a unix timestamp with UTC offset applied (default)
460
-     *                          or no UTC offset applied
461
-     * @return string | int
462
-     * @throws EE_Error
463
-     * @throws InvalidArgumentException
464
-     * @throws InvalidDataTypeException
465
-     * @throws InvalidInterfaceException
466
-     * @throws ReflectionException
467
-     */
468
-    public function datetime($format = false, $gmt = false)
469
-    {
470
-        if ($format) {
471
-            return $this->get_pretty('TXN_timestamp');
472
-        }
473
-        if ($gmt) {
474
-            return $this->get_raw('TXN_timestamp');
475
-        }
476
-        return $this->get('TXN_timestamp');
477
-    }
478
-
479
-
480
-    /**
481
-     * Gets registrations on this transaction
482
-     *
483
-     * @param array   $query_params array of query parameters
484
-     * @param boolean $get_cached   TRUE to retrieve cached registrations or FALSE to pull from the db
485
-     * @return EE_Base_Class[]|EE_Registration[]
486
-     * @throws EE_Error
487
-     * @throws InvalidArgumentException
488
-     * @throws InvalidDataTypeException
489
-     * @throws InvalidInterfaceException
490
-     * @throws ReflectionException
491
-     */
492
-    public function registrations($query_params = array(), $get_cached = false)
493
-    {
494
-        $query_params = (empty($query_params) || ! is_array($query_params))
495
-            ? array(
496
-                'order_by' => array(
497
-                    'Event.EVT_name'     => 'ASC',
498
-                    'Attendee.ATT_lname' => 'ASC',
499
-                    'Attendee.ATT_fname' => 'ASC',
500
-                ),
501
-            )
502
-            : $query_params;
503
-        $query_params = $get_cached ? array() : $query_params;
504
-        return $this->get_many_related('Registration', $query_params);
505
-    }
506
-
507
-
508
-    /**
509
-     * Gets all the attendees for this transaction (handy for use with EE_Attendee's get_registrations_for_event
510
-     * function for getting attendees and how many registrations they each have for an event)
511
-     *
512
-     * @return mixed EE_Attendee[] by default, int if $output is set to 'COUNT'
513
-     * @throws EE_Error
514
-     * @throws InvalidArgumentException
515
-     * @throws InvalidDataTypeException
516
-     * @throws InvalidInterfaceException
517
-     * @throws ReflectionException
518
-     */
519
-    public function attendees()
520
-    {
521
-        return $this->get_many_related('Attendee', array(array('Registration.Transaction.TXN_ID' => $this->ID())));
522
-    }
523
-
524
-
525
-    /**
526
-     * Gets payments for this transaction. Unlike other such functions, order by 'DESC' by default
527
-     *
528
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
529
-     * @return EE_Base_Class[]|EE_Payment[]
530
-     * @throws EE_Error
531
-     * @throws InvalidArgumentException
532
-     * @throws InvalidDataTypeException
533
-     * @throws InvalidInterfaceException
534
-     * @throws ReflectionException
535
-     */
536
-    public function payments($query_params = array())
537
-    {
538
-        return $this->get_many_related('Payment', $query_params);
539
-    }
540
-
541
-
542
-    /**
543
-     * gets only approved payments for this transaction
544
-     *
545
-     * @return EE_Base_Class[]|EE_Payment[]
546
-     * @throws EE_Error
547
-     * @throws InvalidArgumentException
548
-     * @throws ReflectionException
549
-     * @throws InvalidDataTypeException
550
-     * @throws InvalidInterfaceException
551
-     */
552
-    public function approved_payments()
553
-    {
554
-        EE_Registry::instance()->load_model('Payment');
555
-        return $this->get_many_related(
556
-            'Payment',
557
-            array(
558
-                array('STS_ID' => EEM_Payment::status_id_approved),
559
-                'order_by' => array('PAY_timestamp' => 'DESC'),
560
-            )
561
-        );
562
-    }
563
-
564
-
565
-    /**
566
-     * Gets all payments which have not been approved
567
-     *
568
-     * @return EE_Base_Class[]|EEI_Payment[]
569
-     * @throws EE_Error if a model is misconfigured somehow
570
-     * @throws InvalidArgumentException
571
-     * @throws InvalidDataTypeException
572
-     * @throws InvalidInterfaceException
573
-     * @throws ReflectionException
574
-     */
575
-    public function pending_payments()
576
-    {
577
-        return $this->get_many_related(
578
-            'Payment',
579
-            array(
580
-                array(
581
-                    'STS_ID' => EEM_Payment::status_id_pending,
582
-                ),
583
-                'order_by' => array(
584
-                    'PAY_timestamp' => 'DESC',
585
-                ),
586
-            )
587
-        );
588
-    }
589
-
590
-
591
-    /**
592
-     * echoes $this->pretty_status()
593
-     *
594
-     * @param bool $show_icons
595
-     * @throws EE_Error
596
-     * @throws InvalidArgumentException
597
-     * @throws InvalidDataTypeException
598
-     * @throws InvalidInterfaceException
599
-     * @throws ReflectionException
600
-     */
601
-    public function e_pretty_status($show_icons = false)
602
-    {
603
-        echo wp_kses($this->pretty_status($show_icons), AllowedTags::getAllowedTags());
604
-    }
605
-
606
-
607
-    /**
608
-     * returns a pretty version of the status, good for displaying to users
609
-     *
610
-     * @param bool $show_icons
611
-     * @return string
612
-     * @throws EE_Error
613
-     * @throws InvalidArgumentException
614
-     * @throws InvalidDataTypeException
615
-     * @throws InvalidInterfaceException
616
-     * @throws ReflectionException
617
-     */
618
-    public function pretty_status($show_icons = false)
619
-    {
620
-        $status = EEM_Status::instance()->localized_status(
621
-            array($this->status_ID() => esc_html__('unknown', 'event_espresso')),
622
-            false,
623
-            'sentence'
624
-        );
625
-        $icon = '';
626
-        switch ($this->status_ID()) {
627
-            case EEM_Transaction::complete_status_code:
628
-                $icon = $show_icons ? '<span class="dashicons dashicons-yes ee-icon-size-24 green-text"></span>' : '';
629
-                break;
630
-            case EEM_Transaction::incomplete_status_code:
631
-                $icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 lt-blue-text"></span>'
632
-                    : '';
633
-                break;
634
-            case EEM_Transaction::abandoned_status_code:
635
-                $icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 red-text"></span>' : '';
636
-                break;
637
-            case EEM_Transaction::failed_status_code:
638
-                $icon = $show_icons ? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>' : '';
639
-                break;
640
-            case EEM_Transaction::overpaid_status_code:
641
-                $icon = $show_icons ? '<span class="dashicons dashicons-plus ee-icon-size-16 orange-text"></span>' : '';
642
-                break;
643
-        }
644
-        return $icon . $status[ $this->status_ID() ];
645
-    }
646
-
647
-
648
-    /**
649
-     * get Transaction Status
650
-     *
651
-     * @return mixed
652
-     * @throws EE_Error
653
-     * @throws InvalidArgumentException
654
-     * @throws InvalidDataTypeException
655
-     * @throws InvalidInterfaceException
656
-     * @throws ReflectionException
657
-     */
658
-    public function status_ID()
659
-    {
660
-        return $this->get('STS_ID');
661
-    }
662
-
663
-
664
-    /**
665
-     * Returns TRUE or FALSE for whether or not this transaction cost any money
666
-     *
667
-     * @return boolean
668
-     * @throws EE_Error
669
-     * @throws InvalidArgumentException
670
-     * @throws InvalidDataTypeException
671
-     * @throws InvalidInterfaceException
672
-     * @throws ReflectionException
673
-     */
674
-    public function is_free()
675
-    {
676
-        return EEH_Money::compare_floats($this->get('TXN_total'), 0, '==');
677
-    }
678
-
679
-
680
-    /**
681
-     * Returns whether this transaction is complete
682
-     * Useful in templates and other logic for deciding if we should ask for another payment...
683
-     *
684
-     * @return boolean
685
-     * @throws EE_Error
686
-     * @throws InvalidArgumentException
687
-     * @throws InvalidDataTypeException
688
-     * @throws InvalidInterfaceException
689
-     * @throws ReflectionException
690
-     */
691
-    public function is_completed()
692
-    {
693
-        return $this->status_ID() === EEM_Transaction::complete_status_code;
694
-    }
695
-
696
-
697
-    /**
698
-     * Returns whether this transaction is incomplete
699
-     * Useful in templates and other logic for deciding if we should ask for another payment...
700
-     *
701
-     * @return boolean
702
-     * @throws EE_Error
703
-     * @throws InvalidArgumentException
704
-     * @throws InvalidDataTypeException
705
-     * @throws InvalidInterfaceException
706
-     * @throws ReflectionException
707
-     */
708
-    public function is_incomplete()
709
-    {
710
-        return $this->status_ID() === EEM_Transaction::incomplete_status_code;
711
-    }
712
-
713
-
714
-    /**
715
-     * Returns whether this transaction is overpaid
716
-     * Useful in templates and other logic for deciding if monies need to be refunded
717
-     *
718
-     * @return boolean
719
-     * @throws EE_Error
720
-     * @throws InvalidArgumentException
721
-     * @throws InvalidDataTypeException
722
-     * @throws InvalidInterfaceException
723
-     * @throws ReflectionException
724
-     */
725
-    public function is_overpaid()
726
-    {
727
-        return $this->status_ID() === EEM_Transaction::overpaid_status_code;
728
-    }
729
-
730
-
731
-    /**
732
-     * Returns whether this transaction was abandoned
733
-     * meaning that the transaction/registration process was somehow interrupted and never completed
734
-     * but that contact information exists for at least one registrant
735
-     *
736
-     * @return boolean
737
-     * @throws EE_Error
738
-     * @throws InvalidArgumentException
739
-     * @throws InvalidDataTypeException
740
-     * @throws InvalidInterfaceException
741
-     * @throws ReflectionException
742
-     */
743
-    public function is_abandoned()
744
-    {
745
-        return $this->status_ID() === EEM_Transaction::abandoned_status_code;
746
-    }
747
-
748
-
749
-    /**
750
-     * Returns whether this transaction failed
751
-     * meaning that the transaction/registration process was somehow interrupted and never completed
752
-     * and that NO contact information exists for any registrants
753
-     *
754
-     * @return boolean
755
-     * @throws EE_Error
756
-     * @throws InvalidArgumentException
757
-     * @throws InvalidDataTypeException
758
-     * @throws InvalidInterfaceException
759
-     * @throws ReflectionException
760
-     */
761
-    public function failed()
762
-    {
763
-        return $this->status_ID() === EEM_Transaction::failed_status_code;
764
-    }
765
-
766
-
767
-    /**
768
-     * This returns the url for the invoice of this transaction
769
-     *
770
-     * @param string $type 'html' or 'pdf' (default is pdf)
771
-     * @return string
772
-     * @throws EE_Error
773
-     * @throws InvalidArgumentException
774
-     * @throws InvalidDataTypeException
775
-     * @throws InvalidInterfaceException
776
-     * @throws ReflectionException
777
-     */
778
-    public function invoice_url($type = 'html')
779
-    {
780
-        $REG = $this->primary_registration();
781
-        if (! $REG instanceof EE_Registration) {
782
-            return '';
783
-        }
784
-        return $REG->invoice_url($type);
785
-    }
786
-
787
-
788
-    /**
789
-     * Gets the primary registration only
790
-     *
791
-     * @return EE_Base_Class|EE_Registration
792
-     * @throws EE_Error
793
-     * @throws InvalidArgumentException
794
-     * @throws InvalidDataTypeException
795
-     * @throws InvalidInterfaceException
796
-     * @throws ReflectionException
797
-     */
798
-    public function primary_registration()
799
-    {
800
-        $registrations = (array) $this->get_many_related(
801
-            'Registration',
802
-            array(array('REG_count' => EEM_Registration::PRIMARY_REGISTRANT_COUNT))
803
-        );
804
-        foreach ($registrations as $registration) {
805
-            // valid registration that is NOT cancelled or declined ?
806
-            if (
807
-                $registration instanceof EE_Registration
808
-                && ! in_array($registration->status_ID(), EEM_Registration::closed_reg_statuses(), true)
809
-            ) {
810
-                return $registration;
811
-            }
812
-        }
813
-        // nothing valid found, so just return first thing from array of results
814
-        return reset($registrations);
815
-    }
816
-
817
-
818
-    /**
819
-     * Gets the URL for viewing the receipt
820
-     *
821
-     * @param string $type 'pdf' or 'html' (default is 'html')
822
-     * @return string
823
-     * @throws EE_Error
824
-     * @throws InvalidArgumentException
825
-     * @throws InvalidDataTypeException
826
-     * @throws InvalidInterfaceException
827
-     * @throws ReflectionException
828
-     */
829
-    public function receipt_url($type = 'html')
830
-    {
831
-        $REG = $this->primary_registration();
832
-        if (! $REG instanceof EE_Registration) {
833
-            return '';
834
-        }
835
-        return $REG->receipt_url($type);
836
-    }
837
-
838
-
839
-    /**
840
-     * Gets the URL of the thank you page with this registration REG_url_link added as
841
-     * a query parameter
842
-     *
843
-     * @return string
844
-     * @throws EE_Error
845
-     * @throws InvalidArgumentException
846
-     * @throws InvalidDataTypeException
847
-     * @throws InvalidInterfaceException
848
-     * @throws ReflectionException
849
-     */
850
-    public function payment_overview_url()
851
-    {
852
-        $primary_registration = $this->primary_registration();
853
-        return $primary_registration instanceof EE_Registration ? $primary_registration->payment_overview_url() : false;
854
-    }
855
-
856
-
857
-    /**
858
-     * @return string
859
-     * @throws EE_Error
860
-     * @throws InvalidArgumentException
861
-     * @throws InvalidDataTypeException
862
-     * @throws InvalidInterfaceException
863
-     * @throws ReflectionException
864
-     */
865
-    public function gateway_response_on_transaction()
866
-    {
867
-        $payment = $this->get_first_related('Payment');
868
-        return $payment instanceof EE_Payment ? $payment->gateway_response() : '';
869
-    }
870
-
871
-
872
-    /**
873
-     * Get the status object of this object
874
-     *
875
-     * @return EE_Base_Class|EE_Status
876
-     * @throws EE_Error
877
-     * @throws InvalidArgumentException
878
-     * @throws InvalidDataTypeException
879
-     * @throws InvalidInterfaceException
880
-     * @throws ReflectionException
881
-     */
882
-    public function status_obj()
883
-    {
884
-        return $this->get_first_related('Status');
885
-    }
886
-
887
-
888
-    /**
889
-     * Gets all the extra meta info on this payment
890
-     *
891
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
892
-     * @return EE_Base_Class[]|EE_Extra_Meta
893
-     * @throws EE_Error
894
-     * @throws InvalidArgumentException
895
-     * @throws InvalidDataTypeException
896
-     * @throws InvalidInterfaceException
897
-     * @throws ReflectionException
898
-     */
899
-    public function extra_meta($query_params = array())
900
-    {
901
-        return $this->get_many_related('Extra_Meta', $query_params);
902
-    }
903
-
904
-
905
-    /**
906
-     * Wrapper for _add_relation_to
907
-     *
908
-     * @param EE_Registration $registration
909
-     * @return EE_Base_Class the relation was added to
910
-     * @throws EE_Error
911
-     * @throws InvalidArgumentException
912
-     * @throws InvalidDataTypeException
913
-     * @throws InvalidInterfaceException
914
-     * @throws ReflectionException
915
-     */
916
-    public function add_registration(EE_Registration $registration)
917
-    {
918
-        return $this->_add_relation_to($registration, 'Registration');
919
-    }
920
-
921
-
922
-    /**
923
-     * Removes the given registration from being related (even before saving this transaction).
924
-     * If an ID/index is provided and this transaction isn't saved yet, removes it from list of cached relations
925
-     *
926
-     * @param int $registration_or_id
927
-     * @return EE_Base_Class that was removed from being related
928
-     * @throws EE_Error
929
-     * @throws InvalidArgumentException
930
-     * @throws InvalidDataTypeException
931
-     * @throws InvalidInterfaceException
932
-     * @throws ReflectionException
933
-     */
934
-    public function remove_registration_with_id($registration_or_id)
935
-    {
936
-        return $this->_remove_relation_to($registration_or_id, 'Registration');
937
-    }
938
-
939
-
940
-    /**
941
-     * Gets all the line items which are for ACTUAL items
942
-     *
943
-     * @return EE_Line_Item[]
944
-     * @throws EE_Error
945
-     * @throws InvalidArgumentException
946
-     * @throws InvalidDataTypeException
947
-     * @throws InvalidInterfaceException
948
-     * @throws ReflectionException
949
-     */
950
-    public function items_purchased()
951
-    {
952
-        return $this->line_items(array(array('LIN_type' => EEM_Line_Item::type_line_item)));
953
-    }
954
-
955
-
956
-    /**
957
-     * Wrapper for _add_relation_to
958
-     *
959
-     * @param EE_Line_Item $line_item
960
-     * @return EE_Base_Class the relation was added to
961
-     * @throws EE_Error
962
-     * @throws InvalidArgumentException
963
-     * @throws InvalidDataTypeException
964
-     * @throws InvalidInterfaceException
965
-     * @throws ReflectionException
966
-     */
967
-    public function add_line_item(EE_Line_Item $line_item)
968
-    {
969
-        return $this->_add_relation_to($line_item, 'Line_Item');
970
-    }
971
-
972
-
973
-    /**
974
-     * Gets ALL the line items related to this transaction (unstructured)
975
-     *
976
-     * @param array $query_params
977
-     * @return EE_Base_Class[]|EE_Line_Item[]
978
-     * @throws EE_Error
979
-     * @throws InvalidArgumentException
980
-     * @throws InvalidDataTypeException
981
-     * @throws InvalidInterfaceException
982
-     * @throws ReflectionException
983
-     */
984
-    public function line_items($query_params = array())
985
-    {
986
-        return $this->get_many_related('Line_Item', $query_params);
987
-    }
988
-
989
-
990
-    /**
991
-     * Gets all the line items which are taxes on the total
992
-     *
993
-     * @return EE_Line_Item[]
994
-     * @throws EE_Error
995
-     * @throws InvalidArgumentException
996
-     * @throws InvalidDataTypeException
997
-     * @throws InvalidInterfaceException
998
-     * @throws ReflectionException
999
-     */
1000
-    public function tax_items()
1001
-    {
1002
-        return $this->line_items(array(array('LIN_type' => EEM_Line_Item::type_tax)));
1003
-    }
1004
-
1005
-
1006
-    /**
1007
-     * Gets the total line item (which is a parent of all other related line items,
1008
-     * meaning it takes them all into account on its total)
1009
-     *
1010
-     * @param bool $create_if_not_found
1011
-     * @return \EE_Line_Item
1012
-     * @throws EE_Error
1013
-     * @throws InvalidArgumentException
1014
-     * @throws InvalidDataTypeException
1015
-     * @throws InvalidInterfaceException
1016
-     * @throws ReflectionException
1017
-     */
1018
-    public function total_line_item($create_if_not_found = true)
1019
-    {
1020
-        $item = $this->get_first_related('Line_Item', array(array('LIN_type' => EEM_Line_Item::type_total)));
1021
-        if (! $item && $create_if_not_found) {
1022
-            $item = EEH_Line_Item::create_total_line_item($this);
1023
-        }
1024
-        return $item;
1025
-    }
1026
-
1027
-
1028
-    /**
1029
-     * Returns the total amount of tax on this transaction
1030
-     * (assumes there's only one tax subtotal line item)
1031
-     *
1032
-     * @return float
1033
-     * @throws EE_Error
1034
-     * @throws InvalidArgumentException
1035
-     * @throws InvalidDataTypeException
1036
-     * @throws InvalidInterfaceException
1037
-     * @throws ReflectionException
1038
-     */
1039
-    public function tax_total()
1040
-    {
1041
-        $tax_line_item = $this->tax_total_line_item();
1042
-        if ($tax_line_item) {
1043
-            return (float) $tax_line_item->total();
1044
-        }
1045
-        return (float) 0;
1046
-    }
1047
-
1048
-
1049
-    /**
1050
-     * Gets the tax subtotal line item (assumes there's only one)
1051
-     *
1052
-     * @return EE_Line_Item
1053
-     * @throws EE_Error
1054
-     * @throws InvalidArgumentException
1055
-     * @throws InvalidDataTypeException
1056
-     * @throws InvalidInterfaceException
1057
-     * @throws ReflectionException
1058
-     */
1059
-    public function tax_total_line_item()
1060
-    {
1061
-        return EEH_Line_Item::get_taxes_subtotal($this->total_line_item());
1062
-    }
1063
-
1064
-
1065
-    /**
1066
-     * Gets the array of billing info for the gateway and for this transaction's primary registration's attendee.
1067
-     *
1068
-     * @return EE_Form_Section_Proper
1069
-     * @throws EE_Error
1070
-     * @throws InvalidArgumentException
1071
-     * @throws InvalidDataTypeException
1072
-     * @throws InvalidInterfaceException
1073
-     * @throws ReflectionException
1074
-     */
1075
-    public function billing_info()
1076
-    {
1077
-        $payment_method = $this->payment_method();
1078
-        if (! $payment_method) {
1079
-            EE_Error::add_error(
1080
-                esc_html__(
1081
-                    'Could not find billing info for transaction because no gateway has been used for it yet',
1082
-                    'event_espresso'
1083
-                ),
1084
-                __FILE__,
1085
-                __FUNCTION__,
1086
-                __LINE__
1087
-            );
1088
-            return null;
1089
-        }
1090
-        $primary_reg = $this->primary_registration();
1091
-        if (! $primary_reg) {
1092
-            EE_Error::add_error(
1093
-                esc_html__(
1094
-                    'Cannot get billing info for gateway %s on transaction because no primary registration exists',
1095
-                    'event_espresso'
1096
-                ),
1097
-                __FILE__,
1098
-                __FUNCTION__,
1099
-                __LINE__
1100
-            );
1101
-            return null;
1102
-        }
1103
-        $attendee = $primary_reg->attendee();
1104
-        if (! $attendee) {
1105
-            EE_Error::add_error(
1106
-                esc_html__(
1107
-                    'Cannot get billing info for gateway %s on transaction because the primary registration has no attendee exists',
1108
-                    'event_espresso'
1109
-                ),
1110
-                __FILE__,
1111
-                __FUNCTION__,
1112
-                __LINE__
1113
-            );
1114
-            return null;
1115
-        }
1116
-        return $attendee->billing_info_for_payment_method($payment_method);
1117
-    }
1118
-
1119
-
1120
-    /**
1121
-     * Gets PMD_ID
1122
-     *
1123
-     * @return int
1124
-     * @throws EE_Error
1125
-     * @throws InvalidArgumentException
1126
-     * @throws InvalidDataTypeException
1127
-     * @throws InvalidInterfaceException
1128
-     * @throws ReflectionException
1129
-     */
1130
-    public function payment_method_ID()
1131
-    {
1132
-        return $this->get('PMD_ID');
1133
-    }
1134
-
1135
-
1136
-    /**
1137
-     * Sets PMD_ID
1138
-     *
1139
-     * @param int $PMD_ID
1140
-     * @throws EE_Error
1141
-     * @throws InvalidArgumentException
1142
-     * @throws InvalidDataTypeException
1143
-     * @throws InvalidInterfaceException
1144
-     * @throws ReflectionException
1145
-     */
1146
-    public function set_payment_method_ID($PMD_ID)
1147
-    {
1148
-        $this->set('PMD_ID', $PMD_ID);
1149
-    }
1150
-
1151
-
1152
-    /**
1153
-     * Gets the last-used payment method on this transaction
1154
-     * (we COULD just use the last-made payment, but some payment methods, namely
1155
-     * offline ones, dont' create payments)
1156
-     *
1157
-     * @return EE_Payment_Method
1158
-     * @throws EE_Error
1159
-     * @throws InvalidArgumentException
1160
-     * @throws InvalidDataTypeException
1161
-     * @throws InvalidInterfaceException
1162
-     * @throws ReflectionException
1163
-     */
1164
-    public function payment_method()
1165
-    {
1166
-        $pm = $this->get_first_related('Payment_Method');
1167
-        if ($pm instanceof EE_Payment_Method) {
1168
-            return $pm;
1169
-        }
1170
-        $last_payment = $this->last_payment();
1171
-        if ($last_payment instanceof EE_Payment && $last_payment->payment_method()) {
1172
-            return $last_payment->payment_method();
1173
-        }
1174
-        return null;
1175
-    }
1176
-
1177
-
1178
-    /**
1179
-     * Gets the last payment made
1180
-     *
1181
-     * @return EE_Base_Class|EE_Payment
1182
-     * @throws EE_Error
1183
-     * @throws InvalidArgumentException
1184
-     * @throws InvalidDataTypeException
1185
-     * @throws InvalidInterfaceException
1186
-     * @throws ReflectionException
1187
-     */
1188
-    public function last_payment()
1189
-    {
1190
-        return $this->get_first_related('Payment', array('order_by' => array('PAY_ID' => 'desc')));
1191
-    }
1192
-
1193
-
1194
-    /**
1195
-     * Gets all the line items which are unrelated to tickets on this transaction
1196
-     *
1197
-     * @return EE_Line_Item[]
1198
-     * @throws EE_Error
1199
-     * @throws InvalidArgumentException
1200
-     * @throws InvalidDataTypeException
1201
-     * @throws InvalidInterfaceException
1202
-     * @throws ReflectionException
1203
-     */
1204
-    public function non_ticket_line_items()
1205
-    {
1206
-        return EEM_Line_Item::instance()->get_all_non_ticket_line_items_for_transaction($this->ID());
1207
-    }
1208
-
1209
-
1210
-    /**
1211
-     * possibly toggles TXN status
1212
-     *
1213
-     * @param  boolean $update whether to save the TXN
1214
-     * @return bool whether the TXN was saved
1215
-     * @throws EE_Error
1216
-     * @throws InvalidArgumentException
1217
-     * @throws InvalidDataTypeException
1218
-     * @throws InvalidInterfaceException
1219
-     * @throws ReflectionException
1220
-     * @throws RuntimeException
1221
-     */
1222
-    public function update_status_based_on_total_paid($update = true)
1223
-    {
1224
-        // set transaction status based on comparison of TXN_paid vs TXN_total
1225
-        if (EEH_Money::compare_floats($this->paid(), $this->total(), '>')) {
1226
-            $new_txn_status = EEM_Transaction::overpaid_status_code;
1227
-        } elseif (EEH_Money::compare_floats($this->paid(), $this->total())) {
1228
-            $new_txn_status = EEM_Transaction::complete_status_code;
1229
-        } elseif (EEH_Money::compare_floats($this->paid(), $this->total(), '<')) {
1230
-            $new_txn_status = EEM_Transaction::incomplete_status_code;
1231
-        } else {
1232
-            throw new RuntimeException(
1233
-                esc_html__('The total paid calculation for this transaction is inaccurate.', 'event_espresso')
1234
-            );
1235
-        }
1236
-        if ($new_txn_status !== $this->status_ID()) {
1237
-            $this->set_status($new_txn_status);
1238
-            if ($update) {
1239
-                return $this->save() ? true : false;
1240
-            }
1241
-        }
1242
-        return false;
1243
-    }
1244
-
1245
-
1246
-    /**
1247
-     * Updates the transaction's status and total_paid based on all the payments
1248
-     * that apply to it
1249
-     *
1250
-     * @deprecated
1251
-     * @return array|bool
1252
-     * @throws EE_Error
1253
-     * @throws InvalidArgumentException
1254
-     * @throws ReflectionException
1255
-     * @throws InvalidDataTypeException
1256
-     * @throws InvalidInterfaceException
1257
-     */
1258
-    public function update_based_on_payments()
1259
-    {
1260
-        EE_Error::doing_it_wrong(
1261
-            __CLASS__ . '::' . __FUNCTION__,
1262
-            sprintf(
1263
-                esc_html__('This method is deprecated. Please use "%s" instead', 'event_espresso'),
1264
-                'EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()'
1265
-            ),
1266
-            '4.6.0'
1267
-        );
1268
-        /** @type EE_Transaction_Processor $transaction_processor */
1269
-        $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
1270
-        return $transaction_processor->update_transaction_and_registrations_after_checkout_or_payment($this);
1271
-    }
1272
-
1273
-
1274
-    /**
1275
-     * @return string
1276
-     */
1277
-    public function old_txn_status()
1278
-    {
1279
-        return $this->_old_txn_status;
1280
-    }
1281
-
1282
-
1283
-    /**
1284
-     * @param string $old_txn_status
1285
-     */
1286
-    public function set_old_txn_status($old_txn_status)
1287
-    {
1288
-        // only set the first time
1289
-        if ($this->_old_txn_status === null) {
1290
-            $this->_old_txn_status = $old_txn_status;
1291
-        }
1292
-    }
1293
-
1294
-
1295
-    /**
1296
-     * reg_status_updated
1297
-     *
1298
-     * @return bool
1299
-     * @throws EE_Error
1300
-     * @throws InvalidArgumentException
1301
-     * @throws InvalidDataTypeException
1302
-     * @throws InvalidInterfaceException
1303
-     * @throws ReflectionException
1304
-     */
1305
-    public function txn_status_updated()
1306
-    {
1307
-        return $this->status_ID() !== $this->_old_txn_status && $this->_old_txn_status !== null;
1308
-    }
1309
-
1310
-
1311
-    /**
1312
-     * _reg_steps_completed
1313
-     * if $check_all is TRUE, then returns TRUE if ALL reg steps have been marked as completed,
1314
-     * if a $reg_step_slug is provided, then this step will be skipped when testing for completion
1315
-     * if $check_all is FALSE and a $reg_step_slug is provided, then ONLY that reg step will be tested for completion
1316
-     *
1317
-     * @param string $reg_step_slug
1318
-     * @param bool   $check_all
1319
-     * @return bool|int
1320
-     * @throws EE_Error
1321
-     * @throws InvalidArgumentException
1322
-     * @throws InvalidDataTypeException
1323
-     * @throws InvalidInterfaceException
1324
-     * @throws ReflectionException
1325
-     */
1326
-    private function _reg_steps_completed($reg_step_slug = '', $check_all = true)
1327
-    {
1328
-        $reg_steps = $this->reg_steps();
1329
-        if (! is_array($reg_steps) || empty($reg_steps)) {
1330
-            return false;
1331
-        }
1332
-        // loop thru reg steps array)
1333
-        foreach ($reg_steps as $slug => $reg_step_completed) {
1334
-            // if NOT checking ALL steps (only checking one step)
1335
-            if (! $check_all) {
1336
-                // and this is the one
1337
-                if ($slug === $reg_step_slug) {
1338
-                    return $reg_step_completed;
1339
-                }
1340
-                // skip to next reg step in loop
1341
-                continue;
1342
-            }
1343
-            // $check_all must be true, else we would never have gotten to this point
1344
-            if ($slug === $reg_step_slug) {
1345
-                // if we reach this point, then we are testing either:
1346
-                // all_reg_steps_completed_except() or
1347
-                // all_reg_steps_completed_except_final_step(),
1348
-                // and since this is the reg step EXCEPTION being tested
1349
-                // we want to return true (yes true) if this reg step is NOT completed
1350
-                // ie: "is everything completed except the final step?"
1351
-                // "that is correct... the final step is not completed, but all others are."
1352
-                return $reg_step_completed !== true;
1353
-            }
1354
-            if ($reg_step_completed !== true) {
1355
-                // if any reg step is NOT completed, then ALL steps are not completed
1356
-                return false;
1357
-            }
1358
-        }
1359
-        return true;
1360
-    }
1361
-
1362
-
1363
-    /**
1364
-     * all_reg_steps_completed
1365
-     * returns:
1366
-     *    true if ALL reg steps have been marked as completed
1367
-     *        or false if any step is not completed
1368
-     *
1369
-     * @return bool
1370
-     * @throws EE_Error
1371
-     * @throws InvalidArgumentException
1372
-     * @throws InvalidDataTypeException
1373
-     * @throws InvalidInterfaceException
1374
-     * @throws ReflectionException
1375
-     */
1376
-    public function all_reg_steps_completed()
1377
-    {
1378
-        return $this->_reg_steps_completed();
1379
-    }
1380
-
1381
-
1382
-    /**
1383
-     * all_reg_steps_completed_except
1384
-     * returns:
1385
-     *        true if ALL reg steps, except a particular step that you wish to skip over, have been marked as completed
1386
-     *        or false if any other step is not completed
1387
-     *        or false if ALL steps are completed including the exception you are testing !!!
1388
-     *
1389
-     * @param string $exception
1390
-     * @return bool
1391
-     * @throws EE_Error
1392
-     * @throws InvalidArgumentException
1393
-     * @throws InvalidDataTypeException
1394
-     * @throws InvalidInterfaceException
1395
-     * @throws ReflectionException
1396
-     */
1397
-    public function all_reg_steps_completed_except($exception = '')
1398
-    {
1399
-        return $this->_reg_steps_completed($exception);
1400
-    }
1401
-
1402
-
1403
-    /**
1404
-     * all_reg_steps_completed_except
1405
-     * returns:
1406
-     *        true if ALL reg steps, except the final step, have been marked as completed
1407
-     *        or false if any step is not completed
1408
-     *    or false if ALL steps are completed including the final step !!!
1409
-     *
1410
-     * @return bool
1411
-     * @throws EE_Error
1412
-     * @throws InvalidArgumentException
1413
-     * @throws InvalidDataTypeException
1414
-     * @throws InvalidInterfaceException
1415
-     * @throws ReflectionException
1416
-     */
1417
-    public function all_reg_steps_completed_except_final_step()
1418
-    {
1419
-        return $this->_reg_steps_completed('finalize_registration');
1420
-    }
1421
-
1422
-
1423
-    /**
1424
-     * reg_step_completed
1425
-     * returns:
1426
-     *    true if a specific reg step has been marked as completed
1427
-     *    a Unix timestamp if it has been initialized but not yet completed,
1428
-     *    or false if it has not yet been initialized
1429
-     *
1430
-     * @param string $reg_step_slug
1431
-     * @return bool|int
1432
-     * @throws EE_Error
1433
-     * @throws InvalidArgumentException
1434
-     * @throws InvalidDataTypeException
1435
-     * @throws InvalidInterfaceException
1436
-     * @throws ReflectionException
1437
-     */
1438
-    public function reg_step_completed($reg_step_slug)
1439
-    {
1440
-        return $this->_reg_steps_completed($reg_step_slug, false);
1441
-    }
1442
-
1443
-
1444
-    /**
1445
-     * completed_final_reg_step
1446
-     * returns:
1447
-     *    true if the finalize_registration reg step has been marked as completed
1448
-     *    a Unix timestamp if it has been initialized but not yet completed,
1449
-     *    or false if it has not yet been initialized
1450
-     *
1451
-     * @return bool|int
1452
-     * @throws EE_Error
1453
-     * @throws InvalidArgumentException
1454
-     * @throws InvalidDataTypeException
1455
-     * @throws InvalidInterfaceException
1456
-     * @throws ReflectionException
1457
-     */
1458
-    public function final_reg_step_completed()
1459
-    {
1460
-        return $this->_reg_steps_completed('finalize_registration', false);
1461
-    }
1462
-
1463
-
1464
-    /**
1465
-     * set_reg_step_initiated
1466
-     * given a valid TXN_reg_step, this sets it's value to a unix timestamp
1467
-     *
1468
-     * @param string $reg_step_slug
1469
-     * @return boolean
1470
-     * @throws EE_Error
1471
-     * @throws InvalidArgumentException
1472
-     * @throws InvalidDataTypeException
1473
-     * @throws InvalidInterfaceException
1474
-     * @throws ReflectionException
1475
-     */
1476
-    public function set_reg_step_initiated($reg_step_slug)
1477
-    {
1478
-        return $this->_set_reg_step_completed_status($reg_step_slug, time());
1479
-    }
1480
-
1481
-
1482
-    /**
1483
-     * set_reg_step_completed
1484
-     * given a valid TXN_reg_step, this sets the step as completed
1485
-     *
1486
-     * @param string $reg_step_slug
1487
-     * @return boolean
1488
-     * @throws EE_Error
1489
-     * @throws InvalidArgumentException
1490
-     * @throws InvalidDataTypeException
1491
-     * @throws InvalidInterfaceException
1492
-     * @throws ReflectionException
1493
-     */
1494
-    public function set_reg_step_completed($reg_step_slug)
1495
-    {
1496
-        return $this->_set_reg_step_completed_status($reg_step_slug, true);
1497
-    }
1498
-
1499
-
1500
-    /**
1501
-     * set_reg_step_completed
1502
-     * given a valid TXN_reg_step slug, this sets the step as NOT completed
1503
-     *
1504
-     * @param string $reg_step_slug
1505
-     * @return boolean
1506
-     * @throws EE_Error
1507
-     * @throws InvalidArgumentException
1508
-     * @throws InvalidDataTypeException
1509
-     * @throws InvalidInterfaceException
1510
-     * @throws ReflectionException
1511
-     */
1512
-    public function set_reg_step_not_completed($reg_step_slug)
1513
-    {
1514
-        return $this->_set_reg_step_completed_status($reg_step_slug, false);
1515
-    }
1516
-
1517
-
1518
-    /**
1519
-     * set_reg_step_completed
1520
-     * given a valid reg step slug, this sets the TXN_reg_step completed status which is either:
1521
-     *
1522
-     * @param  string      $reg_step_slug
1523
-     * @param  boolean|int $status
1524
-     * @return boolean
1525
-     * @throws EE_Error
1526
-     * @throws InvalidArgumentException
1527
-     * @throws InvalidDataTypeException
1528
-     * @throws InvalidInterfaceException
1529
-     * @throws ReflectionException
1530
-     */
1531
-    private function _set_reg_step_completed_status($reg_step_slug, $status)
1532
-    {
1533
-        // validate status
1534
-        $status = is_bool($status) || is_int($status) ? $status : false;
1535
-        // get reg steps array
1536
-        $txn_reg_steps = $this->reg_steps();
1537
-        // if reg step does NOT exist
1538
-        if (! isset($txn_reg_steps[ $reg_step_slug ])) {
1539
-            return false;
1540
-        }
1541
-        // if  we're trying to complete a step that is already completed
1542
-        if ($txn_reg_steps[ $reg_step_slug ] === true) {
1543
-            return true;
1544
-        }
1545
-        // if  we're trying to complete a step that hasn't even started
1546
-        if ($status === true && $txn_reg_steps[ $reg_step_slug ] === false) {
1547
-            return false;
1548
-        }
1549
-        // if current status value matches the incoming value (no change)
1550
-        // type casting as int means values should collapse to either 0, 1, or a timestamp like 1234567890
1551
-        if ((int) $txn_reg_steps[ $reg_step_slug ] === (int) $status) {
1552
-            // this will happen in cases where multiple AJAX requests occur during the same step
1553
-            return true;
1554
-        }
1555
-        // if we're trying to set a start time, but it has already been set...
1556
-        if (is_numeric($status) && is_numeric($txn_reg_steps[ $reg_step_slug ])) {
1557
-            // skip the update below, but don't return FALSE so that errors won't be displayed
1558
-            return true;
1559
-        }
1560
-        // update completed status
1561
-        $txn_reg_steps[ $reg_step_slug ] = $status;
1562
-        $this->set_reg_steps($txn_reg_steps);
1563
-        $this->save();
1564
-        return true;
1565
-    }
1566
-
1567
-
1568
-    /**
1569
-     * remove_reg_step
1570
-     * given a valid TXN_reg_step slug, this will remove (unset)
1571
-     * the reg step from the TXN reg step array
1572
-     *
1573
-     * @param string $reg_step_slug
1574
-     * @return void
1575
-     * @throws EE_Error
1576
-     * @throws InvalidArgumentException
1577
-     * @throws InvalidDataTypeException
1578
-     * @throws InvalidInterfaceException
1579
-     * @throws ReflectionException
1580
-     */
1581
-    public function remove_reg_step($reg_step_slug)
1582
-    {
1583
-        // get reg steps array
1584
-        $txn_reg_steps = $this->reg_steps();
1585
-        unset($txn_reg_steps[ $reg_step_slug ]);
1586
-        $this->set_reg_steps($txn_reg_steps);
1587
-    }
1588
-
1589
-
1590
-    /**
1591
-     * toggle_failed_transaction_status
1592
-     * upgrades a TXNs status from failed to abandoned,
1593
-     * meaning that contact information has been captured for at least one registrant
1594
-     *
1595
-     * @param bool $save
1596
-     * @return bool
1597
-     * @throws EE_Error
1598
-     * @throws InvalidArgumentException
1599
-     * @throws InvalidDataTypeException
1600
-     * @throws InvalidInterfaceException
1601
-     * @throws ReflectionException
1602
-     */
1603
-    public function toggle_failed_transaction_status($save = true)
1604
-    {
1605
-        // if TXN status is still set as "failed"...
1606
-        if ($this->status_ID() === EEM_Transaction::failed_status_code) {
1607
-            $this->set_status(EEM_Transaction::abandoned_status_code);
1608
-            if ($save) {
1609
-                $this->save();
1610
-            }
1611
-            return true;
1612
-        }
1613
-        return false;
1614
-    }
1615
-
1616
-
1617
-    /**
1618
-     * toggle_abandoned_transaction_status
1619
-     * upgrades a TXNs status from failed or abandoned to incomplete
1620
-     *
1621
-     * @return bool
1622
-     * @throws EE_Error
1623
-     * @throws InvalidArgumentException
1624
-     * @throws InvalidDataTypeException
1625
-     * @throws InvalidInterfaceException
1626
-     * @throws ReflectionException
1627
-     */
1628
-    public function toggle_abandoned_transaction_status()
1629
-    {
1630
-        // if TXN status has not been updated already due to a payment, and is still set as "failed" or "abandoned"...
1631
-        $txn_status = $this->status_ID();
1632
-        if (
1633
-            $txn_status === EEM_Transaction::failed_status_code
1634
-            || $txn_status === EEM_Transaction::abandoned_status_code
1635
-        ) {
1636
-            // if a contact record for the primary registrant has been created
1637
-            if (
1638
-                $this->primary_registration() instanceof EE_Registration
1639
-                && $this->primary_registration()->attendee() instanceof EE_Attendee
1640
-            ) {
1641
-                $this->set_status(EEM_Transaction::incomplete_status_code);
1642
-            } else {
1643
-                // no contact record? yer abandoned!
1644
-                $this->set_status(EEM_Transaction::abandoned_status_code);
1645
-            }
1646
-            return true;
1647
-        }
1648
-        return false;
1649
-    }
1650
-
1651
-
1652
-    /**
1653
-     * checks if an Abandoned TXN has any related payments, and if so,
1654
-     * updates the TXN status based on the amount paid
1655
-     *
1656
-     * @throws EE_Error
1657
-     * @throws InvalidDataTypeException
1658
-     * @throws InvalidInterfaceException
1659
-     * @throws InvalidArgumentException
1660
-     * @throws RuntimeException
1661
-     * @throws ReflectionException
1662
-     */
1663
-    public function verify_abandoned_transaction_status()
1664
-    {
1665
-        if ($this->status_ID() !== EEM_Transaction::abandoned_status_code) {
1666
-            return;
1667
-        }
1668
-        $payments = $this->get_many_related('Payment');
1669
-        if (! empty($payments)) {
1670
-            foreach ($payments as $payment) {
1671
-                if ($payment instanceof EE_Payment) {
1672
-                    // kk this TXN should NOT be abandoned
1673
-                    $this->update_status_based_on_total_paid();
1674
-                    if (! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1675
-                        EE_Error::add_attention(
1676
-                            sprintf(
1677
-                                esc_html__(
1678
-                                    '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.',
1679
-                                    'event_espresso'
1680
-                                ),
1681
-                                $this->ID(),
1682
-                                $this->pretty_status()
1683
-                            )
1684
-                        );
1685
-                    }
1686
-                    // get final reg step status
1687
-                    $finalized = $this->final_reg_step_completed();
1688
-                    // if the 'finalize_registration' step has been initiated (has a timestamp)
1689
-                    // but has not yet been fully completed (TRUE)
1690
-                    if (is_int($finalized) && $finalized !== false && $finalized !== true) {
1691
-                        $this->set_reg_step_completed('finalize_registration');
1692
-                        $this->save();
1693
-                    }
1694
-                }
1695
-            }
1696
-        }
1697
-    }
1698
-
1699
-
1700
-    /**
1701
-     * @since 4.10.4.p
1702
-     * @throws EE_Error
1703
-     * @throws InvalidArgumentException
1704
-     * @throws InvalidDataTypeException
1705
-     * @throws InvalidInterfaceException
1706
-     * @throws ReflectionException
1707
-     * @throws RuntimeException
1708
-     */
1709
-    public function recalculateLineItems()
1710
-    {
1711
-        $total_line_item = $this->total_line_item(false);
1712
-        if ($total_line_item instanceof EE_Line_Item) {
1713
-            EEH_Line_Item::resetIsTaxableForTickets($total_line_item);
1714
-            return EEH_Line_Item::apply_taxes($total_line_item, true);
1715
-        }
1716
-        return false;
1717
-    }
17
+	/**
18
+	 * The length of time in seconds that a lock is applied before being considered expired.
19
+	 * It is not long because a transaction should only be locked for the duration of the request that locked it
20
+	 */
21
+	const LOCK_EXPIRATION = 2;
22
+
23
+	/**
24
+	 * txn status upon initial construction.
25
+	 *
26
+	 * @var string
27
+	 */
28
+	protected $_old_txn_status;
29
+
30
+
31
+	/**
32
+	 * @param array  $props_n_values          incoming values
33
+	 * @param string $timezone                incoming timezone
34
+	 *                                        (if not set the timezone set for the website will be used.)
35
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
36
+	 *                                        date_format and the second value is the time format
37
+	 * @return EE_Transaction
38
+	 * @throws EE_Error
39
+	 * @throws InvalidArgumentException
40
+	 * @throws InvalidDataTypeException
41
+	 * @throws InvalidInterfaceException
42
+	 * @throws ReflectionException
43
+	 */
44
+	public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
45
+	{
46
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
47
+		$txn = $has_object
48
+			? $has_object
49
+			: new self($props_n_values, false, $timezone, $date_formats);
50
+		if (! $has_object) {
51
+			$txn->set_old_txn_status($txn->status_ID());
52
+		}
53
+		return $txn;
54
+	}
55
+
56
+
57
+	/**
58
+	 * @param array  $props_n_values  incoming values from the database
59
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
60
+	 *                                the website will be used.
61
+	 * @return EE_Transaction
62
+	 * @throws EE_Error
63
+	 * @throws InvalidArgumentException
64
+	 * @throws InvalidDataTypeException
65
+	 * @throws InvalidInterfaceException
66
+	 * @throws ReflectionException
67
+	 */
68
+	public static function new_instance_from_db($props_n_values = array(), $timezone = null)
69
+	{
70
+		$txn = new self($props_n_values, true, $timezone);
71
+		$txn->set_old_txn_status($txn->status_ID());
72
+		return $txn;
73
+	}
74
+
75
+
76
+	/**
77
+	 * Sets a meta field indicating that this TXN is locked and should not be updated in the db.
78
+	 * If a lock has already been set, then we will attempt to remove it in case it has expired.
79
+	 * If that also fails, then an exception is thrown.
80
+	 *
81
+	 * @throws EE_Error
82
+	 * @throws InvalidArgumentException
83
+	 * @throws InvalidDataTypeException
84
+	 * @throws InvalidInterfaceException
85
+	 * @throws ReflectionException
86
+	 */
87
+	public function lock()
88
+	{
89
+		// attempt to set lock, but if that fails...
90
+		if (! $this->add_extra_meta('lock', time(), true)) {
91
+			// then attempt to remove the lock in case it is expired
92
+			if ($this->_remove_expired_lock()) {
93
+				// if removal was successful, then try setting lock again
94
+				$this->lock();
95
+			} else {
96
+				// but if the lock can not be removed, then throw an exception
97
+				throw new EE_Error(
98
+					sprintf(
99
+						esc_html__(
100
+							'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.',
101
+							'event_espresso'
102
+						),
103
+						$this->ID()
104
+					)
105
+				);
106
+			}
107
+		}
108
+	}
109
+
110
+
111
+	/**
112
+	 * removes transaction lock applied in EE_Transaction::lock()
113
+	 *
114
+	 * @return int
115
+	 * @throws EE_Error
116
+	 * @throws InvalidArgumentException
117
+	 * @throws InvalidDataTypeException
118
+	 * @throws InvalidInterfaceException
119
+	 * @throws ReflectionException
120
+	 */
121
+	public function unlock()
122
+	{
123
+		return $this->delete_extra_meta('lock');
124
+	}
125
+
126
+
127
+	/**
128
+	 * Decides whether or not now is the right time to update the transaction.
129
+	 * This is useful because we don't always know if it is safe to update the transaction
130
+	 * and its related data. why?
131
+	 * because it's possible that the transaction is being used in another
132
+	 * request and could overwrite anything we save.
133
+	 * So we want to only update the txn once we know that won't happen.
134
+	 * We also check that the lock isn't expired, and remove it if it is
135
+	 *
136
+	 * @return boolean
137
+	 * @throws EE_Error
138
+	 * @throws InvalidArgumentException
139
+	 * @throws InvalidDataTypeException
140
+	 * @throws InvalidInterfaceException
141
+	 * @throws ReflectionException
142
+	 */
143
+	public function is_locked()
144
+	{
145
+		// if TXN is not locked, then return false immediately
146
+		if (! $this->_get_lock()) {
147
+			return false;
148
+		}
149
+		// if not, then let's try and remove the lock in case it's expired...
150
+		// _remove_expired_lock() returns 0 when lock is valid (ie: removed = false)
151
+		// and a positive number if the lock was removed (ie: number of locks deleted),
152
+		// so we need to return the opposite
153
+		return ! $this->_remove_expired_lock() ? true : false;
154
+	}
155
+
156
+
157
+	/**
158
+	 * Gets the meta field indicating that this TXN is locked
159
+	 *
160
+	 * @return int
161
+	 * @throws EE_Error
162
+	 * @throws InvalidArgumentException
163
+	 * @throws InvalidDataTypeException
164
+	 * @throws InvalidInterfaceException
165
+	 * @throws ReflectionException
166
+	 */
167
+	protected function _get_lock()
168
+	{
169
+		return (int) $this->get_extra_meta('lock', true, 0);
170
+	}
171
+
172
+
173
+	/**
174
+	 * If the lock on this transaction is expired, then we want to remove it so that the transaction can be updated
175
+	 *
176
+	 * @return int
177
+	 * @throws EE_Error
178
+	 * @throws InvalidArgumentException
179
+	 * @throws InvalidDataTypeException
180
+	 * @throws InvalidInterfaceException
181
+	 * @throws ReflectionException
182
+	 */
183
+	protected function _remove_expired_lock()
184
+	{
185
+		$locked = $this->_get_lock();
186
+		if ($locked && time() - EE_Transaction::LOCK_EXPIRATION > $locked) {
187
+			return $this->unlock();
188
+		}
189
+		return 0;
190
+	}
191
+
192
+
193
+	/**
194
+	 * Set transaction total
195
+	 *
196
+	 * @param float $total total value of transaction
197
+	 * @throws EE_Error
198
+	 * @throws InvalidArgumentException
199
+	 * @throws InvalidDataTypeException
200
+	 * @throws InvalidInterfaceException
201
+	 * @throws ReflectionException
202
+	 */
203
+	public function set_total($total = 0.00)
204
+	{
205
+		$this->set('TXN_total', (float) $total);
206
+	}
207
+
208
+
209
+	/**
210
+	 * Set Total Amount Paid to Date
211
+	 *
212
+	 * @param float $total_paid total amount paid to date (sum of all payments)
213
+	 * @throws EE_Error
214
+	 * @throws InvalidArgumentException
215
+	 * @throws InvalidDataTypeException
216
+	 * @throws InvalidInterfaceException
217
+	 * @throws ReflectionException
218
+	 */
219
+	public function set_paid($total_paid = 0.00)
220
+	{
221
+		$this->set('TXN_paid', (float) $total_paid);
222
+	}
223
+
224
+
225
+	/**
226
+	 * Set transaction status
227
+	 *
228
+	 * @param string $status        whether the transaction is open, declined, accepted,
229
+	 *                              or any number of custom values that can be set
230
+	 * @throws EE_Error
231
+	 * @throws InvalidArgumentException
232
+	 * @throws InvalidDataTypeException
233
+	 * @throws InvalidInterfaceException
234
+	 * @throws ReflectionException
235
+	 */
236
+	public function set_status($status = '')
237
+	{
238
+		$this->set('STS_ID', $status);
239
+	}
240
+
241
+
242
+	/**
243
+	 * Set hash salt
244
+	 *
245
+	 * @param string $hash_salt required for some payment gateways
246
+	 * @throws EE_Error
247
+	 * @throws InvalidArgumentException
248
+	 * @throws InvalidDataTypeException
249
+	 * @throws InvalidInterfaceException
250
+	 * @throws ReflectionException
251
+	 */
252
+	public function set_hash_salt($hash_salt = '')
253
+	{
254
+		$this->set('TXN_hash_salt', $hash_salt);
255
+	}
256
+
257
+
258
+	/**
259
+	 * Sets TXN_reg_steps array
260
+	 *
261
+	 * @param array $txn_reg_steps
262
+	 * @throws EE_Error
263
+	 * @throws InvalidArgumentException
264
+	 * @throws InvalidDataTypeException
265
+	 * @throws InvalidInterfaceException
266
+	 * @throws ReflectionException
267
+	 */
268
+	public function set_reg_steps(array $txn_reg_steps)
269
+	{
270
+		$this->set('TXN_reg_steps', $txn_reg_steps);
271
+	}
272
+
273
+
274
+	/**
275
+	 * Gets TXN_reg_steps
276
+	 *
277
+	 * @return array
278
+	 * @throws EE_Error
279
+	 * @throws InvalidArgumentException
280
+	 * @throws InvalidDataTypeException
281
+	 * @throws InvalidInterfaceException
282
+	 * @throws ReflectionException
283
+	 */
284
+	public function reg_steps()
285
+	{
286
+		$TXN_reg_steps = $this->get('TXN_reg_steps');
287
+		return is_array($TXN_reg_steps) ? (array) $TXN_reg_steps : array();
288
+	}
289
+
290
+
291
+	/**
292
+	 * @return string of transaction's total cost, with currency symbol and decimal
293
+	 * @throws EE_Error
294
+	 * @throws InvalidArgumentException
295
+	 * @throws InvalidDataTypeException
296
+	 * @throws InvalidInterfaceException
297
+	 * @throws ReflectionException
298
+	 */
299
+	public function pretty_total()
300
+	{
301
+		return $this->get_pretty('TXN_total');
302
+	}
303
+
304
+
305
+	/**
306
+	 * Gets the amount paid in a pretty string (formatted and with currency symbol)
307
+	 *
308
+	 * @return string
309
+	 * @throws EE_Error
310
+	 * @throws InvalidArgumentException
311
+	 * @throws InvalidDataTypeException
312
+	 * @throws InvalidInterfaceException
313
+	 * @throws ReflectionException
314
+	 */
315
+	public function pretty_paid()
316
+	{
317
+		return $this->get_pretty('TXN_paid');
318
+	}
319
+
320
+
321
+	/**
322
+	 * calculate the amount remaining for this transaction and return;
323
+	 *
324
+	 * @return float amount remaining
325
+	 * @throws EE_Error
326
+	 * @throws InvalidArgumentException
327
+	 * @throws InvalidDataTypeException
328
+	 * @throws InvalidInterfaceException
329
+	 * @throws ReflectionException
330
+	 */
331
+	public function remaining()
332
+	{
333
+		return $this->total() - $this->paid();
334
+	}
335
+
336
+
337
+	/**
338
+	 * get Transaction Total
339
+	 *
340
+	 * @return float
341
+	 * @throws EE_Error
342
+	 * @throws InvalidArgumentException
343
+	 * @throws InvalidDataTypeException
344
+	 * @throws InvalidInterfaceException
345
+	 * @throws ReflectionException
346
+	 */
347
+	public function total()
348
+	{
349
+		return (float) $this->get('TXN_total');
350
+	}
351
+
352
+
353
+	/**
354
+	 * get Total Amount Paid to Date
355
+	 *
356
+	 * @return float
357
+	 * @throws EE_Error
358
+	 * @throws InvalidArgumentException
359
+	 * @throws InvalidDataTypeException
360
+	 * @throws InvalidInterfaceException
361
+	 * @throws ReflectionException
362
+	 */
363
+	public function paid()
364
+	{
365
+		return (float) $this->get('TXN_paid');
366
+	}
367
+
368
+
369
+	/**
370
+	 * @return mixed|null
371
+	 * @throws EE_Error
372
+	 * @throws InvalidArgumentException
373
+	 * @throws InvalidDataTypeException
374
+	 * @throws InvalidInterfaceException
375
+	 * @throws ReflectionException
376
+	 */
377
+	public function get_cart_session()
378
+	{
379
+		$session_data = (array) $this->get('TXN_session_data');
380
+		return isset($session_data['cart']) && $session_data['cart'] instanceof EE_Cart
381
+			? $session_data['cart']
382
+			: null;
383
+	}
384
+
385
+
386
+	/**
387
+	 * get Transaction session data
388
+	 *
389
+	 * @return array|mixed
390
+	 * @throws EE_Error
391
+	 * @throws InvalidArgumentException
392
+	 * @throws InvalidDataTypeException
393
+	 * @throws InvalidInterfaceException
394
+	 * @throws ReflectionException
395
+	 */
396
+	public function session_data()
397
+	{
398
+		$session_data = $this->get('TXN_session_data');
399
+		if (empty($session_data)) {
400
+			$session_data = array(
401
+				'id'            => null,
402
+				'user_id'       => null,
403
+				'ip_address'    => null,
404
+				'user_agent'    => null,
405
+				'init_access'   => null,
406
+				'last_access'   => null,
407
+				'pages_visited' => array(),
408
+			);
409
+		}
410
+		return $session_data;
411
+	}
412
+
413
+
414
+	/**
415
+	 * Set session data within the TXN object
416
+	 *
417
+	 * @param EE_Session|array $session_data
418
+	 * @throws EE_Error
419
+	 * @throws InvalidArgumentException
420
+	 * @throws InvalidDataTypeException
421
+	 * @throws InvalidInterfaceException
422
+	 * @throws ReflectionException
423
+	 */
424
+	public function set_txn_session_data($session_data)
425
+	{
426
+		if ($session_data instanceof EE_Session) {
427
+			$this->set('TXN_session_data', $session_data->get_session_data(null, true));
428
+		} else {
429
+			$this->set('TXN_session_data', $session_data);
430
+		}
431
+	}
432
+
433
+
434
+	/**
435
+	 * get Transaction hash salt
436
+	 *
437
+	 * @return mixed
438
+	 * @throws EE_Error
439
+	 * @throws InvalidArgumentException
440
+	 * @throws InvalidDataTypeException
441
+	 * @throws InvalidInterfaceException
442
+	 * @throws ReflectionException
443
+	 */
444
+	public function hash_salt_()
445
+	{
446
+		return $this->get('TXN_hash_salt');
447
+	}
448
+
449
+
450
+	/**
451
+	 * Returns the transaction datetime as either:
452
+	 *            - unix timestamp format ($format = false, $gmt = true)
453
+	 *            - formatted date string including the UTC (timezone) offset ($format = true ($gmt
454
+	 *              has no affect with this option)), this also may include a timezone abbreviation if the
455
+	 *              set timezone in this class differs from what the timezone is on the blog.
456
+	 *            - formatted date string including the UTC (timezone) offset (default).
457
+	 *
458
+	 * @param boolean $format   - whether to return a unix timestamp (default) or formatted date string
459
+	 * @param boolean $gmt      - whether to return a unix timestamp with UTC offset applied (default)
460
+	 *                          or no UTC offset applied
461
+	 * @return string | int
462
+	 * @throws EE_Error
463
+	 * @throws InvalidArgumentException
464
+	 * @throws InvalidDataTypeException
465
+	 * @throws InvalidInterfaceException
466
+	 * @throws ReflectionException
467
+	 */
468
+	public function datetime($format = false, $gmt = false)
469
+	{
470
+		if ($format) {
471
+			return $this->get_pretty('TXN_timestamp');
472
+		}
473
+		if ($gmt) {
474
+			return $this->get_raw('TXN_timestamp');
475
+		}
476
+		return $this->get('TXN_timestamp');
477
+	}
478
+
479
+
480
+	/**
481
+	 * Gets registrations on this transaction
482
+	 *
483
+	 * @param array   $query_params array of query parameters
484
+	 * @param boolean $get_cached   TRUE to retrieve cached registrations or FALSE to pull from the db
485
+	 * @return EE_Base_Class[]|EE_Registration[]
486
+	 * @throws EE_Error
487
+	 * @throws InvalidArgumentException
488
+	 * @throws InvalidDataTypeException
489
+	 * @throws InvalidInterfaceException
490
+	 * @throws ReflectionException
491
+	 */
492
+	public function registrations($query_params = array(), $get_cached = false)
493
+	{
494
+		$query_params = (empty($query_params) || ! is_array($query_params))
495
+			? array(
496
+				'order_by' => array(
497
+					'Event.EVT_name'     => 'ASC',
498
+					'Attendee.ATT_lname' => 'ASC',
499
+					'Attendee.ATT_fname' => 'ASC',
500
+				),
501
+			)
502
+			: $query_params;
503
+		$query_params = $get_cached ? array() : $query_params;
504
+		return $this->get_many_related('Registration', $query_params);
505
+	}
506
+
507
+
508
+	/**
509
+	 * Gets all the attendees for this transaction (handy for use with EE_Attendee's get_registrations_for_event
510
+	 * function for getting attendees and how many registrations they each have for an event)
511
+	 *
512
+	 * @return mixed EE_Attendee[] by default, int if $output is set to 'COUNT'
513
+	 * @throws EE_Error
514
+	 * @throws InvalidArgumentException
515
+	 * @throws InvalidDataTypeException
516
+	 * @throws InvalidInterfaceException
517
+	 * @throws ReflectionException
518
+	 */
519
+	public function attendees()
520
+	{
521
+		return $this->get_many_related('Attendee', array(array('Registration.Transaction.TXN_ID' => $this->ID())));
522
+	}
523
+
524
+
525
+	/**
526
+	 * Gets payments for this transaction. Unlike other such functions, order by 'DESC' by default
527
+	 *
528
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
529
+	 * @return EE_Base_Class[]|EE_Payment[]
530
+	 * @throws EE_Error
531
+	 * @throws InvalidArgumentException
532
+	 * @throws InvalidDataTypeException
533
+	 * @throws InvalidInterfaceException
534
+	 * @throws ReflectionException
535
+	 */
536
+	public function payments($query_params = array())
537
+	{
538
+		return $this->get_many_related('Payment', $query_params);
539
+	}
540
+
541
+
542
+	/**
543
+	 * gets only approved payments for this transaction
544
+	 *
545
+	 * @return EE_Base_Class[]|EE_Payment[]
546
+	 * @throws EE_Error
547
+	 * @throws InvalidArgumentException
548
+	 * @throws ReflectionException
549
+	 * @throws InvalidDataTypeException
550
+	 * @throws InvalidInterfaceException
551
+	 */
552
+	public function approved_payments()
553
+	{
554
+		EE_Registry::instance()->load_model('Payment');
555
+		return $this->get_many_related(
556
+			'Payment',
557
+			array(
558
+				array('STS_ID' => EEM_Payment::status_id_approved),
559
+				'order_by' => array('PAY_timestamp' => 'DESC'),
560
+			)
561
+		);
562
+	}
563
+
564
+
565
+	/**
566
+	 * Gets all payments which have not been approved
567
+	 *
568
+	 * @return EE_Base_Class[]|EEI_Payment[]
569
+	 * @throws EE_Error if a model is misconfigured somehow
570
+	 * @throws InvalidArgumentException
571
+	 * @throws InvalidDataTypeException
572
+	 * @throws InvalidInterfaceException
573
+	 * @throws ReflectionException
574
+	 */
575
+	public function pending_payments()
576
+	{
577
+		return $this->get_many_related(
578
+			'Payment',
579
+			array(
580
+				array(
581
+					'STS_ID' => EEM_Payment::status_id_pending,
582
+				),
583
+				'order_by' => array(
584
+					'PAY_timestamp' => 'DESC',
585
+				),
586
+			)
587
+		);
588
+	}
589
+
590
+
591
+	/**
592
+	 * echoes $this->pretty_status()
593
+	 *
594
+	 * @param bool $show_icons
595
+	 * @throws EE_Error
596
+	 * @throws InvalidArgumentException
597
+	 * @throws InvalidDataTypeException
598
+	 * @throws InvalidInterfaceException
599
+	 * @throws ReflectionException
600
+	 */
601
+	public function e_pretty_status($show_icons = false)
602
+	{
603
+		echo wp_kses($this->pretty_status($show_icons), AllowedTags::getAllowedTags());
604
+	}
605
+
606
+
607
+	/**
608
+	 * returns a pretty version of the status, good for displaying to users
609
+	 *
610
+	 * @param bool $show_icons
611
+	 * @return string
612
+	 * @throws EE_Error
613
+	 * @throws InvalidArgumentException
614
+	 * @throws InvalidDataTypeException
615
+	 * @throws InvalidInterfaceException
616
+	 * @throws ReflectionException
617
+	 */
618
+	public function pretty_status($show_icons = false)
619
+	{
620
+		$status = EEM_Status::instance()->localized_status(
621
+			array($this->status_ID() => esc_html__('unknown', 'event_espresso')),
622
+			false,
623
+			'sentence'
624
+		);
625
+		$icon = '';
626
+		switch ($this->status_ID()) {
627
+			case EEM_Transaction::complete_status_code:
628
+				$icon = $show_icons ? '<span class="dashicons dashicons-yes ee-icon-size-24 green-text"></span>' : '';
629
+				break;
630
+			case EEM_Transaction::incomplete_status_code:
631
+				$icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 lt-blue-text"></span>'
632
+					: '';
633
+				break;
634
+			case EEM_Transaction::abandoned_status_code:
635
+				$icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 red-text"></span>' : '';
636
+				break;
637
+			case EEM_Transaction::failed_status_code:
638
+				$icon = $show_icons ? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>' : '';
639
+				break;
640
+			case EEM_Transaction::overpaid_status_code:
641
+				$icon = $show_icons ? '<span class="dashicons dashicons-plus ee-icon-size-16 orange-text"></span>' : '';
642
+				break;
643
+		}
644
+		return $icon . $status[ $this->status_ID() ];
645
+	}
646
+
647
+
648
+	/**
649
+	 * get Transaction Status
650
+	 *
651
+	 * @return mixed
652
+	 * @throws EE_Error
653
+	 * @throws InvalidArgumentException
654
+	 * @throws InvalidDataTypeException
655
+	 * @throws InvalidInterfaceException
656
+	 * @throws ReflectionException
657
+	 */
658
+	public function status_ID()
659
+	{
660
+		return $this->get('STS_ID');
661
+	}
662
+
663
+
664
+	/**
665
+	 * Returns TRUE or FALSE for whether or not this transaction cost any money
666
+	 *
667
+	 * @return boolean
668
+	 * @throws EE_Error
669
+	 * @throws InvalidArgumentException
670
+	 * @throws InvalidDataTypeException
671
+	 * @throws InvalidInterfaceException
672
+	 * @throws ReflectionException
673
+	 */
674
+	public function is_free()
675
+	{
676
+		return EEH_Money::compare_floats($this->get('TXN_total'), 0, '==');
677
+	}
678
+
679
+
680
+	/**
681
+	 * Returns whether this transaction is complete
682
+	 * Useful in templates and other logic for deciding if we should ask for another payment...
683
+	 *
684
+	 * @return boolean
685
+	 * @throws EE_Error
686
+	 * @throws InvalidArgumentException
687
+	 * @throws InvalidDataTypeException
688
+	 * @throws InvalidInterfaceException
689
+	 * @throws ReflectionException
690
+	 */
691
+	public function is_completed()
692
+	{
693
+		return $this->status_ID() === EEM_Transaction::complete_status_code;
694
+	}
695
+
696
+
697
+	/**
698
+	 * Returns whether this transaction is incomplete
699
+	 * Useful in templates and other logic for deciding if we should ask for another payment...
700
+	 *
701
+	 * @return boolean
702
+	 * @throws EE_Error
703
+	 * @throws InvalidArgumentException
704
+	 * @throws InvalidDataTypeException
705
+	 * @throws InvalidInterfaceException
706
+	 * @throws ReflectionException
707
+	 */
708
+	public function is_incomplete()
709
+	{
710
+		return $this->status_ID() === EEM_Transaction::incomplete_status_code;
711
+	}
712
+
713
+
714
+	/**
715
+	 * Returns whether this transaction is overpaid
716
+	 * Useful in templates and other logic for deciding if monies need to be refunded
717
+	 *
718
+	 * @return boolean
719
+	 * @throws EE_Error
720
+	 * @throws InvalidArgumentException
721
+	 * @throws InvalidDataTypeException
722
+	 * @throws InvalidInterfaceException
723
+	 * @throws ReflectionException
724
+	 */
725
+	public function is_overpaid()
726
+	{
727
+		return $this->status_ID() === EEM_Transaction::overpaid_status_code;
728
+	}
729
+
730
+
731
+	/**
732
+	 * Returns whether this transaction was abandoned
733
+	 * meaning that the transaction/registration process was somehow interrupted and never completed
734
+	 * but that contact information exists for at least one registrant
735
+	 *
736
+	 * @return boolean
737
+	 * @throws EE_Error
738
+	 * @throws InvalidArgumentException
739
+	 * @throws InvalidDataTypeException
740
+	 * @throws InvalidInterfaceException
741
+	 * @throws ReflectionException
742
+	 */
743
+	public function is_abandoned()
744
+	{
745
+		return $this->status_ID() === EEM_Transaction::abandoned_status_code;
746
+	}
747
+
748
+
749
+	/**
750
+	 * Returns whether this transaction failed
751
+	 * meaning that the transaction/registration process was somehow interrupted and never completed
752
+	 * and that NO contact information exists for any registrants
753
+	 *
754
+	 * @return boolean
755
+	 * @throws EE_Error
756
+	 * @throws InvalidArgumentException
757
+	 * @throws InvalidDataTypeException
758
+	 * @throws InvalidInterfaceException
759
+	 * @throws ReflectionException
760
+	 */
761
+	public function failed()
762
+	{
763
+		return $this->status_ID() === EEM_Transaction::failed_status_code;
764
+	}
765
+
766
+
767
+	/**
768
+	 * This returns the url for the invoice of this transaction
769
+	 *
770
+	 * @param string $type 'html' or 'pdf' (default is pdf)
771
+	 * @return string
772
+	 * @throws EE_Error
773
+	 * @throws InvalidArgumentException
774
+	 * @throws InvalidDataTypeException
775
+	 * @throws InvalidInterfaceException
776
+	 * @throws ReflectionException
777
+	 */
778
+	public function invoice_url($type = 'html')
779
+	{
780
+		$REG = $this->primary_registration();
781
+		if (! $REG instanceof EE_Registration) {
782
+			return '';
783
+		}
784
+		return $REG->invoice_url($type);
785
+	}
786
+
787
+
788
+	/**
789
+	 * Gets the primary registration only
790
+	 *
791
+	 * @return EE_Base_Class|EE_Registration
792
+	 * @throws EE_Error
793
+	 * @throws InvalidArgumentException
794
+	 * @throws InvalidDataTypeException
795
+	 * @throws InvalidInterfaceException
796
+	 * @throws ReflectionException
797
+	 */
798
+	public function primary_registration()
799
+	{
800
+		$registrations = (array) $this->get_many_related(
801
+			'Registration',
802
+			array(array('REG_count' => EEM_Registration::PRIMARY_REGISTRANT_COUNT))
803
+		);
804
+		foreach ($registrations as $registration) {
805
+			// valid registration that is NOT cancelled or declined ?
806
+			if (
807
+				$registration instanceof EE_Registration
808
+				&& ! in_array($registration->status_ID(), EEM_Registration::closed_reg_statuses(), true)
809
+			) {
810
+				return $registration;
811
+			}
812
+		}
813
+		// nothing valid found, so just return first thing from array of results
814
+		return reset($registrations);
815
+	}
816
+
817
+
818
+	/**
819
+	 * Gets the URL for viewing the receipt
820
+	 *
821
+	 * @param string $type 'pdf' or 'html' (default is 'html')
822
+	 * @return string
823
+	 * @throws EE_Error
824
+	 * @throws InvalidArgumentException
825
+	 * @throws InvalidDataTypeException
826
+	 * @throws InvalidInterfaceException
827
+	 * @throws ReflectionException
828
+	 */
829
+	public function receipt_url($type = 'html')
830
+	{
831
+		$REG = $this->primary_registration();
832
+		if (! $REG instanceof EE_Registration) {
833
+			return '';
834
+		}
835
+		return $REG->receipt_url($type);
836
+	}
837
+
838
+
839
+	/**
840
+	 * Gets the URL of the thank you page with this registration REG_url_link added as
841
+	 * a query parameter
842
+	 *
843
+	 * @return string
844
+	 * @throws EE_Error
845
+	 * @throws InvalidArgumentException
846
+	 * @throws InvalidDataTypeException
847
+	 * @throws InvalidInterfaceException
848
+	 * @throws ReflectionException
849
+	 */
850
+	public function payment_overview_url()
851
+	{
852
+		$primary_registration = $this->primary_registration();
853
+		return $primary_registration instanceof EE_Registration ? $primary_registration->payment_overview_url() : false;
854
+	}
855
+
856
+
857
+	/**
858
+	 * @return string
859
+	 * @throws EE_Error
860
+	 * @throws InvalidArgumentException
861
+	 * @throws InvalidDataTypeException
862
+	 * @throws InvalidInterfaceException
863
+	 * @throws ReflectionException
864
+	 */
865
+	public function gateway_response_on_transaction()
866
+	{
867
+		$payment = $this->get_first_related('Payment');
868
+		return $payment instanceof EE_Payment ? $payment->gateway_response() : '';
869
+	}
870
+
871
+
872
+	/**
873
+	 * Get the status object of this object
874
+	 *
875
+	 * @return EE_Base_Class|EE_Status
876
+	 * @throws EE_Error
877
+	 * @throws InvalidArgumentException
878
+	 * @throws InvalidDataTypeException
879
+	 * @throws InvalidInterfaceException
880
+	 * @throws ReflectionException
881
+	 */
882
+	public function status_obj()
883
+	{
884
+		return $this->get_first_related('Status');
885
+	}
886
+
887
+
888
+	/**
889
+	 * Gets all the extra meta info on this payment
890
+	 *
891
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
892
+	 * @return EE_Base_Class[]|EE_Extra_Meta
893
+	 * @throws EE_Error
894
+	 * @throws InvalidArgumentException
895
+	 * @throws InvalidDataTypeException
896
+	 * @throws InvalidInterfaceException
897
+	 * @throws ReflectionException
898
+	 */
899
+	public function extra_meta($query_params = array())
900
+	{
901
+		return $this->get_many_related('Extra_Meta', $query_params);
902
+	}
903
+
904
+
905
+	/**
906
+	 * Wrapper for _add_relation_to
907
+	 *
908
+	 * @param EE_Registration $registration
909
+	 * @return EE_Base_Class the relation was added to
910
+	 * @throws EE_Error
911
+	 * @throws InvalidArgumentException
912
+	 * @throws InvalidDataTypeException
913
+	 * @throws InvalidInterfaceException
914
+	 * @throws ReflectionException
915
+	 */
916
+	public function add_registration(EE_Registration $registration)
917
+	{
918
+		return $this->_add_relation_to($registration, 'Registration');
919
+	}
920
+
921
+
922
+	/**
923
+	 * Removes the given registration from being related (even before saving this transaction).
924
+	 * If an ID/index is provided and this transaction isn't saved yet, removes it from list of cached relations
925
+	 *
926
+	 * @param int $registration_or_id
927
+	 * @return EE_Base_Class that was removed from being related
928
+	 * @throws EE_Error
929
+	 * @throws InvalidArgumentException
930
+	 * @throws InvalidDataTypeException
931
+	 * @throws InvalidInterfaceException
932
+	 * @throws ReflectionException
933
+	 */
934
+	public function remove_registration_with_id($registration_or_id)
935
+	{
936
+		return $this->_remove_relation_to($registration_or_id, 'Registration');
937
+	}
938
+
939
+
940
+	/**
941
+	 * Gets all the line items which are for ACTUAL items
942
+	 *
943
+	 * @return EE_Line_Item[]
944
+	 * @throws EE_Error
945
+	 * @throws InvalidArgumentException
946
+	 * @throws InvalidDataTypeException
947
+	 * @throws InvalidInterfaceException
948
+	 * @throws ReflectionException
949
+	 */
950
+	public function items_purchased()
951
+	{
952
+		return $this->line_items(array(array('LIN_type' => EEM_Line_Item::type_line_item)));
953
+	}
954
+
955
+
956
+	/**
957
+	 * Wrapper for _add_relation_to
958
+	 *
959
+	 * @param EE_Line_Item $line_item
960
+	 * @return EE_Base_Class the relation was added to
961
+	 * @throws EE_Error
962
+	 * @throws InvalidArgumentException
963
+	 * @throws InvalidDataTypeException
964
+	 * @throws InvalidInterfaceException
965
+	 * @throws ReflectionException
966
+	 */
967
+	public function add_line_item(EE_Line_Item $line_item)
968
+	{
969
+		return $this->_add_relation_to($line_item, 'Line_Item');
970
+	}
971
+
972
+
973
+	/**
974
+	 * Gets ALL the line items related to this transaction (unstructured)
975
+	 *
976
+	 * @param array $query_params
977
+	 * @return EE_Base_Class[]|EE_Line_Item[]
978
+	 * @throws EE_Error
979
+	 * @throws InvalidArgumentException
980
+	 * @throws InvalidDataTypeException
981
+	 * @throws InvalidInterfaceException
982
+	 * @throws ReflectionException
983
+	 */
984
+	public function line_items($query_params = array())
985
+	{
986
+		return $this->get_many_related('Line_Item', $query_params);
987
+	}
988
+
989
+
990
+	/**
991
+	 * Gets all the line items which are taxes on the total
992
+	 *
993
+	 * @return EE_Line_Item[]
994
+	 * @throws EE_Error
995
+	 * @throws InvalidArgumentException
996
+	 * @throws InvalidDataTypeException
997
+	 * @throws InvalidInterfaceException
998
+	 * @throws ReflectionException
999
+	 */
1000
+	public function tax_items()
1001
+	{
1002
+		return $this->line_items(array(array('LIN_type' => EEM_Line_Item::type_tax)));
1003
+	}
1004
+
1005
+
1006
+	/**
1007
+	 * Gets the total line item (which is a parent of all other related line items,
1008
+	 * meaning it takes them all into account on its total)
1009
+	 *
1010
+	 * @param bool $create_if_not_found
1011
+	 * @return \EE_Line_Item
1012
+	 * @throws EE_Error
1013
+	 * @throws InvalidArgumentException
1014
+	 * @throws InvalidDataTypeException
1015
+	 * @throws InvalidInterfaceException
1016
+	 * @throws ReflectionException
1017
+	 */
1018
+	public function total_line_item($create_if_not_found = true)
1019
+	{
1020
+		$item = $this->get_first_related('Line_Item', array(array('LIN_type' => EEM_Line_Item::type_total)));
1021
+		if (! $item && $create_if_not_found) {
1022
+			$item = EEH_Line_Item::create_total_line_item($this);
1023
+		}
1024
+		return $item;
1025
+	}
1026
+
1027
+
1028
+	/**
1029
+	 * Returns the total amount of tax on this transaction
1030
+	 * (assumes there's only one tax subtotal line item)
1031
+	 *
1032
+	 * @return float
1033
+	 * @throws EE_Error
1034
+	 * @throws InvalidArgumentException
1035
+	 * @throws InvalidDataTypeException
1036
+	 * @throws InvalidInterfaceException
1037
+	 * @throws ReflectionException
1038
+	 */
1039
+	public function tax_total()
1040
+	{
1041
+		$tax_line_item = $this->tax_total_line_item();
1042
+		if ($tax_line_item) {
1043
+			return (float) $tax_line_item->total();
1044
+		}
1045
+		return (float) 0;
1046
+	}
1047
+
1048
+
1049
+	/**
1050
+	 * Gets the tax subtotal line item (assumes there's only one)
1051
+	 *
1052
+	 * @return EE_Line_Item
1053
+	 * @throws EE_Error
1054
+	 * @throws InvalidArgumentException
1055
+	 * @throws InvalidDataTypeException
1056
+	 * @throws InvalidInterfaceException
1057
+	 * @throws ReflectionException
1058
+	 */
1059
+	public function tax_total_line_item()
1060
+	{
1061
+		return EEH_Line_Item::get_taxes_subtotal($this->total_line_item());
1062
+	}
1063
+
1064
+
1065
+	/**
1066
+	 * Gets the array of billing info for the gateway and for this transaction's primary registration's attendee.
1067
+	 *
1068
+	 * @return EE_Form_Section_Proper
1069
+	 * @throws EE_Error
1070
+	 * @throws InvalidArgumentException
1071
+	 * @throws InvalidDataTypeException
1072
+	 * @throws InvalidInterfaceException
1073
+	 * @throws ReflectionException
1074
+	 */
1075
+	public function billing_info()
1076
+	{
1077
+		$payment_method = $this->payment_method();
1078
+		if (! $payment_method) {
1079
+			EE_Error::add_error(
1080
+				esc_html__(
1081
+					'Could not find billing info for transaction because no gateway has been used for it yet',
1082
+					'event_espresso'
1083
+				),
1084
+				__FILE__,
1085
+				__FUNCTION__,
1086
+				__LINE__
1087
+			);
1088
+			return null;
1089
+		}
1090
+		$primary_reg = $this->primary_registration();
1091
+		if (! $primary_reg) {
1092
+			EE_Error::add_error(
1093
+				esc_html__(
1094
+					'Cannot get billing info for gateway %s on transaction because no primary registration exists',
1095
+					'event_espresso'
1096
+				),
1097
+				__FILE__,
1098
+				__FUNCTION__,
1099
+				__LINE__
1100
+			);
1101
+			return null;
1102
+		}
1103
+		$attendee = $primary_reg->attendee();
1104
+		if (! $attendee) {
1105
+			EE_Error::add_error(
1106
+				esc_html__(
1107
+					'Cannot get billing info for gateway %s on transaction because the primary registration has no attendee exists',
1108
+					'event_espresso'
1109
+				),
1110
+				__FILE__,
1111
+				__FUNCTION__,
1112
+				__LINE__
1113
+			);
1114
+			return null;
1115
+		}
1116
+		return $attendee->billing_info_for_payment_method($payment_method);
1117
+	}
1118
+
1119
+
1120
+	/**
1121
+	 * Gets PMD_ID
1122
+	 *
1123
+	 * @return int
1124
+	 * @throws EE_Error
1125
+	 * @throws InvalidArgumentException
1126
+	 * @throws InvalidDataTypeException
1127
+	 * @throws InvalidInterfaceException
1128
+	 * @throws ReflectionException
1129
+	 */
1130
+	public function payment_method_ID()
1131
+	{
1132
+		return $this->get('PMD_ID');
1133
+	}
1134
+
1135
+
1136
+	/**
1137
+	 * Sets PMD_ID
1138
+	 *
1139
+	 * @param int $PMD_ID
1140
+	 * @throws EE_Error
1141
+	 * @throws InvalidArgumentException
1142
+	 * @throws InvalidDataTypeException
1143
+	 * @throws InvalidInterfaceException
1144
+	 * @throws ReflectionException
1145
+	 */
1146
+	public function set_payment_method_ID($PMD_ID)
1147
+	{
1148
+		$this->set('PMD_ID', $PMD_ID);
1149
+	}
1150
+
1151
+
1152
+	/**
1153
+	 * Gets the last-used payment method on this transaction
1154
+	 * (we COULD just use the last-made payment, but some payment methods, namely
1155
+	 * offline ones, dont' create payments)
1156
+	 *
1157
+	 * @return EE_Payment_Method
1158
+	 * @throws EE_Error
1159
+	 * @throws InvalidArgumentException
1160
+	 * @throws InvalidDataTypeException
1161
+	 * @throws InvalidInterfaceException
1162
+	 * @throws ReflectionException
1163
+	 */
1164
+	public function payment_method()
1165
+	{
1166
+		$pm = $this->get_first_related('Payment_Method');
1167
+		if ($pm instanceof EE_Payment_Method) {
1168
+			return $pm;
1169
+		}
1170
+		$last_payment = $this->last_payment();
1171
+		if ($last_payment instanceof EE_Payment && $last_payment->payment_method()) {
1172
+			return $last_payment->payment_method();
1173
+		}
1174
+		return null;
1175
+	}
1176
+
1177
+
1178
+	/**
1179
+	 * Gets the last payment made
1180
+	 *
1181
+	 * @return EE_Base_Class|EE_Payment
1182
+	 * @throws EE_Error
1183
+	 * @throws InvalidArgumentException
1184
+	 * @throws InvalidDataTypeException
1185
+	 * @throws InvalidInterfaceException
1186
+	 * @throws ReflectionException
1187
+	 */
1188
+	public function last_payment()
1189
+	{
1190
+		return $this->get_first_related('Payment', array('order_by' => array('PAY_ID' => 'desc')));
1191
+	}
1192
+
1193
+
1194
+	/**
1195
+	 * Gets all the line items which are unrelated to tickets on this transaction
1196
+	 *
1197
+	 * @return EE_Line_Item[]
1198
+	 * @throws EE_Error
1199
+	 * @throws InvalidArgumentException
1200
+	 * @throws InvalidDataTypeException
1201
+	 * @throws InvalidInterfaceException
1202
+	 * @throws ReflectionException
1203
+	 */
1204
+	public function non_ticket_line_items()
1205
+	{
1206
+		return EEM_Line_Item::instance()->get_all_non_ticket_line_items_for_transaction($this->ID());
1207
+	}
1208
+
1209
+
1210
+	/**
1211
+	 * possibly toggles TXN status
1212
+	 *
1213
+	 * @param  boolean $update whether to save the TXN
1214
+	 * @return bool whether the TXN was saved
1215
+	 * @throws EE_Error
1216
+	 * @throws InvalidArgumentException
1217
+	 * @throws InvalidDataTypeException
1218
+	 * @throws InvalidInterfaceException
1219
+	 * @throws ReflectionException
1220
+	 * @throws RuntimeException
1221
+	 */
1222
+	public function update_status_based_on_total_paid($update = true)
1223
+	{
1224
+		// set transaction status based on comparison of TXN_paid vs TXN_total
1225
+		if (EEH_Money::compare_floats($this->paid(), $this->total(), '>')) {
1226
+			$new_txn_status = EEM_Transaction::overpaid_status_code;
1227
+		} elseif (EEH_Money::compare_floats($this->paid(), $this->total())) {
1228
+			$new_txn_status = EEM_Transaction::complete_status_code;
1229
+		} elseif (EEH_Money::compare_floats($this->paid(), $this->total(), '<')) {
1230
+			$new_txn_status = EEM_Transaction::incomplete_status_code;
1231
+		} else {
1232
+			throw new RuntimeException(
1233
+				esc_html__('The total paid calculation for this transaction is inaccurate.', 'event_espresso')
1234
+			);
1235
+		}
1236
+		if ($new_txn_status !== $this->status_ID()) {
1237
+			$this->set_status($new_txn_status);
1238
+			if ($update) {
1239
+				return $this->save() ? true : false;
1240
+			}
1241
+		}
1242
+		return false;
1243
+	}
1244
+
1245
+
1246
+	/**
1247
+	 * Updates the transaction's status and total_paid based on all the payments
1248
+	 * that apply to it
1249
+	 *
1250
+	 * @deprecated
1251
+	 * @return array|bool
1252
+	 * @throws EE_Error
1253
+	 * @throws InvalidArgumentException
1254
+	 * @throws ReflectionException
1255
+	 * @throws InvalidDataTypeException
1256
+	 * @throws InvalidInterfaceException
1257
+	 */
1258
+	public function update_based_on_payments()
1259
+	{
1260
+		EE_Error::doing_it_wrong(
1261
+			__CLASS__ . '::' . __FUNCTION__,
1262
+			sprintf(
1263
+				esc_html__('This method is deprecated. Please use "%s" instead', 'event_espresso'),
1264
+				'EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()'
1265
+			),
1266
+			'4.6.0'
1267
+		);
1268
+		/** @type EE_Transaction_Processor $transaction_processor */
1269
+		$transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
1270
+		return $transaction_processor->update_transaction_and_registrations_after_checkout_or_payment($this);
1271
+	}
1272
+
1273
+
1274
+	/**
1275
+	 * @return string
1276
+	 */
1277
+	public function old_txn_status()
1278
+	{
1279
+		return $this->_old_txn_status;
1280
+	}
1281
+
1282
+
1283
+	/**
1284
+	 * @param string $old_txn_status
1285
+	 */
1286
+	public function set_old_txn_status($old_txn_status)
1287
+	{
1288
+		// only set the first time
1289
+		if ($this->_old_txn_status === null) {
1290
+			$this->_old_txn_status = $old_txn_status;
1291
+		}
1292
+	}
1293
+
1294
+
1295
+	/**
1296
+	 * reg_status_updated
1297
+	 *
1298
+	 * @return bool
1299
+	 * @throws EE_Error
1300
+	 * @throws InvalidArgumentException
1301
+	 * @throws InvalidDataTypeException
1302
+	 * @throws InvalidInterfaceException
1303
+	 * @throws ReflectionException
1304
+	 */
1305
+	public function txn_status_updated()
1306
+	{
1307
+		return $this->status_ID() !== $this->_old_txn_status && $this->_old_txn_status !== null;
1308
+	}
1309
+
1310
+
1311
+	/**
1312
+	 * _reg_steps_completed
1313
+	 * if $check_all is TRUE, then returns TRUE if ALL reg steps have been marked as completed,
1314
+	 * if a $reg_step_slug is provided, then this step will be skipped when testing for completion
1315
+	 * if $check_all is FALSE and a $reg_step_slug is provided, then ONLY that reg step will be tested for completion
1316
+	 *
1317
+	 * @param string $reg_step_slug
1318
+	 * @param bool   $check_all
1319
+	 * @return bool|int
1320
+	 * @throws EE_Error
1321
+	 * @throws InvalidArgumentException
1322
+	 * @throws InvalidDataTypeException
1323
+	 * @throws InvalidInterfaceException
1324
+	 * @throws ReflectionException
1325
+	 */
1326
+	private function _reg_steps_completed($reg_step_slug = '', $check_all = true)
1327
+	{
1328
+		$reg_steps = $this->reg_steps();
1329
+		if (! is_array($reg_steps) || empty($reg_steps)) {
1330
+			return false;
1331
+		}
1332
+		// loop thru reg steps array)
1333
+		foreach ($reg_steps as $slug => $reg_step_completed) {
1334
+			// if NOT checking ALL steps (only checking one step)
1335
+			if (! $check_all) {
1336
+				// and this is the one
1337
+				if ($slug === $reg_step_slug) {
1338
+					return $reg_step_completed;
1339
+				}
1340
+				// skip to next reg step in loop
1341
+				continue;
1342
+			}
1343
+			// $check_all must be true, else we would never have gotten to this point
1344
+			if ($slug === $reg_step_slug) {
1345
+				// if we reach this point, then we are testing either:
1346
+				// all_reg_steps_completed_except() or
1347
+				// all_reg_steps_completed_except_final_step(),
1348
+				// and since this is the reg step EXCEPTION being tested
1349
+				// we want to return true (yes true) if this reg step is NOT completed
1350
+				// ie: "is everything completed except the final step?"
1351
+				// "that is correct... the final step is not completed, but all others are."
1352
+				return $reg_step_completed !== true;
1353
+			}
1354
+			if ($reg_step_completed !== true) {
1355
+				// if any reg step is NOT completed, then ALL steps are not completed
1356
+				return false;
1357
+			}
1358
+		}
1359
+		return true;
1360
+	}
1361
+
1362
+
1363
+	/**
1364
+	 * all_reg_steps_completed
1365
+	 * returns:
1366
+	 *    true if ALL reg steps have been marked as completed
1367
+	 *        or false if any step is not completed
1368
+	 *
1369
+	 * @return bool
1370
+	 * @throws EE_Error
1371
+	 * @throws InvalidArgumentException
1372
+	 * @throws InvalidDataTypeException
1373
+	 * @throws InvalidInterfaceException
1374
+	 * @throws ReflectionException
1375
+	 */
1376
+	public function all_reg_steps_completed()
1377
+	{
1378
+		return $this->_reg_steps_completed();
1379
+	}
1380
+
1381
+
1382
+	/**
1383
+	 * all_reg_steps_completed_except
1384
+	 * returns:
1385
+	 *        true if ALL reg steps, except a particular step that you wish to skip over, have been marked as completed
1386
+	 *        or false if any other step is not completed
1387
+	 *        or false if ALL steps are completed including the exception you are testing !!!
1388
+	 *
1389
+	 * @param string $exception
1390
+	 * @return bool
1391
+	 * @throws EE_Error
1392
+	 * @throws InvalidArgumentException
1393
+	 * @throws InvalidDataTypeException
1394
+	 * @throws InvalidInterfaceException
1395
+	 * @throws ReflectionException
1396
+	 */
1397
+	public function all_reg_steps_completed_except($exception = '')
1398
+	{
1399
+		return $this->_reg_steps_completed($exception);
1400
+	}
1401
+
1402
+
1403
+	/**
1404
+	 * all_reg_steps_completed_except
1405
+	 * returns:
1406
+	 *        true if ALL reg steps, except the final step, have been marked as completed
1407
+	 *        or false if any step is not completed
1408
+	 *    or false if ALL steps are completed including the final step !!!
1409
+	 *
1410
+	 * @return bool
1411
+	 * @throws EE_Error
1412
+	 * @throws InvalidArgumentException
1413
+	 * @throws InvalidDataTypeException
1414
+	 * @throws InvalidInterfaceException
1415
+	 * @throws ReflectionException
1416
+	 */
1417
+	public function all_reg_steps_completed_except_final_step()
1418
+	{
1419
+		return $this->_reg_steps_completed('finalize_registration');
1420
+	}
1421
+
1422
+
1423
+	/**
1424
+	 * reg_step_completed
1425
+	 * returns:
1426
+	 *    true if a specific reg step has been marked as completed
1427
+	 *    a Unix timestamp if it has been initialized but not yet completed,
1428
+	 *    or false if it has not yet been initialized
1429
+	 *
1430
+	 * @param string $reg_step_slug
1431
+	 * @return bool|int
1432
+	 * @throws EE_Error
1433
+	 * @throws InvalidArgumentException
1434
+	 * @throws InvalidDataTypeException
1435
+	 * @throws InvalidInterfaceException
1436
+	 * @throws ReflectionException
1437
+	 */
1438
+	public function reg_step_completed($reg_step_slug)
1439
+	{
1440
+		return $this->_reg_steps_completed($reg_step_slug, false);
1441
+	}
1442
+
1443
+
1444
+	/**
1445
+	 * completed_final_reg_step
1446
+	 * returns:
1447
+	 *    true if the finalize_registration reg step has been marked as completed
1448
+	 *    a Unix timestamp if it has been initialized but not yet completed,
1449
+	 *    or false if it has not yet been initialized
1450
+	 *
1451
+	 * @return bool|int
1452
+	 * @throws EE_Error
1453
+	 * @throws InvalidArgumentException
1454
+	 * @throws InvalidDataTypeException
1455
+	 * @throws InvalidInterfaceException
1456
+	 * @throws ReflectionException
1457
+	 */
1458
+	public function final_reg_step_completed()
1459
+	{
1460
+		return $this->_reg_steps_completed('finalize_registration', false);
1461
+	}
1462
+
1463
+
1464
+	/**
1465
+	 * set_reg_step_initiated
1466
+	 * given a valid TXN_reg_step, this sets it's value to a unix timestamp
1467
+	 *
1468
+	 * @param string $reg_step_slug
1469
+	 * @return boolean
1470
+	 * @throws EE_Error
1471
+	 * @throws InvalidArgumentException
1472
+	 * @throws InvalidDataTypeException
1473
+	 * @throws InvalidInterfaceException
1474
+	 * @throws ReflectionException
1475
+	 */
1476
+	public function set_reg_step_initiated($reg_step_slug)
1477
+	{
1478
+		return $this->_set_reg_step_completed_status($reg_step_slug, time());
1479
+	}
1480
+
1481
+
1482
+	/**
1483
+	 * set_reg_step_completed
1484
+	 * given a valid TXN_reg_step, this sets the step as completed
1485
+	 *
1486
+	 * @param string $reg_step_slug
1487
+	 * @return boolean
1488
+	 * @throws EE_Error
1489
+	 * @throws InvalidArgumentException
1490
+	 * @throws InvalidDataTypeException
1491
+	 * @throws InvalidInterfaceException
1492
+	 * @throws ReflectionException
1493
+	 */
1494
+	public function set_reg_step_completed($reg_step_slug)
1495
+	{
1496
+		return $this->_set_reg_step_completed_status($reg_step_slug, true);
1497
+	}
1498
+
1499
+
1500
+	/**
1501
+	 * set_reg_step_completed
1502
+	 * given a valid TXN_reg_step slug, this sets the step as NOT completed
1503
+	 *
1504
+	 * @param string $reg_step_slug
1505
+	 * @return boolean
1506
+	 * @throws EE_Error
1507
+	 * @throws InvalidArgumentException
1508
+	 * @throws InvalidDataTypeException
1509
+	 * @throws InvalidInterfaceException
1510
+	 * @throws ReflectionException
1511
+	 */
1512
+	public function set_reg_step_not_completed($reg_step_slug)
1513
+	{
1514
+		return $this->_set_reg_step_completed_status($reg_step_slug, false);
1515
+	}
1516
+
1517
+
1518
+	/**
1519
+	 * set_reg_step_completed
1520
+	 * given a valid reg step slug, this sets the TXN_reg_step completed status which is either:
1521
+	 *
1522
+	 * @param  string      $reg_step_slug
1523
+	 * @param  boolean|int $status
1524
+	 * @return boolean
1525
+	 * @throws EE_Error
1526
+	 * @throws InvalidArgumentException
1527
+	 * @throws InvalidDataTypeException
1528
+	 * @throws InvalidInterfaceException
1529
+	 * @throws ReflectionException
1530
+	 */
1531
+	private function _set_reg_step_completed_status($reg_step_slug, $status)
1532
+	{
1533
+		// validate status
1534
+		$status = is_bool($status) || is_int($status) ? $status : false;
1535
+		// get reg steps array
1536
+		$txn_reg_steps = $this->reg_steps();
1537
+		// if reg step does NOT exist
1538
+		if (! isset($txn_reg_steps[ $reg_step_slug ])) {
1539
+			return false;
1540
+		}
1541
+		// if  we're trying to complete a step that is already completed
1542
+		if ($txn_reg_steps[ $reg_step_slug ] === true) {
1543
+			return true;
1544
+		}
1545
+		// if  we're trying to complete a step that hasn't even started
1546
+		if ($status === true && $txn_reg_steps[ $reg_step_slug ] === false) {
1547
+			return false;
1548
+		}
1549
+		// if current status value matches the incoming value (no change)
1550
+		// type casting as int means values should collapse to either 0, 1, or a timestamp like 1234567890
1551
+		if ((int) $txn_reg_steps[ $reg_step_slug ] === (int) $status) {
1552
+			// this will happen in cases where multiple AJAX requests occur during the same step
1553
+			return true;
1554
+		}
1555
+		// if we're trying to set a start time, but it has already been set...
1556
+		if (is_numeric($status) && is_numeric($txn_reg_steps[ $reg_step_slug ])) {
1557
+			// skip the update below, but don't return FALSE so that errors won't be displayed
1558
+			return true;
1559
+		}
1560
+		// update completed status
1561
+		$txn_reg_steps[ $reg_step_slug ] = $status;
1562
+		$this->set_reg_steps($txn_reg_steps);
1563
+		$this->save();
1564
+		return true;
1565
+	}
1566
+
1567
+
1568
+	/**
1569
+	 * remove_reg_step
1570
+	 * given a valid TXN_reg_step slug, this will remove (unset)
1571
+	 * the reg step from the TXN reg step array
1572
+	 *
1573
+	 * @param string $reg_step_slug
1574
+	 * @return void
1575
+	 * @throws EE_Error
1576
+	 * @throws InvalidArgumentException
1577
+	 * @throws InvalidDataTypeException
1578
+	 * @throws InvalidInterfaceException
1579
+	 * @throws ReflectionException
1580
+	 */
1581
+	public function remove_reg_step($reg_step_slug)
1582
+	{
1583
+		// get reg steps array
1584
+		$txn_reg_steps = $this->reg_steps();
1585
+		unset($txn_reg_steps[ $reg_step_slug ]);
1586
+		$this->set_reg_steps($txn_reg_steps);
1587
+	}
1588
+
1589
+
1590
+	/**
1591
+	 * toggle_failed_transaction_status
1592
+	 * upgrades a TXNs status from failed to abandoned,
1593
+	 * meaning that contact information has been captured for at least one registrant
1594
+	 *
1595
+	 * @param bool $save
1596
+	 * @return bool
1597
+	 * @throws EE_Error
1598
+	 * @throws InvalidArgumentException
1599
+	 * @throws InvalidDataTypeException
1600
+	 * @throws InvalidInterfaceException
1601
+	 * @throws ReflectionException
1602
+	 */
1603
+	public function toggle_failed_transaction_status($save = true)
1604
+	{
1605
+		// if TXN status is still set as "failed"...
1606
+		if ($this->status_ID() === EEM_Transaction::failed_status_code) {
1607
+			$this->set_status(EEM_Transaction::abandoned_status_code);
1608
+			if ($save) {
1609
+				$this->save();
1610
+			}
1611
+			return true;
1612
+		}
1613
+		return false;
1614
+	}
1615
+
1616
+
1617
+	/**
1618
+	 * toggle_abandoned_transaction_status
1619
+	 * upgrades a TXNs status from failed or abandoned to incomplete
1620
+	 *
1621
+	 * @return bool
1622
+	 * @throws EE_Error
1623
+	 * @throws InvalidArgumentException
1624
+	 * @throws InvalidDataTypeException
1625
+	 * @throws InvalidInterfaceException
1626
+	 * @throws ReflectionException
1627
+	 */
1628
+	public function toggle_abandoned_transaction_status()
1629
+	{
1630
+		// if TXN status has not been updated already due to a payment, and is still set as "failed" or "abandoned"...
1631
+		$txn_status = $this->status_ID();
1632
+		if (
1633
+			$txn_status === EEM_Transaction::failed_status_code
1634
+			|| $txn_status === EEM_Transaction::abandoned_status_code
1635
+		) {
1636
+			// if a contact record for the primary registrant has been created
1637
+			if (
1638
+				$this->primary_registration() instanceof EE_Registration
1639
+				&& $this->primary_registration()->attendee() instanceof EE_Attendee
1640
+			) {
1641
+				$this->set_status(EEM_Transaction::incomplete_status_code);
1642
+			} else {
1643
+				// no contact record? yer abandoned!
1644
+				$this->set_status(EEM_Transaction::abandoned_status_code);
1645
+			}
1646
+			return true;
1647
+		}
1648
+		return false;
1649
+	}
1650
+
1651
+
1652
+	/**
1653
+	 * checks if an Abandoned TXN has any related payments, and if so,
1654
+	 * updates the TXN status based on the amount paid
1655
+	 *
1656
+	 * @throws EE_Error
1657
+	 * @throws InvalidDataTypeException
1658
+	 * @throws InvalidInterfaceException
1659
+	 * @throws InvalidArgumentException
1660
+	 * @throws RuntimeException
1661
+	 * @throws ReflectionException
1662
+	 */
1663
+	public function verify_abandoned_transaction_status()
1664
+	{
1665
+		if ($this->status_ID() !== EEM_Transaction::abandoned_status_code) {
1666
+			return;
1667
+		}
1668
+		$payments = $this->get_many_related('Payment');
1669
+		if (! empty($payments)) {
1670
+			foreach ($payments as $payment) {
1671
+				if ($payment instanceof EE_Payment) {
1672
+					// kk this TXN should NOT be abandoned
1673
+					$this->update_status_based_on_total_paid();
1674
+					if (! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1675
+						EE_Error::add_attention(
1676
+							sprintf(
1677
+								esc_html__(
1678
+									'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.',
1679
+									'event_espresso'
1680
+								),
1681
+								$this->ID(),
1682
+								$this->pretty_status()
1683
+							)
1684
+						);
1685
+					}
1686
+					// get final reg step status
1687
+					$finalized = $this->final_reg_step_completed();
1688
+					// if the 'finalize_registration' step has been initiated (has a timestamp)
1689
+					// but has not yet been fully completed (TRUE)
1690
+					if (is_int($finalized) && $finalized !== false && $finalized !== true) {
1691
+						$this->set_reg_step_completed('finalize_registration');
1692
+						$this->save();
1693
+					}
1694
+				}
1695
+			}
1696
+		}
1697
+	}
1698
+
1699
+
1700
+	/**
1701
+	 * @since 4.10.4.p
1702
+	 * @throws EE_Error
1703
+	 * @throws InvalidArgumentException
1704
+	 * @throws InvalidDataTypeException
1705
+	 * @throws InvalidInterfaceException
1706
+	 * @throws ReflectionException
1707
+	 * @throws RuntimeException
1708
+	 */
1709
+	public function recalculateLineItems()
1710
+	{
1711
+		$total_line_item = $this->total_line_item(false);
1712
+		if ($total_line_item instanceof EE_Line_Item) {
1713
+			EEH_Line_Item::resetIsTaxableForTickets($total_line_item);
1714
+			return EEH_Line_Item::apply_taxes($total_line_item, true);
1715
+		}
1716
+		return false;
1717
+	}
1718 1718
 }
Please login to merge, or discard this patch.
core/db_classes/EE_CSV.class.php 2 patches
Indentation   +669 added lines, -669 removed lines patch added patch discarded remove patch
@@ -14,673 +14,673 @@
 block discarded – undo
14 14
 class EE_CSV
15 15
 {
16 16
 
17
-    // instance of the EE_CSV object
18
-    private static $_instance = null;
19
-
20
-
21
-    // multidimensional array to store update & error messages
22
-    // var $_notices = array( 'updates' => array(), 'errors' => array() );
23
-
24
-
25
-    private $_primary_keys;
26
-
27
-    /**
28
-     *
29
-     * @var EE_Registry
30
-     */
31
-    private $EE;
32
-    /**
33
-     * string used for 1st cell in exports, which indicates that the following 2 rows will be metadata keys and values
34
-     */
35
-    const metadata_header = 'Event Espresso Export Meta Data';
36
-
37
-    /**
38
-     *        private constructor to prevent direct creation
39
-     *
40
-     * @Constructor
41
-     * @access private
42
-     * @return void
43
-     */
44
-    private function __construct()
45
-    {
46
-        global $wpdb;
47
-
48
-        $this->_primary_keys = array(
49
-            $wpdb->prefix . 'esp_answer'                  => array('ANS_ID'),
50
-            $wpdb->prefix . 'esp_attendee'                => array('ATT_ID'),
51
-            $wpdb->prefix . 'esp_datetime'                => array('DTT_ID'),
52
-            $wpdb->prefix . 'esp_event_question_group'    => array('EQG_ID'),
53
-            $wpdb->prefix . 'esp_message_template'        => array('MTP_ID'),
54
-            $wpdb->prefix . 'esp_payment'                 => array('PAY_ID'),
55
-            $wpdb->prefix . 'esp_price'                   => array('PRC_ID'),
56
-            $wpdb->prefix . 'esp_price_type'              => array('PRT_ID'),
57
-            $wpdb->prefix . 'esp_question'                => array('QST_ID'),
58
-            $wpdb->prefix . 'esp_question_group'          => array('QSG_ID'),
59
-            $wpdb->prefix . 'esp_question_group_question' => array('QGQ_ID'),
60
-            $wpdb->prefix . 'esp_question_option'         => array('QSO_ID'),
61
-            $wpdb->prefix . 'esp_registration'            => array('REG_ID'),
62
-            $wpdb->prefix . 'esp_status'                  => array('STS_ID'),
63
-            $wpdb->prefix . 'esp_transaction'             => array('TXN_ID'),
64
-            $wpdb->prefix . 'esp_transaction'             => array('TXN_ID'),
65
-            $wpdb->prefix . 'events_detail'               => array('id'),
66
-            $wpdb->prefix . 'events_category_detail'      => array('id'),
67
-            $wpdb->prefix . 'events_category_rel'         => array('id'),
68
-            $wpdb->prefix . 'events_venue'                => array('id'),
69
-            $wpdb->prefix . 'events_venue_rel'            => array('emeta_id'),
70
-            $wpdb->prefix . 'events_locale'               => array('id'),
71
-            $wpdb->prefix . 'events_locale_rel'           => array('id'),
72
-            $wpdb->prefix . 'events_personnel'            => array('id'),
73
-            $wpdb->prefix . 'events_personnel_rel'        => array('id'),
74
-        );
75
-    }
76
-
77
-
78
-    /**
79
-     *        @ singleton method used to instantiate class object
80
-     *        @ access public
81
-     *
82
-     * @return EE_CSV
83
-     */
84
-    public static function instance()
85
-    {
86
-        // check if class object is instantiated
87
-        if (self::$_instance === null or ! is_object(self::$_instance) or ! (self::$_instance instanceof EE_CSV)) {
88
-            self::$_instance = new self();
89
-        }
90
-        return self::$_instance;
91
-    }
92
-
93
-    /**
94
-     * Opens a unicode or utf file (normal file_get_contents has difficulty readin ga unicode file. @see
95
-     * http://stackoverflow.com/questions/15092764/how-to-read-unicode-text-file-in-php
96
-     *
97
-     * @param string $file_path
98
-     * @return string
99
-     * @throws EE_Error
100
-     */
101
-    private function read_unicode_file($file_path)
102
-    {
103
-        $fc = "";
104
-        $fh = fopen($file_path, "rb");
105
-        if (! $fh) {
106
-            throw new EE_Error(sprintf(esc_html__("Cannot open file for read: %s<br>\n", 'event_espresso'), $file_path));
107
-        }
108
-        $flen = filesize($file_path);
109
-        $bc = fread($fh, $flen);
110
-        for ($i = 0; $i < $flen; $i++) {
111
-            $c = substr($bc, $i, 1);
112
-            if ((ord($c) != 0) && (ord($c) != 13)) {
113
-                $fc = $fc . $c;
114
-            }
115
-        }
116
-        if ((ord(substr($fc, 0, 1)) == 255) && (ord(substr($fc, 1, 1)) == 254)) {
117
-            $fc = substr($fc, 2);
118
-        }
119
-        return ($fc);
120
-    }
121
-
122
-
123
-    /**
124
-     * Generic CSV-functionality to turn an entire CSV file into a single array that's
125
-     * NOT in a specific format to EE. It's just a 2-level array, with top-level arrays
126
-     * representing each row in the CSV file, and the second-level arrays being each column in that row
127
-     *
128
-     * @param string $path_to_file
129
-     * @return array of arrays. Top-level array has rows, second-level array has each item
130
-     */
131
-    public function import_csv_to_multi_dimensional_array($path_to_file)
132
-    {
133
-        // needed to deal with Mac line endings
134
-        ini_set('auto_detect_line_endings', true);
135
-
136
-        // because fgetcsv does not correctly deal with backslashed quotes such as \"
137
-        // we'll read the file into a string
138
-        $file_contents = $this->read_unicode_file($path_to_file);
139
-        // replace backslashed quotes with CSV enclosures
140
-        $file_contents = str_replace('\\"', '"""', $file_contents);
141
-        // HEY YOU! PUT THAT FILE BACK!!!
142
-        file_put_contents($path_to_file, $file_contents);
143
-
144
-        if (($file_handle = fopen($path_to_file, "r")) !== false) {
145
-            # Set the parent multidimensional array key to 0.
146
-            $nn = 0;
147
-            $csvarray = array();
148
-
149
-            // in PHP 5.3 fgetcsv accepts a 5th parameter, but the pre 5.3 versions of fgetcsv choke if passed more than 4 - is that crazy or what?
150
-            if (version_compare(PHP_VERSION, '5.3.0') < 0) {
151
-                //  PHP 5.2- version
152
-                // loop through each row of the file
153
-                while (($data = fgetcsv($file_handle, 0, ',', '"')) !== false) {
154
-                    $csvarray[] = $data;
155
-                }
156
-            } else {
157
-                // loop through each row of the file
158
-                while (($data = fgetcsv($file_handle, 0, ',', '"', '\\')) !== false) {
159
-                    $csvarray[] = $data;
160
-                }
161
-            }
162
-            # Close the File.
163
-            fclose($file_handle);
164
-            return $csvarray;
165
-        } else {
166
-            EE_Error::add_error(
167
-                sprintf(esc_html__("An error occurred - the file: %s could not opened.", "event_espresso"), $path_to_file),
168
-                __FILE__,
169
-                __FUNCTION__,
170
-                __LINE__
171
-            );
172
-            return false;
173
-        }
174
-    }
175
-
176
-
177
-    /**
178
-     * @Import contents of csv file and store values in an array to be manipulated by other functions
179
-     * @access public
180
-     * @param string  $path_to_file         - the csv file to be imported including the path to it's location.
181
-     *                                      If $model_name is provided, assumes that each row in the CSV represents a
182
-     *                                      model object for that model If $model_name ISN'T provided, assumes that
183
-     *                                      before model object data, there is a row where the first entry is simply
184
-     *                                      'MODEL', and next entry is the model's name, (untranslated) like Event, and
185
-     *                                      then maybe a row of headers, and then the model data. Eg.
186
-     *                                      '<br>MODEL,Event,<br>EVT_ID,EVT_name,...<br>1,Monkey
187
-     *                                      Party,...<br>2,Llamarama,...<br>MODEL,Venue,<br>VNU_ID,VNU_name<br>1,The
188
-     *                                      Forest
189
-     * @param string  $model_name           model name if we know what model we're importing
190
-     * @param boolean $first_row_is_headers - whether the first row of data is headers or not - TRUE = headers, FALSE =
191
-     *                                      data
192
-     * @return mixed - array on success - multi dimensional with headers as keys (if headers exist) OR string on fail -
193
-     *               error message like the following array('Event'=>array( array('EVT_ID'=>1,'EVT_name'=>'bob
194
-     *               party',...), array('EVT_ID'=>2,'EVT_name'=>'llamarama',...),
195
-     *                                      ...
196
-     *                                      )
197
-     *                                      'Venue'=>array(
198
-     *                                      array('VNU_ID'=>1,'VNU_name'=>'the shack',...),
199
-     *                                      array('VNU_ID'=>2,'VNU_name'=>'tree house',...),
200
-     *                                      ...
201
-     *                                      )
202
-     *                                      ...
203
-     *                                      )
204
-     */
205
-    public function import_csv_to_model_data_array($path_to_file, $model_name = false, $first_row_is_headers = true)
206
-    {
207
-        $multi_dimensional_array = $this->import_csv_to_multi_dimensional_array($path_to_file);
208
-        if (! $multi_dimensional_array) {
209
-            return false;
210
-        }
211
-        // gotta start somewhere
212
-        $row = 1;
213
-        // array to store csv data in
214
-        $ee_formatted_data = array();
215
-        // array to store headers (column names)
216
-        $headers = array();
217
-        foreach ($multi_dimensional_array as $data) {
218
-            // if first cell is MODEL, then second cell is the MODEL name
219
-            if ($data[0] == 'MODEL') {
220
-                $model_name = $data[1];
221
-                // don't bother looking for model data in this row. The rest of this
222
-                // row should be blank
223
-                // AND pretend this is the first row again
224
-                $row = 1;
225
-                // reset headers
226
-                $headers = array();
227
-                continue;
228
-            }
229
-            if (strpos($data[0], EE_CSV::metadata_header) !== false) {
230
-                $model_name = EE_CSV::metadata_header;
231
-                // store like model data, we just won't try importing it etc.
232
-                $row = 1;
233
-                continue;
234
-            }
235
-
236
-
237
-            // how many columns are there?
238
-            $columns = count($data);
239
-
240
-            $model_entry = array();
241
-            // loop through each column
242
-            for ($i = 0; $i < $columns; $i++) {
243
-                // replace csv_enclosures with backslashed quotes
244
-                $data[ $i ] = str_replace('"""', '\\"', $data[ $i ]);
245
-                // do we need to grab the column names?
246
-                if ($row === 1) {
247
-                    if ($first_row_is_headers) {
248
-                        // store the column names to use for keys
249
-                        $column_name = $data[ $i ];
250
-                        // check it's not blank... sometimes CSV editign programs adda bunch of empty columns onto the end...
251
-                        if (! $column_name) {
252
-                            continue;
253
-                        }
254
-                        $matches = array();
255
-                        if ($model_name == EE_CSV::metadata_header) {
256
-                            $headers[ $i ] = $column_name;
257
-                        } else {
258
-                            // now get the db table name from it (the part between square brackets)
259
-                            $success = preg_match('~(.*)\[(.*)\]~', $column_name, $matches);
260
-                            if (! $success) {
261
-                                EE_Error::add_error(
262
-                                    sprintf(
263
-                                        esc_html__(
264
-                                            "The column titled %s is invalid for importing. It must be be in the format of 'Nice Name[model_field_name]' in row %s",
265
-                                            "event_espresso"
266
-                                        ),
267
-                                        $column_name,
268
-                                        implode(",", $data)
269
-                                    ),
270
-                                    __FILE__,
271
-                                    __FUNCTION__,
272
-                                    __LINE__
273
-                                );
274
-                                return false;
275
-                            }
276
-                            $headers[ $i ] = $matches[2];
277
-                        }
278
-                    } else {
279
-                        // no column names means our final array will just use counters for keys
280
-                        $model_entry[ $headers[ $i ] ] = $data[ $i ];
281
-                        $headers[ $i ] = $i;
282
-                    }
283
-                    // and we need to store csv data
284
-                } else {
285
-                    // this column isn' ta header, store it if there is a header for it
286
-                    if (isset($headers[ $i ])) {
287
-                        $model_entry[ $headers[ $i ] ] = $data[ $i ];
288
-                    }
289
-                }
290
-            }
291
-            // save the row's data IF it's a non-header-row
292
-            if (! $first_row_is_headers || ($first_row_is_headers && $row > 1)) {
293
-                $ee_formatted_data[ $model_name ][] = $model_entry;
294
-            }
295
-            // advance to next row
296
-            $row++;
297
-        }
298
-
299
-        // delete the uploaded file
300
-        unlink($path_to_file);
301
-        // echo '<pre style="height:auto;border:2px solid lightblue;">' . print_r( $ee_formatted_data, TRUE ) . '</pre><br /><span style="font-size:10px;font-weight:normal;">' . __FILE__ . '<br />line no: ' . __LINE__ . '</span>';
302
-        // die();
303
-
304
-        // it's good to give back
305
-        return $ee_formatted_data;
306
-    }
307
-
308
-
309
-    public function save_csv_to_db($csv_data_array, $model_name = false)
310
-    {
311
-        EE_Error::doing_it_wrong(
312
-            'save_csv_to_db',
313
-            esc_html__(
314
-                'Function moved to EE_Import and renamed to save_csv_data_array_to_db',
315
-                'event_espresso'
316
-            ),
317
-            '4.6.7'
318
-        );
319
-        return EE_Import::instance()->save_csv_data_array_to_db($csv_data_array, $model_name);
320
-    }
321
-
322
-    /**
323
-     * Sends HTTP headers to indicate that the browser should download a file,
324
-     * and starts writing the file to PHP's output. Returns the file handle so other functions can
325
-     * also write to it
326
-     *
327
-     * @param string $new_filename the name of the file that the user will download
328
-     * @return resource, like the results of fopen(), which can be used for fwrite, fputcsv2, etc.
329
-     */
330
-    public function begin_sending_csv($filename)
331
-    {
332
-        // grab file extension
333
-        $ext = substr(strrchr($filename, '.'), 1);
334
-        if ($ext == '.csv' or $ext == '.xls') {
335
-            str_replace($ext, '', $filename);
336
-        }
337
-        $filename .= '.csv';
338
-
339
-        // if somebody's been naughty and already started outputting stuff, trash it
340
-        // and start writing our stuff.
341
-        if (ob_get_length()) {
342
-            @ob_flush();
343
-            @flush();
344
-            @ob_end_flush();
345
-        }
346
-        @ob_start();
347
-        header("Pragma: public");
348
-        header("Expires: 0");
349
-        header("Pragma: no-cache");
350
-        header("Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
351
-        // header("Content-Type: application/force-download");
352
-        // header("Content-Type: application/octet-stream");
353
-        // header("Content-Type: application/download");
354
-        header('Content-disposition: attachment; filename=' . $filename);
355
-        header("Content-Type: text/csv; charset=utf-8");
356
-        do_action('AHEE__EE_CSV__begin_sending_csv__headers');
357
-        echo apply_filters(
358
-            'FHEE__EE_CSV__begin_sending_csv__start_writing',
359
-            "\xEF\xBB\xBF"
360
-        ); // makes excel open it as UTF-8. UTF-8 BOM, see http://stackoverflow.com/a/4440143/2773835
361
-        $fh = fopen('php://output', 'w');
362
-        return $fh;
363
-    }
364
-
365
-    /**
366
-     * Writes some meta data to the CSV as a bunch of columns. Initially we're only
367
-     * mentioning the version and timezone
368
-     *
369
-     * @param resource $filehandle
370
-     */
371
-    public function write_metadata_to_csv($filehandle)
372
-    {
373
-        $data_row = array(EE_CSV::metadata_header);// do NOT translate because this exact string is used when importing
374
-        $this->fputcsv2($filehandle, $data_row);
375
-        $meta_data = array(
376
-            0 => array(
377
-                'version'        => espresso_version(),
378
-                'timezone'       => EEH_DTT_Helper::get_timezone(),
379
-                'time_of_export' => current_time('mysql'),
380
-                'site_url'       => site_url(),
381
-            ),
382
-        );
383
-        $this->write_data_array_to_csv($filehandle, $meta_data);
384
-    }
385
-
386
-
387
-    /**
388
-     * Writes $data to the csv file open in $filehandle. uses the array indices of $data for column headers
389
-     *
390
-     * @param array   $data                 2D array, first numerically-indexed, and next-level-down preferably indexed
391
-     *                                      by string
392
-     * @param boolean $add_csv_column_names whether or not we should add the keys in the bottom-most array as a row for
393
-     *                                      headers in the CSV. Eg, if $data looked like
394
-     *                                      array(0=>array('EVT_ID'=>1,'EVT_name'=>'monkey'...), 1=>array(...),...))
395
-     *                                      then the first row we'd write to the CSV would be "EVT_ID,EVT_name,..."
396
-     * @return boolean if we successfully wrote to the CSV or not. If there's no $data, we consider that a success
397
-     *                 (because we wrote everything there was...nothing)
398
-     */
399
-    public function write_data_array_to_csv($filehandle, $data)
400
-    {
401
-
402
-
403
-        // determine if $data is actually a 2d array
404
-        if ($data && is_array($data) && is_array(EEH_Array::get_one_item_from_array($data))) {
405
-            // make sure top level is numerically indexed,
406
-
407
-            if (EEH_Array::is_associative_array($data)) {
408
-                throw new EE_Error(
409
-                    sprintf(
410
-                        esc_html__(
411
-                            "top-level array must be numerically indexed. Does these look like numbers to you? %s",
412
-                            "event_espresso"
413
-                        ),
414
-                        implode(",", array_keys($data))
415
-                    )
416
-                );
417
-            }
418
-            $item_in_top_level_array = EEH_Array::get_one_item_from_array($data);
419
-            // now, is the last item in the top-level array of $data an associative or numeric array?
420
-            if (EEH_Array::is_associative_array($item_in_top_level_array)) {
421
-                // its associative, so we want to output its keys as column headers
422
-                $keys = array_keys($item_in_top_level_array);
423
-                $this->fputcsv2($filehandle, $keys);
424
-            }
425
-            // start writing data
426
-            foreach ($data as $data_row) {
427
-                $this->fputcsv2($filehandle, $data_row);
428
-            }
429
-            return true;
430
-        } else {
431
-            // no data TO write... so we can assume that's a success
432
-            return true;
433
-        }
434
-        // //if 2nd level is indexed by strings, use those as csv column headers (ie, the first row)
435
-        //
436
-        //
437
-        // $no_table = TRUE;
438
-        //
439
-        // // loop through data and add each row to the file/stream as csv
440
-        // foreach ( $data as $model_name => $model_data ) {
441
-        // // test first row to see if it is data or a model name
442
-        // $model = EE_Registry::instance();->load_model($model_name);
443
-        // //if the model really exists,
444
-        // if ( $model ) {
445
-        //
446
-        // // we have a table name
447
-        // $no_table = FALSE;
448
-        //
449
-        // // put the tablename into an array cuz that's how fputcsv rolls
450
-        // $model_name_row = array( 'MODEL', $model_name );
451
-        //
452
-        // // add table name to csv output
453
-        // echo self::fputcsv2($filehandle, $model_name_row);
454
-        //
455
-        // // now get the rest of the data
456
-        // foreach ( $model_data as $row ) {
457
-        // // output the row
458
-        // echo self::fputcsv2($filehandle, $row);
459
-        // }
460
-        //
461
-        // }
462
-        //
463
-        // if ( $no_table ) {
464
-        // // no table so just put the data
465
-        // echo self::fputcsv2($filehandle, $model_data);
466
-        // }
467
-        //
468
-        // } // END OF foreach ( $data )
469
-    }
470
-
471
-    /**
472
-     * Should be called after begin_sending_csv(), and one or more write_data_array_to_csv()s.
473
-     * Calls exit to prevent polluting the CSV file with other junk
474
-     *
475
-     * @param resource $fh filehandle where we're writing the CSV to
476
-     */
477
-    public function end_sending_csv($fh)
478
-    {
479
-        fclose($fh);
480
-        exit(0);
481
-    }
482
-
483
-    /**
484
-     * Given an open file, writes all the model data to it in the format the importer expects.
485
-     * Usually preceded by begin_sending_csv($filename), and followed by end_sending_csv($filehandle).
486
-     *
487
-     * @param resource $filehandle
488
-     * @param array    $model_data_array is assumed to be a 3d array: 1st layer has keys of model names (eg 'Event'),
489
-     *                                   next layer is numerically indexed to represent each model object (eg, each
490
-     *                                   individual event), and the last layer has all the attributes o fthat model
491
-     *                                   object (eg, the event's id, name, etc)
492
-     * @return boolean success
493
-     */
494
-    public function write_model_data_to_csv($filehandle, $model_data_array)
495
-    {
496
-        $this->write_metadata_to_csv($filehandle);
497
-        foreach ($model_data_array as $model_name => $model_instance_arrays) {
498
-            // first: output a special row stating the model
499
-            $this->fputcsv2($filehandle, array('MODEL', $model_name));
500
-            // if we have items to put in the CSV, do it normally
501
-
502
-            if (! empty($model_instance_arrays)) {
503
-                $this->write_data_array_to_csv($filehandle, $model_instance_arrays);
504
-            } else {
505
-                // echo "no data to write... so just write the headers";
506
-                // so there's actually NO model objects for that model.
507
-                // probably still want to show the columns
508
-                $model = EE_Registry::instance()->load_model($model_name);
509
-                $column_names = array();
510
-                foreach ($model->field_settings() as $field) {
511
-                    $column_names[ $field->get_nicename() . "[" . $field->get_name() . "]" ] = null;
512
-                }
513
-                $this->write_data_array_to_csv($filehandle, array($column_names));
514
-            }
515
-        }
516
-    }
517
-
518
-    /**
519
-     * Writes the CSV file to the output buffer, with rows corresponding to $model_data_array,
520
-     * and dies (in order to avoid other plugins from messing up the csv output)
521
-     *
522
-     * @param string $filename         the filename you want to give the file
523
-     * @param array  $model_data_array 3d array, as described in EE_CSV::write_model_data_to_csv()
524
-     * @return bool | void writes CSV file to output and dies
525
-     */
526
-    public function export_multiple_model_data_to_csv($filename, $model_data_array)
527
-    {
528
-        $filehandle = $this->begin_sending_csv($filename);
529
-        $this->write_model_data_to_csv($filehandle, $model_data_array);
530
-        $this->end_sending_csv($filehandle);
531
-    }
532
-
533
-    /**
534
-     * @Export contents of an array to csv file
535
-     * @access public
536
-     * @param array  $data     - the array of data to be converted to csv and exported
537
-     * @param string $filename - name for newly created csv file
538
-     * @return TRUE on success, FALSE on fail
539
-     */
540
-    public function export_array_to_csv($data = false, $filename = false)
541
-    {
542
-
543
-        // no data file?? get outta here
544
-        if (! $data or ! is_array($data) or empty($data)) {
545
-            return false;
546
-        }
547
-
548
-        // no filename?? get outta here
549
-        if (! $filename) {
550
-            return false;
551
-        }
552
-
553
-
554
-        // somebody told me i might need this ???
555
-        global $wpdb;
556
-        $prefix = $wpdb->prefix;
557
-
558
-
559
-        $fh = $this->begin_sending_csv($filename);
560
-
561
-
562
-        $this->end_sending_csv($fh);
563
-    }
564
-
565
-
566
-    /**
567
-     * @Determine the maximum upload file size based on php.ini settings
568
-     * @access    public
569
-     * @param int $percent_of_max - desired percentage of the max upload_mb
570
-     * @return int KB
571
-     */
572
-    public function get_max_upload_size($percent_of_max = false)
573
-    {
574
-
575
-        $max_upload = (int) (ini_get('upload_max_filesize'));
576
-        $max_post = (int) (ini_get('post_max_size'));
577
-        $memory_limit = (int) (ini_get('memory_limit'));
578
-
579
-        // determine the smallest of the three values from above
580
-        $upload_mb = min($max_upload, $max_post, $memory_limit);
581
-
582
-        // convert MB to KB
583
-        $upload_mb = $upload_mb * 1024;
584
-
585
-        // don't want the full monty? then reduce the max uplaod size
586
-        if ($percent_of_max) {
587
-            // is percent_of_max like this -> 50 or like this -> 0.50 ?
588
-            if ($percent_of_max > 1) {
589
-                // chnages 50 to 0.50
590
-                $percent_of_max = $percent_of_max / 100;
591
-            }
592
-            // make upload_mb a percentage of the max upload_mb
593
-            $upload_mb = $upload_mb * $percent_of_max;
594
-        }
595
-
596
-        return $upload_mb;
597
-    }
598
-
599
-
600
-    /**
601
-     * @Drop   in replacement for PHP's fputcsv function - but this one works!!!
602
-     * @access private
603
-     * @param resource $fh         - file handle - what we are writing to
604
-     * @param array    $row        - individual row of csv data
605
-     * @param string   $delimiter  - csv delimiter
606
-     * @param string   $enclosure  - csv enclosure
607
-     * @param string   $mysql_null - allows php NULL to be overridden with MySQl's insertable NULL value
608
-     * @return void
609
-     */
610
-    private function fputcsv2($fh, array $row, $delimiter = ',', $enclosure = '"', $mysql_null = false)
611
-    {
612
-        // Allow user to filter the csv delimiter and enclosure for other countries csv standards
613
-        $delimiter = apply_filters('FHEE__EE_CSV__fputcsv2__delimiter', $delimiter);
614
-        $enclosure = apply_filters('FHEE__EE_CSV__fputcsv2__enclosure', $enclosure);
615
-
616
-        $delimiter_esc = preg_quote($delimiter, '/');
617
-        $enclosure_esc = preg_quote($enclosure, '/');
618
-
619
-        $output = array();
620
-        foreach ($row as $field_value) {
621
-            if (is_object($field_value) || is_array($field_value)) {
622
-                $field_value = serialize($field_value);
623
-            }
624
-            if ($field_value === null && $mysql_null) {
625
-                $output[] = 'NULL';
626
-                continue;
627
-            }
628
-
629
-            $output[] = preg_match("/(?:${delimiter_esc}|${enclosure_esc}|\s)/", $field_value) ?
630
-                ($enclosure . str_replace($enclosure, $enclosure . $enclosure, $field_value) . $enclosure)
631
-                : $field_value;
632
-        }
633
-
634
-        fwrite($fh, join($delimiter, $output) . PHP_EOL);
635
-    }
636
-
637
-
638
-    // /**
639
-    //  * @CSV    Import / Export messages
640
-    //  * @access public
641
-    //  * @return void
642
-    //  */
643
-    // public function csv_admin_notices()
644
-    // {
645
-    //
646
-    //     // We play both kinds of music here! Country AND Western! - err... I mean, cycle through both types of notices
647
-    //     foreach (array('updates', 'errors') as $type) {
648
-    //
649
-    //         // if particular notice type is not empty, then "You've got Mail"
650
-    //         if (! empty($this->_notices[ $type ])) {
651
-    //
652
-    //             // is it an update or an error ?
653
-    //             $msg_class = $type == 'updates' ? 'updated' : 'error';
654
-    //             echo '<div id="message" class="' . $msg_class . '">';
655
-    //             // display each notice, however many that may be
656
-    //             foreach ($this->_notices[ $type ] as $message) {
657
-    //                 echo '<p>' . $message . '</p>';
658
-    //             }
659
-    //             // wrap it up
660
-    //             echo '</div>';
661
-    //         }
662
-    //     }
663
-    // }
664
-
665
-    /**
666
-     * Gets the date format to use in teh csv. filterable
667
-     *
668
-     * @param string $current_format
669
-     * @return string
670
-     */
671
-    public function get_date_format_for_csv($current_format = null)
672
-    {
673
-        return apply_filters('FHEE__EE_CSV__get_date_format_for_csv__format', 'Y-m-d', $current_format);
674
-    }
675
-
676
-    /**
677
-     * Gets the time format we want to use in CSV reports. Filterable
678
-     *
679
-     * @param string $current_format
680
-     * @return string
681
-     */
682
-    public function get_time_format_for_csv($current_format = null)
683
-    {
684
-        return apply_filters('FHEE__EE_CSV__get_time_format_for_csv__format', 'H:i:s', $current_format);
685
-    }
17
+	// instance of the EE_CSV object
18
+	private static $_instance = null;
19
+
20
+
21
+	// multidimensional array to store update & error messages
22
+	// var $_notices = array( 'updates' => array(), 'errors' => array() );
23
+
24
+
25
+	private $_primary_keys;
26
+
27
+	/**
28
+	 *
29
+	 * @var EE_Registry
30
+	 */
31
+	private $EE;
32
+	/**
33
+	 * string used for 1st cell in exports, which indicates that the following 2 rows will be metadata keys and values
34
+	 */
35
+	const metadata_header = 'Event Espresso Export Meta Data';
36
+
37
+	/**
38
+	 *        private constructor to prevent direct creation
39
+	 *
40
+	 * @Constructor
41
+	 * @access private
42
+	 * @return void
43
+	 */
44
+	private function __construct()
45
+	{
46
+		global $wpdb;
47
+
48
+		$this->_primary_keys = array(
49
+			$wpdb->prefix . 'esp_answer'                  => array('ANS_ID'),
50
+			$wpdb->prefix . 'esp_attendee'                => array('ATT_ID'),
51
+			$wpdb->prefix . 'esp_datetime'                => array('DTT_ID'),
52
+			$wpdb->prefix . 'esp_event_question_group'    => array('EQG_ID'),
53
+			$wpdb->prefix . 'esp_message_template'        => array('MTP_ID'),
54
+			$wpdb->prefix . 'esp_payment'                 => array('PAY_ID'),
55
+			$wpdb->prefix . 'esp_price'                   => array('PRC_ID'),
56
+			$wpdb->prefix . 'esp_price_type'              => array('PRT_ID'),
57
+			$wpdb->prefix . 'esp_question'                => array('QST_ID'),
58
+			$wpdb->prefix . 'esp_question_group'          => array('QSG_ID'),
59
+			$wpdb->prefix . 'esp_question_group_question' => array('QGQ_ID'),
60
+			$wpdb->prefix . 'esp_question_option'         => array('QSO_ID'),
61
+			$wpdb->prefix . 'esp_registration'            => array('REG_ID'),
62
+			$wpdb->prefix . 'esp_status'                  => array('STS_ID'),
63
+			$wpdb->prefix . 'esp_transaction'             => array('TXN_ID'),
64
+			$wpdb->prefix . 'esp_transaction'             => array('TXN_ID'),
65
+			$wpdb->prefix . 'events_detail'               => array('id'),
66
+			$wpdb->prefix . 'events_category_detail'      => array('id'),
67
+			$wpdb->prefix . 'events_category_rel'         => array('id'),
68
+			$wpdb->prefix . 'events_venue'                => array('id'),
69
+			$wpdb->prefix . 'events_venue_rel'            => array('emeta_id'),
70
+			$wpdb->prefix . 'events_locale'               => array('id'),
71
+			$wpdb->prefix . 'events_locale_rel'           => array('id'),
72
+			$wpdb->prefix . 'events_personnel'            => array('id'),
73
+			$wpdb->prefix . 'events_personnel_rel'        => array('id'),
74
+		);
75
+	}
76
+
77
+
78
+	/**
79
+	 *        @ singleton method used to instantiate class object
80
+	 *        @ access public
81
+	 *
82
+	 * @return EE_CSV
83
+	 */
84
+	public static function instance()
85
+	{
86
+		// check if class object is instantiated
87
+		if (self::$_instance === null or ! is_object(self::$_instance) or ! (self::$_instance instanceof EE_CSV)) {
88
+			self::$_instance = new self();
89
+		}
90
+		return self::$_instance;
91
+	}
92
+
93
+	/**
94
+	 * Opens a unicode or utf file (normal file_get_contents has difficulty readin ga unicode file. @see
95
+	 * http://stackoverflow.com/questions/15092764/how-to-read-unicode-text-file-in-php
96
+	 *
97
+	 * @param string $file_path
98
+	 * @return string
99
+	 * @throws EE_Error
100
+	 */
101
+	private function read_unicode_file($file_path)
102
+	{
103
+		$fc = "";
104
+		$fh = fopen($file_path, "rb");
105
+		if (! $fh) {
106
+			throw new EE_Error(sprintf(esc_html__("Cannot open file for read: %s<br>\n", 'event_espresso'), $file_path));
107
+		}
108
+		$flen = filesize($file_path);
109
+		$bc = fread($fh, $flen);
110
+		for ($i = 0; $i < $flen; $i++) {
111
+			$c = substr($bc, $i, 1);
112
+			if ((ord($c) != 0) && (ord($c) != 13)) {
113
+				$fc = $fc . $c;
114
+			}
115
+		}
116
+		if ((ord(substr($fc, 0, 1)) == 255) && (ord(substr($fc, 1, 1)) == 254)) {
117
+			$fc = substr($fc, 2);
118
+		}
119
+		return ($fc);
120
+	}
121
+
122
+
123
+	/**
124
+	 * Generic CSV-functionality to turn an entire CSV file into a single array that's
125
+	 * NOT in a specific format to EE. It's just a 2-level array, with top-level arrays
126
+	 * representing each row in the CSV file, and the second-level arrays being each column in that row
127
+	 *
128
+	 * @param string $path_to_file
129
+	 * @return array of arrays. Top-level array has rows, second-level array has each item
130
+	 */
131
+	public function import_csv_to_multi_dimensional_array($path_to_file)
132
+	{
133
+		// needed to deal with Mac line endings
134
+		ini_set('auto_detect_line_endings', true);
135
+
136
+		// because fgetcsv does not correctly deal with backslashed quotes such as \"
137
+		// we'll read the file into a string
138
+		$file_contents = $this->read_unicode_file($path_to_file);
139
+		// replace backslashed quotes with CSV enclosures
140
+		$file_contents = str_replace('\\"', '"""', $file_contents);
141
+		// HEY YOU! PUT THAT FILE BACK!!!
142
+		file_put_contents($path_to_file, $file_contents);
143
+
144
+		if (($file_handle = fopen($path_to_file, "r")) !== false) {
145
+			# Set the parent multidimensional array key to 0.
146
+			$nn = 0;
147
+			$csvarray = array();
148
+
149
+			// in PHP 5.3 fgetcsv accepts a 5th parameter, but the pre 5.3 versions of fgetcsv choke if passed more than 4 - is that crazy or what?
150
+			if (version_compare(PHP_VERSION, '5.3.0') < 0) {
151
+				//  PHP 5.2- version
152
+				// loop through each row of the file
153
+				while (($data = fgetcsv($file_handle, 0, ',', '"')) !== false) {
154
+					$csvarray[] = $data;
155
+				}
156
+			} else {
157
+				// loop through each row of the file
158
+				while (($data = fgetcsv($file_handle, 0, ',', '"', '\\')) !== false) {
159
+					$csvarray[] = $data;
160
+				}
161
+			}
162
+			# Close the File.
163
+			fclose($file_handle);
164
+			return $csvarray;
165
+		} else {
166
+			EE_Error::add_error(
167
+				sprintf(esc_html__("An error occurred - the file: %s could not opened.", "event_espresso"), $path_to_file),
168
+				__FILE__,
169
+				__FUNCTION__,
170
+				__LINE__
171
+			);
172
+			return false;
173
+		}
174
+	}
175
+
176
+
177
+	/**
178
+	 * @Import contents of csv file and store values in an array to be manipulated by other functions
179
+	 * @access public
180
+	 * @param string  $path_to_file         - the csv file to be imported including the path to it's location.
181
+	 *                                      If $model_name is provided, assumes that each row in the CSV represents a
182
+	 *                                      model object for that model If $model_name ISN'T provided, assumes that
183
+	 *                                      before model object data, there is a row where the first entry is simply
184
+	 *                                      'MODEL', and next entry is the model's name, (untranslated) like Event, and
185
+	 *                                      then maybe a row of headers, and then the model data. Eg.
186
+	 *                                      '<br>MODEL,Event,<br>EVT_ID,EVT_name,...<br>1,Monkey
187
+	 *                                      Party,...<br>2,Llamarama,...<br>MODEL,Venue,<br>VNU_ID,VNU_name<br>1,The
188
+	 *                                      Forest
189
+	 * @param string  $model_name           model name if we know what model we're importing
190
+	 * @param boolean $first_row_is_headers - whether the first row of data is headers or not - TRUE = headers, FALSE =
191
+	 *                                      data
192
+	 * @return mixed - array on success - multi dimensional with headers as keys (if headers exist) OR string on fail -
193
+	 *               error message like the following array('Event'=>array( array('EVT_ID'=>1,'EVT_name'=>'bob
194
+	 *               party',...), array('EVT_ID'=>2,'EVT_name'=>'llamarama',...),
195
+	 *                                      ...
196
+	 *                                      )
197
+	 *                                      'Venue'=>array(
198
+	 *                                      array('VNU_ID'=>1,'VNU_name'=>'the shack',...),
199
+	 *                                      array('VNU_ID'=>2,'VNU_name'=>'tree house',...),
200
+	 *                                      ...
201
+	 *                                      )
202
+	 *                                      ...
203
+	 *                                      )
204
+	 */
205
+	public function import_csv_to_model_data_array($path_to_file, $model_name = false, $first_row_is_headers = true)
206
+	{
207
+		$multi_dimensional_array = $this->import_csv_to_multi_dimensional_array($path_to_file);
208
+		if (! $multi_dimensional_array) {
209
+			return false;
210
+		}
211
+		// gotta start somewhere
212
+		$row = 1;
213
+		// array to store csv data in
214
+		$ee_formatted_data = array();
215
+		// array to store headers (column names)
216
+		$headers = array();
217
+		foreach ($multi_dimensional_array as $data) {
218
+			// if first cell is MODEL, then second cell is the MODEL name
219
+			if ($data[0] == 'MODEL') {
220
+				$model_name = $data[1];
221
+				// don't bother looking for model data in this row. The rest of this
222
+				// row should be blank
223
+				// AND pretend this is the first row again
224
+				$row = 1;
225
+				// reset headers
226
+				$headers = array();
227
+				continue;
228
+			}
229
+			if (strpos($data[0], EE_CSV::metadata_header) !== false) {
230
+				$model_name = EE_CSV::metadata_header;
231
+				// store like model data, we just won't try importing it etc.
232
+				$row = 1;
233
+				continue;
234
+			}
235
+
236
+
237
+			// how many columns are there?
238
+			$columns = count($data);
239
+
240
+			$model_entry = array();
241
+			// loop through each column
242
+			for ($i = 0; $i < $columns; $i++) {
243
+				// replace csv_enclosures with backslashed quotes
244
+				$data[ $i ] = str_replace('"""', '\\"', $data[ $i ]);
245
+				// do we need to grab the column names?
246
+				if ($row === 1) {
247
+					if ($first_row_is_headers) {
248
+						// store the column names to use for keys
249
+						$column_name = $data[ $i ];
250
+						// check it's not blank... sometimes CSV editign programs adda bunch of empty columns onto the end...
251
+						if (! $column_name) {
252
+							continue;
253
+						}
254
+						$matches = array();
255
+						if ($model_name == EE_CSV::metadata_header) {
256
+							$headers[ $i ] = $column_name;
257
+						} else {
258
+							// now get the db table name from it (the part between square brackets)
259
+							$success = preg_match('~(.*)\[(.*)\]~', $column_name, $matches);
260
+							if (! $success) {
261
+								EE_Error::add_error(
262
+									sprintf(
263
+										esc_html__(
264
+											"The column titled %s is invalid for importing. It must be be in the format of 'Nice Name[model_field_name]' in row %s",
265
+											"event_espresso"
266
+										),
267
+										$column_name,
268
+										implode(",", $data)
269
+									),
270
+									__FILE__,
271
+									__FUNCTION__,
272
+									__LINE__
273
+								);
274
+								return false;
275
+							}
276
+							$headers[ $i ] = $matches[2];
277
+						}
278
+					} else {
279
+						// no column names means our final array will just use counters for keys
280
+						$model_entry[ $headers[ $i ] ] = $data[ $i ];
281
+						$headers[ $i ] = $i;
282
+					}
283
+					// and we need to store csv data
284
+				} else {
285
+					// this column isn' ta header, store it if there is a header for it
286
+					if (isset($headers[ $i ])) {
287
+						$model_entry[ $headers[ $i ] ] = $data[ $i ];
288
+					}
289
+				}
290
+			}
291
+			// save the row's data IF it's a non-header-row
292
+			if (! $first_row_is_headers || ($first_row_is_headers && $row > 1)) {
293
+				$ee_formatted_data[ $model_name ][] = $model_entry;
294
+			}
295
+			// advance to next row
296
+			$row++;
297
+		}
298
+
299
+		// delete the uploaded file
300
+		unlink($path_to_file);
301
+		// echo '<pre style="height:auto;border:2px solid lightblue;">' . print_r( $ee_formatted_data, TRUE ) . '</pre><br /><span style="font-size:10px;font-weight:normal;">' . __FILE__ . '<br />line no: ' . __LINE__ . '</span>';
302
+		// die();
303
+
304
+		// it's good to give back
305
+		return $ee_formatted_data;
306
+	}
307
+
308
+
309
+	public function save_csv_to_db($csv_data_array, $model_name = false)
310
+	{
311
+		EE_Error::doing_it_wrong(
312
+			'save_csv_to_db',
313
+			esc_html__(
314
+				'Function moved to EE_Import and renamed to save_csv_data_array_to_db',
315
+				'event_espresso'
316
+			),
317
+			'4.6.7'
318
+		);
319
+		return EE_Import::instance()->save_csv_data_array_to_db($csv_data_array, $model_name);
320
+	}
321
+
322
+	/**
323
+	 * Sends HTTP headers to indicate that the browser should download a file,
324
+	 * and starts writing the file to PHP's output. Returns the file handle so other functions can
325
+	 * also write to it
326
+	 *
327
+	 * @param string $new_filename the name of the file that the user will download
328
+	 * @return resource, like the results of fopen(), which can be used for fwrite, fputcsv2, etc.
329
+	 */
330
+	public function begin_sending_csv($filename)
331
+	{
332
+		// grab file extension
333
+		$ext = substr(strrchr($filename, '.'), 1);
334
+		if ($ext == '.csv' or $ext == '.xls') {
335
+			str_replace($ext, '', $filename);
336
+		}
337
+		$filename .= '.csv';
338
+
339
+		// if somebody's been naughty and already started outputting stuff, trash it
340
+		// and start writing our stuff.
341
+		if (ob_get_length()) {
342
+			@ob_flush();
343
+			@flush();
344
+			@ob_end_flush();
345
+		}
346
+		@ob_start();
347
+		header("Pragma: public");
348
+		header("Expires: 0");
349
+		header("Pragma: no-cache");
350
+		header("Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
351
+		// header("Content-Type: application/force-download");
352
+		// header("Content-Type: application/octet-stream");
353
+		// header("Content-Type: application/download");
354
+		header('Content-disposition: attachment; filename=' . $filename);
355
+		header("Content-Type: text/csv; charset=utf-8");
356
+		do_action('AHEE__EE_CSV__begin_sending_csv__headers');
357
+		echo apply_filters(
358
+			'FHEE__EE_CSV__begin_sending_csv__start_writing',
359
+			"\xEF\xBB\xBF"
360
+		); // makes excel open it as UTF-8. UTF-8 BOM, see http://stackoverflow.com/a/4440143/2773835
361
+		$fh = fopen('php://output', 'w');
362
+		return $fh;
363
+	}
364
+
365
+	/**
366
+	 * Writes some meta data to the CSV as a bunch of columns. Initially we're only
367
+	 * mentioning the version and timezone
368
+	 *
369
+	 * @param resource $filehandle
370
+	 */
371
+	public function write_metadata_to_csv($filehandle)
372
+	{
373
+		$data_row = array(EE_CSV::metadata_header);// do NOT translate because this exact string is used when importing
374
+		$this->fputcsv2($filehandle, $data_row);
375
+		$meta_data = array(
376
+			0 => array(
377
+				'version'        => espresso_version(),
378
+				'timezone'       => EEH_DTT_Helper::get_timezone(),
379
+				'time_of_export' => current_time('mysql'),
380
+				'site_url'       => site_url(),
381
+			),
382
+		);
383
+		$this->write_data_array_to_csv($filehandle, $meta_data);
384
+	}
385
+
386
+
387
+	/**
388
+	 * Writes $data to the csv file open in $filehandle. uses the array indices of $data for column headers
389
+	 *
390
+	 * @param array   $data                 2D array, first numerically-indexed, and next-level-down preferably indexed
391
+	 *                                      by string
392
+	 * @param boolean $add_csv_column_names whether or not we should add the keys in the bottom-most array as a row for
393
+	 *                                      headers in the CSV. Eg, if $data looked like
394
+	 *                                      array(0=>array('EVT_ID'=>1,'EVT_name'=>'monkey'...), 1=>array(...),...))
395
+	 *                                      then the first row we'd write to the CSV would be "EVT_ID,EVT_name,..."
396
+	 * @return boolean if we successfully wrote to the CSV or not. If there's no $data, we consider that a success
397
+	 *                 (because we wrote everything there was...nothing)
398
+	 */
399
+	public function write_data_array_to_csv($filehandle, $data)
400
+	{
401
+
402
+
403
+		// determine if $data is actually a 2d array
404
+		if ($data && is_array($data) && is_array(EEH_Array::get_one_item_from_array($data))) {
405
+			// make sure top level is numerically indexed,
406
+
407
+			if (EEH_Array::is_associative_array($data)) {
408
+				throw new EE_Error(
409
+					sprintf(
410
+						esc_html__(
411
+							"top-level array must be numerically indexed. Does these look like numbers to you? %s",
412
+							"event_espresso"
413
+						),
414
+						implode(",", array_keys($data))
415
+					)
416
+				);
417
+			}
418
+			$item_in_top_level_array = EEH_Array::get_one_item_from_array($data);
419
+			// now, is the last item in the top-level array of $data an associative or numeric array?
420
+			if (EEH_Array::is_associative_array($item_in_top_level_array)) {
421
+				// its associative, so we want to output its keys as column headers
422
+				$keys = array_keys($item_in_top_level_array);
423
+				$this->fputcsv2($filehandle, $keys);
424
+			}
425
+			// start writing data
426
+			foreach ($data as $data_row) {
427
+				$this->fputcsv2($filehandle, $data_row);
428
+			}
429
+			return true;
430
+		} else {
431
+			// no data TO write... so we can assume that's a success
432
+			return true;
433
+		}
434
+		// //if 2nd level is indexed by strings, use those as csv column headers (ie, the first row)
435
+		//
436
+		//
437
+		// $no_table = TRUE;
438
+		//
439
+		// // loop through data and add each row to the file/stream as csv
440
+		// foreach ( $data as $model_name => $model_data ) {
441
+		// // test first row to see if it is data or a model name
442
+		// $model = EE_Registry::instance();->load_model($model_name);
443
+		// //if the model really exists,
444
+		// if ( $model ) {
445
+		//
446
+		// // we have a table name
447
+		// $no_table = FALSE;
448
+		//
449
+		// // put the tablename into an array cuz that's how fputcsv rolls
450
+		// $model_name_row = array( 'MODEL', $model_name );
451
+		//
452
+		// // add table name to csv output
453
+		// echo self::fputcsv2($filehandle, $model_name_row);
454
+		//
455
+		// // now get the rest of the data
456
+		// foreach ( $model_data as $row ) {
457
+		// // output the row
458
+		// echo self::fputcsv2($filehandle, $row);
459
+		// }
460
+		//
461
+		// }
462
+		//
463
+		// if ( $no_table ) {
464
+		// // no table so just put the data
465
+		// echo self::fputcsv2($filehandle, $model_data);
466
+		// }
467
+		//
468
+		// } // END OF foreach ( $data )
469
+	}
470
+
471
+	/**
472
+	 * Should be called after begin_sending_csv(), and one or more write_data_array_to_csv()s.
473
+	 * Calls exit to prevent polluting the CSV file with other junk
474
+	 *
475
+	 * @param resource $fh filehandle where we're writing the CSV to
476
+	 */
477
+	public function end_sending_csv($fh)
478
+	{
479
+		fclose($fh);
480
+		exit(0);
481
+	}
482
+
483
+	/**
484
+	 * Given an open file, writes all the model data to it in the format the importer expects.
485
+	 * Usually preceded by begin_sending_csv($filename), and followed by end_sending_csv($filehandle).
486
+	 *
487
+	 * @param resource $filehandle
488
+	 * @param array    $model_data_array is assumed to be a 3d array: 1st layer has keys of model names (eg 'Event'),
489
+	 *                                   next layer is numerically indexed to represent each model object (eg, each
490
+	 *                                   individual event), and the last layer has all the attributes o fthat model
491
+	 *                                   object (eg, the event's id, name, etc)
492
+	 * @return boolean success
493
+	 */
494
+	public function write_model_data_to_csv($filehandle, $model_data_array)
495
+	{
496
+		$this->write_metadata_to_csv($filehandle);
497
+		foreach ($model_data_array as $model_name => $model_instance_arrays) {
498
+			// first: output a special row stating the model
499
+			$this->fputcsv2($filehandle, array('MODEL', $model_name));
500
+			// if we have items to put in the CSV, do it normally
501
+
502
+			if (! empty($model_instance_arrays)) {
503
+				$this->write_data_array_to_csv($filehandle, $model_instance_arrays);
504
+			} else {
505
+				// echo "no data to write... so just write the headers";
506
+				// so there's actually NO model objects for that model.
507
+				// probably still want to show the columns
508
+				$model = EE_Registry::instance()->load_model($model_name);
509
+				$column_names = array();
510
+				foreach ($model->field_settings() as $field) {
511
+					$column_names[ $field->get_nicename() . "[" . $field->get_name() . "]" ] = null;
512
+				}
513
+				$this->write_data_array_to_csv($filehandle, array($column_names));
514
+			}
515
+		}
516
+	}
517
+
518
+	/**
519
+	 * Writes the CSV file to the output buffer, with rows corresponding to $model_data_array,
520
+	 * and dies (in order to avoid other plugins from messing up the csv output)
521
+	 *
522
+	 * @param string $filename         the filename you want to give the file
523
+	 * @param array  $model_data_array 3d array, as described in EE_CSV::write_model_data_to_csv()
524
+	 * @return bool | void writes CSV file to output and dies
525
+	 */
526
+	public function export_multiple_model_data_to_csv($filename, $model_data_array)
527
+	{
528
+		$filehandle = $this->begin_sending_csv($filename);
529
+		$this->write_model_data_to_csv($filehandle, $model_data_array);
530
+		$this->end_sending_csv($filehandle);
531
+	}
532
+
533
+	/**
534
+	 * @Export contents of an array to csv file
535
+	 * @access public
536
+	 * @param array  $data     - the array of data to be converted to csv and exported
537
+	 * @param string $filename - name for newly created csv file
538
+	 * @return TRUE on success, FALSE on fail
539
+	 */
540
+	public function export_array_to_csv($data = false, $filename = false)
541
+	{
542
+
543
+		// no data file?? get outta here
544
+		if (! $data or ! is_array($data) or empty($data)) {
545
+			return false;
546
+		}
547
+
548
+		// no filename?? get outta here
549
+		if (! $filename) {
550
+			return false;
551
+		}
552
+
553
+
554
+		// somebody told me i might need this ???
555
+		global $wpdb;
556
+		$prefix = $wpdb->prefix;
557
+
558
+
559
+		$fh = $this->begin_sending_csv($filename);
560
+
561
+
562
+		$this->end_sending_csv($fh);
563
+	}
564
+
565
+
566
+	/**
567
+	 * @Determine the maximum upload file size based on php.ini settings
568
+	 * @access    public
569
+	 * @param int $percent_of_max - desired percentage of the max upload_mb
570
+	 * @return int KB
571
+	 */
572
+	public function get_max_upload_size($percent_of_max = false)
573
+	{
574
+
575
+		$max_upload = (int) (ini_get('upload_max_filesize'));
576
+		$max_post = (int) (ini_get('post_max_size'));
577
+		$memory_limit = (int) (ini_get('memory_limit'));
578
+
579
+		// determine the smallest of the three values from above
580
+		$upload_mb = min($max_upload, $max_post, $memory_limit);
581
+
582
+		// convert MB to KB
583
+		$upload_mb = $upload_mb * 1024;
584
+
585
+		// don't want the full monty? then reduce the max uplaod size
586
+		if ($percent_of_max) {
587
+			// is percent_of_max like this -> 50 or like this -> 0.50 ?
588
+			if ($percent_of_max > 1) {
589
+				// chnages 50 to 0.50
590
+				$percent_of_max = $percent_of_max / 100;
591
+			}
592
+			// make upload_mb a percentage of the max upload_mb
593
+			$upload_mb = $upload_mb * $percent_of_max;
594
+		}
595
+
596
+		return $upload_mb;
597
+	}
598
+
599
+
600
+	/**
601
+	 * @Drop   in replacement for PHP's fputcsv function - but this one works!!!
602
+	 * @access private
603
+	 * @param resource $fh         - file handle - what we are writing to
604
+	 * @param array    $row        - individual row of csv data
605
+	 * @param string   $delimiter  - csv delimiter
606
+	 * @param string   $enclosure  - csv enclosure
607
+	 * @param string   $mysql_null - allows php NULL to be overridden with MySQl's insertable NULL value
608
+	 * @return void
609
+	 */
610
+	private function fputcsv2($fh, array $row, $delimiter = ',', $enclosure = '"', $mysql_null = false)
611
+	{
612
+		// Allow user to filter the csv delimiter and enclosure for other countries csv standards
613
+		$delimiter = apply_filters('FHEE__EE_CSV__fputcsv2__delimiter', $delimiter);
614
+		$enclosure = apply_filters('FHEE__EE_CSV__fputcsv2__enclosure', $enclosure);
615
+
616
+		$delimiter_esc = preg_quote($delimiter, '/');
617
+		$enclosure_esc = preg_quote($enclosure, '/');
618
+
619
+		$output = array();
620
+		foreach ($row as $field_value) {
621
+			if (is_object($field_value) || is_array($field_value)) {
622
+				$field_value = serialize($field_value);
623
+			}
624
+			if ($field_value === null && $mysql_null) {
625
+				$output[] = 'NULL';
626
+				continue;
627
+			}
628
+
629
+			$output[] = preg_match("/(?:${delimiter_esc}|${enclosure_esc}|\s)/", $field_value) ?
630
+				($enclosure . str_replace($enclosure, $enclosure . $enclosure, $field_value) . $enclosure)
631
+				: $field_value;
632
+		}
633
+
634
+		fwrite($fh, join($delimiter, $output) . PHP_EOL);
635
+	}
636
+
637
+
638
+	// /**
639
+	//  * @CSV    Import / Export messages
640
+	//  * @access public
641
+	//  * @return void
642
+	//  */
643
+	// public function csv_admin_notices()
644
+	// {
645
+	//
646
+	//     // We play both kinds of music here! Country AND Western! - err... I mean, cycle through both types of notices
647
+	//     foreach (array('updates', 'errors') as $type) {
648
+	//
649
+	//         // if particular notice type is not empty, then "You've got Mail"
650
+	//         if (! empty($this->_notices[ $type ])) {
651
+	//
652
+	//             // is it an update or an error ?
653
+	//             $msg_class = $type == 'updates' ? 'updated' : 'error';
654
+	//             echo '<div id="message" class="' . $msg_class . '">';
655
+	//             // display each notice, however many that may be
656
+	//             foreach ($this->_notices[ $type ] as $message) {
657
+	//                 echo '<p>' . $message . '</p>';
658
+	//             }
659
+	//             // wrap it up
660
+	//             echo '</div>';
661
+	//         }
662
+	//     }
663
+	// }
664
+
665
+	/**
666
+	 * Gets the date format to use in teh csv. filterable
667
+	 *
668
+	 * @param string $current_format
669
+	 * @return string
670
+	 */
671
+	public function get_date_format_for_csv($current_format = null)
672
+	{
673
+		return apply_filters('FHEE__EE_CSV__get_date_format_for_csv__format', 'Y-m-d', $current_format);
674
+	}
675
+
676
+	/**
677
+	 * Gets the time format we want to use in CSV reports. Filterable
678
+	 *
679
+	 * @param string $current_format
680
+	 * @return string
681
+	 */
682
+	public function get_time_format_for_csv($current_format = null)
683
+	{
684
+		return apply_filters('FHEE__EE_CSV__get_time_format_for_csv__format', 'H:i:s', $current_format);
685
+	}
686 686
 }
Please login to merge, or discard this patch.
Spacing   +48 added lines, -48 removed lines patch added patch discarded remove patch
@@ -46,31 +46,31 @@  discard block
 block discarded – undo
46 46
         global $wpdb;
47 47
 
48 48
         $this->_primary_keys = array(
49
-            $wpdb->prefix . 'esp_answer'                  => array('ANS_ID'),
50
-            $wpdb->prefix . 'esp_attendee'                => array('ATT_ID'),
51
-            $wpdb->prefix . 'esp_datetime'                => array('DTT_ID'),
52
-            $wpdb->prefix . 'esp_event_question_group'    => array('EQG_ID'),
53
-            $wpdb->prefix . 'esp_message_template'        => array('MTP_ID'),
54
-            $wpdb->prefix . 'esp_payment'                 => array('PAY_ID'),
55
-            $wpdb->prefix . 'esp_price'                   => array('PRC_ID'),
56
-            $wpdb->prefix . 'esp_price_type'              => array('PRT_ID'),
57
-            $wpdb->prefix . 'esp_question'                => array('QST_ID'),
58
-            $wpdb->prefix . 'esp_question_group'          => array('QSG_ID'),
59
-            $wpdb->prefix . 'esp_question_group_question' => array('QGQ_ID'),
60
-            $wpdb->prefix . 'esp_question_option'         => array('QSO_ID'),
61
-            $wpdb->prefix . 'esp_registration'            => array('REG_ID'),
62
-            $wpdb->prefix . 'esp_status'                  => array('STS_ID'),
63
-            $wpdb->prefix . 'esp_transaction'             => array('TXN_ID'),
64
-            $wpdb->prefix . 'esp_transaction'             => array('TXN_ID'),
65
-            $wpdb->prefix . 'events_detail'               => array('id'),
66
-            $wpdb->prefix . 'events_category_detail'      => array('id'),
67
-            $wpdb->prefix . 'events_category_rel'         => array('id'),
68
-            $wpdb->prefix . 'events_venue'                => array('id'),
69
-            $wpdb->prefix . 'events_venue_rel'            => array('emeta_id'),
70
-            $wpdb->prefix . 'events_locale'               => array('id'),
71
-            $wpdb->prefix . 'events_locale_rel'           => array('id'),
72
-            $wpdb->prefix . 'events_personnel'            => array('id'),
73
-            $wpdb->prefix . 'events_personnel_rel'        => array('id'),
49
+            $wpdb->prefix.'esp_answer'                  => array('ANS_ID'),
50
+            $wpdb->prefix.'esp_attendee'                => array('ATT_ID'),
51
+            $wpdb->prefix.'esp_datetime'                => array('DTT_ID'),
52
+            $wpdb->prefix.'esp_event_question_group'    => array('EQG_ID'),
53
+            $wpdb->prefix.'esp_message_template'        => array('MTP_ID'),
54
+            $wpdb->prefix.'esp_payment'                 => array('PAY_ID'),
55
+            $wpdb->prefix.'esp_price'                   => array('PRC_ID'),
56
+            $wpdb->prefix.'esp_price_type'              => array('PRT_ID'),
57
+            $wpdb->prefix.'esp_question'                => array('QST_ID'),
58
+            $wpdb->prefix.'esp_question_group'          => array('QSG_ID'),
59
+            $wpdb->prefix.'esp_question_group_question' => array('QGQ_ID'),
60
+            $wpdb->prefix.'esp_question_option'         => array('QSO_ID'),
61
+            $wpdb->prefix.'esp_registration'            => array('REG_ID'),
62
+            $wpdb->prefix.'esp_status'                  => array('STS_ID'),
63
+            $wpdb->prefix.'esp_transaction'             => array('TXN_ID'),
64
+            $wpdb->prefix.'esp_transaction'             => array('TXN_ID'),
65
+            $wpdb->prefix.'events_detail'               => array('id'),
66
+            $wpdb->prefix.'events_category_detail'      => array('id'),
67
+            $wpdb->prefix.'events_category_rel'         => array('id'),
68
+            $wpdb->prefix.'events_venue'                => array('id'),
69
+            $wpdb->prefix.'events_venue_rel'            => array('emeta_id'),
70
+            $wpdb->prefix.'events_locale'               => array('id'),
71
+            $wpdb->prefix.'events_locale_rel'           => array('id'),
72
+            $wpdb->prefix.'events_personnel'            => array('id'),
73
+            $wpdb->prefix.'events_personnel_rel'        => array('id'),
74 74
         );
75 75
     }
76 76
 
@@ -102,7 +102,7 @@  discard block
 block discarded – undo
102 102
     {
103 103
         $fc = "";
104 104
         $fh = fopen($file_path, "rb");
105
-        if (! $fh) {
105
+        if ( ! $fh) {
106 106
             throw new EE_Error(sprintf(esc_html__("Cannot open file for read: %s<br>\n", 'event_espresso'), $file_path));
107 107
         }
108 108
         $flen = filesize($file_path);
@@ -110,7 +110,7 @@  discard block
 block discarded – undo
110 110
         for ($i = 0; $i < $flen; $i++) {
111 111
             $c = substr($bc, $i, 1);
112 112
             if ((ord($c) != 0) && (ord($c) != 13)) {
113
-                $fc = $fc . $c;
113
+                $fc = $fc.$c;
114 114
             }
115 115
         }
116 116
         if ((ord(substr($fc, 0, 1)) == 255) && (ord(substr($fc, 1, 1)) == 254)) {
@@ -205,7 +205,7 @@  discard block
 block discarded – undo
205 205
     public function import_csv_to_model_data_array($path_to_file, $model_name = false, $first_row_is_headers = true)
206 206
     {
207 207
         $multi_dimensional_array = $this->import_csv_to_multi_dimensional_array($path_to_file);
208
-        if (! $multi_dimensional_array) {
208
+        if ( ! $multi_dimensional_array) {
209 209
             return false;
210 210
         }
211 211
         // gotta start somewhere
@@ -241,23 +241,23 @@  discard block
 block discarded – undo
241 241
             // loop through each column
242 242
             for ($i = 0; $i < $columns; $i++) {
243 243
                 // replace csv_enclosures with backslashed quotes
244
-                $data[ $i ] = str_replace('"""', '\\"', $data[ $i ]);
244
+                $data[$i] = str_replace('"""', '\\"', $data[$i]);
245 245
                 // do we need to grab the column names?
246 246
                 if ($row === 1) {
247 247
                     if ($first_row_is_headers) {
248 248
                         // store the column names to use for keys
249
-                        $column_name = $data[ $i ];
249
+                        $column_name = $data[$i];
250 250
                         // check it's not blank... sometimes CSV editign programs adda bunch of empty columns onto the end...
251
-                        if (! $column_name) {
251
+                        if ( ! $column_name) {
252 252
                             continue;
253 253
                         }
254 254
                         $matches = array();
255 255
                         if ($model_name == EE_CSV::metadata_header) {
256
-                            $headers[ $i ] = $column_name;
256
+                            $headers[$i] = $column_name;
257 257
                         } else {
258 258
                             // now get the db table name from it (the part between square brackets)
259 259
                             $success = preg_match('~(.*)\[(.*)\]~', $column_name, $matches);
260
-                            if (! $success) {
260
+                            if ( ! $success) {
261 261
                                 EE_Error::add_error(
262 262
                                     sprintf(
263 263
                                         esc_html__(
@@ -273,24 +273,24 @@  discard block
 block discarded – undo
273 273
                                 );
274 274
                                 return false;
275 275
                             }
276
-                            $headers[ $i ] = $matches[2];
276
+                            $headers[$i] = $matches[2];
277 277
                         }
278 278
                     } else {
279 279
                         // no column names means our final array will just use counters for keys
280
-                        $model_entry[ $headers[ $i ] ] = $data[ $i ];
281
-                        $headers[ $i ] = $i;
280
+                        $model_entry[$headers[$i]] = $data[$i];
281
+                        $headers[$i] = $i;
282 282
                     }
283 283
                     // and we need to store csv data
284 284
                 } else {
285 285
                     // this column isn' ta header, store it if there is a header for it
286
-                    if (isset($headers[ $i ])) {
287
-                        $model_entry[ $headers[ $i ] ] = $data[ $i ];
286
+                    if (isset($headers[$i])) {
287
+                        $model_entry[$headers[$i]] = $data[$i];
288 288
                     }
289 289
                 }
290 290
             }
291 291
             // save the row's data IF it's a non-header-row
292
-            if (! $first_row_is_headers || ($first_row_is_headers && $row > 1)) {
293
-                $ee_formatted_data[ $model_name ][] = $model_entry;
292
+            if ( ! $first_row_is_headers || ($first_row_is_headers && $row > 1)) {
293
+                $ee_formatted_data[$model_name][] = $model_entry;
294 294
             }
295 295
             // advance to next row
296 296
             $row++;
@@ -351,7 +351,7 @@  discard block
 block discarded – undo
351 351
         // header("Content-Type: application/force-download");
352 352
         // header("Content-Type: application/octet-stream");
353 353
         // header("Content-Type: application/download");
354
-        header('Content-disposition: attachment; filename=' . $filename);
354
+        header('Content-disposition: attachment; filename='.$filename);
355 355
         header("Content-Type: text/csv; charset=utf-8");
356 356
         do_action('AHEE__EE_CSV__begin_sending_csv__headers');
357 357
         echo apply_filters(
@@ -370,7 +370,7 @@  discard block
 block discarded – undo
370 370
      */
371 371
     public function write_metadata_to_csv($filehandle)
372 372
     {
373
-        $data_row = array(EE_CSV::metadata_header);// do NOT translate because this exact string is used when importing
373
+        $data_row = array(EE_CSV::metadata_header); // do NOT translate because this exact string is used when importing
374 374
         $this->fputcsv2($filehandle, $data_row);
375 375
         $meta_data = array(
376 376
             0 => array(
@@ -499,7 +499,7 @@  discard block
 block discarded – undo
499 499
             $this->fputcsv2($filehandle, array('MODEL', $model_name));
500 500
             // if we have items to put in the CSV, do it normally
501 501
 
502
-            if (! empty($model_instance_arrays)) {
502
+            if ( ! empty($model_instance_arrays)) {
503 503
                 $this->write_data_array_to_csv($filehandle, $model_instance_arrays);
504 504
             } else {
505 505
                 // echo "no data to write... so just write the headers";
@@ -508,7 +508,7 @@  discard block
 block discarded – undo
508 508
                 $model = EE_Registry::instance()->load_model($model_name);
509 509
                 $column_names = array();
510 510
                 foreach ($model->field_settings() as $field) {
511
-                    $column_names[ $field->get_nicename() . "[" . $field->get_name() . "]" ] = null;
511
+                    $column_names[$field->get_nicename()."[".$field->get_name()."]"] = null;
512 512
                 }
513 513
                 $this->write_data_array_to_csv($filehandle, array($column_names));
514 514
             }
@@ -541,12 +541,12 @@  discard block
 block discarded – undo
541 541
     {
542 542
 
543 543
         // no data file?? get outta here
544
-        if (! $data or ! is_array($data) or empty($data)) {
544
+        if ( ! $data or ! is_array($data) or empty($data)) {
545 545
             return false;
546 546
         }
547 547
 
548 548
         // no filename?? get outta here
549
-        if (! $filename) {
549
+        if ( ! $filename) {
550 550
             return false;
551 551
         }
552 552
 
@@ -627,11 +627,11 @@  discard block
 block discarded – undo
627 627
             }
628 628
 
629 629
             $output[] = preg_match("/(?:${delimiter_esc}|${enclosure_esc}|\s)/", $field_value) ?
630
-                ($enclosure . str_replace($enclosure, $enclosure . $enclosure, $field_value) . $enclosure)
630
+                ($enclosure.str_replace($enclosure, $enclosure.$enclosure, $field_value).$enclosure)
631 631
                 : $field_value;
632 632
         }
633 633
 
634
-        fwrite($fh, join($delimiter, $output) . PHP_EOL);
634
+        fwrite($fh, join($delimiter, $output).PHP_EOL);
635 635
     }
636 636
 
637 637
 
Please login to merge, or discard this patch.
core/db_classes/EE_Event.class.php 1 patch
Indentation   +1439 added lines, -1439 removed lines patch added patch discarded remove patch
@@ -16,1443 +16,1443 @@
 block discarded – undo
16 16
 class EE_Event extends EE_CPT_Base implements EEI_Line_Item_Object, EEI_Admin_Links, EEI_Has_Icon, EEI_Event
17 17
 {
18 18
 
19
-    /**
20
-     * cached value for the the logical active status for the event
21
-     *
22
-     * @see get_active_status()
23
-     * @var string
24
-     */
25
-    protected $_active_status = '';
26
-
27
-    /**
28
-     * This is just used for caching the Primary Datetime for the Event on initial retrieval
29
-     *
30
-     * @var EE_Datetime
31
-     */
32
-    protected $_Primary_Datetime;
33
-
34
-    /**
35
-     * @var EventSpacesCalculator $available_spaces_calculator
36
-     */
37
-    protected $available_spaces_calculator;
38
-
39
-
40
-    /**
41
-     * @param array  $props_n_values          incoming values
42
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
43
-     *                                        used.)
44
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
45
-     *                                        date_format and the second value is the time format
46
-     * @return EE_Event
47
-     * @throws EE_Error
48
-     * @throws ReflectionException
49
-     */
50
-    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
51
-    {
52
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
53
-        return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
54
-    }
55
-
56
-
57
-    /**
58
-     * @param array  $props_n_values  incoming values from the database
59
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
60
-     *                                the website will be used.
61
-     * @return EE_Event
62
-     * @throws EE_Error
63
-     * @throws ReflectionException
64
-     */
65
-    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
66
-    {
67
-        return new self($props_n_values, true, $timezone);
68
-    }
69
-
70
-
71
-    /**
72
-     * @return EventSpacesCalculator
73
-     * @throws \EE_Error
74
-     */
75
-    public function getAvailableSpacesCalculator()
76
-    {
77
-        if (! $this->available_spaces_calculator instanceof EventSpacesCalculator) {
78
-            $this->available_spaces_calculator = new EventSpacesCalculator($this);
79
-        }
80
-        return $this->available_spaces_calculator;
81
-    }
82
-
83
-
84
-    /**
85
-     * Overrides parent set() method so that all calls to set( 'status', $status ) can be routed to internal methods
86
-     *
87
-     * @param string $field_name
88
-     * @param mixed  $field_value
89
-     * @param bool   $use_default
90
-     * @throws EE_Error
91
-     * @throws ReflectionException
92
-     */
93
-    public function set($field_name, $field_value, $use_default = false)
94
-    {
95
-        switch ($field_name) {
96
-            case 'status':
97
-                $this->set_status($field_value, $use_default);
98
-                break;
99
-            default:
100
-                parent::set($field_name, $field_value, $use_default);
101
-        }
102
-    }
103
-
104
-
105
-    /**
106
-     *    set_status
107
-     * Checks if event status is being changed to SOLD OUT
108
-     * and updates event meta data with previous event status
109
-     * so that we can revert things if/when the event is no longer sold out
110
-     *
111
-     * @access public
112
-     * @param string $new_status
113
-     * @param bool   $use_default
114
-     * @return void
115
-     * @throws EE_Error
116
-     * @throws ReflectionException
117
-     */
118
-    public function set_status($new_status = null, $use_default = false)
119
-    {
120
-        // if nothing is set, and we aren't explicitly wanting to reset the status, then just leave
121
-        if (empty($new_status) && ! $use_default) {
122
-            return;
123
-        }
124
-        // get current Event status
125
-        $old_status = $this->status();
126
-        // if status has changed
127
-        if ($old_status !== $new_status) {
128
-            // TO sold_out
129
-            if ($new_status === EEM_Event::sold_out) {
130
-                // save the previous event status so that we can revert if the event is no longer sold out
131
-                $this->add_post_meta('_previous_event_status', $old_status);
132
-                do_action('AHEE__EE_Event__set_status__to_sold_out', $this, $old_status, $new_status);
133
-                // OR FROM  sold_out
134
-            } elseif ($old_status === EEM_Event::sold_out) {
135
-                $this->delete_post_meta('_previous_event_status');
136
-                do_action('AHEE__EE_Event__set_status__from_sold_out', $this, $old_status, $new_status);
137
-            }
138
-            // clear out the active status so that it gets reset the next time it is requested
139
-            $this->_active_status = null;
140
-            // update status
141
-            parent::set('status', $new_status, $use_default);
142
-            do_action('AHEE__EE_Event__set_status__after_update', $this);
143
-            return;
144
-        }
145
-        // even though the old value matches the new value, it's still good to
146
-        // allow the parent set method to have a say
147
-        parent::set('status', $new_status, $use_default);
148
-    }
149
-
150
-
151
-    /**
152
-     * Gets all the datetimes for this event
153
-     *
154
-     * @param array $query_params @see
155
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
156
-     * @return EE_Base_Class[]|EE_Datetime[]
157
-     * @throws EE_Error
158
-     * @throws ReflectionException
159
-     */
160
-    public function datetimes($query_params = array())
161
-    {
162
-        return $this->get_many_related('Datetime', $query_params);
163
-    }
164
-
165
-
166
-    /**
167
-     * Gets all the datetimes for this event, ordered by DTT_EVT_start in ascending order
168
-     *
169
-     * @return EE_Base_Class[]|EE_Datetime[]
170
-     * @throws EE_Error
171
-     * @throws ReflectionException
172
-     */
173
-    public function datetimes_in_chronological_order()
174
-    {
175
-        return $this->get_many_related('Datetime', array('order_by' => array('DTT_EVT_start' => 'ASC')));
176
-    }
177
-
178
-
179
-    /**
180
-     * Gets all the datetimes for this event, ordered by the DTT_order on the datetime.
181
-     * @darren, we should probably UNSET timezone on the EEM_Datetime model
182
-     * after running our query, so that this timezone isn't set for EVERY query
183
-     * on EEM_Datetime for the rest of the request, no?
184
-     *
185
-     * @param boolean $show_expired whether or not to include expired events
186
-     * @param boolean $show_deleted whether or not to include deleted events
187
-     * @param null    $limit
188
-     * @return EE_Datetime[]
189
-     * @throws EE_Error
190
-     * @throws ReflectionException
191
-     */
192
-    public function datetimes_ordered($show_expired = true, $show_deleted = false, $limit = null)
193
-    {
194
-        return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_event_ordered_by_DTT_order(
195
-            $this->ID(),
196
-            $show_expired,
197
-            $show_deleted,
198
-            $limit
199
-        );
200
-    }
201
-
202
-
203
-    /**
204
-     * Returns one related datetime. Mostly only used by some legacy code.
205
-     *
206
-     * @return EE_Base_Class|EE_Datetime
207
-     * @throws EE_Error
208
-     * @throws ReflectionException
209
-     */
210
-    public function first_datetime()
211
-    {
212
-        return $this->get_first_related('Datetime');
213
-    }
214
-
215
-
216
-    /**
217
-     * Returns the 'primary' datetime for the event
218
-     *
219
-     * @param bool $try_to_exclude_expired
220
-     * @param bool $try_to_exclude_deleted
221
-     * @return EE_Datetime
222
-     * @throws EE_Error
223
-     * @throws ReflectionException
224
-     */
225
-    public function primary_datetime($try_to_exclude_expired = true, $try_to_exclude_deleted = true)
226
-    {
227
-        if (! empty($this->_Primary_Datetime)) {
228
-            return $this->_Primary_Datetime;
229
-        }
230
-        $this->_Primary_Datetime = EEM_Datetime::instance($this->_timezone)->get_primary_datetime_for_event(
231
-            $this->ID(),
232
-            $try_to_exclude_expired,
233
-            $try_to_exclude_deleted
234
-        );
235
-        return $this->_Primary_Datetime;
236
-    }
237
-
238
-
239
-    /**
240
-     * Gets all the tickets available for purchase of this event
241
-     *
242
-     * @param array $query_params @see
243
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
244
-     * @return EE_Base_Class[]|EE_Ticket[]
245
-     * @throws EE_Error
246
-     * @throws ReflectionException
247
-     */
248
-    public function tickets($query_params = array())
249
-    {
250
-        // first get all datetimes
251
-        $datetimes = $this->datetimes_ordered();
252
-        if (! $datetimes) {
253
-            return array();
254
-        }
255
-        $datetime_ids = array();
256
-        foreach ($datetimes as $datetime) {
257
-            $datetime_ids[] = $datetime->ID();
258
-        }
259
-        $where_params = array('Datetime.DTT_ID' => array('IN', $datetime_ids));
260
-        // if incoming $query_params has where conditions let's merge but not override existing.
261
-        if (is_array($query_params) && isset($query_params[0])) {
262
-            $where_params = array_merge($query_params[0], $where_params);
263
-            unset($query_params[0]);
264
-        }
265
-        // now add $where_params to $query_params
266
-        $query_params[0] = $where_params;
267
-        return EEM_Ticket::instance()->get_all($query_params);
268
-    }
269
-
270
-
271
-    /**
272
-     * get all unexpired untrashed tickets
273
-     *
274
-     * @return EE_Ticket[]
275
-     * @throws EE_Error
276
-     */
277
-    public function active_tickets()
278
-    {
279
-        return $this->tickets(
280
-            array(
281
-                array(
282
-                    'TKT_end_date' => array('>=', EEM_Ticket::instance()->current_time_for_query('TKT_end_date')),
283
-                    'TKT_deleted'  => false,
284
-                ),
285
-            )
286
-        );
287
-    }
288
-
289
-
290
-    /**
291
-     * @return bool
292
-     * @throws EE_Error
293
-     * @throws ReflectionException
294
-     */
295
-    public function additional_limit()
296
-    {
297
-        return $this->get('EVT_additional_limit');
298
-    }
299
-
300
-
301
-    /**
302
-     * @return bool
303
-     * @throws EE_Error
304
-     * @throws ReflectionException
305
-     */
306
-    public function allow_overflow()
307
-    {
308
-        return $this->get('EVT_allow_overflow');
309
-    }
310
-
311
-
312
-    /**
313
-     * @return bool
314
-     * @throws EE_Error
315
-     * @throws ReflectionException
316
-     */
317
-    public function created()
318
-    {
319
-        return $this->get('EVT_created');
320
-    }
321
-
322
-
323
-    /**
324
-     * @return bool
325
-     * @throws EE_Error
326
-     * @throws ReflectionException
327
-     */
328
-    public function description()
329
-    {
330
-        return $this->get('EVT_desc');
331
-    }
332
-
333
-
334
-    /**
335
-     * Runs do_shortcode and wpautop on the description
336
-     *
337
-     * @return string of html
338
-     * @throws EE_Error
339
-     * @throws ReflectionException
340
-     */
341
-    public function description_filtered()
342
-    {
343
-        return $this->get_pretty('EVT_desc');
344
-    }
345
-
346
-
347
-    /**
348
-     * @return bool
349
-     * @throws EE_Error
350
-     * @throws ReflectionException
351
-     */
352
-    public function display_description()
353
-    {
354
-        return $this->get('EVT_display_desc');
355
-    }
356
-
357
-
358
-    /**
359
-     * @return bool
360
-     * @throws EE_Error
361
-     * @throws ReflectionException
362
-     */
363
-    public function display_ticket_selector()
364
-    {
365
-        return (bool) $this->get('EVT_display_ticket_selector');
366
-    }
367
-
368
-
369
-    /**
370
-     * @return string
371
-     * @throws EE_Error
372
-     * @throws ReflectionException
373
-     */
374
-    public function external_url()
375
-    {
376
-        return $this->get('EVT_external_URL');
377
-    }
378
-
379
-
380
-    /**
381
-     * @return bool
382
-     * @throws EE_Error
383
-     * @throws ReflectionException
384
-     */
385
-    public function member_only()
386
-    {
387
-        return $this->get('EVT_member_only');
388
-    }
389
-
390
-
391
-    /**
392
-     * @return bool
393
-     * @throws EE_Error
394
-     * @throws ReflectionException
395
-     */
396
-    public function phone()
397
-    {
398
-        return $this->get('EVT_phone');
399
-    }
400
-
401
-
402
-    /**
403
-     * @return bool
404
-     * @throws EE_Error
405
-     * @throws ReflectionException
406
-     */
407
-    public function modified()
408
-    {
409
-        return $this->get('EVT_modified');
410
-    }
411
-
412
-
413
-    /**
414
-     * @return bool
415
-     * @throws EE_Error
416
-     * @throws ReflectionException
417
-     */
418
-    public function name()
419
-    {
420
-        return $this->get('EVT_name');
421
-    }
422
-
423
-
424
-    /**
425
-     * @return bool
426
-     * @throws EE_Error
427
-     * @throws ReflectionException
428
-     */
429
-    public function order()
430
-    {
431
-        return $this->get('EVT_order');
432
-    }
433
-
434
-
435
-    /**
436
-     * @return bool|string
437
-     * @throws EE_Error
438
-     * @throws ReflectionException
439
-     */
440
-    public function default_registration_status()
441
-    {
442
-        $event_default_registration_status = $this->get('EVT_default_registration_status');
443
-        return ! empty($event_default_registration_status)
444
-            ? $event_default_registration_status
445
-            : EE_Registry::instance()->CFG->registration->default_STS_ID;
446
-    }
447
-
448
-
449
-    /**
450
-     * @param int  $num_words
451
-     * @param null $more
452
-     * @param bool $not_full_desc
453
-     * @return bool|string
454
-     * @throws EE_Error
455
-     * @throws ReflectionException
456
-     */
457
-    public function short_description($num_words = 55, $more = null, $not_full_desc = false)
458
-    {
459
-        $short_desc = $this->get('EVT_short_desc');
460
-        if (! empty($short_desc) || $not_full_desc) {
461
-            return $short_desc;
462
-        }
463
-        $full_desc = $this->get('EVT_desc');
464
-        return wp_trim_words($full_desc, $num_words, $more);
465
-    }
466
-
467
-
468
-    /**
469
-     * @return bool
470
-     * @throws EE_Error
471
-     * @throws ReflectionException
472
-     */
473
-    public function slug()
474
-    {
475
-        return $this->get('EVT_slug');
476
-    }
477
-
478
-
479
-    /**
480
-     * @return bool
481
-     * @throws EE_Error
482
-     * @throws ReflectionException
483
-     */
484
-    public function timezone_string()
485
-    {
486
-        return $this->get('EVT_timezone_string');
487
-    }
488
-
489
-
490
-    /**
491
-     * @return bool
492
-     * @throws EE_Error
493
-     * @throws ReflectionException
494
-     */
495
-    public function visible_on()
496
-    {
497
-        return $this->get('EVT_visible_on');
498
-    }
499
-
500
-
501
-    /**
502
-     * @return int
503
-     * @throws EE_Error
504
-     * @throws ReflectionException
505
-     */
506
-    public function wp_user()
507
-    {
508
-        return $this->get('EVT_wp_user');
509
-    }
510
-
511
-
512
-    /**
513
-     * @return bool
514
-     * @throws EE_Error
515
-     * @throws ReflectionException
516
-     */
517
-    public function donations()
518
-    {
519
-        return $this->get('EVT_donations');
520
-    }
521
-
522
-
523
-    /**
524
-     * @param $limit
525
-     * @throws EE_Error
526
-     */
527
-    public function set_additional_limit($limit)
528
-    {
529
-        $this->set('EVT_additional_limit', $limit);
530
-    }
531
-
532
-
533
-    /**
534
-     * @param $created
535
-     * @throws EE_Error
536
-     */
537
-    public function set_created($created)
538
-    {
539
-        $this->set('EVT_created', $created);
540
-    }
541
-
542
-
543
-    /**
544
-     * @param $desc
545
-     * @throws EE_Error
546
-     */
547
-    public function set_description($desc)
548
-    {
549
-        $this->set('EVT_desc', $desc);
550
-    }
551
-
552
-
553
-    /**
554
-     * @param $display_desc
555
-     * @throws EE_Error
556
-     */
557
-    public function set_display_description($display_desc)
558
-    {
559
-        $this->set('EVT_display_desc', $display_desc);
560
-    }
561
-
562
-
563
-    /**
564
-     * @param $display_ticket_selector
565
-     * @throws EE_Error
566
-     */
567
-    public function set_display_ticket_selector($display_ticket_selector)
568
-    {
569
-        $this->set('EVT_display_ticket_selector', $display_ticket_selector);
570
-    }
571
-
572
-
573
-    /**
574
-     * @param $external_url
575
-     * @throws EE_Error
576
-     */
577
-    public function set_external_url($external_url)
578
-    {
579
-        $this->set('EVT_external_URL', $external_url);
580
-    }
581
-
582
-
583
-    /**
584
-     * @param $member_only
585
-     * @throws EE_Error
586
-     */
587
-    public function set_member_only($member_only)
588
-    {
589
-        $this->set('EVT_member_only', $member_only);
590
-    }
591
-
592
-
593
-    /**
594
-     * @param $event_phone
595
-     * @throws EE_Error
596
-     */
597
-    public function set_event_phone($event_phone)
598
-    {
599
-        $this->set('EVT_phone', $event_phone);
600
-    }
601
-
602
-
603
-    /**
604
-     * @param $modified
605
-     * @throws EE_Error
606
-     */
607
-    public function set_modified($modified)
608
-    {
609
-        $this->set('EVT_modified', $modified);
610
-    }
611
-
612
-
613
-    /**
614
-     * @param $name
615
-     * @throws EE_Error
616
-     */
617
-    public function set_name($name)
618
-    {
619
-        $this->set('EVT_name', $name);
620
-    }
621
-
622
-
623
-    /**
624
-     * @param $order
625
-     * @throws EE_Error
626
-     */
627
-    public function set_order($order)
628
-    {
629
-        $this->set('EVT_order', $order);
630
-    }
631
-
632
-
633
-    /**
634
-     * @param $short_desc
635
-     * @throws EE_Error
636
-     */
637
-    public function set_short_description($short_desc)
638
-    {
639
-        $this->set('EVT_short_desc', $short_desc);
640
-    }
641
-
642
-
643
-    /**
644
-     * @param $slug
645
-     * @throws EE_Error
646
-     */
647
-    public function set_slug($slug)
648
-    {
649
-        $this->set('EVT_slug', $slug);
650
-    }
651
-
652
-
653
-    /**
654
-     * @param $timezone_string
655
-     * @throws EE_Error
656
-     */
657
-    public function set_timezone_string($timezone_string)
658
-    {
659
-        $this->set('EVT_timezone_string', $timezone_string);
660
-    }
661
-
662
-
663
-    /**
664
-     * @param $visible_on
665
-     * @throws EE_Error
666
-     */
667
-    public function set_visible_on($visible_on)
668
-    {
669
-        $this->set('EVT_visible_on', $visible_on);
670
-    }
671
-
672
-
673
-    /**
674
-     * @param $wp_user
675
-     * @throws EE_Error
676
-     */
677
-    public function set_wp_user($wp_user)
678
-    {
679
-        $this->set('EVT_wp_user', $wp_user);
680
-    }
681
-
682
-
683
-    /**
684
-     * @param $default_registration_status
685
-     * @throws EE_Error
686
-     */
687
-    public function set_default_registration_status($default_registration_status)
688
-    {
689
-        $this->set('EVT_default_registration_status', $default_registration_status);
690
-    }
691
-
692
-
693
-    /**
694
-     * @param $donations
695
-     * @throws EE_Error
696
-     */
697
-    public function set_donations($donations)
698
-    {
699
-        $this->set('EVT_donations', $donations);
700
-    }
701
-
702
-
703
-    /**
704
-     * Adds a venue to this event
705
-     *
706
-     * @param EE_Venue /int $venue_id_or_obj
707
-     * @return EE_Base_Class|EE_Venue
708
-     * @throws EE_Error
709
-     * @throws ReflectionException
710
-     */
711
-    public function add_venue($venue_id_or_obj)
712
-    {
713
-        return $this->_add_relation_to($venue_id_or_obj, 'Venue');
714
-    }
715
-
716
-
717
-    /**
718
-     * Removes a venue from the event
719
-     *
720
-     * @param EE_Venue /int $venue_id_or_obj
721
-     * @return EE_Base_Class|EE_Venue
722
-     * @throws EE_Error
723
-     * @throws ReflectionException
724
-     */
725
-    public function remove_venue($venue_id_or_obj)
726
-    {
727
-        return $this->_remove_relation_to($venue_id_or_obj, 'Venue');
728
-    }
729
-
730
-
731
-    /**
732
-     * Gets all the venues related ot the event. May provide additional $query_params if desired
733
-     *
734
-     * @param array $query_params @see
735
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
736
-     * @return EE_Base_Class[]|EE_Venue[]
737
-     * @throws EE_Error
738
-     * @throws ReflectionException
739
-     */
740
-    public function venues($query_params = array())
741
-    {
742
-        return $this->get_many_related('Venue', $query_params);
743
-    }
744
-
745
-
746
-    /**
747
-     * check if event id is present and if event is published
748
-     *
749
-     * @access public
750
-     * @return boolean true yes, false no
751
-     * @throws EE_Error
752
-     * @throws ReflectionException
753
-     */
754
-    private function _has_ID_and_is_published()
755
-    {
756
-        // first check if event id is present and not NULL,
757
-        // then check if this event is published (or any of the equivalent "published" statuses)
758
-        return
759
-            $this->ID() && $this->ID() !== null
760
-            && (
761
-                $this->status() === 'publish'
762
-                || $this->status() === EEM_Event::sold_out
763
-                || $this->status() === EEM_Event::postponed
764
-                || $this->status() === EEM_Event::cancelled
765
-            );
766
-    }
767
-
768
-
769
-    /**
770
-     * This simply compares the internal dates with NOW and determines if the event is upcoming or not.
771
-     *
772
-     * @access public
773
-     * @return boolean true yes, false no
774
-     * @throws EE_Error
775
-     * @throws ReflectionException
776
-     */
777
-    public function is_upcoming()
778
-    {
779
-        // check if event id is present and if this event is published
780
-        if ($this->is_inactive()) {
781
-            return false;
782
-        }
783
-        // set initial value
784
-        $upcoming = false;
785
-        // next let's get all datetimes and loop through them
786
-        $datetimes = $this->datetimes_in_chronological_order();
787
-        foreach ($datetimes as $datetime) {
788
-            if ($datetime instanceof EE_Datetime) {
789
-                // if this dtt is expired then we continue cause one of the other datetimes might be upcoming.
790
-                if ($datetime->is_expired()) {
791
-                    continue;
792
-                }
793
-                // if this dtt is active then we return false.
794
-                if ($datetime->is_active()) {
795
-                    return false;
796
-                }
797
-                // otherwise let's check upcoming status
798
-                $upcoming = $datetime->is_upcoming();
799
-            }
800
-        }
801
-        return $upcoming;
802
-    }
803
-
804
-
805
-    /**
806
-     * @return bool
807
-     * @throws EE_Error
808
-     * @throws ReflectionException
809
-     */
810
-    public function is_active()
811
-    {
812
-        // check if event id is present and if this event is published
813
-        if ($this->is_inactive()) {
814
-            return false;
815
-        }
816
-        // set initial value
817
-        $active = false;
818
-        // next let's get all datetimes and loop through them
819
-        $datetimes = $this->datetimes_in_chronological_order();
820
-        foreach ($datetimes as $datetime) {
821
-            if ($datetime instanceof EE_Datetime) {
822
-                // if this dtt is expired then we continue cause one of the other datetimes might be active.
823
-                if ($datetime->is_expired()) {
824
-                    continue;
825
-                }
826
-                // if this dtt is upcoming then we return false.
827
-                if ($datetime->is_upcoming()) {
828
-                    return false;
829
-                }
830
-                // otherwise let's check active status
831
-                $active = $datetime->is_active();
832
-            }
833
-        }
834
-        return $active;
835
-    }
836
-
837
-
838
-    /**
839
-     * @return bool
840
-     * @throws EE_Error
841
-     * @throws ReflectionException
842
-     */
843
-    public function is_expired()
844
-    {
845
-        // check if event id is present and if this event is published
846
-        if ($this->is_inactive()) {
847
-            return false;
848
-        }
849
-        // set initial value
850
-        $expired = false;
851
-        // first let's get all datetimes and loop through them
852
-        $datetimes = $this->datetimes_in_chronological_order();
853
-        foreach ($datetimes as $datetime) {
854
-            if ($datetime instanceof EE_Datetime) {
855
-                // if this dtt is upcoming or active then we return false.
856
-                if ($datetime->is_upcoming() || $datetime->is_active()) {
857
-                    return false;
858
-                }
859
-                // otherwise let's check active status
860
-                $expired = $datetime->is_expired();
861
-            }
862
-        }
863
-        return $expired;
864
-    }
865
-
866
-
867
-    /**
868
-     * @return bool
869
-     * @throws EE_Error
870
-     */
871
-    public function is_inactive()
872
-    {
873
-        // check if event id is present and if this event is published
874
-        if ($this->_has_ID_and_is_published()) {
875
-            return false;
876
-        }
877
-        return true;
878
-    }
879
-
880
-
881
-    /**
882
-     * calculate spaces remaining based on "saleable" tickets
883
-     *
884
-     * @param array $tickets
885
-     * @param bool  $filtered
886
-     * @return int|float
887
-     * @throws EE_Error
888
-     * @throws DomainException
889
-     * @throws UnexpectedEntityException
890
-     */
891
-    public function spaces_remaining($tickets = array(), $filtered = true)
892
-    {
893
-        $this->getAvailableSpacesCalculator()->setActiveTickets($tickets);
894
-        $spaces_remaining = $this->getAvailableSpacesCalculator()->spacesRemaining();
895
-        return $filtered
896
-            ? apply_filters(
897
-                'FHEE_EE_Event__spaces_remaining',
898
-                $spaces_remaining,
899
-                $this,
900
-                $tickets
901
-            )
902
-            : $spaces_remaining;
903
-    }
904
-
905
-
906
-    /**
907
-     *    perform_sold_out_status_check
908
-     *    checks all of this events's datetime  reg_limit - sold values to determine if ANY datetimes have spaces
909
-     *    available... if NOT, then the event status will get toggled to 'sold_out'
910
-     *
911
-     * @return bool    return the ACTUAL sold out state.
912
-     * @throws EE_Error
913
-     * @throws DomainException
914
-     * @throws UnexpectedEntityException
915
-     * @throws ReflectionException
916
-     */
917
-    public function perform_sold_out_status_check()
918
-    {
919
-        // get all tickets
920
-        $tickets = $this->tickets(
921
-            array(
922
-                'default_where_conditions' => 'none',
923
-                'order_by' => array('TKT_qty' => 'ASC'),
924
-            )
925
-        );
926
-        $all_expired = true;
927
-        foreach ($tickets as $ticket) {
928
-            if (! $ticket->is_expired()) {
929
-                $all_expired = false;
930
-                break;
931
-            }
932
-        }
933
-        // if all the tickets are just expired, then don't update the event status to sold out
934
-        if ($all_expired) {
935
-            return true;
936
-        }
937
-        $spaces_remaining = $this->spaces_remaining($tickets);
938
-        if ($spaces_remaining < 1) {
939
-            if ($this->status() !== EEM_Event::post_status_private) {
940
-                $this->set_status(EEM_Event::sold_out);
941
-                $this->save();
942
-            }
943
-            $sold_out = true;
944
-        } else {
945
-            $sold_out = false;
946
-            // was event previously marked as sold out ?
947
-            if ($this->status() === EEM_Event::sold_out) {
948
-                // revert status to previous value, if it was set
949
-                $previous_event_status = $this->get_post_meta('_previous_event_status', true);
950
-                if ($previous_event_status) {
951
-                    $this->set_status($previous_event_status);
952
-                    $this->save();
953
-                }
954
-            }
955
-        }
956
-        do_action('AHEE__EE_Event__perform_sold_out_status_check__end', $this, $sold_out, $spaces_remaining, $tickets);
957
-        return $sold_out;
958
-    }
959
-
960
-
961
-    /**
962
-     * This returns the total remaining spaces for sale on this event.
963
-     *
964
-     * @uses EE_Event::total_available_spaces()
965
-     * @return float|int
966
-     * @throws EE_Error
967
-     * @throws DomainException
968
-     * @throws UnexpectedEntityException
969
-     */
970
-    public function spaces_remaining_for_sale()
971
-    {
972
-        return $this->total_available_spaces(true);
973
-    }
974
-
975
-
976
-    /**
977
-     * This returns the total spaces available for an event
978
-     * while considering all the qtys on the tickets and the reg limits
979
-     * on the datetimes attached to this event.
980
-     *
981
-     * @param   bool $consider_sold Whether to consider any tickets that have already sold in our calculation.
982
-     *                              If this is false, then we return the most tickets that could ever be sold
983
-     *                              for this event with the datetime and tickets setup on the event under optimal
984
-     *                              selling conditions.  Otherwise we return a live calculation of spaces available
985
-     *                              based on tickets sold.  Depending on setup and stage of sales, this
986
-     *                              may appear to equal remaining tickets.  However, the more tickets are
987
-     *                              sold out, the more accurate the "live" total is.
988
-     * @return float|int
989
-     * @throws EE_Error
990
-     * @throws DomainException
991
-     * @throws UnexpectedEntityException
992
-     */
993
-    public function total_available_spaces($consider_sold = false)
994
-    {
995
-        $spaces_available = $consider_sold
996
-            ? $this->getAvailableSpacesCalculator()->spacesRemaining()
997
-            : $this->getAvailableSpacesCalculator()->totalSpacesAvailable();
998
-        return apply_filters(
999
-            'FHEE_EE_Event__total_available_spaces__spaces_available',
1000
-            $spaces_available,
1001
-            $this,
1002
-            $this->getAvailableSpacesCalculator()->getDatetimes(),
1003
-            $this->getAvailableSpacesCalculator()->getActiveTickets()
1004
-        );
1005
-    }
1006
-
1007
-
1008
-    /**
1009
-     * Checks if the event is set to sold out
1010
-     *
1011
-     * @param  bool $actual whether or not to perform calculations to not only figure the
1012
-     *                      actual status but also to flip the status if necessary to sold
1013
-     *                      out If false, we just check the existing status of the event
1014
-     * @return boolean
1015
-     * @throws EE_Error
1016
-     */
1017
-    public function is_sold_out($actual = false)
1018
-    {
1019
-        if (! $actual) {
1020
-            return $this->status() === EEM_Event::sold_out;
1021
-        }
1022
-        return $this->perform_sold_out_status_check();
1023
-    }
1024
-
1025
-
1026
-    /**
1027
-     * Checks if the event is marked as postponed
1028
-     *
1029
-     * @return boolean
1030
-     */
1031
-    public function is_postponed()
1032
-    {
1033
-        return $this->status() === EEM_Event::postponed;
1034
-    }
1035
-
1036
-
1037
-    /**
1038
-     * Checks if the event is marked as cancelled
1039
-     *
1040
-     * @return boolean
1041
-     */
1042
-    public function is_cancelled()
1043
-    {
1044
-        return $this->status() === EEM_Event::cancelled;
1045
-    }
1046
-
1047
-
1048
-    /**
1049
-     * Get the logical active status in a hierarchical order for all the datetimes.  Note
1050
-     * Basically, we order the datetimes by EVT_start_date.  Then first test on whether the event is published.  If its
1051
-     * NOT published then we test for whether its expired or not.  IF it IS published then we test first on whether an
1052
-     * event has any active dates.  If no active dates then we check for any upcoming dates.  If no upcoming dates then
1053
-     * the event is considered expired.
1054
-     * NOTE: this method does NOT calculate whether the datetimes are sold out when event is published.  Sold Out is a
1055
-     * status set on the EVENT when it is not published and thus is done
1056
-     *
1057
-     * @param bool $reset
1058
-     * @return bool | string - based on EE_Datetime active constants or FALSE if error.
1059
-     * @throws EE_Error
1060
-     * @throws ReflectionException
1061
-     */
1062
-    public function get_active_status($reset = false)
1063
-    {
1064
-        // if the active status has already been set, then just use that value (unless we are resetting it)
1065
-        if (! empty($this->_active_status) && ! $reset) {
1066
-            return $this->_active_status;
1067
-        }
1068
-        // first check if event id is present on this object
1069
-        if (! $this->ID()) {
1070
-            return false;
1071
-        }
1072
-        $where_params_for_event = array(array('EVT_ID' => $this->ID()));
1073
-        // if event is published:
1074
-        if ($this->status() === EEM_Event::post_status_publish || $this->status() === EEM_Event::post_status_private) {
1075
-            // active?
1076
-            if (
1077
-                EEM_Datetime::instance()->get_datetime_count_for_status(
1078
-                    EE_Datetime::active,
1079
-                    $where_params_for_event
1080
-                ) > 0
1081
-            ) {
1082
-                $this->_active_status = EE_Datetime::active;
1083
-            } else {
1084
-                // upcoming?
1085
-                if (
1086
-                    EEM_Datetime::instance()->get_datetime_count_for_status(
1087
-                        EE_Datetime::upcoming,
1088
-                        $where_params_for_event
1089
-                    ) > 0
1090
-                ) {
1091
-                    $this->_active_status = EE_Datetime::upcoming;
1092
-                } else {
1093
-                    // expired?
1094
-                    if (
1095
-                        EEM_Datetime::instance()->get_datetime_count_for_status(
1096
-                            EE_Datetime::expired,
1097
-                            $where_params_for_event
1098
-                        ) > 0
1099
-                    ) {
1100
-                        $this->_active_status = EE_Datetime::expired;
1101
-                    } else {
1102
-                        // it would be odd if things make it this far because it basically means there are no datetime's
1103
-                        // attached to the event.  So in this case it will just be considered inactive.
1104
-                        $this->_active_status = EE_Datetime::inactive;
1105
-                    }
1106
-                }
1107
-            }
1108
-        } else {
1109
-            // the event is not published, so let's just set it's active status according to its' post status
1110
-            switch ($this->status()) {
1111
-                case EEM_Event::sold_out:
1112
-                    $this->_active_status = EE_Datetime::sold_out;
1113
-                    break;
1114
-                case EEM_Event::cancelled:
1115
-                    $this->_active_status = EE_Datetime::cancelled;
1116
-                    break;
1117
-                case EEM_Event::postponed:
1118
-                    $this->_active_status = EE_Datetime::postponed;
1119
-                    break;
1120
-                default:
1121
-                    $this->_active_status = EE_Datetime::inactive;
1122
-            }
1123
-        }
1124
-        return $this->_active_status;
1125
-    }
1126
-
1127
-
1128
-    /**
1129
-     *    pretty_active_status
1130
-     *
1131
-     * @access public
1132
-     * @param boolean $echo whether to return (FALSE), or echo out the result (TRUE)
1133
-     * @return mixed void|string
1134
-     * @throws EE_Error
1135
-     * @throws ReflectionException
1136
-     */
1137
-    public function pretty_active_status($echo = true)
1138
-    {
1139
-        $active_status = $this->get_active_status();
1140
-        $status = '<span class="ee-status event-active-status-' . esc_attr($active_status) . '">'
1141
-                  . EEH_Template::pretty_status($active_status, false, 'sentence')
1142
-                  . '</span>';
1143
-        if ($echo) {
1144
-            echo wp_kses($status, AllowedTags::getAllowedTags());
1145
-            return '';
1146
-        }
1147
-        return $status; // already escaped
1148
-    }
1149
-
1150
-
1151
-    /**
1152
-     * @return bool|int
1153
-     * @throws EE_Error
1154
-     * @throws ReflectionException
1155
-     */
1156
-    public function get_number_of_tickets_sold()
1157
-    {
1158
-        $tkt_sold = 0;
1159
-        if (! $this->ID()) {
1160
-            return 0;
1161
-        }
1162
-        $datetimes = $this->datetimes();
1163
-        foreach ($datetimes as $datetime) {
1164
-            if ($datetime instanceof EE_Datetime) {
1165
-                $tkt_sold += $datetime->sold();
1166
-            }
1167
-        }
1168
-        return $tkt_sold;
1169
-    }
1170
-
1171
-
1172
-    /**
1173
-     * This just returns a count of all the registrations for this event
1174
-     *
1175
-     * @access  public
1176
-     * @return int
1177
-     * @throws EE_Error
1178
-     */
1179
-    public function get_count_of_all_registrations()
1180
-    {
1181
-        return EEM_Event::instance()->count_related($this, 'Registration');
1182
-    }
1183
-
1184
-
1185
-    /**
1186
-     * This returns the ticket with the earliest start time that is
1187
-     * available for this event (across all datetimes attached to the event)
1188
-     *
1189
-     * @return EE_Base_Class|EE_Ticket|null
1190
-     * @throws EE_Error
1191
-     * @throws ReflectionException
1192
-     */
1193
-    public function get_ticket_with_earliest_start_time()
1194
-    {
1195
-        $where['Datetime.EVT_ID'] = $this->ID();
1196
-        $query_params = array($where, 'order_by' => array('TKT_start_date' => 'ASC'));
1197
-        return EE_Registry::instance()->load_model('Ticket')->get_one($query_params);
1198
-    }
1199
-
1200
-
1201
-    /**
1202
-     * This returns the ticket with the latest end time that is available
1203
-     * for this event (across all datetimes attached to the event)
1204
-     *
1205
-     * @return EE_Base_Class|EE_Ticket|null
1206
-     * @throws EE_Error
1207
-     * @throws ReflectionException
1208
-     */
1209
-    public function get_ticket_with_latest_end_time()
1210
-    {
1211
-        $where['Datetime.EVT_ID'] = $this->ID();
1212
-        $query_params = array($where, 'order_by' => array('TKT_end_date' => 'DESC'));
1213
-        return EE_Registry::instance()->load_model('Ticket')->get_one($query_params);
1214
-    }
1215
-
1216
-
1217
-    /**
1218
-     * This returns the number of different ticket types currently on sale for this event.
1219
-     *
1220
-     * @return int
1221
-     * @throws EE_Error
1222
-     * @throws ReflectionException
1223
-     */
1224
-    public function countTicketsOnSale()
1225
-    {
1226
-        $where = array(
1227
-            'Datetime.EVT_ID' => $this->ID(),
1228
-            'TKT_start_date'  => array('<', time()),
1229
-            'TKT_end_date'    => array('>', time()),
1230
-        );
1231
-        return EEM_Ticket::instance()->count(array($where));
1232
-    }
1233
-
1234
-
1235
-    /**
1236
-     * This returns whether there are any tickets on sale for this event.
1237
-     *
1238
-     * @return bool true = YES tickets on sale.
1239
-     * @throws EE_Error
1240
-     */
1241
-    public function tickets_on_sale()
1242
-    {
1243
-        return $this->countTicketsOnSale() > 0;
1244
-    }
1245
-
1246
-
1247
-    /**
1248
-     * Gets the URL for viewing this event on the front-end. Overrides parent
1249
-     * to check for an external URL first
1250
-     *
1251
-     * @return string
1252
-     * @throws EE_Error
1253
-     */
1254
-    public function get_permalink()
1255
-    {
1256
-        if ($this->external_url()) {
1257
-            return $this->external_url();
1258
-        }
1259
-        return parent::get_permalink();
1260
-    }
1261
-
1262
-
1263
-    /**
1264
-     * Gets the first term for 'espresso_event_categories' we can find
1265
-     *
1266
-     * @param array $query_params @see
1267
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1268
-     * @return EE_Base_Class|EE_Term|null
1269
-     * @throws EE_Error
1270
-     * @throws ReflectionException
1271
-     */
1272
-    public function first_event_category($query_params = array())
1273
-    {
1274
-        $query_params[0]['Term_Taxonomy.taxonomy'] = 'espresso_event_categories';
1275
-        $query_params[0]['Term_Taxonomy.Event.EVT_ID'] = $this->ID();
1276
-        return EEM_Term::instance()->get_one($query_params);
1277
-    }
1278
-
1279
-
1280
-    /**
1281
-     * Gets all terms for 'espresso_event_categories' we can find
1282
-     *
1283
-     * @param array $query_params
1284
-     * @return EE_Base_Class[]|EE_Term[]
1285
-     * @throws EE_Error
1286
-     * @throws ReflectionException
1287
-     */
1288
-    public function get_all_event_categories($query_params = array())
1289
-    {
1290
-        $query_params[0]['Term_Taxonomy.taxonomy'] = 'espresso_event_categories';
1291
-        $query_params[0]['Term_Taxonomy.Event.EVT_ID'] = $this->ID();
1292
-        return EEM_Term::instance()->get_all($query_params);
1293
-    }
1294
-
1295
-
1296
-    /**
1297
-     * Adds a question group to this event
1298
-     *
1299
-     * @param EE_Question_Group|int $question_group_id_or_obj
1300
-     * @param bool $for_primary if true, the question group will be added for the primary
1301
-     *                                           registrant, if false will be added for others. default: false
1302
-     * @return EE_Base_Class|EE_Question_Group
1303
-     * @throws EE_Error
1304
-     * @throws InvalidArgumentException
1305
-     * @throws InvalidDataTypeException
1306
-     * @throws InvalidInterfaceException
1307
-     * @throws ReflectionException
1308
-     */
1309
-    public function add_question_group($question_group_id_or_obj, $for_primary = false)
1310
-    {
1311
-        // If the row already exists, it will be updated. If it doesn't, it will be inserted.
1312
-        // That's in EE_HABTM_Relation::add_relation_to().
1313
-        return $this->_add_relation_to(
1314
-            $question_group_id_or_obj,
1315
-            'Question_Group',
1316
-            [
1317
-                EEM_Event_Question_Group::instance()->fieldNameForContext($for_primary) => true
1318
-            ]
1319
-        );
1320
-    }
1321
-
1322
-
1323
-    /**
1324
-     * Removes a question group from the event
1325
-     *
1326
-     * @param EE_Question_Group|int $question_group_id_or_obj
1327
-     * @param bool $for_primary if true, the question group will be removed from the primary
1328
-     *                                           registrant, if false will be removed from others. default: false
1329
-     * @return EE_Base_Class|EE_Question_Group
1330
-     * @throws EE_Error
1331
-     * @throws InvalidArgumentException
1332
-     * @throws ReflectionException
1333
-     * @throws InvalidDataTypeException
1334
-     * @throws InvalidInterfaceException
1335
-     */
1336
-    public function remove_question_group($question_group_id_or_obj, $for_primary = false)
1337
-    {
1338
-        // If the question group is used for the other type (primary or additional)
1339
-        // then just update it. If not, delete it outright.
1340
-        $existing_relation = $this->get_first_related(
1341
-            'Event_Question_Group',
1342
-            [
1343
-                [
1344
-                    'QSG_ID' => EEM_Question_Group::instance()->ensure_is_ID($question_group_id_or_obj)
1345
-                ]
1346
-            ]
1347
-        );
1348
-        $field_to_update = EEM_Event_Question_Group::instance()->fieldNameForContext($for_primary);
1349
-        $other_field = EEM_Event_Question_Group::instance()->fieldNameForContext(! $for_primary);
1350
-        if ($existing_relation->get($other_field) === false) {
1351
-            // Delete it. It's now no longer for primary or additional question groups.
1352
-            return $this->_remove_relation_to($question_group_id_or_obj, 'Question_Group');
1353
-        }
1354
-        // Just update it. They'll still use this question group for the other category
1355
-        $existing_relation->save(
1356
-            [
1357
-                $field_to_update => false
1358
-            ]
1359
-        );
1360
-    }
1361
-
1362
-
1363
-    /**
1364
-     * Gets all the question groups, ordering them by QSG_order ascending
1365
-     *
1366
-     * @param array $query_params @see
1367
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1368
-     * @return EE_Base_Class[]|EE_Question_Group[]
1369
-     * @throws EE_Error
1370
-     * @throws ReflectionException
1371
-     */
1372
-    public function question_groups($query_params = array())
1373
-    {
1374
-        $query_params = ! empty($query_params) ? $query_params : array('order_by' => array('QSG_order' => 'ASC'));
1375
-        return $this->get_many_related('Question_Group', $query_params);
1376
-    }
1377
-
1378
-
1379
-    /**
1380
-     * Implementation for EEI_Has_Icon interface method.
1381
-     *
1382
-     * @see EEI_Visual_Representation for comments
1383
-     * @return string
1384
-     */
1385
-    public function get_icon()
1386
-    {
1387
-        return '<span class="dashicons dashicons-flag"></span>';
1388
-    }
1389
-
1390
-
1391
-    /**
1392
-     * Implementation for EEI_Admin_Links interface method.
1393
-     *
1394
-     * @see EEI_Admin_Links for comments
1395
-     * @return string
1396
-     * @throws EE_Error
1397
-     */
1398
-    public function get_admin_details_link()
1399
-    {
1400
-        return $this->get_admin_edit_link();
1401
-    }
1402
-
1403
-
1404
-    /**
1405
-     * Implementation for EEI_Admin_Links interface method.
1406
-     *
1407
-     * @return string
1408
-     * @throws EE_Error*@throws ReflectionException
1409
-     * @see EEI_Admin_Links for comments
1410
-     */
1411
-    public function get_admin_edit_link()
1412
-    {
1413
-        return EEH_URL::add_query_args_and_nonce(
1414
-            array(
1415
-                'page'   => 'espresso_events',
1416
-                'action' => 'edit',
1417
-                'post'   => $this->ID(),
1418
-            ),
1419
-            admin_url('admin.php')
1420
-        );
1421
-    }
1422
-
1423
-
1424
-    /**
1425
-     * Implementation for EEI_Admin_Links interface method.
1426
-     *
1427
-     * @see EEI_Admin_Links for comments
1428
-     * @return string
1429
-     */
1430
-    public function get_admin_settings_link()
1431
-    {
1432
-        return EEH_URL::add_query_args_and_nonce(
1433
-            array(
1434
-                'page'   => 'espresso_events',
1435
-                'action' => 'default_event_settings',
1436
-            ),
1437
-            admin_url('admin.php')
1438
-        );
1439
-    }
1440
-
1441
-
1442
-    /**
1443
-     * Implementation for EEI_Admin_Links interface method.
1444
-     *
1445
-     * @see EEI_Admin_Links for comments
1446
-     * @return string
1447
-     */
1448
-    public function get_admin_overview_link()
1449
-    {
1450
-        return EEH_URL::add_query_args_and_nonce(
1451
-            array(
1452
-                'page'   => 'espresso_events',
1453
-                'action' => 'default',
1454
-            ),
1455
-            admin_url('admin.php')
1456
-        );
1457
-    }
19
+	/**
20
+	 * cached value for the the logical active status for the event
21
+	 *
22
+	 * @see get_active_status()
23
+	 * @var string
24
+	 */
25
+	protected $_active_status = '';
26
+
27
+	/**
28
+	 * This is just used for caching the Primary Datetime for the Event on initial retrieval
29
+	 *
30
+	 * @var EE_Datetime
31
+	 */
32
+	protected $_Primary_Datetime;
33
+
34
+	/**
35
+	 * @var EventSpacesCalculator $available_spaces_calculator
36
+	 */
37
+	protected $available_spaces_calculator;
38
+
39
+
40
+	/**
41
+	 * @param array  $props_n_values          incoming values
42
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
43
+	 *                                        used.)
44
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
45
+	 *                                        date_format and the second value is the time format
46
+	 * @return EE_Event
47
+	 * @throws EE_Error
48
+	 * @throws ReflectionException
49
+	 */
50
+	public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
51
+	{
52
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
53
+		return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
54
+	}
55
+
56
+
57
+	/**
58
+	 * @param array  $props_n_values  incoming values from the database
59
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
60
+	 *                                the website will be used.
61
+	 * @return EE_Event
62
+	 * @throws EE_Error
63
+	 * @throws ReflectionException
64
+	 */
65
+	public static function new_instance_from_db($props_n_values = array(), $timezone = null)
66
+	{
67
+		return new self($props_n_values, true, $timezone);
68
+	}
69
+
70
+
71
+	/**
72
+	 * @return EventSpacesCalculator
73
+	 * @throws \EE_Error
74
+	 */
75
+	public function getAvailableSpacesCalculator()
76
+	{
77
+		if (! $this->available_spaces_calculator instanceof EventSpacesCalculator) {
78
+			$this->available_spaces_calculator = new EventSpacesCalculator($this);
79
+		}
80
+		return $this->available_spaces_calculator;
81
+	}
82
+
83
+
84
+	/**
85
+	 * Overrides parent set() method so that all calls to set( 'status', $status ) can be routed to internal methods
86
+	 *
87
+	 * @param string $field_name
88
+	 * @param mixed  $field_value
89
+	 * @param bool   $use_default
90
+	 * @throws EE_Error
91
+	 * @throws ReflectionException
92
+	 */
93
+	public function set($field_name, $field_value, $use_default = false)
94
+	{
95
+		switch ($field_name) {
96
+			case 'status':
97
+				$this->set_status($field_value, $use_default);
98
+				break;
99
+			default:
100
+				parent::set($field_name, $field_value, $use_default);
101
+		}
102
+	}
103
+
104
+
105
+	/**
106
+	 *    set_status
107
+	 * Checks if event status is being changed to SOLD OUT
108
+	 * and updates event meta data with previous event status
109
+	 * so that we can revert things if/when the event is no longer sold out
110
+	 *
111
+	 * @access public
112
+	 * @param string $new_status
113
+	 * @param bool   $use_default
114
+	 * @return void
115
+	 * @throws EE_Error
116
+	 * @throws ReflectionException
117
+	 */
118
+	public function set_status($new_status = null, $use_default = false)
119
+	{
120
+		// if nothing is set, and we aren't explicitly wanting to reset the status, then just leave
121
+		if (empty($new_status) && ! $use_default) {
122
+			return;
123
+		}
124
+		// get current Event status
125
+		$old_status = $this->status();
126
+		// if status has changed
127
+		if ($old_status !== $new_status) {
128
+			// TO sold_out
129
+			if ($new_status === EEM_Event::sold_out) {
130
+				// save the previous event status so that we can revert if the event is no longer sold out
131
+				$this->add_post_meta('_previous_event_status', $old_status);
132
+				do_action('AHEE__EE_Event__set_status__to_sold_out', $this, $old_status, $new_status);
133
+				// OR FROM  sold_out
134
+			} elseif ($old_status === EEM_Event::sold_out) {
135
+				$this->delete_post_meta('_previous_event_status');
136
+				do_action('AHEE__EE_Event__set_status__from_sold_out', $this, $old_status, $new_status);
137
+			}
138
+			// clear out the active status so that it gets reset the next time it is requested
139
+			$this->_active_status = null;
140
+			// update status
141
+			parent::set('status', $new_status, $use_default);
142
+			do_action('AHEE__EE_Event__set_status__after_update', $this);
143
+			return;
144
+		}
145
+		// even though the old value matches the new value, it's still good to
146
+		// allow the parent set method to have a say
147
+		parent::set('status', $new_status, $use_default);
148
+	}
149
+
150
+
151
+	/**
152
+	 * Gets all the datetimes for this event
153
+	 *
154
+	 * @param array $query_params @see
155
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
156
+	 * @return EE_Base_Class[]|EE_Datetime[]
157
+	 * @throws EE_Error
158
+	 * @throws ReflectionException
159
+	 */
160
+	public function datetimes($query_params = array())
161
+	{
162
+		return $this->get_many_related('Datetime', $query_params);
163
+	}
164
+
165
+
166
+	/**
167
+	 * Gets all the datetimes for this event, ordered by DTT_EVT_start in ascending order
168
+	 *
169
+	 * @return EE_Base_Class[]|EE_Datetime[]
170
+	 * @throws EE_Error
171
+	 * @throws ReflectionException
172
+	 */
173
+	public function datetimes_in_chronological_order()
174
+	{
175
+		return $this->get_many_related('Datetime', array('order_by' => array('DTT_EVT_start' => 'ASC')));
176
+	}
177
+
178
+
179
+	/**
180
+	 * Gets all the datetimes for this event, ordered by the DTT_order on the datetime.
181
+	 * @darren, we should probably UNSET timezone on the EEM_Datetime model
182
+	 * after running our query, so that this timezone isn't set for EVERY query
183
+	 * on EEM_Datetime for the rest of the request, no?
184
+	 *
185
+	 * @param boolean $show_expired whether or not to include expired events
186
+	 * @param boolean $show_deleted whether or not to include deleted events
187
+	 * @param null    $limit
188
+	 * @return EE_Datetime[]
189
+	 * @throws EE_Error
190
+	 * @throws ReflectionException
191
+	 */
192
+	public function datetimes_ordered($show_expired = true, $show_deleted = false, $limit = null)
193
+	{
194
+		return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_event_ordered_by_DTT_order(
195
+			$this->ID(),
196
+			$show_expired,
197
+			$show_deleted,
198
+			$limit
199
+		);
200
+	}
201
+
202
+
203
+	/**
204
+	 * Returns one related datetime. Mostly only used by some legacy code.
205
+	 *
206
+	 * @return EE_Base_Class|EE_Datetime
207
+	 * @throws EE_Error
208
+	 * @throws ReflectionException
209
+	 */
210
+	public function first_datetime()
211
+	{
212
+		return $this->get_first_related('Datetime');
213
+	}
214
+
215
+
216
+	/**
217
+	 * Returns the 'primary' datetime for the event
218
+	 *
219
+	 * @param bool $try_to_exclude_expired
220
+	 * @param bool $try_to_exclude_deleted
221
+	 * @return EE_Datetime
222
+	 * @throws EE_Error
223
+	 * @throws ReflectionException
224
+	 */
225
+	public function primary_datetime($try_to_exclude_expired = true, $try_to_exclude_deleted = true)
226
+	{
227
+		if (! empty($this->_Primary_Datetime)) {
228
+			return $this->_Primary_Datetime;
229
+		}
230
+		$this->_Primary_Datetime = EEM_Datetime::instance($this->_timezone)->get_primary_datetime_for_event(
231
+			$this->ID(),
232
+			$try_to_exclude_expired,
233
+			$try_to_exclude_deleted
234
+		);
235
+		return $this->_Primary_Datetime;
236
+	}
237
+
238
+
239
+	/**
240
+	 * Gets all the tickets available for purchase of this event
241
+	 *
242
+	 * @param array $query_params @see
243
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
244
+	 * @return EE_Base_Class[]|EE_Ticket[]
245
+	 * @throws EE_Error
246
+	 * @throws ReflectionException
247
+	 */
248
+	public function tickets($query_params = array())
249
+	{
250
+		// first get all datetimes
251
+		$datetimes = $this->datetimes_ordered();
252
+		if (! $datetimes) {
253
+			return array();
254
+		}
255
+		$datetime_ids = array();
256
+		foreach ($datetimes as $datetime) {
257
+			$datetime_ids[] = $datetime->ID();
258
+		}
259
+		$where_params = array('Datetime.DTT_ID' => array('IN', $datetime_ids));
260
+		// if incoming $query_params has where conditions let's merge but not override existing.
261
+		if (is_array($query_params) && isset($query_params[0])) {
262
+			$where_params = array_merge($query_params[0], $where_params);
263
+			unset($query_params[0]);
264
+		}
265
+		// now add $where_params to $query_params
266
+		$query_params[0] = $where_params;
267
+		return EEM_Ticket::instance()->get_all($query_params);
268
+	}
269
+
270
+
271
+	/**
272
+	 * get all unexpired untrashed tickets
273
+	 *
274
+	 * @return EE_Ticket[]
275
+	 * @throws EE_Error
276
+	 */
277
+	public function active_tickets()
278
+	{
279
+		return $this->tickets(
280
+			array(
281
+				array(
282
+					'TKT_end_date' => array('>=', EEM_Ticket::instance()->current_time_for_query('TKT_end_date')),
283
+					'TKT_deleted'  => false,
284
+				),
285
+			)
286
+		);
287
+	}
288
+
289
+
290
+	/**
291
+	 * @return bool
292
+	 * @throws EE_Error
293
+	 * @throws ReflectionException
294
+	 */
295
+	public function additional_limit()
296
+	{
297
+		return $this->get('EVT_additional_limit');
298
+	}
299
+
300
+
301
+	/**
302
+	 * @return bool
303
+	 * @throws EE_Error
304
+	 * @throws ReflectionException
305
+	 */
306
+	public function allow_overflow()
307
+	{
308
+		return $this->get('EVT_allow_overflow');
309
+	}
310
+
311
+
312
+	/**
313
+	 * @return bool
314
+	 * @throws EE_Error
315
+	 * @throws ReflectionException
316
+	 */
317
+	public function created()
318
+	{
319
+		return $this->get('EVT_created');
320
+	}
321
+
322
+
323
+	/**
324
+	 * @return bool
325
+	 * @throws EE_Error
326
+	 * @throws ReflectionException
327
+	 */
328
+	public function description()
329
+	{
330
+		return $this->get('EVT_desc');
331
+	}
332
+
333
+
334
+	/**
335
+	 * Runs do_shortcode and wpautop on the description
336
+	 *
337
+	 * @return string of html
338
+	 * @throws EE_Error
339
+	 * @throws ReflectionException
340
+	 */
341
+	public function description_filtered()
342
+	{
343
+		return $this->get_pretty('EVT_desc');
344
+	}
345
+
346
+
347
+	/**
348
+	 * @return bool
349
+	 * @throws EE_Error
350
+	 * @throws ReflectionException
351
+	 */
352
+	public function display_description()
353
+	{
354
+		return $this->get('EVT_display_desc');
355
+	}
356
+
357
+
358
+	/**
359
+	 * @return bool
360
+	 * @throws EE_Error
361
+	 * @throws ReflectionException
362
+	 */
363
+	public function display_ticket_selector()
364
+	{
365
+		return (bool) $this->get('EVT_display_ticket_selector');
366
+	}
367
+
368
+
369
+	/**
370
+	 * @return string
371
+	 * @throws EE_Error
372
+	 * @throws ReflectionException
373
+	 */
374
+	public function external_url()
375
+	{
376
+		return $this->get('EVT_external_URL');
377
+	}
378
+
379
+
380
+	/**
381
+	 * @return bool
382
+	 * @throws EE_Error
383
+	 * @throws ReflectionException
384
+	 */
385
+	public function member_only()
386
+	{
387
+		return $this->get('EVT_member_only');
388
+	}
389
+
390
+
391
+	/**
392
+	 * @return bool
393
+	 * @throws EE_Error
394
+	 * @throws ReflectionException
395
+	 */
396
+	public function phone()
397
+	{
398
+		return $this->get('EVT_phone');
399
+	}
400
+
401
+
402
+	/**
403
+	 * @return bool
404
+	 * @throws EE_Error
405
+	 * @throws ReflectionException
406
+	 */
407
+	public function modified()
408
+	{
409
+		return $this->get('EVT_modified');
410
+	}
411
+
412
+
413
+	/**
414
+	 * @return bool
415
+	 * @throws EE_Error
416
+	 * @throws ReflectionException
417
+	 */
418
+	public function name()
419
+	{
420
+		return $this->get('EVT_name');
421
+	}
422
+
423
+
424
+	/**
425
+	 * @return bool
426
+	 * @throws EE_Error
427
+	 * @throws ReflectionException
428
+	 */
429
+	public function order()
430
+	{
431
+		return $this->get('EVT_order');
432
+	}
433
+
434
+
435
+	/**
436
+	 * @return bool|string
437
+	 * @throws EE_Error
438
+	 * @throws ReflectionException
439
+	 */
440
+	public function default_registration_status()
441
+	{
442
+		$event_default_registration_status = $this->get('EVT_default_registration_status');
443
+		return ! empty($event_default_registration_status)
444
+			? $event_default_registration_status
445
+			: EE_Registry::instance()->CFG->registration->default_STS_ID;
446
+	}
447
+
448
+
449
+	/**
450
+	 * @param int  $num_words
451
+	 * @param null $more
452
+	 * @param bool $not_full_desc
453
+	 * @return bool|string
454
+	 * @throws EE_Error
455
+	 * @throws ReflectionException
456
+	 */
457
+	public function short_description($num_words = 55, $more = null, $not_full_desc = false)
458
+	{
459
+		$short_desc = $this->get('EVT_short_desc');
460
+		if (! empty($short_desc) || $not_full_desc) {
461
+			return $short_desc;
462
+		}
463
+		$full_desc = $this->get('EVT_desc');
464
+		return wp_trim_words($full_desc, $num_words, $more);
465
+	}
466
+
467
+
468
+	/**
469
+	 * @return bool
470
+	 * @throws EE_Error
471
+	 * @throws ReflectionException
472
+	 */
473
+	public function slug()
474
+	{
475
+		return $this->get('EVT_slug');
476
+	}
477
+
478
+
479
+	/**
480
+	 * @return bool
481
+	 * @throws EE_Error
482
+	 * @throws ReflectionException
483
+	 */
484
+	public function timezone_string()
485
+	{
486
+		return $this->get('EVT_timezone_string');
487
+	}
488
+
489
+
490
+	/**
491
+	 * @return bool
492
+	 * @throws EE_Error
493
+	 * @throws ReflectionException
494
+	 */
495
+	public function visible_on()
496
+	{
497
+		return $this->get('EVT_visible_on');
498
+	}
499
+
500
+
501
+	/**
502
+	 * @return int
503
+	 * @throws EE_Error
504
+	 * @throws ReflectionException
505
+	 */
506
+	public function wp_user()
507
+	{
508
+		return $this->get('EVT_wp_user');
509
+	}
510
+
511
+
512
+	/**
513
+	 * @return bool
514
+	 * @throws EE_Error
515
+	 * @throws ReflectionException
516
+	 */
517
+	public function donations()
518
+	{
519
+		return $this->get('EVT_donations');
520
+	}
521
+
522
+
523
+	/**
524
+	 * @param $limit
525
+	 * @throws EE_Error
526
+	 */
527
+	public function set_additional_limit($limit)
528
+	{
529
+		$this->set('EVT_additional_limit', $limit);
530
+	}
531
+
532
+
533
+	/**
534
+	 * @param $created
535
+	 * @throws EE_Error
536
+	 */
537
+	public function set_created($created)
538
+	{
539
+		$this->set('EVT_created', $created);
540
+	}
541
+
542
+
543
+	/**
544
+	 * @param $desc
545
+	 * @throws EE_Error
546
+	 */
547
+	public function set_description($desc)
548
+	{
549
+		$this->set('EVT_desc', $desc);
550
+	}
551
+
552
+
553
+	/**
554
+	 * @param $display_desc
555
+	 * @throws EE_Error
556
+	 */
557
+	public function set_display_description($display_desc)
558
+	{
559
+		$this->set('EVT_display_desc', $display_desc);
560
+	}
561
+
562
+
563
+	/**
564
+	 * @param $display_ticket_selector
565
+	 * @throws EE_Error
566
+	 */
567
+	public function set_display_ticket_selector($display_ticket_selector)
568
+	{
569
+		$this->set('EVT_display_ticket_selector', $display_ticket_selector);
570
+	}
571
+
572
+
573
+	/**
574
+	 * @param $external_url
575
+	 * @throws EE_Error
576
+	 */
577
+	public function set_external_url($external_url)
578
+	{
579
+		$this->set('EVT_external_URL', $external_url);
580
+	}
581
+
582
+
583
+	/**
584
+	 * @param $member_only
585
+	 * @throws EE_Error
586
+	 */
587
+	public function set_member_only($member_only)
588
+	{
589
+		$this->set('EVT_member_only', $member_only);
590
+	}
591
+
592
+
593
+	/**
594
+	 * @param $event_phone
595
+	 * @throws EE_Error
596
+	 */
597
+	public function set_event_phone($event_phone)
598
+	{
599
+		$this->set('EVT_phone', $event_phone);
600
+	}
601
+
602
+
603
+	/**
604
+	 * @param $modified
605
+	 * @throws EE_Error
606
+	 */
607
+	public function set_modified($modified)
608
+	{
609
+		$this->set('EVT_modified', $modified);
610
+	}
611
+
612
+
613
+	/**
614
+	 * @param $name
615
+	 * @throws EE_Error
616
+	 */
617
+	public function set_name($name)
618
+	{
619
+		$this->set('EVT_name', $name);
620
+	}
621
+
622
+
623
+	/**
624
+	 * @param $order
625
+	 * @throws EE_Error
626
+	 */
627
+	public function set_order($order)
628
+	{
629
+		$this->set('EVT_order', $order);
630
+	}
631
+
632
+
633
+	/**
634
+	 * @param $short_desc
635
+	 * @throws EE_Error
636
+	 */
637
+	public function set_short_description($short_desc)
638
+	{
639
+		$this->set('EVT_short_desc', $short_desc);
640
+	}
641
+
642
+
643
+	/**
644
+	 * @param $slug
645
+	 * @throws EE_Error
646
+	 */
647
+	public function set_slug($slug)
648
+	{
649
+		$this->set('EVT_slug', $slug);
650
+	}
651
+
652
+
653
+	/**
654
+	 * @param $timezone_string
655
+	 * @throws EE_Error
656
+	 */
657
+	public function set_timezone_string($timezone_string)
658
+	{
659
+		$this->set('EVT_timezone_string', $timezone_string);
660
+	}
661
+
662
+
663
+	/**
664
+	 * @param $visible_on
665
+	 * @throws EE_Error
666
+	 */
667
+	public function set_visible_on($visible_on)
668
+	{
669
+		$this->set('EVT_visible_on', $visible_on);
670
+	}
671
+
672
+
673
+	/**
674
+	 * @param $wp_user
675
+	 * @throws EE_Error
676
+	 */
677
+	public function set_wp_user($wp_user)
678
+	{
679
+		$this->set('EVT_wp_user', $wp_user);
680
+	}
681
+
682
+
683
+	/**
684
+	 * @param $default_registration_status
685
+	 * @throws EE_Error
686
+	 */
687
+	public function set_default_registration_status($default_registration_status)
688
+	{
689
+		$this->set('EVT_default_registration_status', $default_registration_status);
690
+	}
691
+
692
+
693
+	/**
694
+	 * @param $donations
695
+	 * @throws EE_Error
696
+	 */
697
+	public function set_donations($donations)
698
+	{
699
+		$this->set('EVT_donations', $donations);
700
+	}
701
+
702
+
703
+	/**
704
+	 * Adds a venue to this event
705
+	 *
706
+	 * @param EE_Venue /int $venue_id_or_obj
707
+	 * @return EE_Base_Class|EE_Venue
708
+	 * @throws EE_Error
709
+	 * @throws ReflectionException
710
+	 */
711
+	public function add_venue($venue_id_or_obj)
712
+	{
713
+		return $this->_add_relation_to($venue_id_or_obj, 'Venue');
714
+	}
715
+
716
+
717
+	/**
718
+	 * Removes a venue from the event
719
+	 *
720
+	 * @param EE_Venue /int $venue_id_or_obj
721
+	 * @return EE_Base_Class|EE_Venue
722
+	 * @throws EE_Error
723
+	 * @throws ReflectionException
724
+	 */
725
+	public function remove_venue($venue_id_or_obj)
726
+	{
727
+		return $this->_remove_relation_to($venue_id_or_obj, 'Venue');
728
+	}
729
+
730
+
731
+	/**
732
+	 * Gets all the venues related ot the event. May provide additional $query_params if desired
733
+	 *
734
+	 * @param array $query_params @see
735
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
736
+	 * @return EE_Base_Class[]|EE_Venue[]
737
+	 * @throws EE_Error
738
+	 * @throws ReflectionException
739
+	 */
740
+	public function venues($query_params = array())
741
+	{
742
+		return $this->get_many_related('Venue', $query_params);
743
+	}
744
+
745
+
746
+	/**
747
+	 * check if event id is present and if event is published
748
+	 *
749
+	 * @access public
750
+	 * @return boolean true yes, false no
751
+	 * @throws EE_Error
752
+	 * @throws ReflectionException
753
+	 */
754
+	private function _has_ID_and_is_published()
755
+	{
756
+		// first check if event id is present and not NULL,
757
+		// then check if this event is published (or any of the equivalent "published" statuses)
758
+		return
759
+			$this->ID() && $this->ID() !== null
760
+			&& (
761
+				$this->status() === 'publish'
762
+				|| $this->status() === EEM_Event::sold_out
763
+				|| $this->status() === EEM_Event::postponed
764
+				|| $this->status() === EEM_Event::cancelled
765
+			);
766
+	}
767
+
768
+
769
+	/**
770
+	 * This simply compares the internal dates with NOW and determines if the event is upcoming or not.
771
+	 *
772
+	 * @access public
773
+	 * @return boolean true yes, false no
774
+	 * @throws EE_Error
775
+	 * @throws ReflectionException
776
+	 */
777
+	public function is_upcoming()
778
+	{
779
+		// check if event id is present and if this event is published
780
+		if ($this->is_inactive()) {
781
+			return false;
782
+		}
783
+		// set initial value
784
+		$upcoming = false;
785
+		// next let's get all datetimes and loop through them
786
+		$datetimes = $this->datetimes_in_chronological_order();
787
+		foreach ($datetimes as $datetime) {
788
+			if ($datetime instanceof EE_Datetime) {
789
+				// if this dtt is expired then we continue cause one of the other datetimes might be upcoming.
790
+				if ($datetime->is_expired()) {
791
+					continue;
792
+				}
793
+				// if this dtt is active then we return false.
794
+				if ($datetime->is_active()) {
795
+					return false;
796
+				}
797
+				// otherwise let's check upcoming status
798
+				$upcoming = $datetime->is_upcoming();
799
+			}
800
+		}
801
+		return $upcoming;
802
+	}
803
+
804
+
805
+	/**
806
+	 * @return bool
807
+	 * @throws EE_Error
808
+	 * @throws ReflectionException
809
+	 */
810
+	public function is_active()
811
+	{
812
+		// check if event id is present and if this event is published
813
+		if ($this->is_inactive()) {
814
+			return false;
815
+		}
816
+		// set initial value
817
+		$active = false;
818
+		// next let's get all datetimes and loop through them
819
+		$datetimes = $this->datetimes_in_chronological_order();
820
+		foreach ($datetimes as $datetime) {
821
+			if ($datetime instanceof EE_Datetime) {
822
+				// if this dtt is expired then we continue cause one of the other datetimes might be active.
823
+				if ($datetime->is_expired()) {
824
+					continue;
825
+				}
826
+				// if this dtt is upcoming then we return false.
827
+				if ($datetime->is_upcoming()) {
828
+					return false;
829
+				}
830
+				// otherwise let's check active status
831
+				$active = $datetime->is_active();
832
+			}
833
+		}
834
+		return $active;
835
+	}
836
+
837
+
838
+	/**
839
+	 * @return bool
840
+	 * @throws EE_Error
841
+	 * @throws ReflectionException
842
+	 */
843
+	public function is_expired()
844
+	{
845
+		// check if event id is present and if this event is published
846
+		if ($this->is_inactive()) {
847
+			return false;
848
+		}
849
+		// set initial value
850
+		$expired = false;
851
+		// first let's get all datetimes and loop through them
852
+		$datetimes = $this->datetimes_in_chronological_order();
853
+		foreach ($datetimes as $datetime) {
854
+			if ($datetime instanceof EE_Datetime) {
855
+				// if this dtt is upcoming or active then we return false.
856
+				if ($datetime->is_upcoming() || $datetime->is_active()) {
857
+					return false;
858
+				}
859
+				// otherwise let's check active status
860
+				$expired = $datetime->is_expired();
861
+			}
862
+		}
863
+		return $expired;
864
+	}
865
+
866
+
867
+	/**
868
+	 * @return bool
869
+	 * @throws EE_Error
870
+	 */
871
+	public function is_inactive()
872
+	{
873
+		// check if event id is present and if this event is published
874
+		if ($this->_has_ID_and_is_published()) {
875
+			return false;
876
+		}
877
+		return true;
878
+	}
879
+
880
+
881
+	/**
882
+	 * calculate spaces remaining based on "saleable" tickets
883
+	 *
884
+	 * @param array $tickets
885
+	 * @param bool  $filtered
886
+	 * @return int|float
887
+	 * @throws EE_Error
888
+	 * @throws DomainException
889
+	 * @throws UnexpectedEntityException
890
+	 */
891
+	public function spaces_remaining($tickets = array(), $filtered = true)
892
+	{
893
+		$this->getAvailableSpacesCalculator()->setActiveTickets($tickets);
894
+		$spaces_remaining = $this->getAvailableSpacesCalculator()->spacesRemaining();
895
+		return $filtered
896
+			? apply_filters(
897
+				'FHEE_EE_Event__spaces_remaining',
898
+				$spaces_remaining,
899
+				$this,
900
+				$tickets
901
+			)
902
+			: $spaces_remaining;
903
+	}
904
+
905
+
906
+	/**
907
+	 *    perform_sold_out_status_check
908
+	 *    checks all of this events's datetime  reg_limit - sold values to determine if ANY datetimes have spaces
909
+	 *    available... if NOT, then the event status will get toggled to 'sold_out'
910
+	 *
911
+	 * @return bool    return the ACTUAL sold out state.
912
+	 * @throws EE_Error
913
+	 * @throws DomainException
914
+	 * @throws UnexpectedEntityException
915
+	 * @throws ReflectionException
916
+	 */
917
+	public function perform_sold_out_status_check()
918
+	{
919
+		// get all tickets
920
+		$tickets = $this->tickets(
921
+			array(
922
+				'default_where_conditions' => 'none',
923
+				'order_by' => array('TKT_qty' => 'ASC'),
924
+			)
925
+		);
926
+		$all_expired = true;
927
+		foreach ($tickets as $ticket) {
928
+			if (! $ticket->is_expired()) {
929
+				$all_expired = false;
930
+				break;
931
+			}
932
+		}
933
+		// if all the tickets are just expired, then don't update the event status to sold out
934
+		if ($all_expired) {
935
+			return true;
936
+		}
937
+		$spaces_remaining = $this->spaces_remaining($tickets);
938
+		if ($spaces_remaining < 1) {
939
+			if ($this->status() !== EEM_Event::post_status_private) {
940
+				$this->set_status(EEM_Event::sold_out);
941
+				$this->save();
942
+			}
943
+			$sold_out = true;
944
+		} else {
945
+			$sold_out = false;
946
+			// was event previously marked as sold out ?
947
+			if ($this->status() === EEM_Event::sold_out) {
948
+				// revert status to previous value, if it was set
949
+				$previous_event_status = $this->get_post_meta('_previous_event_status', true);
950
+				if ($previous_event_status) {
951
+					$this->set_status($previous_event_status);
952
+					$this->save();
953
+				}
954
+			}
955
+		}
956
+		do_action('AHEE__EE_Event__perform_sold_out_status_check__end', $this, $sold_out, $spaces_remaining, $tickets);
957
+		return $sold_out;
958
+	}
959
+
960
+
961
+	/**
962
+	 * This returns the total remaining spaces for sale on this event.
963
+	 *
964
+	 * @uses EE_Event::total_available_spaces()
965
+	 * @return float|int
966
+	 * @throws EE_Error
967
+	 * @throws DomainException
968
+	 * @throws UnexpectedEntityException
969
+	 */
970
+	public function spaces_remaining_for_sale()
971
+	{
972
+		return $this->total_available_spaces(true);
973
+	}
974
+
975
+
976
+	/**
977
+	 * This returns the total spaces available for an event
978
+	 * while considering all the qtys on the tickets and the reg limits
979
+	 * on the datetimes attached to this event.
980
+	 *
981
+	 * @param   bool $consider_sold Whether to consider any tickets that have already sold in our calculation.
982
+	 *                              If this is false, then we return the most tickets that could ever be sold
983
+	 *                              for this event with the datetime and tickets setup on the event under optimal
984
+	 *                              selling conditions.  Otherwise we return a live calculation of spaces available
985
+	 *                              based on tickets sold.  Depending on setup and stage of sales, this
986
+	 *                              may appear to equal remaining tickets.  However, the more tickets are
987
+	 *                              sold out, the more accurate the "live" total is.
988
+	 * @return float|int
989
+	 * @throws EE_Error
990
+	 * @throws DomainException
991
+	 * @throws UnexpectedEntityException
992
+	 */
993
+	public function total_available_spaces($consider_sold = false)
994
+	{
995
+		$spaces_available = $consider_sold
996
+			? $this->getAvailableSpacesCalculator()->spacesRemaining()
997
+			: $this->getAvailableSpacesCalculator()->totalSpacesAvailable();
998
+		return apply_filters(
999
+			'FHEE_EE_Event__total_available_spaces__spaces_available',
1000
+			$spaces_available,
1001
+			$this,
1002
+			$this->getAvailableSpacesCalculator()->getDatetimes(),
1003
+			$this->getAvailableSpacesCalculator()->getActiveTickets()
1004
+		);
1005
+	}
1006
+
1007
+
1008
+	/**
1009
+	 * Checks if the event is set to sold out
1010
+	 *
1011
+	 * @param  bool $actual whether or not to perform calculations to not only figure the
1012
+	 *                      actual status but also to flip the status if necessary to sold
1013
+	 *                      out If false, we just check the existing status of the event
1014
+	 * @return boolean
1015
+	 * @throws EE_Error
1016
+	 */
1017
+	public function is_sold_out($actual = false)
1018
+	{
1019
+		if (! $actual) {
1020
+			return $this->status() === EEM_Event::sold_out;
1021
+		}
1022
+		return $this->perform_sold_out_status_check();
1023
+	}
1024
+
1025
+
1026
+	/**
1027
+	 * Checks if the event is marked as postponed
1028
+	 *
1029
+	 * @return boolean
1030
+	 */
1031
+	public function is_postponed()
1032
+	{
1033
+		return $this->status() === EEM_Event::postponed;
1034
+	}
1035
+
1036
+
1037
+	/**
1038
+	 * Checks if the event is marked as cancelled
1039
+	 *
1040
+	 * @return boolean
1041
+	 */
1042
+	public function is_cancelled()
1043
+	{
1044
+		return $this->status() === EEM_Event::cancelled;
1045
+	}
1046
+
1047
+
1048
+	/**
1049
+	 * Get the logical active status in a hierarchical order for all the datetimes.  Note
1050
+	 * Basically, we order the datetimes by EVT_start_date.  Then first test on whether the event is published.  If its
1051
+	 * NOT published then we test for whether its expired or not.  IF it IS published then we test first on whether an
1052
+	 * event has any active dates.  If no active dates then we check for any upcoming dates.  If no upcoming dates then
1053
+	 * the event is considered expired.
1054
+	 * NOTE: this method does NOT calculate whether the datetimes are sold out when event is published.  Sold Out is a
1055
+	 * status set on the EVENT when it is not published and thus is done
1056
+	 *
1057
+	 * @param bool $reset
1058
+	 * @return bool | string - based on EE_Datetime active constants or FALSE if error.
1059
+	 * @throws EE_Error
1060
+	 * @throws ReflectionException
1061
+	 */
1062
+	public function get_active_status($reset = false)
1063
+	{
1064
+		// if the active status has already been set, then just use that value (unless we are resetting it)
1065
+		if (! empty($this->_active_status) && ! $reset) {
1066
+			return $this->_active_status;
1067
+		}
1068
+		// first check if event id is present on this object
1069
+		if (! $this->ID()) {
1070
+			return false;
1071
+		}
1072
+		$where_params_for_event = array(array('EVT_ID' => $this->ID()));
1073
+		// if event is published:
1074
+		if ($this->status() === EEM_Event::post_status_publish || $this->status() === EEM_Event::post_status_private) {
1075
+			// active?
1076
+			if (
1077
+				EEM_Datetime::instance()->get_datetime_count_for_status(
1078
+					EE_Datetime::active,
1079
+					$where_params_for_event
1080
+				) > 0
1081
+			) {
1082
+				$this->_active_status = EE_Datetime::active;
1083
+			} else {
1084
+				// upcoming?
1085
+				if (
1086
+					EEM_Datetime::instance()->get_datetime_count_for_status(
1087
+						EE_Datetime::upcoming,
1088
+						$where_params_for_event
1089
+					) > 0
1090
+				) {
1091
+					$this->_active_status = EE_Datetime::upcoming;
1092
+				} else {
1093
+					// expired?
1094
+					if (
1095
+						EEM_Datetime::instance()->get_datetime_count_for_status(
1096
+							EE_Datetime::expired,
1097
+							$where_params_for_event
1098
+						) > 0
1099
+					) {
1100
+						$this->_active_status = EE_Datetime::expired;
1101
+					} else {
1102
+						// it would be odd if things make it this far because it basically means there are no datetime's
1103
+						// attached to the event.  So in this case it will just be considered inactive.
1104
+						$this->_active_status = EE_Datetime::inactive;
1105
+					}
1106
+				}
1107
+			}
1108
+		} else {
1109
+			// the event is not published, so let's just set it's active status according to its' post status
1110
+			switch ($this->status()) {
1111
+				case EEM_Event::sold_out:
1112
+					$this->_active_status = EE_Datetime::sold_out;
1113
+					break;
1114
+				case EEM_Event::cancelled:
1115
+					$this->_active_status = EE_Datetime::cancelled;
1116
+					break;
1117
+				case EEM_Event::postponed:
1118
+					$this->_active_status = EE_Datetime::postponed;
1119
+					break;
1120
+				default:
1121
+					$this->_active_status = EE_Datetime::inactive;
1122
+			}
1123
+		}
1124
+		return $this->_active_status;
1125
+	}
1126
+
1127
+
1128
+	/**
1129
+	 *    pretty_active_status
1130
+	 *
1131
+	 * @access public
1132
+	 * @param boolean $echo whether to return (FALSE), or echo out the result (TRUE)
1133
+	 * @return mixed void|string
1134
+	 * @throws EE_Error
1135
+	 * @throws ReflectionException
1136
+	 */
1137
+	public function pretty_active_status($echo = true)
1138
+	{
1139
+		$active_status = $this->get_active_status();
1140
+		$status = '<span class="ee-status event-active-status-' . esc_attr($active_status) . '">'
1141
+				  . EEH_Template::pretty_status($active_status, false, 'sentence')
1142
+				  . '</span>';
1143
+		if ($echo) {
1144
+			echo wp_kses($status, AllowedTags::getAllowedTags());
1145
+			return '';
1146
+		}
1147
+		return $status; // already escaped
1148
+	}
1149
+
1150
+
1151
+	/**
1152
+	 * @return bool|int
1153
+	 * @throws EE_Error
1154
+	 * @throws ReflectionException
1155
+	 */
1156
+	public function get_number_of_tickets_sold()
1157
+	{
1158
+		$tkt_sold = 0;
1159
+		if (! $this->ID()) {
1160
+			return 0;
1161
+		}
1162
+		$datetimes = $this->datetimes();
1163
+		foreach ($datetimes as $datetime) {
1164
+			if ($datetime instanceof EE_Datetime) {
1165
+				$tkt_sold += $datetime->sold();
1166
+			}
1167
+		}
1168
+		return $tkt_sold;
1169
+	}
1170
+
1171
+
1172
+	/**
1173
+	 * This just returns a count of all the registrations for this event
1174
+	 *
1175
+	 * @access  public
1176
+	 * @return int
1177
+	 * @throws EE_Error
1178
+	 */
1179
+	public function get_count_of_all_registrations()
1180
+	{
1181
+		return EEM_Event::instance()->count_related($this, 'Registration');
1182
+	}
1183
+
1184
+
1185
+	/**
1186
+	 * This returns the ticket with the earliest start time that is
1187
+	 * available for this event (across all datetimes attached to the event)
1188
+	 *
1189
+	 * @return EE_Base_Class|EE_Ticket|null
1190
+	 * @throws EE_Error
1191
+	 * @throws ReflectionException
1192
+	 */
1193
+	public function get_ticket_with_earliest_start_time()
1194
+	{
1195
+		$where['Datetime.EVT_ID'] = $this->ID();
1196
+		$query_params = array($where, 'order_by' => array('TKT_start_date' => 'ASC'));
1197
+		return EE_Registry::instance()->load_model('Ticket')->get_one($query_params);
1198
+	}
1199
+
1200
+
1201
+	/**
1202
+	 * This returns the ticket with the latest end time that is available
1203
+	 * for this event (across all datetimes attached to the event)
1204
+	 *
1205
+	 * @return EE_Base_Class|EE_Ticket|null
1206
+	 * @throws EE_Error
1207
+	 * @throws ReflectionException
1208
+	 */
1209
+	public function get_ticket_with_latest_end_time()
1210
+	{
1211
+		$where['Datetime.EVT_ID'] = $this->ID();
1212
+		$query_params = array($where, 'order_by' => array('TKT_end_date' => 'DESC'));
1213
+		return EE_Registry::instance()->load_model('Ticket')->get_one($query_params);
1214
+	}
1215
+
1216
+
1217
+	/**
1218
+	 * This returns the number of different ticket types currently on sale for this event.
1219
+	 *
1220
+	 * @return int
1221
+	 * @throws EE_Error
1222
+	 * @throws ReflectionException
1223
+	 */
1224
+	public function countTicketsOnSale()
1225
+	{
1226
+		$where = array(
1227
+			'Datetime.EVT_ID' => $this->ID(),
1228
+			'TKT_start_date'  => array('<', time()),
1229
+			'TKT_end_date'    => array('>', time()),
1230
+		);
1231
+		return EEM_Ticket::instance()->count(array($where));
1232
+	}
1233
+
1234
+
1235
+	/**
1236
+	 * This returns whether there are any tickets on sale for this event.
1237
+	 *
1238
+	 * @return bool true = YES tickets on sale.
1239
+	 * @throws EE_Error
1240
+	 */
1241
+	public function tickets_on_sale()
1242
+	{
1243
+		return $this->countTicketsOnSale() > 0;
1244
+	}
1245
+
1246
+
1247
+	/**
1248
+	 * Gets the URL for viewing this event on the front-end. Overrides parent
1249
+	 * to check for an external URL first
1250
+	 *
1251
+	 * @return string
1252
+	 * @throws EE_Error
1253
+	 */
1254
+	public function get_permalink()
1255
+	{
1256
+		if ($this->external_url()) {
1257
+			return $this->external_url();
1258
+		}
1259
+		return parent::get_permalink();
1260
+	}
1261
+
1262
+
1263
+	/**
1264
+	 * Gets the first term for 'espresso_event_categories' we can find
1265
+	 *
1266
+	 * @param array $query_params @see
1267
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1268
+	 * @return EE_Base_Class|EE_Term|null
1269
+	 * @throws EE_Error
1270
+	 * @throws ReflectionException
1271
+	 */
1272
+	public function first_event_category($query_params = array())
1273
+	{
1274
+		$query_params[0]['Term_Taxonomy.taxonomy'] = 'espresso_event_categories';
1275
+		$query_params[0]['Term_Taxonomy.Event.EVT_ID'] = $this->ID();
1276
+		return EEM_Term::instance()->get_one($query_params);
1277
+	}
1278
+
1279
+
1280
+	/**
1281
+	 * Gets all terms for 'espresso_event_categories' we can find
1282
+	 *
1283
+	 * @param array $query_params
1284
+	 * @return EE_Base_Class[]|EE_Term[]
1285
+	 * @throws EE_Error
1286
+	 * @throws ReflectionException
1287
+	 */
1288
+	public function get_all_event_categories($query_params = array())
1289
+	{
1290
+		$query_params[0]['Term_Taxonomy.taxonomy'] = 'espresso_event_categories';
1291
+		$query_params[0]['Term_Taxonomy.Event.EVT_ID'] = $this->ID();
1292
+		return EEM_Term::instance()->get_all($query_params);
1293
+	}
1294
+
1295
+
1296
+	/**
1297
+	 * Adds a question group to this event
1298
+	 *
1299
+	 * @param EE_Question_Group|int $question_group_id_or_obj
1300
+	 * @param bool $for_primary if true, the question group will be added for the primary
1301
+	 *                                           registrant, if false will be added for others. default: false
1302
+	 * @return EE_Base_Class|EE_Question_Group
1303
+	 * @throws EE_Error
1304
+	 * @throws InvalidArgumentException
1305
+	 * @throws InvalidDataTypeException
1306
+	 * @throws InvalidInterfaceException
1307
+	 * @throws ReflectionException
1308
+	 */
1309
+	public function add_question_group($question_group_id_or_obj, $for_primary = false)
1310
+	{
1311
+		// If the row already exists, it will be updated. If it doesn't, it will be inserted.
1312
+		// That's in EE_HABTM_Relation::add_relation_to().
1313
+		return $this->_add_relation_to(
1314
+			$question_group_id_or_obj,
1315
+			'Question_Group',
1316
+			[
1317
+				EEM_Event_Question_Group::instance()->fieldNameForContext($for_primary) => true
1318
+			]
1319
+		);
1320
+	}
1321
+
1322
+
1323
+	/**
1324
+	 * Removes a question group from the event
1325
+	 *
1326
+	 * @param EE_Question_Group|int $question_group_id_or_obj
1327
+	 * @param bool $for_primary if true, the question group will be removed from the primary
1328
+	 *                                           registrant, if false will be removed from others. default: false
1329
+	 * @return EE_Base_Class|EE_Question_Group
1330
+	 * @throws EE_Error
1331
+	 * @throws InvalidArgumentException
1332
+	 * @throws ReflectionException
1333
+	 * @throws InvalidDataTypeException
1334
+	 * @throws InvalidInterfaceException
1335
+	 */
1336
+	public function remove_question_group($question_group_id_or_obj, $for_primary = false)
1337
+	{
1338
+		// If the question group is used for the other type (primary or additional)
1339
+		// then just update it. If not, delete it outright.
1340
+		$existing_relation = $this->get_first_related(
1341
+			'Event_Question_Group',
1342
+			[
1343
+				[
1344
+					'QSG_ID' => EEM_Question_Group::instance()->ensure_is_ID($question_group_id_or_obj)
1345
+				]
1346
+			]
1347
+		);
1348
+		$field_to_update = EEM_Event_Question_Group::instance()->fieldNameForContext($for_primary);
1349
+		$other_field = EEM_Event_Question_Group::instance()->fieldNameForContext(! $for_primary);
1350
+		if ($existing_relation->get($other_field) === false) {
1351
+			// Delete it. It's now no longer for primary or additional question groups.
1352
+			return $this->_remove_relation_to($question_group_id_or_obj, 'Question_Group');
1353
+		}
1354
+		// Just update it. They'll still use this question group for the other category
1355
+		$existing_relation->save(
1356
+			[
1357
+				$field_to_update => false
1358
+			]
1359
+		);
1360
+	}
1361
+
1362
+
1363
+	/**
1364
+	 * Gets all the question groups, ordering them by QSG_order ascending
1365
+	 *
1366
+	 * @param array $query_params @see
1367
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1368
+	 * @return EE_Base_Class[]|EE_Question_Group[]
1369
+	 * @throws EE_Error
1370
+	 * @throws ReflectionException
1371
+	 */
1372
+	public function question_groups($query_params = array())
1373
+	{
1374
+		$query_params = ! empty($query_params) ? $query_params : array('order_by' => array('QSG_order' => 'ASC'));
1375
+		return $this->get_many_related('Question_Group', $query_params);
1376
+	}
1377
+
1378
+
1379
+	/**
1380
+	 * Implementation for EEI_Has_Icon interface method.
1381
+	 *
1382
+	 * @see EEI_Visual_Representation for comments
1383
+	 * @return string
1384
+	 */
1385
+	public function get_icon()
1386
+	{
1387
+		return '<span class="dashicons dashicons-flag"></span>';
1388
+	}
1389
+
1390
+
1391
+	/**
1392
+	 * Implementation for EEI_Admin_Links interface method.
1393
+	 *
1394
+	 * @see EEI_Admin_Links for comments
1395
+	 * @return string
1396
+	 * @throws EE_Error
1397
+	 */
1398
+	public function get_admin_details_link()
1399
+	{
1400
+		return $this->get_admin_edit_link();
1401
+	}
1402
+
1403
+
1404
+	/**
1405
+	 * Implementation for EEI_Admin_Links interface method.
1406
+	 *
1407
+	 * @return string
1408
+	 * @throws EE_Error*@throws ReflectionException
1409
+	 * @see EEI_Admin_Links for comments
1410
+	 */
1411
+	public function get_admin_edit_link()
1412
+	{
1413
+		return EEH_URL::add_query_args_and_nonce(
1414
+			array(
1415
+				'page'   => 'espresso_events',
1416
+				'action' => 'edit',
1417
+				'post'   => $this->ID(),
1418
+			),
1419
+			admin_url('admin.php')
1420
+		);
1421
+	}
1422
+
1423
+
1424
+	/**
1425
+	 * Implementation for EEI_Admin_Links interface method.
1426
+	 *
1427
+	 * @see EEI_Admin_Links for comments
1428
+	 * @return string
1429
+	 */
1430
+	public function get_admin_settings_link()
1431
+	{
1432
+		return EEH_URL::add_query_args_and_nonce(
1433
+			array(
1434
+				'page'   => 'espresso_events',
1435
+				'action' => 'default_event_settings',
1436
+			),
1437
+			admin_url('admin.php')
1438
+		);
1439
+	}
1440
+
1441
+
1442
+	/**
1443
+	 * Implementation for EEI_Admin_Links interface method.
1444
+	 *
1445
+	 * @see EEI_Admin_Links for comments
1446
+	 * @return string
1447
+	 */
1448
+	public function get_admin_overview_link()
1449
+	{
1450
+		return EEH_URL::add_query_args_and_nonce(
1451
+			array(
1452
+				'page'   => 'espresso_events',
1453
+				'action' => 'default',
1454
+			),
1455
+			admin_url('admin.php')
1456
+		);
1457
+	}
1458 1458
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Base_Class.class.php 1 patch
Indentation   +3347 added lines, -3347 removed lines patch added patch discarded remove patch
@@ -14,3363 +14,3363 @@
 block discarded – undo
14 14
 abstract class EE_Base_Class
15 15
 {
16 16
 
17
-    /**
18
-     * This is an array of the original properties and values provided during construction
19
-     * of this model object. (keys are model field names, values are their values).
20
-     * This list is important to remember so that when we are merging data from the db, we know
21
-     * which values to override and which to not override.
22
-     *
23
-     * @var array
24
-     */
25
-    protected $_props_n_values_provided_in_constructor;
26
-
27
-    /**
28
-     * Timezone
29
-     * This gets set by the "set_timezone()" method so that we know what timezone incoming strings|timestamps are in.
30
-     * This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT
31
-     * all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have
32
-     * access to it.
33
-     *
34
-     * @var string
35
-     */
36
-    protected $_timezone;
37
-
38
-    /**
39
-     * date format
40
-     * pattern or format for displaying dates
41
-     *
42
-     * @var string $_dt_frmt
43
-     */
44
-    protected $_dt_frmt;
45
-
46
-    /**
47
-     * time format
48
-     * pattern or format for displaying time
49
-     *
50
-     * @var string $_tm_frmt
51
-     */
52
-    protected $_tm_frmt;
53
-
54
-    /**
55
-     * This property is for holding a cached array of object properties indexed by property name as the key.
56
-     * The purpose of this is for setting a cache on properties that may have calculated values after a
57
-     * prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having
58
-     * to recalculate. Used by _set_cached_property() and _get_cached_property() methods.
59
-     *
60
-     * @var array
61
-     */
62
-    protected $_cached_properties = array();
63
-
64
-    /**
65
-     * An array containing keys of the related model, and values are either an array of related mode objects or a
66
-     * single
67
-     * related model object. see the model's _model_relations. The keys should match those specified. And if the
68
-     * relation is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object,
69
-     * all others have an array)
70
-     *
71
-     * @var array
72
-     */
73
-    protected $_model_relations = array();
74
-
75
-    /**
76
-     * Array where keys are field names (see the model's _fields property) and values are their values. To see what
77
-     * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
78
-     *
79
-     * @var array
80
-     */
81
-    protected $_fields = array();
82
-
83
-    /**
84
-     * @var boolean indicating whether or not this model object is intended to ever be saved
85
-     * For example, we might create model objects intended to only be used for the duration
86
-     * of this request and to be thrown away, and if they were accidentally saved
87
-     * it would be a bug.
88
-     */
89
-    protected $_allow_persist = true;
90
-
91
-    /**
92
-     * @var boolean indicating whether or not this model object's properties have changed since construction
93
-     */
94
-    protected $_has_changes = false;
95
-
96
-    /**
97
-     * @var EEM_Base
98
-     */
99
-    protected $_model;
100
-
101
-    /**
102
-     * This is a cache of results from custom selections done on a query that constructs this entity. The only purpose
103
-     * for these values is for retrieval of the results, they are not further queryable and they are not persisted to
104
-     * the db.  They also do not automatically update if there are any changes to the data that produced their results.
105
-     * The format is a simple array of field_alias => field_value.  So for instance if a custom select was something
106
-     * like,  "Select COUNT(Registration.REG_ID) as Registration_Count ...", then the resulting value will be in this
107
-     * array as:
108
-     * array(
109
-     *  'Registration_Count' => 24
110
-     * );
111
-     * Note: if the custom select configuration for the query included a data type, the value will be in the data type
112
-     * provided for the query (@see EventEspresso\core\domain\values\model\CustomSelects::__construct phpdocs for more
113
-     * info)
114
-     *
115
-     * @var array
116
-     */
117
-    protected $custom_selection_results = array();
118
-
119
-
120
-    /**
121
-     * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children
122
-     * play nice
123
-     *
124
-     * @param array   $fieldValues                             where each key is a field (ie, array key in the 2nd
125
-     *                                                         layer of the model's _fields array, (eg, EVT_ID,
126
-     *                                                         TXN_amount, QST_name, etc) and values are their values
127
-     * @param boolean $bydb                                    a flag for setting if the class is instantiated by the
128
-     *                                                         corresponding db model or not.
129
-     * @param string  $timezone                                indicate what timezone you want any datetime fields to
130
-     *                                                         be in when instantiating a EE_Base_Class object.
131
-     * @param array   $date_formats                            An array of date formats to set on construct where first
132
-     *                                                         value is the date_format and second value is the time
133
-     *                                                         format.
134
-     * @throws InvalidArgumentException
135
-     * @throws InvalidInterfaceException
136
-     * @throws InvalidDataTypeException
137
-     * @throws EE_Error
138
-     * @throws ReflectionException
139
-     */
140
-    protected function __construct($fieldValues = array(), $bydb = false, $timezone = '', $date_formats = array())
141
-    {
142
-        $className = get_class($this);
143
-        do_action("AHEE__{$className}__construct", $this, $fieldValues);
144
-        $model = $this->get_model();
145
-        $model_fields = $model->field_settings(false);
146
-        // ensure $fieldValues is an array
147
-        $fieldValues = is_array($fieldValues) ? $fieldValues : array($fieldValues);
148
-        // verify client code has not passed any invalid field names
149
-        foreach ($fieldValues as $field_name => $field_value) {
150
-            if (! isset($model_fields[ $field_name ])) {
151
-                throw new EE_Error(
152
-                    sprintf(
153
-                        esc_html__(
154
-                            'Invalid field (%s) passed to constructor of %s. Allowed fields are :%s',
155
-                            'event_espresso'
156
-                        ),
157
-                        $field_name,
158
-                        get_class($this),
159
-                        implode(', ', array_keys($model_fields))
160
-                    )
161
-                );
162
-            }
163
-        }
164
-        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
165
-        if (! empty($date_formats) && is_array($date_formats)) {
166
-            list($this->_dt_frmt, $this->_tm_frmt) = $date_formats;
167
-        } else {
168
-            // set default formats for date and time
169
-            $this->_dt_frmt = (string) get_option('date_format', 'Y-m-d');
170
-            $this->_tm_frmt = (string) get_option('time_format', 'g:i a');
171
-        }
172
-        // if db model is instantiating
173
-        if ($bydb) {
174
-            // client code has indicated these field values are from the database
175
-            foreach ($model_fields as $fieldName => $field) {
176
-                $this->set_from_db(
177
-                    $fieldName,
178
-                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null
179
-                );
180
-            }
181
-        } else {
182
-            // we're constructing a brand
183
-            // new instance of the model object. Generally, this means we'll need to do more field validation
184
-            foreach ($model_fields as $fieldName => $field) {
185
-                $this->set(
186
-                    $fieldName,
187
-                    isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null,
188
-                    true
189
-                );
190
-            }
191
-        }
192
-        // remember what values were passed to this constructor
193
-        $this->_props_n_values_provided_in_constructor = $fieldValues;
194
-        // remember in entity mapper
195
-        if (! $bydb && $model->has_primary_key_field() && $this->ID()) {
196
-            $model->add_to_entity_map($this);
197
-        }
198
-        // setup all the relations
199
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
200
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
201
-                $this->_model_relations[ $relation_name ] = null;
202
-            } else {
203
-                $this->_model_relations[ $relation_name ] = array();
204
-            }
205
-        }
206
-        /**
207
-         * Action done at the end of each model object construction
208
-         *
209
-         * @param EE_Base_Class $this the model object just created
210
-         */
211
-        do_action('AHEE__EE_Base_Class__construct__finished', $this);
212
-    }
213
-
214
-
215
-    /**
216
-     * Gets whether or not this model object is allowed to persist/be saved to the database.
217
-     *
218
-     * @return boolean
219
-     */
220
-    public function allow_persist()
221
-    {
222
-        return $this->_allow_persist;
223
-    }
224
-
225
-
226
-    /**
227
-     * Sets whether or not this model object should be allowed to be saved to the DB.
228
-     * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
229
-     * you got new information that somehow made you change your mind.
230
-     *
231
-     * @param boolean $allow_persist
232
-     * @return boolean
233
-     */
234
-    public function set_allow_persist($allow_persist)
235
-    {
236
-        return $this->_allow_persist = $allow_persist;
237
-    }
238
-
239
-
240
-    /**
241
-     * Gets the field's original value when this object was constructed during this request.
242
-     * This can be helpful when determining if a model object has changed or not
243
-     *
244
-     * @param string $field_name
245
-     * @return mixed|null
246
-     * @throws ReflectionException
247
-     * @throws InvalidArgumentException
248
-     * @throws InvalidInterfaceException
249
-     * @throws InvalidDataTypeException
250
-     * @throws EE_Error
251
-     */
252
-    public function get_original($field_name)
253
-    {
254
-        if (
255
-            isset($this->_props_n_values_provided_in_constructor[ $field_name ])
256
-            && $field_settings = $this->get_model()->field_settings_for($field_name)
257
-        ) {
258
-            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
259
-        }
260
-        return null;
261
-    }
262
-
263
-
264
-    /**
265
-     * @param EE_Base_Class $obj
266
-     * @return string
267
-     */
268
-    public function get_class($obj)
269
-    {
270
-        return get_class($obj);
271
-    }
272
-
273
-
274
-    /**
275
-     * Overrides parent because parent expects old models.
276
-     * This also doesn't do any validation, and won't work for serialized arrays
277
-     *
278
-     * @param    string $field_name
279
-     * @param    mixed  $field_value
280
-     * @param bool      $use_default
281
-     * @throws InvalidArgumentException
282
-     * @throws InvalidInterfaceException
283
-     * @throws InvalidDataTypeException
284
-     * @throws EE_Error
285
-     * @throws ReflectionException
286
-     * @throws ReflectionException
287
-     * @throws ReflectionException
288
-     */
289
-    public function set($field_name, $field_value, $use_default = false)
290
-    {
291
-        // if not using default and nothing has changed, and object has already been setup (has ID),
292
-        // then don't do anything
293
-        if (
294
-            ! $use_default
295
-            && $this->_fields[ $field_name ] === $field_value
296
-            && $this->ID()
297
-        ) {
298
-            return;
299
-        }
300
-        $model = $this->get_model();
301
-        $this->_has_changes = true;
302
-        $field_obj = $model->field_settings_for($field_name);
303
-        if ($field_obj instanceof EE_Model_Field_Base) {
304
-            // if ( method_exists( $field_obj, 'set_timezone' )) {
305
-            if ($field_obj instanceof EE_Datetime_Field) {
306
-                $field_obj->set_timezone($this->_timezone);
307
-                $field_obj->set_date_format($this->_dt_frmt);
308
-                $field_obj->set_time_format($this->_tm_frmt);
309
-            }
310
-            $holder_of_value = $field_obj->prepare_for_set($field_value);
311
-            // should the value be null?
312
-            if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
313
-                $this->_fields[ $field_name ] = $field_obj->get_default_value();
314
-                /**
315
-                 * To save having to refactor all the models, if a default value is used for a
316
-                 * EE_Datetime_Field, and that value is not null nor is it a DateTime
317
-                 * object.  Then let's do a set again to ensure that it becomes a DateTime
318
-                 * object.
319
-                 *
320
-                 * @since 4.6.10+
321
-                 */
322
-                if (
323
-                    $field_obj instanceof EE_Datetime_Field
324
-                    && $this->_fields[ $field_name ] !== null
325
-                    && ! $this->_fields[ $field_name ] instanceof DateTime
326
-                ) {
327
-                    empty($this->_fields[ $field_name ])
328
-                        ? $this->set($field_name, time())
329
-                        : $this->set($field_name, $this->_fields[ $field_name ]);
330
-                }
331
-            } else {
332
-                $this->_fields[ $field_name ] = $holder_of_value;
333
-            }
334
-            // if we're not in the constructor...
335
-            // now check if what we set was a primary key
336
-            if (
17
+	/**
18
+	 * This is an array of the original properties and values provided during construction
19
+	 * of this model object. (keys are model field names, values are their values).
20
+	 * This list is important to remember so that when we are merging data from the db, we know
21
+	 * which values to override and which to not override.
22
+	 *
23
+	 * @var array
24
+	 */
25
+	protected $_props_n_values_provided_in_constructor;
26
+
27
+	/**
28
+	 * Timezone
29
+	 * This gets set by the "set_timezone()" method so that we know what timezone incoming strings|timestamps are in.
30
+	 * This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT
31
+	 * all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have
32
+	 * access to it.
33
+	 *
34
+	 * @var string
35
+	 */
36
+	protected $_timezone;
37
+
38
+	/**
39
+	 * date format
40
+	 * pattern or format for displaying dates
41
+	 *
42
+	 * @var string $_dt_frmt
43
+	 */
44
+	protected $_dt_frmt;
45
+
46
+	/**
47
+	 * time format
48
+	 * pattern or format for displaying time
49
+	 *
50
+	 * @var string $_tm_frmt
51
+	 */
52
+	protected $_tm_frmt;
53
+
54
+	/**
55
+	 * This property is for holding a cached array of object properties indexed by property name as the key.
56
+	 * The purpose of this is for setting a cache on properties that may have calculated values after a
57
+	 * prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having
58
+	 * to recalculate. Used by _set_cached_property() and _get_cached_property() methods.
59
+	 *
60
+	 * @var array
61
+	 */
62
+	protected $_cached_properties = array();
63
+
64
+	/**
65
+	 * An array containing keys of the related model, and values are either an array of related mode objects or a
66
+	 * single
67
+	 * related model object. see the model's _model_relations. The keys should match those specified. And if the
68
+	 * relation is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object,
69
+	 * all others have an array)
70
+	 *
71
+	 * @var array
72
+	 */
73
+	protected $_model_relations = array();
74
+
75
+	/**
76
+	 * Array where keys are field names (see the model's _fields property) and values are their values. To see what
77
+	 * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
78
+	 *
79
+	 * @var array
80
+	 */
81
+	protected $_fields = array();
82
+
83
+	/**
84
+	 * @var boolean indicating whether or not this model object is intended to ever be saved
85
+	 * For example, we might create model objects intended to only be used for the duration
86
+	 * of this request and to be thrown away, and if they were accidentally saved
87
+	 * it would be a bug.
88
+	 */
89
+	protected $_allow_persist = true;
90
+
91
+	/**
92
+	 * @var boolean indicating whether or not this model object's properties have changed since construction
93
+	 */
94
+	protected $_has_changes = false;
95
+
96
+	/**
97
+	 * @var EEM_Base
98
+	 */
99
+	protected $_model;
100
+
101
+	/**
102
+	 * This is a cache of results from custom selections done on a query that constructs this entity. The only purpose
103
+	 * for these values is for retrieval of the results, they are not further queryable and they are not persisted to
104
+	 * the db.  They also do not automatically update if there are any changes to the data that produced their results.
105
+	 * The format is a simple array of field_alias => field_value.  So for instance if a custom select was something
106
+	 * like,  "Select COUNT(Registration.REG_ID) as Registration_Count ...", then the resulting value will be in this
107
+	 * array as:
108
+	 * array(
109
+	 *  'Registration_Count' => 24
110
+	 * );
111
+	 * Note: if the custom select configuration for the query included a data type, the value will be in the data type
112
+	 * provided for the query (@see EventEspresso\core\domain\values\model\CustomSelects::__construct phpdocs for more
113
+	 * info)
114
+	 *
115
+	 * @var array
116
+	 */
117
+	protected $custom_selection_results = array();
118
+
119
+
120
+	/**
121
+	 * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children
122
+	 * play nice
123
+	 *
124
+	 * @param array   $fieldValues                             where each key is a field (ie, array key in the 2nd
125
+	 *                                                         layer of the model's _fields array, (eg, EVT_ID,
126
+	 *                                                         TXN_amount, QST_name, etc) and values are their values
127
+	 * @param boolean $bydb                                    a flag for setting if the class is instantiated by the
128
+	 *                                                         corresponding db model or not.
129
+	 * @param string  $timezone                                indicate what timezone you want any datetime fields to
130
+	 *                                                         be in when instantiating a EE_Base_Class object.
131
+	 * @param array   $date_formats                            An array of date formats to set on construct where first
132
+	 *                                                         value is the date_format and second value is the time
133
+	 *                                                         format.
134
+	 * @throws InvalidArgumentException
135
+	 * @throws InvalidInterfaceException
136
+	 * @throws InvalidDataTypeException
137
+	 * @throws EE_Error
138
+	 * @throws ReflectionException
139
+	 */
140
+	protected function __construct($fieldValues = array(), $bydb = false, $timezone = '', $date_formats = array())
141
+	{
142
+		$className = get_class($this);
143
+		do_action("AHEE__{$className}__construct", $this, $fieldValues);
144
+		$model = $this->get_model();
145
+		$model_fields = $model->field_settings(false);
146
+		// ensure $fieldValues is an array
147
+		$fieldValues = is_array($fieldValues) ? $fieldValues : array($fieldValues);
148
+		// verify client code has not passed any invalid field names
149
+		foreach ($fieldValues as $field_name => $field_value) {
150
+			if (! isset($model_fields[ $field_name ])) {
151
+				throw new EE_Error(
152
+					sprintf(
153
+						esc_html__(
154
+							'Invalid field (%s) passed to constructor of %s. Allowed fields are :%s',
155
+							'event_espresso'
156
+						),
157
+						$field_name,
158
+						get_class($this),
159
+						implode(', ', array_keys($model_fields))
160
+					)
161
+				);
162
+			}
163
+		}
164
+		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
165
+		if (! empty($date_formats) && is_array($date_formats)) {
166
+			list($this->_dt_frmt, $this->_tm_frmt) = $date_formats;
167
+		} else {
168
+			// set default formats for date and time
169
+			$this->_dt_frmt = (string) get_option('date_format', 'Y-m-d');
170
+			$this->_tm_frmt = (string) get_option('time_format', 'g:i a');
171
+		}
172
+		// if db model is instantiating
173
+		if ($bydb) {
174
+			// client code has indicated these field values are from the database
175
+			foreach ($model_fields as $fieldName => $field) {
176
+				$this->set_from_db(
177
+					$fieldName,
178
+					isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null
179
+				);
180
+			}
181
+		} else {
182
+			// we're constructing a brand
183
+			// new instance of the model object. Generally, this means we'll need to do more field validation
184
+			foreach ($model_fields as $fieldName => $field) {
185
+				$this->set(
186
+					$fieldName,
187
+					isset($fieldValues[ $fieldName ]) ? $fieldValues[ $fieldName ] : null,
188
+					true
189
+				);
190
+			}
191
+		}
192
+		// remember what values were passed to this constructor
193
+		$this->_props_n_values_provided_in_constructor = $fieldValues;
194
+		// remember in entity mapper
195
+		if (! $bydb && $model->has_primary_key_field() && $this->ID()) {
196
+			$model->add_to_entity_map($this);
197
+		}
198
+		// setup all the relations
199
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
200
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
201
+				$this->_model_relations[ $relation_name ] = null;
202
+			} else {
203
+				$this->_model_relations[ $relation_name ] = array();
204
+			}
205
+		}
206
+		/**
207
+		 * Action done at the end of each model object construction
208
+		 *
209
+		 * @param EE_Base_Class $this the model object just created
210
+		 */
211
+		do_action('AHEE__EE_Base_Class__construct__finished', $this);
212
+	}
213
+
214
+
215
+	/**
216
+	 * Gets whether or not this model object is allowed to persist/be saved to the database.
217
+	 *
218
+	 * @return boolean
219
+	 */
220
+	public function allow_persist()
221
+	{
222
+		return $this->_allow_persist;
223
+	}
224
+
225
+
226
+	/**
227
+	 * Sets whether or not this model object should be allowed to be saved to the DB.
228
+	 * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
229
+	 * you got new information that somehow made you change your mind.
230
+	 *
231
+	 * @param boolean $allow_persist
232
+	 * @return boolean
233
+	 */
234
+	public function set_allow_persist($allow_persist)
235
+	{
236
+		return $this->_allow_persist = $allow_persist;
237
+	}
238
+
239
+
240
+	/**
241
+	 * Gets the field's original value when this object was constructed during this request.
242
+	 * This can be helpful when determining if a model object has changed or not
243
+	 *
244
+	 * @param string $field_name
245
+	 * @return mixed|null
246
+	 * @throws ReflectionException
247
+	 * @throws InvalidArgumentException
248
+	 * @throws InvalidInterfaceException
249
+	 * @throws InvalidDataTypeException
250
+	 * @throws EE_Error
251
+	 */
252
+	public function get_original($field_name)
253
+	{
254
+		if (
255
+			isset($this->_props_n_values_provided_in_constructor[ $field_name ])
256
+			&& $field_settings = $this->get_model()->field_settings_for($field_name)
257
+		) {
258
+			return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
259
+		}
260
+		return null;
261
+	}
262
+
263
+
264
+	/**
265
+	 * @param EE_Base_Class $obj
266
+	 * @return string
267
+	 */
268
+	public function get_class($obj)
269
+	{
270
+		return get_class($obj);
271
+	}
272
+
273
+
274
+	/**
275
+	 * Overrides parent because parent expects old models.
276
+	 * This also doesn't do any validation, and won't work for serialized arrays
277
+	 *
278
+	 * @param    string $field_name
279
+	 * @param    mixed  $field_value
280
+	 * @param bool      $use_default
281
+	 * @throws InvalidArgumentException
282
+	 * @throws InvalidInterfaceException
283
+	 * @throws InvalidDataTypeException
284
+	 * @throws EE_Error
285
+	 * @throws ReflectionException
286
+	 * @throws ReflectionException
287
+	 * @throws ReflectionException
288
+	 */
289
+	public function set($field_name, $field_value, $use_default = false)
290
+	{
291
+		// if not using default and nothing has changed, and object has already been setup (has ID),
292
+		// then don't do anything
293
+		if (
294
+			! $use_default
295
+			&& $this->_fields[ $field_name ] === $field_value
296
+			&& $this->ID()
297
+		) {
298
+			return;
299
+		}
300
+		$model = $this->get_model();
301
+		$this->_has_changes = true;
302
+		$field_obj = $model->field_settings_for($field_name);
303
+		if ($field_obj instanceof EE_Model_Field_Base) {
304
+			// if ( method_exists( $field_obj, 'set_timezone' )) {
305
+			if ($field_obj instanceof EE_Datetime_Field) {
306
+				$field_obj->set_timezone($this->_timezone);
307
+				$field_obj->set_date_format($this->_dt_frmt);
308
+				$field_obj->set_time_format($this->_tm_frmt);
309
+			}
310
+			$holder_of_value = $field_obj->prepare_for_set($field_value);
311
+			// should the value be null?
312
+			if (($field_value === null || $holder_of_value === null || $holder_of_value === '') && $use_default) {
313
+				$this->_fields[ $field_name ] = $field_obj->get_default_value();
314
+				/**
315
+				 * To save having to refactor all the models, if a default value is used for a
316
+				 * EE_Datetime_Field, and that value is not null nor is it a DateTime
317
+				 * object.  Then let's do a set again to ensure that it becomes a DateTime
318
+				 * object.
319
+				 *
320
+				 * @since 4.6.10+
321
+				 */
322
+				if (
323
+					$field_obj instanceof EE_Datetime_Field
324
+					&& $this->_fields[ $field_name ] !== null
325
+					&& ! $this->_fields[ $field_name ] instanceof DateTime
326
+				) {
327
+					empty($this->_fields[ $field_name ])
328
+						? $this->set($field_name, time())
329
+						: $this->set($field_name, $this->_fields[ $field_name ]);
330
+				}
331
+			} else {
332
+				$this->_fields[ $field_name ] = $holder_of_value;
333
+			}
334
+			// if we're not in the constructor...
335
+			// now check if what we set was a primary key
336
+			if (
337 337
 // note: props_n_values_provided_in_constructor is only set at the END of the constructor
338
-                $this->_props_n_values_provided_in_constructor
339
-                && $field_value
340
-                && $field_name === $model->primary_key_name()
341
-            ) {
342
-                // if so, we want all this object's fields to be filled either with
343
-                // what we've explicitly set on this model
344
-                // or what we have in the db
345
-                // echo "setting primary key!";
346
-                $fields_on_model = self::_get_model(get_class($this))->field_settings();
347
-                $obj_in_db = self::_get_model(get_class($this))->get_one_by_ID($field_value);
348
-                foreach ($fields_on_model as $field_obj) {
349
-                    if (
350
-                        ! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
351
-                        && $field_obj->get_name() !== $field_name
352
-                    ) {
353
-                        $this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
354
-                    }
355
-                }
356
-                // oh this model object has an ID? well make sure its in the entity mapper
357
-                $model->add_to_entity_map($this);
358
-            }
359
-            // let's unset any cache for this field_name from the $_cached_properties property.
360
-            $this->_clear_cached_property($field_name);
361
-        } else {
362
-            throw new EE_Error(
363
-                sprintf(
364
-                    esc_html__(
365
-                        'A valid EE_Model_Field_Base could not be found for the given field name: %s',
366
-                        'event_espresso'
367
-                    ),
368
-                    $field_name
369
-                )
370
-            );
371
-        }
372
-    }
373
-
374
-
375
-    /**
376
-     * Set custom select values for model.
377
-     *
378
-     * @param array $custom_select_values
379
-     */
380
-    public function setCustomSelectsValues(array $custom_select_values)
381
-    {
382
-        $this->custom_selection_results = $custom_select_values;
383
-    }
384
-
385
-
386
-    /**
387
-     * Returns the custom select value for the provided alias if its set.
388
-     * If not set, returns null.
389
-     *
390
-     * @param string $alias
391
-     * @return string|int|float|null
392
-     */
393
-    public function getCustomSelect($alias)
394
-    {
395
-        return isset($this->custom_selection_results[ $alias ])
396
-            ? $this->custom_selection_results[ $alias ]
397
-            : null;
398
-    }
399
-
400
-
401
-    /**
402
-     * This sets the field value on the db column if it exists for the given $column_name or
403
-     * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
404
-     *
405
-     * @see EE_message::get_column_value for related documentation on the necessity of this method.
406
-     * @param string $field_name  Must be the exact column name.
407
-     * @param mixed  $field_value The value to set.
408
-     * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
409
-     * @throws InvalidArgumentException
410
-     * @throws InvalidInterfaceException
411
-     * @throws InvalidDataTypeException
412
-     * @throws EE_Error
413
-     * @throws ReflectionException
414
-     */
415
-    public function set_field_or_extra_meta($field_name, $field_value)
416
-    {
417
-        if ($this->get_model()->has_field($field_name)) {
418
-            $this->set($field_name, $field_value);
419
-            return true;
420
-        }
421
-        // ensure this object is saved first so that extra meta can be properly related.
422
-        $this->save();
423
-        return $this->update_extra_meta($field_name, $field_value);
424
-    }
425
-
426
-
427
-    /**
428
-     * This retrieves the value of the db column set on this class or if that's not present
429
-     * it will attempt to retrieve from extra_meta if found.
430
-     * Example Usage:
431
-     * Via EE_Message child class:
432
-     * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
433
-     * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
434
-     * also have additional main fields specific to the messenger.  The system accommodates those extra
435
-     * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
436
-     * value for those extra fields dynamically via the EE_message object.
437
-     *
438
-     * @param  string $field_name expecting the fully qualified field name.
439
-     * @return mixed|null  value for the field if found.  null if not found.
440
-     * @throws ReflectionException
441
-     * @throws InvalidArgumentException
442
-     * @throws InvalidInterfaceException
443
-     * @throws InvalidDataTypeException
444
-     * @throws EE_Error
445
-     */
446
-    public function get_field_or_extra_meta($field_name)
447
-    {
448
-        if ($this->get_model()->has_field($field_name)) {
449
-            $column_value = $this->get($field_name);
450
-        } else {
451
-            // This isn't a column in the main table, let's see if it is in the extra meta.
452
-            $column_value = $this->get_extra_meta($field_name, true, null);
453
-        }
454
-        return $column_value;
455
-    }
456
-
457
-
458
-    /**
459
-     * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
460
-     * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
461
-     * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
462
-     * available to all child classes that may be using the EE_Datetime_Field for a field data type.
463
-     *
464
-     * @access public
465
-     * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
466
-     * @return void
467
-     * @throws InvalidArgumentException
468
-     * @throws InvalidInterfaceException
469
-     * @throws InvalidDataTypeException
470
-     * @throws EE_Error
471
-     * @throws ReflectionException
472
-     */
473
-    public function set_timezone($timezone = '')
474
-    {
475
-        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
476
-        // make sure we clear all cached properties because they won't be relevant now
477
-        $this->_clear_cached_properties();
478
-        // make sure we update field settings and the date for all EE_Datetime_Fields
479
-        $model_fields = $this->get_model()->field_settings(false);
480
-        foreach ($model_fields as $field_name => $field_obj) {
481
-            if ($field_obj instanceof EE_Datetime_Field) {
482
-                $field_obj->set_timezone($this->_timezone);
483
-                if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
484
-                    EEH_DTT_Helper::setTimezone($this->_fields[ $field_name ], new DateTimeZone($this->_timezone));
485
-                }
486
-            }
487
-        }
488
-    }
489
-
490
-
491
-    /**
492
-     * This just returns whatever is set for the current timezone.
493
-     *
494
-     * @access public
495
-     * @return string timezone string
496
-     */
497
-    public function get_timezone()
498
-    {
499
-        return $this->_timezone;
500
-    }
501
-
502
-
503
-    /**
504
-     * This sets the internal date format to what is sent in to be used as the new default for the class
505
-     * internally instead of wp set date format options
506
-     *
507
-     * @since 4.6
508
-     * @param string $format should be a format recognizable by PHP date() functions.
509
-     */
510
-    public function set_date_format($format)
511
-    {
512
-        $this->_dt_frmt = $format;
513
-        // clear cached_properties because they won't be relevant now.
514
-        $this->_clear_cached_properties();
515
-    }
516
-
517
-
518
-    /**
519
-     * This sets the internal time format string to what is sent in to be used as the new default for the
520
-     * class internally instead of wp set time format options.
521
-     *
522
-     * @since 4.6
523
-     * @param string $format should be a format recognizable by PHP date() functions.
524
-     */
525
-    public function set_time_format($format)
526
-    {
527
-        $this->_tm_frmt = $format;
528
-        // clear cached_properties because they won't be relevant now.
529
-        $this->_clear_cached_properties();
530
-    }
531
-
532
-
533
-    /**
534
-     * This returns the current internal set format for the date and time formats.
535
-     *
536
-     * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
537
-     *                             where the first value is the date format and the second value is the time format.
538
-     * @return mixed string|array
539
-     */
540
-    public function get_format($full = true)
541
-    {
542
-        return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : array($this->_dt_frmt, $this->_tm_frmt);
543
-    }
544
-
545
-
546
-    /**
547
-     * cache
548
-     * stores the passed model object on the current model object.
549
-     * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
550
-     *
551
-     * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg
552
-     *                                       'Registration' associated with this model object
553
-     * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
554
-     *                                       that could be a payment or a registration)
555
-     * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
556
-     *                                       items which will be stored in an array on this object
557
-     * @throws ReflectionException
558
-     * @throws InvalidArgumentException
559
-     * @throws InvalidInterfaceException
560
-     * @throws InvalidDataTypeException
561
-     * @throws EE_Error
562
-     * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
563
-     *                                       related thing, no array)
564
-     */
565
-    public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
566
-    {
567
-        // its entirely possible that there IS no related object yet in which case there is nothing to cache.
568
-        if (! $object_to_cache instanceof EE_Base_Class) {
569
-            return false;
570
-        }
571
-        // also get "how" the object is related, or throw an error
572
-        if (! $relationship_to_model = $this->get_model()->related_settings_for($relationName)) {
573
-            throw new EE_Error(
574
-                sprintf(
575
-                    esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
576
-                    $relationName,
577
-                    get_class($this)
578
-                )
579
-            );
580
-        }
581
-        // how many things are related ?
582
-        if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
583
-            // if it's a "belongs to" relationship, then there's only one related model object
584
-            // eg, if this is a registration, there's only 1 attendee for it
585
-            // so for these model objects just set it to be cached
586
-            $this->_model_relations[ $relationName ] = $object_to_cache;
587
-            $return = true;
588
-        } else {
589
-            // otherwise, this is the "many" side of a one to many relationship,
590
-            // so we'll add the object to the array of related objects for that type.
591
-            // eg: if this is an event, there are many registrations for that event,
592
-            // so we cache the registrations in an array
593
-            if (! is_array($this->_model_relations[ $relationName ])) {
594
-                // if for some reason, the cached item is a model object,
595
-                // then stick that in the array, otherwise start with an empty array
596
-                $this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ]
597
-                                                           instanceof
598
-                                                           EE_Base_Class
599
-                    ? array($this->_model_relations[ $relationName ]) : array();
600
-            }
601
-            // first check for a cache_id which is normally empty
602
-            if (! empty($cache_id)) {
603
-                // if the cache_id exists, then it means we are purposely trying to cache this
604
-                // with a known key that can then be used to retrieve the object later on
605
-                $this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
606
-                $return = $cache_id;
607
-            } elseif ($object_to_cache->ID()) {
608
-                // OR the cached object originally came from the db, so let's just use it's PK for an ID
609
-                $this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
610
-                $return = $object_to_cache->ID();
611
-            } else {
612
-                // OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
613
-                $this->_model_relations[ $relationName ][] = $object_to_cache;
614
-                // move the internal pointer to the end of the array
615
-                end($this->_model_relations[ $relationName ]);
616
-                // and grab the key so that we can return it
617
-                $return = key($this->_model_relations[ $relationName ]);
618
-            }
619
-        }
620
-        return $return;
621
-    }
622
-
623
-
624
-    /**
625
-     * For adding an item to the cached_properties property.
626
-     *
627
-     * @access protected
628
-     * @param string      $fieldname the property item the corresponding value is for.
629
-     * @param mixed       $value     The value we are caching.
630
-     * @param string|null $cache_type
631
-     * @return void
632
-     * @throws ReflectionException
633
-     * @throws InvalidArgumentException
634
-     * @throws InvalidInterfaceException
635
-     * @throws InvalidDataTypeException
636
-     * @throws EE_Error
637
-     */
638
-    protected function _set_cached_property($fieldname, $value, $cache_type = null)
639
-    {
640
-        // first make sure this property exists
641
-        $this->get_model()->field_settings_for($fieldname);
642
-        $cache_type = empty($cache_type) ? 'standard' : $cache_type;
643
-        $this->_cached_properties[ $fieldname ][ $cache_type ] = $value;
644
-    }
645
-
646
-
647
-    /**
648
-     * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
649
-     * This also SETS the cache if we return the actual property!
650
-     *
651
-     * @param string $fieldname        the name of the property we're trying to retrieve
652
-     * @param bool   $pretty
653
-     * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
654
-     *                                 (in cases where the same property may be used for different outputs
655
-     *                                 - i.e. datetime, money etc.)
656
-     *                                 It can also accept certain pre-defined "schema" strings
657
-     *                                 to define how to output the property.
658
-     *                                 see the field's prepare_for_pretty_echoing for what strings can be used
659
-     * @return mixed                   whatever the value for the property is we're retrieving
660
-     * @throws ReflectionException
661
-     * @throws InvalidArgumentException
662
-     * @throws InvalidInterfaceException
663
-     * @throws InvalidDataTypeException
664
-     * @throws EE_Error
665
-     */
666
-    protected function _get_cached_property($fieldname, $pretty = false, $extra_cache_ref = null)
667
-    {
668
-        // verify the field exists
669
-        $model = $this->get_model();
670
-        $model->field_settings_for($fieldname);
671
-        $cache_type = $pretty ? 'pretty' : 'standard';
672
-        $cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
673
-        if (isset($this->_cached_properties[ $fieldname ][ $cache_type ])) {
674
-            return $this->_cached_properties[ $fieldname ][ $cache_type ];
675
-        }
676
-        $value = $this->_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
677
-        $this->_set_cached_property($fieldname, $value, $cache_type);
678
-        return $value;
679
-    }
680
-
681
-
682
-    /**
683
-     * If the cache didn't fetch the needed item, this fetches it.
684
-     *
685
-     * @param string $fieldname
686
-     * @param bool   $pretty
687
-     * @param string $extra_cache_ref
688
-     * @return mixed
689
-     * @throws InvalidArgumentException
690
-     * @throws InvalidInterfaceException
691
-     * @throws InvalidDataTypeException
692
-     * @throws EE_Error
693
-     * @throws ReflectionException
694
-     */
695
-    protected function _get_fresh_property($fieldname, $pretty = false, $extra_cache_ref = null)
696
-    {
697
-        $field_obj = $this->get_model()->field_settings_for($fieldname);
698
-        // If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
699
-        if ($field_obj instanceof EE_Datetime_Field) {
700
-            $this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
701
-        }
702
-        if (! isset($this->_fields[ $fieldname ])) {
703
-            $this->_fields[ $fieldname ] = null;
704
-        }
705
-        $value = $pretty
706
-            ? $field_obj->prepare_for_pretty_echoing($this->_fields[ $fieldname ], $extra_cache_ref)
707
-            : $field_obj->prepare_for_get($this->_fields[ $fieldname ]);
708
-        return $value;
709
-    }
710
-
711
-
712
-    /**
713
-     * set timezone, formats, and output for EE_Datetime_Field objects
714
-     *
715
-     * @param \EE_Datetime_Field $datetime_field
716
-     * @param bool               $pretty
717
-     * @param null               $date_or_time
718
-     * @return void
719
-     * @throws InvalidArgumentException
720
-     * @throws InvalidInterfaceException
721
-     * @throws InvalidDataTypeException
722
-     * @throws EE_Error
723
-     */
724
-    protected function _prepare_datetime_field(
725
-        EE_Datetime_Field $datetime_field,
726
-        $pretty = false,
727
-        $date_or_time = null
728
-    ) {
729
-        $datetime_field->set_timezone($this->_timezone);
730
-        $datetime_field->set_date_format($this->_dt_frmt, $pretty);
731
-        $datetime_field->set_time_format($this->_tm_frmt, $pretty);
732
-        // set the output returned
733
-        switch ($date_or_time) {
734
-            case 'D':
735
-                $datetime_field->set_date_time_output('date');
736
-                break;
737
-            case 'T':
738
-                $datetime_field->set_date_time_output('time');
739
-                break;
740
-            default:
741
-                $datetime_field->set_date_time_output();
742
-        }
743
-    }
744
-
745
-
746
-    /**
747
-     * This just takes care of clearing out the cached_properties
748
-     *
749
-     * @return void
750
-     */
751
-    protected function _clear_cached_properties()
752
-    {
753
-        $this->_cached_properties = array();
754
-    }
755
-
756
-
757
-    /**
758
-     * This just clears out ONE property if it exists in the cache
759
-     *
760
-     * @param  string $property_name the property to remove if it exists (from the _cached_properties array)
761
-     * @return void
762
-     */
763
-    protected function _clear_cached_property($property_name)
764
-    {
765
-        if (isset($this->_cached_properties[ $property_name ])) {
766
-            unset($this->_cached_properties[ $property_name ]);
767
-        }
768
-    }
769
-
770
-
771
-    /**
772
-     * Ensures that this related thing is a model object.
773
-     *
774
-     * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
775
-     * @param string $model_name   name of the related thing, eg 'Attendee',
776
-     * @return EE_Base_Class
777
-     * @throws ReflectionException
778
-     * @throws InvalidArgumentException
779
-     * @throws InvalidInterfaceException
780
-     * @throws InvalidDataTypeException
781
-     * @throws EE_Error
782
-     */
783
-    protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
784
-    {
785
-        $other_model_instance = self::_get_model_instance_with_name(
786
-            self::_get_model_classname($model_name),
787
-            $this->_timezone
788
-        );
789
-        return $other_model_instance->ensure_is_obj($object_or_id);
790
-    }
791
-
792
-
793
-    /**
794
-     * Forgets the cached model of the given relation Name. So the next time we request it,
795
-     * we will fetch it again from the database. (Handy if you know it's changed somehow).
796
-     * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
797
-     * then only remove that one object from our cached array. Otherwise, clear the entire list
798
-     *
799
-     * @param string $relationName                         one of the keys in the _model_relations array on the model.
800
-     *                                                     Eg 'Registration'
801
-     * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
802
-     *                                                     if you intend to use $clear_all = TRUE, or the relation only
803
-     *                                                     has 1 object anyways (ie, it's a BelongsToRelation)
804
-     * @param bool   $clear_all                            This flags clearing the entire cache relation property if
805
-     *                                                     this is HasMany or HABTM.
806
-     * @throws ReflectionException
807
-     * @throws InvalidArgumentException
808
-     * @throws InvalidInterfaceException
809
-     * @throws InvalidDataTypeException
810
-     * @throws EE_Error
811
-     * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a
812
-     *                                                     relation from all
813
-     */
814
-    public function clear_cache($relationName, $object_to_remove_or_index_into_array = null, $clear_all = false)
815
-    {
816
-        $relationship_to_model = $this->get_model()->related_settings_for($relationName);
817
-        $index_in_cache = '';
818
-        if (! $relationship_to_model) {
819
-            throw new EE_Error(
820
-                sprintf(
821
-                    esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
822
-                    $relationName,
823
-                    get_class($this)
824
-                )
825
-            );
826
-        }
827
-        if ($clear_all) {
828
-            $obj_removed = true;
829
-            $this->_model_relations[ $relationName ] = null;
830
-        } elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
831
-            $obj_removed = $this->_model_relations[ $relationName ];
832
-            $this->_model_relations[ $relationName ] = null;
833
-        } else {
834
-            if (
835
-                $object_to_remove_or_index_into_array instanceof EE_Base_Class
836
-                && $object_to_remove_or_index_into_array->ID()
837
-            ) {
838
-                $index_in_cache = $object_to_remove_or_index_into_array->ID();
839
-                if (
840
-                    is_array($this->_model_relations[ $relationName ])
841
-                    && ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
842
-                ) {
843
-                    $index_found_at = null;
844
-                    // find this object in the array even though it has a different key
845
-                    foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
846
-                        /** @noinspection TypeUnsafeComparisonInspection */
847
-                        if (
848
-                            $obj instanceof EE_Base_Class
849
-                            && (
850
-                                $obj == $object_to_remove_or_index_into_array
851
-                                || $obj->ID() === $object_to_remove_or_index_into_array->ID()
852
-                            )
853
-                        ) {
854
-                            $index_found_at = $index;
855
-                            break;
856
-                        }
857
-                    }
858
-                    if ($index_found_at) {
859
-                        $index_in_cache = $index_found_at;
860
-                    } else {
861
-                        // it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
862
-                        // if it wasn't in it to begin with. So we're done
863
-                        return $object_to_remove_or_index_into_array;
864
-                    }
865
-                }
866
-            } elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
867
-                // so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
868
-                foreach ($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want) {
869
-                    /** @noinspection TypeUnsafeComparisonInspection */
870
-                    if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
871
-                        $index_in_cache = $index;
872
-                    }
873
-                }
874
-            } else {
875
-                $index_in_cache = $object_to_remove_or_index_into_array;
876
-            }
877
-            // supposedly we've found it. But it could just be that the client code
878
-            // provided a bad index/object
879
-            if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
880
-                $obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
881
-                unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
882
-            } else {
883
-                // that thing was never cached anyways.
884
-                $obj_removed = null;
885
-            }
886
-        }
887
-        return $obj_removed;
888
-    }
889
-
890
-
891
-    /**
892
-     * update_cache_after_object_save
893
-     * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
894
-     * obtained after being saved to the db
895
-     *
896
-     * @param string        $relationName       - the type of object that is cached
897
-     * @param EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
898
-     * @param string        $current_cache_id   - the ID that was used when originally caching the object
899
-     * @return boolean TRUE on success, FALSE on fail
900
-     * @throws ReflectionException
901
-     * @throws InvalidArgumentException
902
-     * @throws InvalidInterfaceException
903
-     * @throws InvalidDataTypeException
904
-     * @throws EE_Error
905
-     */
906
-    public function update_cache_after_object_save(
907
-        $relationName,
908
-        EE_Base_Class $newly_saved_object,
909
-        $current_cache_id = ''
910
-    ) {
911
-        // verify that incoming object is of the correct type
912
-        $obj_class = 'EE_' . $relationName;
913
-        if ($newly_saved_object instanceof $obj_class) {
914
-            /* @type EE_Base_Class $newly_saved_object */
915
-            // now get the type of relation
916
-            $relationship_to_model = $this->get_model()->related_settings_for($relationName);
917
-            // if this is a 1:1 relationship
918
-            if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
919
-                // then just replace the cached object with the newly saved object
920
-                $this->_model_relations[ $relationName ] = $newly_saved_object;
921
-                return true;
922
-                // or if it's some kind of sordid feral polyamorous relationship...
923
-            }
924
-            if (
925
-                is_array($this->_model_relations[ $relationName ])
926
-                && isset($this->_model_relations[ $relationName ][ $current_cache_id ])
927
-            ) {
928
-                // then remove the current cached item
929
-                unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
930
-                // and cache the newly saved object using it's new ID
931
-                $this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
932
-                return true;
933
-            }
934
-        }
935
-        return false;
936
-    }
937
-
938
-
939
-    /**
940
-     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
941
-     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
942
-     *
943
-     * @param string $relationName
944
-     * @return EE_Base_Class
945
-     */
946
-    public function get_one_from_cache($relationName)
947
-    {
948
-        $cached_array_or_object = isset($this->_model_relations[ $relationName ])
949
-            ? $this->_model_relations[ $relationName ]
950
-            : null;
951
-        if (is_array($cached_array_or_object)) {
952
-            return array_shift($cached_array_or_object);
953
-        }
954
-        return $cached_array_or_object;
955
-    }
956
-
957
-
958
-    /**
959
-     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
960
-     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
961
-     *
962
-     * @param string $relationName
963
-     * @throws ReflectionException
964
-     * @throws InvalidArgumentException
965
-     * @throws InvalidInterfaceException
966
-     * @throws InvalidDataTypeException
967
-     * @throws EE_Error
968
-     * @return EE_Base_Class[] NOT necessarily indexed by primary keys
969
-     */
970
-    public function get_all_from_cache($relationName)
971
-    {
972
-        $objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : array();
973
-        // if the result is not an array, but exists, make it an array
974
-        $objects = is_array($objects) ? $objects : array($objects);
975
-        // bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
976
-        // basically, if this model object was stored in the session, and these cached model objects
977
-        // already have IDs, let's make sure they're in their model's entity mapper
978
-        // otherwise we will have duplicates next time we call
979
-        // EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
980
-        $model = EE_Registry::instance()->load_model($relationName);
981
-        foreach ($objects as $model_object) {
982
-            if ($model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
983
-                // ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
984
-                if ($model_object->ID()) {
985
-                    $model->add_to_entity_map($model_object);
986
-                }
987
-            } else {
988
-                throw new EE_Error(
989
-                    sprintf(
990
-                        esc_html__(
991
-                            'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
992
-                            'event_espresso'
993
-                        ),
994
-                        $relationName,
995
-                        gettype($model_object)
996
-                    )
997
-                );
998
-            }
999
-        }
1000
-        return $objects;
1001
-    }
1002
-
1003
-
1004
-    /**
1005
-     * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
1006
-     * matching the given query conditions.
1007
-     *
1008
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1009
-     * @param int   $limit              How many objects to return.
1010
-     * @param array $query_params       Any additional conditions on the query.
1011
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1012
-     *                                  you can indicate just the columns you want returned
1013
-     * @return array|EE_Base_Class[]
1014
-     * @throws ReflectionException
1015
-     * @throws InvalidArgumentException
1016
-     * @throws InvalidInterfaceException
1017
-     * @throws InvalidDataTypeException
1018
-     * @throws EE_Error
1019
-     */
1020
-    public function next_x($field_to_order_by = null, $limit = 1, $query_params = array(), $columns_to_select = null)
1021
-    {
1022
-        $model = $this->get_model();
1023
-        $field = empty($field_to_order_by) && $model->has_primary_key_field()
1024
-            ? $model->get_primary_key_field()->get_name()
1025
-            : $field_to_order_by;
1026
-        $current_value = ! empty($field) ? $this->get($field) : null;
1027
-        if (empty($field) || empty($current_value)) {
1028
-            return array();
1029
-        }
1030
-        return $model->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
1031
-    }
1032
-
1033
-
1034
-    /**
1035
-     * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
1036
-     * matching the given query conditions.
1037
-     *
1038
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1039
-     * @param int   $limit              How many objects to return.
1040
-     * @param array $query_params       Any additional conditions on the query.
1041
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1042
-     *                                  you can indicate just the columns you want returned
1043
-     * @return array|EE_Base_Class[]
1044
-     * @throws ReflectionException
1045
-     * @throws InvalidArgumentException
1046
-     * @throws InvalidInterfaceException
1047
-     * @throws InvalidDataTypeException
1048
-     * @throws EE_Error
1049
-     */
1050
-    public function previous_x(
1051
-        $field_to_order_by = null,
1052
-        $limit = 1,
1053
-        $query_params = array(),
1054
-        $columns_to_select = null
1055
-    ) {
1056
-        $model = $this->get_model();
1057
-        $field = empty($field_to_order_by) && $model->has_primary_key_field()
1058
-            ? $model->get_primary_key_field()->get_name()
1059
-            : $field_to_order_by;
1060
-        $current_value = ! empty($field) ? $this->get($field) : null;
1061
-        if (empty($field) || empty($current_value)) {
1062
-            return array();
1063
-        }
1064
-        return $model->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
1065
-    }
1066
-
1067
-
1068
-    /**
1069
-     * Returns the next EE_Base_Class object in sequence from this object as found in the database
1070
-     * matching the given query conditions.
1071
-     *
1072
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1073
-     * @param array $query_params       Any additional conditions on the query.
1074
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1075
-     *                                  you can indicate just the columns you want returned
1076
-     * @return array|EE_Base_Class
1077
-     * @throws ReflectionException
1078
-     * @throws InvalidArgumentException
1079
-     * @throws InvalidInterfaceException
1080
-     * @throws InvalidDataTypeException
1081
-     * @throws EE_Error
1082
-     */
1083
-    public function next($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1084
-    {
1085
-        $model = $this->get_model();
1086
-        $field = empty($field_to_order_by) && $model->has_primary_key_field()
1087
-            ? $model->get_primary_key_field()->get_name()
1088
-            : $field_to_order_by;
1089
-        $current_value = ! empty($field) ? $this->get($field) : null;
1090
-        if (empty($field) || empty($current_value)) {
1091
-            return array();
1092
-        }
1093
-        return $model->next($current_value, $field, $query_params, $columns_to_select);
1094
-    }
1095
-
1096
-
1097
-    /**
1098
-     * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1099
-     * matching the given query conditions.
1100
-     *
1101
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1102
-     * @param array $query_params       Any additional conditions on the query.
1103
-     * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1104
-     *                                  you can indicate just the column you want returned
1105
-     * @return array|EE_Base_Class
1106
-     * @throws ReflectionException
1107
-     * @throws InvalidArgumentException
1108
-     * @throws InvalidInterfaceException
1109
-     * @throws InvalidDataTypeException
1110
-     * @throws EE_Error
1111
-     */
1112
-    public function previous($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1113
-    {
1114
-        $model = $this->get_model();
1115
-        $field = empty($field_to_order_by) && $model->has_primary_key_field()
1116
-            ? $model->get_primary_key_field()->get_name()
1117
-            : $field_to_order_by;
1118
-        $current_value = ! empty($field) ? $this->get($field) : null;
1119
-        if (empty($field) || empty($current_value)) {
1120
-            return array();
1121
-        }
1122
-        return $model->previous($current_value, $field, $query_params, $columns_to_select);
1123
-    }
1124
-
1125
-
1126
-    /**
1127
-     * Overrides parent because parent expects old models.
1128
-     * This also doesn't do any validation, and won't work for serialized arrays
1129
-     *
1130
-     * @param string $field_name
1131
-     * @param mixed  $field_value_from_db
1132
-     * @throws ReflectionException
1133
-     * @throws InvalidArgumentException
1134
-     * @throws InvalidInterfaceException
1135
-     * @throws InvalidDataTypeException
1136
-     * @throws EE_Error
1137
-     */
1138
-    public function set_from_db($field_name, $field_value_from_db)
1139
-    {
1140
-        $field_obj = $this->get_model()->field_settings_for($field_name);
1141
-        if ($field_obj instanceof EE_Model_Field_Base) {
1142
-            // you would think the DB has no NULLs for non-null label fields right? wrong!
1143
-            // eg, a CPT model object could have an entry in the posts table, but no
1144
-            // entry in the meta table. Meaning that all its columns in the meta table
1145
-            // are null! yikes! so when we find one like that, use defaults for its meta columns
1146
-            if ($field_value_from_db === null) {
1147
-                if ($field_obj->is_nullable()) {
1148
-                    // if the field allows nulls, then let it be null
1149
-                    $field_value = null;
1150
-                } else {
1151
-                    $field_value = $field_obj->get_default_value();
1152
-                }
1153
-            } else {
1154
-                $field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
1155
-            }
1156
-            $this->_fields[ $field_name ] = $field_value;
1157
-            $this->_clear_cached_property($field_name);
1158
-        }
1159
-    }
1160
-
1161
-
1162
-    /**
1163
-     * verifies that the specified field is of the correct type
1164
-     *
1165
-     * @param string $field_name
1166
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1167
-     *                                (in cases where the same property may be used for different outputs
1168
-     *                                - i.e. datetime, money etc.)
1169
-     * @return mixed
1170
-     * @throws ReflectionException
1171
-     * @throws InvalidArgumentException
1172
-     * @throws InvalidInterfaceException
1173
-     * @throws InvalidDataTypeException
1174
-     * @throws EE_Error
1175
-     */
1176
-    public function get($field_name, $extra_cache_ref = null)
1177
-    {
1178
-        return $this->_get_cached_property($field_name, false, $extra_cache_ref);
1179
-    }
1180
-
1181
-
1182
-    /**
1183
-     * This method simply returns the RAW unprocessed value for the given property in this class
1184
-     *
1185
-     * @param  string $field_name A valid fieldname
1186
-     * @return mixed              Whatever the raw value stored on the property is.
1187
-     * @throws ReflectionException
1188
-     * @throws InvalidArgumentException
1189
-     * @throws InvalidInterfaceException
1190
-     * @throws InvalidDataTypeException
1191
-     * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
1192
-     */
1193
-    public function get_raw($field_name)
1194
-    {
1195
-        $field_settings = $this->get_model()->field_settings_for($field_name);
1196
-        return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
1197
-            ? $this->_fields[ $field_name ]->format('U')
1198
-            : $this->_fields[ $field_name ];
1199
-    }
1200
-
1201
-
1202
-    /**
1203
-     * This is used to return the internal DateTime object used for a field that is a
1204
-     * EE_Datetime_Field.
1205
-     *
1206
-     * @param string $field_name               The field name retrieving the DateTime object.
1207
-     * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1208
-     * @throws EE_Error an error is set and false returned.  If the field IS an
1209
-     *                                         EE_Datetime_Field and but the field value is null, then
1210
-     *                                         just null is returned (because that indicates that likely
1211
-     *                                         this field is nullable).
1212
-     * @throws InvalidArgumentException
1213
-     * @throws InvalidDataTypeException
1214
-     * @throws InvalidInterfaceException
1215
-     * @throws ReflectionException
1216
-     */
1217
-    public function get_DateTime_object($field_name)
1218
-    {
1219
-        $field_settings = $this->get_model()->field_settings_for($field_name);
1220
-        if (! $field_settings instanceof EE_Datetime_Field) {
1221
-            EE_Error::add_error(
1222
-                sprintf(
1223
-                    esc_html__(
1224
-                        'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1225
-                        'event_espresso'
1226
-                    ),
1227
-                    $field_name
1228
-                ),
1229
-                __FILE__,
1230
-                __FUNCTION__,
1231
-                __LINE__
1232
-            );
1233
-            return false;
1234
-        }
1235
-        return isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime
1236
-            ? clone $this->_fields[ $field_name ]
1237
-            : null;
1238
-    }
1239
-
1240
-
1241
-    /**
1242
-     * To be used in template to immediately echo out the value, and format it for output.
1243
-     * Eg, should call stripslashes and whatnot before echoing
1244
-     *
1245
-     * @param string $field_name      the name of the field as it appears in the DB
1246
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1247
-     *                                (in cases where the same property may be used for different outputs
1248
-     *                                - i.e. datetime, money etc.)
1249
-     * @return void
1250
-     * @throws ReflectionException
1251
-     * @throws InvalidArgumentException
1252
-     * @throws InvalidInterfaceException
1253
-     * @throws InvalidDataTypeException
1254
-     * @throws EE_Error
1255
-     */
1256
-    public function e($field_name, $extra_cache_ref = null)
1257
-    {
1258
-        echo wp_kses($this->get_pretty($field_name, $extra_cache_ref), AllowedTags::getWithFormTags());
1259
-    }
1260
-
1261
-
1262
-    /**
1263
-     * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1264
-     * can be easily used as the value of form input.
1265
-     *
1266
-     * @param string $field_name
1267
-     * @return void
1268
-     * @throws ReflectionException
1269
-     * @throws InvalidArgumentException
1270
-     * @throws InvalidInterfaceException
1271
-     * @throws InvalidDataTypeException
1272
-     * @throws EE_Error
1273
-     */
1274
-    public function f($field_name)
1275
-    {
1276
-        $this->e($field_name, 'form_input');
1277
-    }
1278
-
1279
-
1280
-    /**
1281
-     * Same as `f()` but just returns the value instead of echoing it
1282
-     *
1283
-     * @param string $field_name
1284
-     * @return string
1285
-     * @throws ReflectionException
1286
-     * @throws InvalidArgumentException
1287
-     * @throws InvalidInterfaceException
1288
-     * @throws InvalidDataTypeException
1289
-     * @throws EE_Error
1290
-     */
1291
-    public function get_f($field_name)
1292
-    {
1293
-        return (string) $this->get_pretty($field_name, 'form_input');
1294
-    }
1295
-
1296
-
1297
-    /**
1298
-     * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1299
-     * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1300
-     * to see what options are available.
1301
-     *
1302
-     * @param string $field_name
1303
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1304
-     *                                (in cases where the same property may be used for different outputs
1305
-     *                                - i.e. datetime, money etc.)
1306
-     * @return mixed
1307
-     * @throws ReflectionException
1308
-     * @throws InvalidArgumentException
1309
-     * @throws InvalidInterfaceException
1310
-     * @throws InvalidDataTypeException
1311
-     * @throws EE_Error
1312
-     */
1313
-    public function get_pretty($field_name, $extra_cache_ref = null)
1314
-    {
1315
-        return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1316
-    }
1317
-
1318
-
1319
-    /**
1320
-     * This simply returns the datetime for the given field name
1321
-     * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1322
-     * (and the equivalent e_date, e_time, e_datetime).
1323
-     *
1324
-     * @access   protected
1325
-     * @param string   $field_name   Field on the instantiated EE_Base_Class child object
1326
-     * @param string   $dt_frmt      valid datetime format used for date
1327
-     *                               (if '' then we just use the default on the field,
1328
-     *                               if NULL we use the last-used format)
1329
-     * @param string   $tm_frmt      Same as above except this is for time format
1330
-     * @param string   $date_or_time if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1331
-     * @param  boolean $echo         Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1332
-     * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1333
-     *                               if field is not a valid dtt field, or void if echoing
1334
-     * @throws ReflectionException
1335
-     * @throws InvalidArgumentException
1336
-     * @throws InvalidInterfaceException
1337
-     * @throws InvalidDataTypeException
1338
-     * @throws EE_Error
1339
-     */
1340
-    protected function _get_datetime($field_name, $dt_frmt = '', $tm_frmt = '', $date_or_time = '', $echo = false)
1341
-    {
1342
-        // clear cached property
1343
-        $this->_clear_cached_property($field_name);
1344
-        // reset format properties because they are used in get()
1345
-        $this->_dt_frmt = $dt_frmt !== '' ? $dt_frmt : $this->_dt_frmt;
1346
-        $this->_tm_frmt = $tm_frmt !== '' ? $tm_frmt : $this->_tm_frmt;
1347
-        if ($echo) {
1348
-            $this->e($field_name, $date_or_time);
1349
-            return '';
1350
-        }
1351
-        return $this->get($field_name, $date_or_time);
1352
-    }
1353
-
1354
-
1355
-    /**
1356
-     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1357
-     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1358
-     * other echoes the pretty value for dtt)
1359
-     *
1360
-     * @param  string $field_name name of model object datetime field holding the value
1361
-     * @param  string $format     format for the date returned (if NULL we use default in dt_frmt property)
1362
-     * @return string            datetime value formatted
1363
-     * @throws ReflectionException
1364
-     * @throws InvalidArgumentException
1365
-     * @throws InvalidInterfaceException
1366
-     * @throws InvalidDataTypeException
1367
-     * @throws EE_Error
1368
-     */
1369
-    public function get_date($field_name, $format = '')
1370
-    {
1371
-        return $this->_get_datetime($field_name, $format, null, 'D');
1372
-    }
1373
-
1374
-
1375
-    /**
1376
-     * @param        $field_name
1377
-     * @param string $format
1378
-     * @throws ReflectionException
1379
-     * @throws InvalidArgumentException
1380
-     * @throws InvalidInterfaceException
1381
-     * @throws InvalidDataTypeException
1382
-     * @throws EE_Error
1383
-     */
1384
-    public function e_date($field_name, $format = '')
1385
-    {
1386
-        $this->_get_datetime($field_name, $format, null, 'D', true);
1387
-    }
1388
-
1389
-
1390
-    /**
1391
-     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1392
-     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1393
-     * other echoes the pretty value for dtt)
1394
-     *
1395
-     * @param  string $field_name name of model object datetime field holding the value
1396
-     * @param  string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1397
-     * @return string             datetime value formatted
1398
-     * @throws ReflectionException
1399
-     * @throws InvalidArgumentException
1400
-     * @throws InvalidInterfaceException
1401
-     * @throws InvalidDataTypeException
1402
-     * @throws EE_Error
1403
-     */
1404
-    public function get_time($field_name, $format = '')
1405
-    {
1406
-        return $this->_get_datetime($field_name, null, $format, 'T');
1407
-    }
1408
-
1409
-
1410
-    /**
1411
-     * @param        $field_name
1412
-     * @param string $format
1413
-     * @throws ReflectionException
1414
-     * @throws InvalidArgumentException
1415
-     * @throws InvalidInterfaceException
1416
-     * @throws InvalidDataTypeException
1417
-     * @throws EE_Error
1418
-     */
1419
-    public function e_time($field_name, $format = '')
1420
-    {
1421
-        $this->_get_datetime($field_name, null, $format, 'T', true);
1422
-    }
1423
-
1424
-
1425
-    /**
1426
-     * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1427
-     * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1428
-     * other echoes the pretty value for dtt)
1429
-     *
1430
-     * @param  string $field_name name of model object datetime field holding the value
1431
-     * @param  string $dt_frmt    format for the date returned (if NULL we use default in dt_frmt property)
1432
-     * @param  string $tm_frmt    format for the time returned (if NULL we use default in tm_frmt property)
1433
-     * @return string             datetime value formatted
1434
-     * @throws ReflectionException
1435
-     * @throws InvalidArgumentException
1436
-     * @throws InvalidInterfaceException
1437
-     * @throws InvalidDataTypeException
1438
-     * @throws EE_Error
1439
-     */
1440
-    public function get_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1441
-    {
1442
-        return $this->_get_datetime($field_name, $dt_frmt, $tm_frmt);
1443
-    }
1444
-
1445
-
1446
-    /**
1447
-     * @param string $field_name
1448
-     * @param string $dt_frmt
1449
-     * @param string $tm_frmt
1450
-     * @throws ReflectionException
1451
-     * @throws InvalidArgumentException
1452
-     * @throws InvalidInterfaceException
1453
-     * @throws InvalidDataTypeException
1454
-     * @throws EE_Error
1455
-     */
1456
-    public function e_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1457
-    {
1458
-        $this->_get_datetime($field_name, $dt_frmt, $tm_frmt, null, true);
1459
-    }
1460
-
1461
-
1462
-    /**
1463
-     * Get the i8ln value for a date using the WordPress @see date_i18n function.
1464
-     *
1465
-     * @param string $field_name The EE_Datetime_Field reference for the date being retrieved.
1466
-     * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format
1467
-     *                           on the object will be used.
1468
-     * @return string Date and time string in set locale or false if no field exists for the given
1469
-     * @throws ReflectionException
1470
-     * @throws InvalidArgumentException
1471
-     * @throws InvalidInterfaceException
1472
-     * @throws InvalidDataTypeException
1473
-     * @throws EE_Error
1474
-     *                           field name.
1475
-     */
1476
-    public function get_i18n_datetime($field_name, $format = '')
1477
-    {
1478
-        $format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1479
-        return date_i18n(
1480
-            $format,
1481
-            EEH_DTT_Helper::get_timestamp_with_offset(
1482
-                $this->get_raw($field_name),
1483
-                $this->_timezone
1484
-            )
1485
-        );
1486
-    }
1487
-
1488
-
1489
-    /**
1490
-     * This method validates whether the given field name is a valid field on the model object as well as it is of a
1491
-     * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
1492
-     * thrown.
1493
-     *
1494
-     * @param  string $field_name The field name being checked
1495
-     * @throws ReflectionException
1496
-     * @throws InvalidArgumentException
1497
-     * @throws InvalidInterfaceException
1498
-     * @throws InvalidDataTypeException
1499
-     * @throws EE_Error
1500
-     * @return EE_Datetime_Field
1501
-     */
1502
-    protected function _get_dtt_field_settings($field_name)
1503
-    {
1504
-        $field = $this->get_model()->field_settings_for($field_name);
1505
-        // check if field is dtt
1506
-        if ($field instanceof EE_Datetime_Field) {
1507
-            return $field;
1508
-        }
1509
-        throw new EE_Error(
1510
-            sprintf(
1511
-                esc_html__(
1512
-                    'The field name "%s" has been requested for the EE_Base_Class datetime functions and it is not a valid EE_Datetime_Field.  Please check the spelling of the field and make sure it has been setup as a EE_Datetime_Field in the %s model constructor',
1513
-                    'event_espresso'
1514
-                ),
1515
-                $field_name,
1516
-                self::_get_model_classname(get_class($this))
1517
-            )
1518
-        );
1519
-    }
1520
-
1521
-
1522
-
1523
-
1524
-    /**
1525
-     * NOTE ABOUT BELOW:
1526
-     * These convenience date and time setters are for setting date and time independently.  In other words you might
1527
-     * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1528
-     * you want to set both date and time at the same time, you can just use the models default set($fieldname,$value)
1529
-     * method and make sure you send the entire datetime value for setting.
1530
-     */
1531
-    /**
1532
-     * sets the time on a datetime property
1533
-     *
1534
-     * @access protected
1535
-     * @param string|Datetime $time      a valid time string for php datetime functions (or DateTime object)
1536
-     * @param string          $fieldname the name of the field the time is being set on (must match a EE_Datetime_Field)
1537
-     * @throws ReflectionException
1538
-     * @throws InvalidArgumentException
1539
-     * @throws InvalidInterfaceException
1540
-     * @throws InvalidDataTypeException
1541
-     * @throws EE_Error
1542
-     */
1543
-    protected function _set_time_for($time, $fieldname)
1544
-    {
1545
-        $this->_set_date_time('T', $time, $fieldname);
1546
-    }
1547
-
1548
-
1549
-    /**
1550
-     * sets the date on a datetime property
1551
-     *
1552
-     * @access protected
1553
-     * @param string|DateTime $date      a valid date string for php datetime functions ( or DateTime object)
1554
-     * @param string          $fieldname the name of the field the date is being set on (must match a EE_Datetime_Field)
1555
-     * @throws ReflectionException
1556
-     * @throws InvalidArgumentException
1557
-     * @throws InvalidInterfaceException
1558
-     * @throws InvalidDataTypeException
1559
-     * @throws EE_Error
1560
-     */
1561
-    protected function _set_date_for($date, $fieldname)
1562
-    {
1563
-        $this->_set_date_time('D', $date, $fieldname);
1564
-    }
1565
-
1566
-
1567
-    /**
1568
-     * This takes care of setting a date or time independently on a given model object property. This method also
1569
-     * verifies that the given fieldname matches a model object property and is for a EE_Datetime_Field field
1570
-     *
1571
-     * @access protected
1572
-     * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
1573
-     * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
1574
-     * @param string          $fieldname      the name of the field the date OR time is being set on (must match a
1575
-     *                                        EE_Datetime_Field property)
1576
-     * @throws ReflectionException
1577
-     * @throws InvalidArgumentException
1578
-     * @throws InvalidInterfaceException
1579
-     * @throws InvalidDataTypeException
1580
-     * @throws EE_Error
1581
-     */
1582
-    protected function _set_date_time($what = 'T', $datetime_value, $fieldname)
1583
-    {
1584
-        $field = $this->_get_dtt_field_settings($fieldname);
1585
-        $field->set_timezone($this->_timezone);
1586
-        $field->set_date_format($this->_dt_frmt);
1587
-        $field->set_time_format($this->_tm_frmt);
1588
-        switch ($what) {
1589
-            case 'T':
1590
-                $this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_time(
1591
-                    $datetime_value,
1592
-                    $this->_fields[ $fieldname ]
1593
-                );
1594
-                $this->_has_changes = true;
1595
-                break;
1596
-            case 'D':
1597
-                $this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_date(
1598
-                    $datetime_value,
1599
-                    $this->_fields[ $fieldname ]
1600
-                );
1601
-                $this->_has_changes = true;
1602
-                break;
1603
-            case 'B':
1604
-                $this->_fields[ $fieldname ] = $field->prepare_for_set($datetime_value);
1605
-                $this->_has_changes = true;
1606
-                break;
1607
-        }
1608
-        $this->_clear_cached_property($fieldname);
1609
-    }
1610
-
1611
-
1612
-    /**
1613
-     * This will return a timestamp for the website timezone but ONLY when the current website timezone is different
1614
-     * than the timezone set for the website. NOTE, this currently only works well with methods that return values.  If
1615
-     * you use it with methods that echo values the $_timestamp property may not get reset to its original value and
1616
-     * that could lead to some unexpected results!
1617
-     *
1618
-     * @access public
1619
-     * @param string $field_name               This is the name of the field on the object that contains the date/time
1620
-     *                                         value being returned.
1621
-     * @param string $callback                 must match a valid method in this class (defaults to get_datetime)
1622
-     * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
1623
-     * @param string $prepend                  You can include something to prepend on the timestamp
1624
-     * @param string $append                   You can include something to append on the timestamp
1625
-     * @throws ReflectionException
1626
-     * @throws InvalidArgumentException
1627
-     * @throws InvalidInterfaceException
1628
-     * @throws InvalidDataTypeException
1629
-     * @throws EE_Error
1630
-     * @return string timestamp
1631
-     */
1632
-    public function display_in_my_timezone(
1633
-        $field_name,
1634
-        $callback = 'get_datetime',
1635
-        $args = null,
1636
-        $prepend = '',
1637
-        $append = ''
1638
-    ) {
1639
-        $timezone = EEH_DTT_Helper::get_timezone();
1640
-        if ($timezone === $this->_timezone) {
1641
-            return '';
1642
-        }
1643
-        $original_timezone = $this->_timezone;
1644
-        $this->set_timezone($timezone);
1645
-        $fn = (array) $field_name;
1646
-        $args = array_merge($fn, (array) $args);
1647
-        if (! method_exists($this, $callback)) {
1648
-            throw new EE_Error(
1649
-                sprintf(
1650
-                    esc_html__(
1651
-                        'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
1652
-                        'event_espresso'
1653
-                    ),
1654
-                    $callback
1655
-                )
1656
-            );
1657
-        }
1658
-        $args = (array) $args;
1659
-        $return = $prepend . call_user_func_array(array($this, $callback), $args) . $append;
1660
-        $this->set_timezone($original_timezone);
1661
-        return $return;
1662
-    }
1663
-
1664
-
1665
-    /**
1666
-     * Deletes this model object.
1667
-     * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
1668
-     * override
1669
-     * `EE_Base_Class::_delete` NOT this class.
1670
-     *
1671
-     * @return boolean | int
1672
-     * @throws ReflectionException
1673
-     * @throws InvalidArgumentException
1674
-     * @throws InvalidInterfaceException
1675
-     * @throws InvalidDataTypeException
1676
-     * @throws EE_Error
1677
-     */
1678
-    public function delete()
1679
-    {
1680
-        /**
1681
-         * Called just before the `EE_Base_Class::_delete` method call.
1682
-         * Note:
1683
-         * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1684
-         * should be aware that `_delete` may not always result in a permanent delete.
1685
-         * For example, `EE_Soft_Delete_Base_Class::_delete`
1686
-         * soft deletes (trash) the object and does not permanently delete it.
1687
-         *
1688
-         * @param EE_Base_Class $model_object about to be 'deleted'
1689
-         */
1690
-        do_action('AHEE__EE_Base_Class__delete__before', $this);
1691
-        $result = $this->_delete();
1692
-        /**
1693
-         * Called just after the `EE_Base_Class::_delete` method call.
1694
-         * Note:
1695
-         * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1696
-         * should be aware that `_delete` may not always result in a permanent delete.
1697
-         * For example `EE_Soft_Base_Class::_delete`
1698
-         * soft deletes (trash) the object and does not permanently delete it.
1699
-         *
1700
-         * @param EE_Base_Class $model_object that was just 'deleted'
1701
-         * @param boolean       $result
1702
-         */
1703
-        do_action('AHEE__EE_Base_Class__delete__end', $this, $result);
1704
-        return $result;
1705
-    }
1706
-
1707
-
1708
-    /**
1709
-     * Calls the specific delete method for the instantiated class.
1710
-     * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
1711
-     * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
1712
-     * `EE_Base_Class::delete`
1713
-     *
1714
-     * @return bool|int
1715
-     * @throws ReflectionException
1716
-     * @throws InvalidArgumentException
1717
-     * @throws InvalidInterfaceException
1718
-     * @throws InvalidDataTypeException
1719
-     * @throws EE_Error
1720
-     */
1721
-    protected function _delete()
1722
-    {
1723
-        return $this->delete_permanently();
1724
-    }
1725
-
1726
-
1727
-    /**
1728
-     * Deletes this model object permanently from db
1729
-     * (but keep in mind related models may block the delete and return an error)
1730
-     *
1731
-     * @return bool | int
1732
-     * @throws ReflectionException
1733
-     * @throws InvalidArgumentException
1734
-     * @throws InvalidInterfaceException
1735
-     * @throws InvalidDataTypeException
1736
-     * @throws EE_Error
1737
-     */
1738
-    public function delete_permanently()
1739
-    {
1740
-        /**
1741
-         * Called just before HARD deleting a model object
1742
-         *
1743
-         * @param EE_Base_Class $model_object about to be 'deleted'
1744
-         */
1745
-        do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
1746
-        $model = $this->get_model();
1747
-        $result = $model->delete_permanently_by_ID($this->ID());
1748
-        $this->refresh_cache_of_related_objects();
1749
-        /**
1750
-         * Called just after HARD deleting a model object
1751
-         *
1752
-         * @param EE_Base_Class $model_object that was just 'deleted'
1753
-         * @param boolean       $result
1754
-         */
1755
-        do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
1756
-        return $result;
1757
-    }
1758
-
1759
-
1760
-    /**
1761
-     * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
1762
-     * related model objects
1763
-     *
1764
-     * @throws ReflectionException
1765
-     * @throws InvalidArgumentException
1766
-     * @throws InvalidInterfaceException
1767
-     * @throws InvalidDataTypeException
1768
-     * @throws EE_Error
1769
-     */
1770
-    public function refresh_cache_of_related_objects()
1771
-    {
1772
-        $model = $this->get_model();
1773
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1774
-            if (! empty($this->_model_relations[ $relation_name ])) {
1775
-                $related_objects = $this->_model_relations[ $relation_name ];
1776
-                if ($relation_obj instanceof EE_Belongs_To_Relation) {
1777
-                    // this relation only stores a single model object, not an array
1778
-                    // but let's make it consistent
1779
-                    $related_objects = array($related_objects);
1780
-                }
1781
-                foreach ($related_objects as $related_object) {
1782
-                    // only refresh their cache if they're in memory
1783
-                    if ($related_object instanceof EE_Base_Class) {
1784
-                        $related_object->clear_cache(
1785
-                            $model->get_this_model_name(),
1786
-                            $this
1787
-                        );
1788
-                    }
1789
-                }
1790
-            }
1791
-        }
1792
-    }
1793
-
1794
-
1795
-    /**
1796
-     *        Saves this object to the database. An array may be supplied to set some values on this
1797
-     * object just before saving.
1798
-     *
1799
-     * @access public
1800
-     * @param array $set_cols_n_values  keys are field names, values are their new values,
1801
-     *                                  if provided during the save() method
1802
-     *                                  (often client code will change the fields' values before calling save)
1803
-     * @return false|int|string         1 on a successful update;
1804
-     *                                  the ID of the new entry on insert;
1805
-     *                                  0 on failure, or if the model object isn't allowed to persist
1806
-     *                                  (as determined by EE_Base_Class::allow_persist())
1807
-     * @throws InvalidInterfaceException
1808
-     * @throws InvalidDataTypeException
1809
-     * @throws EE_Error
1810
-     * @throws InvalidArgumentException
1811
-     * @throws ReflectionException
1812
-     * @throws ReflectionException
1813
-     * @throws ReflectionException
1814
-     */
1815
-    public function save($set_cols_n_values = array())
1816
-    {
1817
-        $model = $this->get_model();
1818
-        /**
1819
-         * Filters the fields we're about to save on the model object
1820
-         *
1821
-         * @param array         $set_cols_n_values
1822
-         * @param EE_Base_Class $model_object
1823
-         */
1824
-        $set_cols_n_values = (array) apply_filters(
1825
-            'FHEE__EE_Base_Class__save__set_cols_n_values',
1826
-            $set_cols_n_values,
1827
-            $this
1828
-        );
1829
-        // set attributes as provided in $set_cols_n_values
1830
-        foreach ($set_cols_n_values as $column => $value) {
1831
-            $this->set($column, $value);
1832
-        }
1833
-        // no changes ? then don't do anything
1834
-        if (! $this->_has_changes && $this->ID() && $model->get_primary_key_field()->is_auto_increment()) {
1835
-            return 0;
1836
-        }
1837
-        /**
1838
-         * Saving a model object.
1839
-         * Before we perform a save, this action is fired.
1840
-         *
1841
-         * @param EE_Base_Class $model_object the model object about to be saved.
1842
-         */
1843
-        do_action('AHEE__EE_Base_Class__save__begin', $this);
1844
-        if (! $this->allow_persist()) {
1845
-            return 0;
1846
-        }
1847
-        // now get current attribute values
1848
-        $save_cols_n_values = $this->_fields;
1849
-        // if the object already has an ID, update it. Otherwise, insert it
1850
-        // also: change the assumption about values passed to the model NOT being prepare dby the model object.
1851
-        // They have been
1852
-        $old_assumption_concerning_value_preparation = $model
1853
-            ->get_assumption_concerning_values_already_prepared_by_model_object();
1854
-        $model->assume_values_already_prepared_by_model_object(true);
1855
-        // does this model have an autoincrement PK?
1856
-        if ($model->has_primary_key_field()) {
1857
-            if ($model->get_primary_key_field()->is_auto_increment()) {
1858
-                // ok check if it's set, if so: update; if not, insert
1859
-                if (! empty($save_cols_n_values[ $model->primary_key_name() ])) {
1860
-                    $results = $model->update_by_ID($save_cols_n_values, $this->ID());
1861
-                } else {
1862
-                    unset($save_cols_n_values[ $model->primary_key_name() ]);
1863
-                    $results = $model->insert($save_cols_n_values);
1864
-                    if ($results) {
1865
-                        // if successful, set the primary key
1866
-                        // but don't use the normal SET method, because it will check if
1867
-                        // an item with the same ID exists in the mapper & db, then
1868
-                        // will find it in the db (because we just added it) and THAT object
1869
-                        // will get added to the mapper before we can add this one!
1870
-                        // but if we just avoid using the SET method, all that headache can be avoided
1871
-                        $pk_field_name = $model->primary_key_name();
1872
-                        $this->_fields[ $pk_field_name ] = $results;
1873
-                        $this->_clear_cached_property($pk_field_name);
1874
-                        $model->add_to_entity_map($this);
1875
-                        $this->_update_cached_related_model_objs_fks();
1876
-                    }
1877
-                }
1878
-            } else {// PK is NOT auto-increment
1879
-                // so check if one like it already exists in the db
1880
-                if ($model->exists_by_ID($this->ID())) {
1881
-                    if (WP_DEBUG && ! $this->in_entity_map()) {
1882
-                        throw new EE_Error(
1883
-                            sprintf(
1884
-                                esc_html__(
1885
-                                    'Using a model object %1$s that is NOT in the entity map, can lead to unexpected errors. You should either: %4$s 1. Put it in the entity mapper by calling %2$s %4$s 2. Discard this model object and use what is in the entity mapper %4$s 3. Fetch from the database using %3$s',
1886
-                                    'event_espresso'
1887
-                                ),
1888
-                                get_class($this),
1889
-                                get_class($model) . '::instance()->add_to_entity_map()',
1890
-                                get_class($model) . '::instance()->get_one_by_ID()',
1891
-                                '<br />'
1892
-                            )
1893
-                        );
1894
-                    }
1895
-                    $results = $model->update_by_ID($save_cols_n_values, $this->ID());
1896
-                } else {
1897
-                    $results = $model->insert($save_cols_n_values);
1898
-                    $this->_update_cached_related_model_objs_fks();
1899
-                }
1900
-            }
1901
-        } else {// there is NO primary key
1902
-            $already_in_db = false;
1903
-            foreach ($model->unique_indexes() as $index) {
1904
-                $uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
1905
-                if ($model->exists(array($uniqueness_where_params))) {
1906
-                    $already_in_db = true;
1907
-                }
1908
-            }
1909
-            if ($already_in_db) {
1910
-                $combined_pk_fields_n_values = array_intersect_key(
1911
-                    $save_cols_n_values,
1912
-                    $model->get_combined_primary_key_fields()
1913
-                );
1914
-                $results = $model->update(
1915
-                    $save_cols_n_values,
1916
-                    $combined_pk_fields_n_values
1917
-                );
1918
-            } else {
1919
-                $results = $model->insert($save_cols_n_values);
1920
-            }
1921
-        }
1922
-        // restore the old assumption about values being prepared by the model object
1923
-        $model->assume_values_already_prepared_by_model_object(
1924
-            $old_assumption_concerning_value_preparation
1925
-        );
1926
-        /**
1927
-         * After saving the model object this action is called
1928
-         *
1929
-         * @param EE_Base_Class $model_object which was just saved
1930
-         * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
1931
-         *                                    the new ID (or 0 if an error occurred and it wasn't updated)
1932
-         */
1933
-        do_action('AHEE__EE_Base_Class__save__end', $this, $results);
1934
-        $this->_has_changes = false;
1935
-        return $results;
1936
-    }
1937
-
1938
-
1939
-    /**
1940
-     * Updates the foreign key on related models objects pointing to this to have this model object's ID
1941
-     * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1942
-     * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1943
-     * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1944
-     * saved it to the db. We also create a registration and don't save it to the DB, but we DO cache it on the
1945
-     * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1946
-     * or not they exist in the DB (if they do, their DB records will be automatically updated)
1947
-     *
1948
-     * @return void
1949
-     * @throws ReflectionException
1950
-     * @throws InvalidArgumentException
1951
-     * @throws InvalidInterfaceException
1952
-     * @throws InvalidDataTypeException
1953
-     * @throws EE_Error
1954
-     */
1955
-    protected function _update_cached_related_model_objs_fks()
1956
-    {
1957
-        $model = $this->get_model();
1958
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1959
-            if ($relation_obj instanceof EE_Has_Many_Relation) {
1960
-                foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1961
-                    $fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1962
-                        $model->get_this_model_name()
1963
-                    );
1964
-                    $related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1965
-                    if ($related_model_obj_in_cache->ID()) {
1966
-                        $related_model_obj_in_cache->save();
1967
-                    }
1968
-                }
1969
-            }
1970
-        }
1971
-    }
1972
-
1973
-
1974
-    /**
1975
-     * Saves this model object and its NEW cached relations to the database.
1976
-     * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
1977
-     * In order for that to work, we would need to mark model objects as dirty/clean...
1978
-     * because otherwise, there's a potential for infinite looping of saving
1979
-     * Saves the cached related model objects, and ensures the relation between them
1980
-     * and this object and properly setup
1981
-     *
1982
-     * @return int ID of new model object on save; 0 on failure+
1983
-     * @throws ReflectionException
1984
-     * @throws InvalidArgumentException
1985
-     * @throws InvalidInterfaceException
1986
-     * @throws InvalidDataTypeException
1987
-     * @throws EE_Error
1988
-     */
1989
-    public function save_new_cached_related_model_objs()
1990
-    {
1991
-        // make sure this has been saved
1992
-        if (! $this->ID()) {
1993
-            $id = $this->save();
1994
-        } else {
1995
-            $id = $this->ID();
1996
-        }
1997
-        // now save all the NEW cached model objects  (ie they don't exist in the DB)
1998
-        foreach ($this->get_model()->relation_settings() as $relationName => $relationObj) {
1999
-            if ($this->_model_relations[ $relationName ]) {
2000
-                // is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
2001
-                // or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
2002
-                /* @var $related_model_obj EE_Base_Class */
2003
-                if ($relationObj instanceof EE_Belongs_To_Relation) {
2004
-                    // add a relation to that relation type (which saves the appropriate thing in the process)
2005
-                    // but ONLY if it DOES NOT exist in the DB
2006
-                    $related_model_obj = $this->_model_relations[ $relationName ];
2007
-                    // if( ! $related_model_obj->ID()){
2008
-                    $this->_add_relation_to($related_model_obj, $relationName);
2009
-                    $related_model_obj->save_new_cached_related_model_objs();
2010
-                    // }
2011
-                } else {
2012
-                    foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
2013
-                        // add a relation to that relation type (which saves the appropriate thing in the process)
2014
-                        // but ONLY if it DOES NOT exist in the DB
2015
-                        // if( ! $related_model_obj->ID()){
2016
-                        $this->_add_relation_to($related_model_obj, $relationName);
2017
-                        $related_model_obj->save_new_cached_related_model_objs();
2018
-                        // }
2019
-                    }
2020
-                }
2021
-            }
2022
-        }
2023
-        return $id;
2024
-    }
2025
-
2026
-
2027
-    /**
2028
-     * for getting a model while instantiated.
2029
-     *
2030
-     * @return EEM_Base | EEM_CPT_Base
2031
-     * @throws ReflectionException
2032
-     * @throws InvalidArgumentException
2033
-     * @throws InvalidInterfaceException
2034
-     * @throws InvalidDataTypeException
2035
-     * @throws EE_Error
2036
-     */
2037
-    public function get_model()
2038
-    {
2039
-        if (! $this->_model) {
2040
-            $modelName = self::_get_model_classname(get_class($this));
2041
-            $this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
2042
-        } else {
2043
-            $this->_model->set_timezone($this->_timezone);
2044
-        }
2045
-        return $this->_model;
2046
-    }
2047
-
2048
-
2049
-    /**
2050
-     * @param $props_n_values
2051
-     * @param $classname
2052
-     * @return mixed bool|EE_Base_Class|EEM_CPT_Base
2053
-     * @throws ReflectionException
2054
-     * @throws InvalidArgumentException
2055
-     * @throws InvalidInterfaceException
2056
-     * @throws InvalidDataTypeException
2057
-     * @throws EE_Error
2058
-     */
2059
-    protected static function _get_object_from_entity_mapper($props_n_values, $classname)
2060
-    {
2061
-        // TODO: will not work for Term_Relationships because they have no PK!
2062
-        $primary_id_ref = self::_get_primary_key_name($classname);
2063
-        if (
2064
-            array_key_exists($primary_id_ref, $props_n_values)
2065
-            && ! empty($props_n_values[ $primary_id_ref ])
2066
-        ) {
2067
-            $id = $props_n_values[ $primary_id_ref ];
2068
-            return self::_get_model($classname)->get_from_entity_map($id);
2069
-        }
2070
-        return false;
2071
-    }
2072
-
2073
-
2074
-    /**
2075
-     * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
2076
-     * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
2077
-     * primary key for the model AND it is not null, then we check the db. If there's a an object we return it.  If not
2078
-     * we return false.
2079
-     *
2080
-     * @param  array  $props_n_values   incoming array of properties and their values
2081
-     * @param  string $classname        the classname of the child class
2082
-     * @param null    $timezone
2083
-     * @param array   $date_formats     incoming date_formats in an array where the first value is the
2084
-     *                                  date_format and the second value is the time format
2085
-     * @return mixed (EE_Base_Class|bool)
2086
-     * @throws InvalidArgumentException
2087
-     * @throws InvalidInterfaceException
2088
-     * @throws InvalidDataTypeException
2089
-     * @throws EE_Error
2090
-     * @throws ReflectionException
2091
-     * @throws ReflectionException
2092
-     * @throws ReflectionException
2093
-     */
2094
-    protected static function _check_for_object($props_n_values, $classname, $timezone = null, $date_formats = array())
2095
-    {
2096
-        $existing = null;
2097
-        $model = self::_get_model($classname, $timezone);
2098
-        if ($model->has_primary_key_field()) {
2099
-            $primary_id_ref = self::_get_primary_key_name($classname);
2100
-            if (
2101
-                array_key_exists($primary_id_ref, $props_n_values)
2102
-                && ! empty($props_n_values[ $primary_id_ref ])
2103
-            ) {
2104
-                $existing = $model->get_one_by_ID(
2105
-                    $props_n_values[ $primary_id_ref ]
2106
-                );
2107
-            }
2108
-        } elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
2109
-            // no primary key on this model, but there's still a matching item in the DB
2110
-            $existing = self::_get_model($classname, $timezone)->get_one_by_ID(
2111
-                self::_get_model($classname, $timezone)
2112
-                    ->get_index_primary_key_string($props_n_values)
2113
-            );
2114
-        }
2115
-        if ($existing) {
2116
-            // set date formats if present before setting values
2117
-            if (! empty($date_formats) && is_array($date_formats)) {
2118
-                $existing->set_date_format($date_formats[0]);
2119
-                $existing->set_time_format($date_formats[1]);
2120
-            } else {
2121
-                // set default formats for date and time
2122
-                $existing->set_date_format(get_option('date_format'));
2123
-                $existing->set_time_format(get_option('time_format'));
2124
-            }
2125
-            foreach ($props_n_values as $property => $field_value) {
2126
-                $existing->set($property, $field_value);
2127
-            }
2128
-            return $existing;
2129
-        }
2130
-        return false;
2131
-    }
2132
-
2133
-
2134
-    /**
2135
-     * Gets the EEM_*_Model for this class
2136
-     *
2137
-     * @access public now, as this is more convenient
2138
-     * @param      $classname
2139
-     * @param null $timezone
2140
-     * @throws ReflectionException
2141
-     * @throws InvalidArgumentException
2142
-     * @throws InvalidInterfaceException
2143
-     * @throws InvalidDataTypeException
2144
-     * @throws EE_Error
2145
-     * @return EEM_Base
2146
-     */
2147
-    protected static function _get_model($classname, $timezone = null)
2148
-    {
2149
-        // find model for this class
2150
-        if (! $classname) {
2151
-            throw new EE_Error(
2152
-                sprintf(
2153
-                    esc_html__(
2154
-                        'What were you thinking calling _get_model(%s)?? You need to specify the class name',
2155
-                        'event_espresso'
2156
-                    ),
2157
-                    $classname
2158
-                )
2159
-            );
2160
-        }
2161
-        $modelName = self::_get_model_classname($classname);
2162
-        return self::_get_model_instance_with_name($modelName, $timezone);
2163
-    }
2164
-
2165
-
2166
-    /**
2167
-     * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
2168
-     *
2169
-     * @param string $model_classname
2170
-     * @param null   $timezone
2171
-     * @return EEM_Base
2172
-     * @throws ReflectionException
2173
-     * @throws InvalidArgumentException
2174
-     * @throws InvalidInterfaceException
2175
-     * @throws InvalidDataTypeException
2176
-     * @throws EE_Error
2177
-     */
2178
-    protected static function _get_model_instance_with_name($model_classname, $timezone = null)
2179
-    {
2180
-        $model_classname = str_replace('EEM_', '', $model_classname);
2181
-        $model = EE_Registry::instance()->load_model($model_classname);
2182
-        $model->set_timezone($timezone);
2183
-        return $model;
2184
-    }
2185
-
2186
-
2187
-    /**
2188
-     * If a model name is provided (eg Registration), gets the model classname for that model.
2189
-     * Also works if a model class's classname is provided (eg EE_Registration).
2190
-     *
2191
-     * @param null $model_name
2192
-     * @return string like EEM_Attendee
2193
-     */
2194
-    private static function _get_model_classname($model_name = null)
2195
-    {
2196
-        if (strpos($model_name, 'EE_') === 0) {
2197
-            $model_classname = str_replace('EE_', 'EEM_', $model_name);
2198
-        } else {
2199
-            $model_classname = 'EEM_' . $model_name;
2200
-        }
2201
-        return $model_classname;
2202
-    }
2203
-
2204
-
2205
-    /**
2206
-     * returns the name of the primary key attribute
2207
-     *
2208
-     * @param null $classname
2209
-     * @throws ReflectionException
2210
-     * @throws InvalidArgumentException
2211
-     * @throws InvalidInterfaceException
2212
-     * @throws InvalidDataTypeException
2213
-     * @throws EE_Error
2214
-     * @return string
2215
-     */
2216
-    protected static function _get_primary_key_name($classname = null)
2217
-    {
2218
-        if (! $classname) {
2219
-            throw new EE_Error(
2220
-                sprintf(
2221
-                    esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
2222
-                    $classname
2223
-                )
2224
-            );
2225
-        }
2226
-        return self::_get_model($classname)->get_primary_key_field()->get_name();
2227
-    }
2228
-
2229
-
2230
-    /**
2231
-     * Gets the value of the primary key.
2232
-     * If the object hasn't yet been saved, it should be whatever the model field's default was
2233
-     * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
2234
-     * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
2235
-     *
2236
-     * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
2237
-     * @throws ReflectionException
2238
-     * @throws InvalidArgumentException
2239
-     * @throws InvalidInterfaceException
2240
-     * @throws InvalidDataTypeException
2241
-     * @throws EE_Error
2242
-     */
2243
-    public function ID()
2244
-    {
2245
-        $model = $this->get_model();
2246
-        // now that we know the name of the variable, use a variable variable to get its value and return its
2247
-        if ($model->has_primary_key_field()) {
2248
-            return $this->_fields[ $model->primary_key_name() ];
2249
-        }
2250
-        return $model->get_index_primary_key_string($this->_fields);
2251
-    }
2252
-
2253
-
2254
-    /**
2255
-     * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2256
-     * model is related to a group of events, the $relationName should be 'Event', and should be a key in the EE
2257
-     * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2258
-     *
2259
-     * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2260
-     * @param string $relationName                     eg 'Events','Question',etc.
2261
-     *                                                 an attendee to a group, you also want to specify which role they
2262
-     *                                                 will have in that group. So you would use this parameter to
2263
-     *                                                 specify array('role-column-name'=>'role-id')
2264
-     * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2265
-     *                                                 allow you to further constrict the relation to being added.
2266
-     *                                                 However, keep in mind that the columns (keys) given must match a
2267
-     *                                                 column on the JOIN table and currently only the HABTM models
2268
-     *                                                 accept these additional conditions.  Also remember that if an
2269
-     *                                                 exact match isn't found for these extra cols/val pairs, then a
2270
-     *                                                 NEW row is created in the join table.
2271
-     * @param null   $cache_id
2272
-     * @throws ReflectionException
2273
-     * @throws InvalidArgumentException
2274
-     * @throws InvalidInterfaceException
2275
-     * @throws InvalidDataTypeException
2276
-     * @throws EE_Error
2277
-     * @return EE_Base_Class the object the relation was added to
2278
-     */
2279
-    public function _add_relation_to(
2280
-        $otherObjectModelObjectOrID,
2281
-        $relationName,
2282
-        $extra_join_model_fields_n_values = array(),
2283
-        $cache_id = null
2284
-    ) {
2285
-        $model = $this->get_model();
2286
-        // if this thing exists in the DB, save the relation to the DB
2287
-        if ($this->ID()) {
2288
-            $otherObject = $model->add_relationship_to(
2289
-                $this,
2290
-                $otherObjectModelObjectOrID,
2291
-                $relationName,
2292
-                $extra_join_model_fields_n_values
2293
-            );
2294
-            // clear cache so future get_many_related and get_first_related() return new results.
2295
-            $this->clear_cache($relationName, $otherObject, true);
2296
-            if ($otherObject instanceof EE_Base_Class) {
2297
-                $otherObject->clear_cache($model->get_this_model_name(), $this);
2298
-            }
2299
-        } else {
2300
-            // this thing doesn't exist in the DB,  so just cache it
2301
-            if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2302
-                throw new EE_Error(
2303
-                    sprintf(
2304
-                        esc_html__(
2305
-                            'Before a model object is saved to the database, calls to _add_relation_to must be passed an actual object, not just an ID. You provided %s as the model object to a %s',
2306
-                            'event_espresso'
2307
-                        ),
2308
-                        $otherObjectModelObjectOrID,
2309
-                        get_class($this)
2310
-                    )
2311
-                );
2312
-            }
2313
-            $otherObject = $otherObjectModelObjectOrID;
2314
-            $this->cache($relationName, $otherObjectModelObjectOrID, $cache_id);
2315
-        }
2316
-        if ($otherObject instanceof EE_Base_Class) {
2317
-            // fix the reciprocal relation too
2318
-            if ($otherObject->ID()) {
2319
-                // its saved so assumed relations exist in the DB, so we can just
2320
-                // clear the cache so future queries use the updated info in the DB
2321
-                $otherObject->clear_cache(
2322
-                    $model->get_this_model_name(),
2323
-                    null,
2324
-                    true
2325
-                );
2326
-            } else {
2327
-                // it's not saved, so it caches relations like this
2328
-                $otherObject->cache($model->get_this_model_name(), $this);
2329
-            }
2330
-        }
2331
-        return $otherObject;
2332
-    }
2333
-
2334
-
2335
-    /**
2336
-     * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2337
-     * model is related to a group of events, the $relationName should be 'Events', and should be a key in the EE
2338
-     * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2339
-     * from the cache
2340
-     *
2341
-     * @param mixed  $otherObjectModelObjectOrID
2342
-     *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2343
-     *                to the DB yet
2344
-     * @param string $relationName
2345
-     * @param array  $where_query
2346
-     *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2347
-     *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2348
-     *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2349
-     *                remember that if an exact match isn't found for these extra cols/val pairs, then no row is
2350
-     *                deleted.
2351
-     * @return EE_Base_Class the relation was removed from
2352
-     * @throws ReflectionException
2353
-     * @throws InvalidArgumentException
2354
-     * @throws InvalidInterfaceException
2355
-     * @throws InvalidDataTypeException
2356
-     * @throws EE_Error
2357
-     */
2358
-    public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = array())
2359
-    {
2360
-        if ($this->ID()) {
2361
-            // if this exists in the DB, save the relation change to the DB too
2362
-            $otherObject = $this->get_model()->remove_relationship_to(
2363
-                $this,
2364
-                $otherObjectModelObjectOrID,
2365
-                $relationName,
2366
-                $where_query
2367
-            );
2368
-            $this->clear_cache(
2369
-                $relationName,
2370
-                $otherObject
2371
-            );
2372
-        } else {
2373
-            // this doesn't exist in the DB, just remove it from the cache
2374
-            $otherObject = $this->clear_cache(
2375
-                $relationName,
2376
-                $otherObjectModelObjectOrID
2377
-            );
2378
-        }
2379
-        if ($otherObject instanceof EE_Base_Class) {
2380
-            $otherObject->clear_cache(
2381
-                $this->get_model()->get_this_model_name(),
2382
-                $this
2383
-            );
2384
-        }
2385
-        return $otherObject;
2386
-    }
2387
-
2388
-
2389
-    /**
2390
-     * Removes ALL the related things for the $relationName.
2391
-     *
2392
-     * @param string $relationName
2393
-     * @param array  $where_query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2394
-     * @return EE_Base_Class
2395
-     * @throws ReflectionException
2396
-     * @throws InvalidArgumentException
2397
-     * @throws InvalidInterfaceException
2398
-     * @throws InvalidDataTypeException
2399
-     * @throws EE_Error
2400
-     */
2401
-    public function _remove_relations($relationName, $where_query_params = array())
2402
-    {
2403
-        if ($this->ID()) {
2404
-            // if this exists in the DB, save the relation change to the DB too
2405
-            $otherObjects = $this->get_model()->remove_relations(
2406
-                $this,
2407
-                $relationName,
2408
-                $where_query_params
2409
-            );
2410
-            $this->clear_cache(
2411
-                $relationName,
2412
-                null,
2413
-                true
2414
-            );
2415
-        } else {
2416
-            // this doesn't exist in the DB, just remove it from the cache
2417
-            $otherObjects = $this->clear_cache(
2418
-                $relationName,
2419
-                null,
2420
-                true
2421
-            );
2422
-        }
2423
-        if (is_array($otherObjects)) {
2424
-            foreach ($otherObjects as $otherObject) {
2425
-                $otherObject->clear_cache(
2426
-                    $this->get_model()->get_this_model_name(),
2427
-                    $this
2428
-                );
2429
-            }
2430
-        }
2431
-        return $otherObjects;
2432
-    }
2433
-
2434
-
2435
-    /**
2436
-     * Gets all the related model objects of the specified type. Eg, if the current class if
2437
-     * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
2438
-     * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
2439
-     * because we want to get even deleted items etc.
2440
-     *
2441
-     * @param string $relationName key in the model's _model_relations array
2442
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2443
-     * @return EE_Base_Class[]     Results not necessarily indexed by IDs, because some results might not have primary
2444
-     *                             keys or might not be saved yet. Consider using EEM_Base::get_IDs() on these
2445
-     *                             results if you want IDs
2446
-     * @throws ReflectionException
2447
-     * @throws InvalidArgumentException
2448
-     * @throws InvalidInterfaceException
2449
-     * @throws InvalidDataTypeException
2450
-     * @throws EE_Error
2451
-     */
2452
-    public function get_many_related($relationName, $query_params = array())
2453
-    {
2454
-        if ($this->ID()) {
2455
-            // this exists in the DB, so get the related things from either the cache or the DB
2456
-            // if there are query parameters, forget about caching the related model objects.
2457
-            if ($query_params) {
2458
-                $related_model_objects = $this->get_model()->get_all_related(
2459
-                    $this,
2460
-                    $relationName,
2461
-                    $query_params
2462
-                );
2463
-            } else {
2464
-                // did we already cache the result of this query?
2465
-                $cached_results = $this->get_all_from_cache($relationName);
2466
-                if (! $cached_results) {
2467
-                    $related_model_objects = $this->get_model()->get_all_related(
2468
-                        $this,
2469
-                        $relationName,
2470
-                        $query_params
2471
-                    );
2472
-                    // if no query parameters were passed, then we got all the related model objects
2473
-                    // for that relation. We can cache them then.
2474
-                    foreach ($related_model_objects as $related_model_object) {
2475
-                        $this->cache($relationName, $related_model_object);
2476
-                    }
2477
-                } else {
2478
-                    $related_model_objects = $cached_results;
2479
-                }
2480
-            }
2481
-        } else {
2482
-            // this doesn't exist in the DB, so just get the related things from the cache
2483
-            $related_model_objects = $this->get_all_from_cache($relationName);
2484
-        }
2485
-        return $related_model_objects;
2486
-    }
2487
-
2488
-
2489
-    /**
2490
-     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2491
-     * unless otherwise specified in the $query_params
2492
-     *
2493
-     * @param string $relation_name  model_name like 'Event', or 'Registration'
2494
-     * @param array  $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2495
-     * @param string $field_to_count name of field to count by. By default, uses primary key
2496
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2497
-     *                               that by the setting $distinct to TRUE;
2498
-     * @return int
2499
-     * @throws ReflectionException
2500
-     * @throws InvalidArgumentException
2501
-     * @throws InvalidInterfaceException
2502
-     * @throws InvalidDataTypeException
2503
-     * @throws EE_Error
2504
-     */
2505
-    public function count_related($relation_name, $query_params = array(), $field_to_count = null, $distinct = false)
2506
-    {
2507
-        return $this->get_model()->count_related(
2508
-            $this,
2509
-            $relation_name,
2510
-            $query_params,
2511
-            $field_to_count,
2512
-            $distinct
2513
-        );
2514
-    }
2515
-
2516
-
2517
-    /**
2518
-     * Instead of getting the related model objects, simply sums up the values of the specified field.
2519
-     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2520
-     *
2521
-     * @param string $relation_name model_name like 'Event', or 'Registration'
2522
-     * @param array  $query_params  @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2523
-     * @param string $field_to_sum  name of field to count by.
2524
-     *                              By default, uses primary key
2525
-     *                              (which doesn't make much sense, so you should probably change it)
2526
-     * @return int
2527
-     * @throws ReflectionException
2528
-     * @throws InvalidArgumentException
2529
-     * @throws InvalidInterfaceException
2530
-     * @throws InvalidDataTypeException
2531
-     * @throws EE_Error
2532
-     */
2533
-    public function sum_related($relation_name, $query_params = array(), $field_to_sum = null)
2534
-    {
2535
-        return $this->get_model()->sum_related(
2536
-            $this,
2537
-            $relation_name,
2538
-            $query_params,
2539
-            $field_to_sum
2540
-        );
2541
-    }
2542
-
2543
-
2544
-    /**
2545
-     * Gets the first (ie, one) related model object of the specified type.
2546
-     *
2547
-     * @param string $relationName key in the model's _model_relations array
2548
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2549
-     * @return EE_Base_Class (not an array, a single object)
2550
-     * @throws ReflectionException
2551
-     * @throws InvalidArgumentException
2552
-     * @throws InvalidInterfaceException
2553
-     * @throws InvalidDataTypeException
2554
-     * @throws EE_Error
2555
-     */
2556
-    public function get_first_related($relationName, $query_params = array())
2557
-    {
2558
-        $model = $this->get_model();
2559
-        if ($this->ID()) {// this exists in the DB, get from the cache OR the DB
2560
-            // if they've provided some query parameters, don't bother trying to cache the result
2561
-            // also make sure we're not caching the result of get_first_related
2562
-            // on a relation which should have an array of objects (because the cache might have an array of objects)
2563
-            if (
2564
-                $query_params
2565
-                || ! $model->related_settings_for($relationName)
2566
-                     instanceof
2567
-                     EE_Belongs_To_Relation
2568
-            ) {
2569
-                $related_model_object = $model->get_first_related(
2570
-                    $this,
2571
-                    $relationName,
2572
-                    $query_params
2573
-                );
2574
-            } else {
2575
-                // first, check if we've already cached the result of this query
2576
-                $cached_result = $this->get_one_from_cache($relationName);
2577
-                if (! $cached_result) {
2578
-                    $related_model_object = $model->get_first_related(
2579
-                        $this,
2580
-                        $relationName,
2581
-                        $query_params
2582
-                    );
2583
-                    $this->cache($relationName, $related_model_object);
2584
-                } else {
2585
-                    $related_model_object = $cached_result;
2586
-                }
2587
-            }
2588
-        } else {
2589
-            $related_model_object = null;
2590
-            // this doesn't exist in the Db,
2591
-            // but maybe the relation is of type belongs to, and so the related thing might
2592
-            if ($model->related_settings_for($relationName) instanceof EE_Belongs_To_Relation) {
2593
-                $related_model_object = $model->get_first_related(
2594
-                    $this,
2595
-                    $relationName,
2596
-                    $query_params
2597
-                );
2598
-            }
2599
-            // this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
2600
-            // just get what's cached on this object
2601
-            if (! $related_model_object) {
2602
-                $related_model_object = $this->get_one_from_cache($relationName);
2603
-            }
2604
-        }
2605
-        return $related_model_object;
2606
-    }
2607
-
2608
-
2609
-    /**
2610
-     * Does a delete on all related objects of type $relationName and removes
2611
-     * the current model object's relation to them. If they can't be deleted (because
2612
-     * of blocking related model objects) does nothing. If the related model objects are
2613
-     * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2614
-     * If this model object doesn't exist yet in the DB, just removes its related things
2615
-     *
2616
-     * @param string $relationName
2617
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2618
-     * @return int how many deleted
2619
-     * @throws ReflectionException
2620
-     * @throws InvalidArgumentException
2621
-     * @throws InvalidInterfaceException
2622
-     * @throws InvalidDataTypeException
2623
-     * @throws EE_Error
2624
-     */
2625
-    public function delete_related($relationName, $query_params = array())
2626
-    {
2627
-        if ($this->ID()) {
2628
-            $count = $this->get_model()->delete_related(
2629
-                $this,
2630
-                $relationName,
2631
-                $query_params
2632
-            );
2633
-        } else {
2634
-            $count = count($this->get_all_from_cache($relationName));
2635
-            $this->clear_cache($relationName, null, true);
2636
-        }
2637
-        return $count;
2638
-    }
2639
-
2640
-
2641
-    /**
2642
-     * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2643
-     * the current model object's relation to them. If they can't be deleted (because
2644
-     * of blocking related model objects) just does a soft delete on it instead, if possible.
2645
-     * If the related thing isn't a soft-deletable model object, this function is identical
2646
-     * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2647
-     *
2648
-     * @param string $relationName
2649
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2650
-     * @return int how many deleted (including those soft deleted)
2651
-     * @throws ReflectionException
2652
-     * @throws InvalidArgumentException
2653
-     * @throws InvalidInterfaceException
2654
-     * @throws InvalidDataTypeException
2655
-     * @throws EE_Error
2656
-     */
2657
-    public function delete_related_permanently($relationName, $query_params = array())
2658
-    {
2659
-        if ($this->ID()) {
2660
-            $count = $this->get_model()->delete_related_permanently(
2661
-                $this,
2662
-                $relationName,
2663
-                $query_params
2664
-            );
2665
-        } else {
2666
-            $count = count($this->get_all_from_cache($relationName));
2667
-        }
2668
-        $this->clear_cache($relationName, null, true);
2669
-        return $count;
2670
-    }
2671
-
2672
-
2673
-    /**
2674
-     * is_set
2675
-     * Just a simple utility function children can use for checking if property exists
2676
-     *
2677
-     * @access  public
2678
-     * @param  string $field_name property to check
2679
-     * @return bool                              TRUE if existing,FALSE if not.
2680
-     */
2681
-    public function is_set($field_name)
2682
-    {
2683
-        return isset($this->_fields[ $field_name ]);
2684
-    }
2685
-
2686
-
2687
-    /**
2688
-     * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
2689
-     * EE_Error exception if they don't
2690
-     *
2691
-     * @param  mixed (string|array) $properties properties to check
2692
-     * @throws EE_Error
2693
-     * @return bool                              TRUE if existing, throw EE_Error if not.
2694
-     */
2695
-    protected function _property_exists($properties)
2696
-    {
2697
-        foreach ((array) $properties as $property_name) {
2698
-            // first make sure this property exists
2699
-            if (! $this->_fields[ $property_name ]) {
2700
-                throw new EE_Error(
2701
-                    sprintf(
2702
-                        esc_html__(
2703
-                            'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
2704
-                            'event_espresso'
2705
-                        ),
2706
-                        $property_name
2707
-                    )
2708
-                );
2709
-            }
2710
-        }
2711
-        return true;
2712
-    }
2713
-
2714
-
2715
-    /**
2716
-     * This simply returns an array of model fields for this object
2717
-     *
2718
-     * @return array
2719
-     * @throws ReflectionException
2720
-     * @throws InvalidArgumentException
2721
-     * @throws InvalidInterfaceException
2722
-     * @throws InvalidDataTypeException
2723
-     * @throws EE_Error
2724
-     */
2725
-    public function model_field_array()
2726
-    {
2727
-        $fields = $this->get_model()->field_settings(false);
2728
-        $properties = array();
2729
-        // remove prepended underscore
2730
-        foreach ($fields as $field_name => $settings) {
2731
-            $properties[ $field_name ] = $this->get($field_name);
2732
-        }
2733
-        return $properties;
2734
-    }
2735
-
2736
-
2737
-    /**
2738
-     * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2739
-     * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2740
-     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments.
2741
-     * Instead of requiring a plugin to extend the EE_Base_Class
2742
-     * (which works fine is there's only 1 plugin, but when will that happen?)
2743
-     * they can add a hook onto 'filters_hook_espresso__{className}__{methodName}'
2744
-     * (eg, filters_hook_espresso__EE_Answer__my_great_function)
2745
-     * and accepts 2 arguments: the object on which the function was called,
2746
-     * and an array of the original arguments passed to the function.
2747
-     * Whatever their callback function returns will be returned by this function.
2748
-     * Example: in functions.php (or in a plugin):
2749
-     *      add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3);
2750
-     *      function my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2751
-     *          $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2752
-     *          return $previousReturnValue.$returnString;
2753
-     *      }
2754
-     * require('EE_Answer.class.php');
2755
-     * $answer= EE_Answer::new_instance(array('REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'));
2756
-     * echo $answer->my_callback('monkeys',100);
2757
-     * //will output "you called my_callback! and passed args:monkeys,100"
2758
-     *
2759
-     * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2760
-     * @param array  $args       array of original arguments passed to the function
2761
-     * @throws EE_Error
2762
-     * @return mixed whatever the plugin which calls add_filter decides
2763
-     */
2764
-    public function __call($methodName, $args)
2765
-    {
2766
-        $className = get_class($this);
2767
-        $tagName = "FHEE__{$className}__{$methodName}";
2768
-        if (! has_filter($tagName)) {
2769
-            throw new EE_Error(
2770
-                sprintf(
2771
-                    esc_html__(
2772
-                        "Method %s on class %s does not exist! You can create one with the following code in functions.php or in a plugin: add_filter('%s','my_callback',10,3);function my_callback(\$previousReturnValue,EE_Base_Class \$object, \$argsArray){/*function body*/return \$whatever;}",
2773
-                        'event_espresso'
2774
-                    ),
2775
-                    $methodName,
2776
-                    $className,
2777
-                    $tagName
2778
-                )
2779
-            );
2780
-        }
2781
-        return apply_filters($tagName, null, $this, $args);
2782
-    }
2783
-
2784
-
2785
-    /**
2786
-     * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
2787
-     * A $previous_value can be specified in case there are many meta rows with the same key
2788
-     *
2789
-     * @param string $meta_key
2790
-     * @param mixed  $meta_value
2791
-     * @param mixed  $previous_value
2792
-     * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
2793
-     *                  NOTE: if the values haven't changed, returns 0
2794
-     * @throws InvalidArgumentException
2795
-     * @throws InvalidInterfaceException
2796
-     * @throws InvalidDataTypeException
2797
-     * @throws EE_Error
2798
-     * @throws ReflectionException
2799
-     */
2800
-    public function update_extra_meta($meta_key, $meta_value, $previous_value = null)
2801
-    {
2802
-        $query_params = array(
2803
-            array(
2804
-                'EXM_key'  => $meta_key,
2805
-                'OBJ_ID'   => $this->ID(),
2806
-                'EXM_type' => $this->get_model()->get_this_model_name(),
2807
-            ),
2808
-        );
2809
-        if ($previous_value !== null) {
2810
-            $query_params[0]['EXM_value'] = $meta_value;
2811
-        }
2812
-        $existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2813
-        if (! $existing_rows_like_that) {
2814
-            return $this->add_extra_meta($meta_key, $meta_value);
2815
-        }
2816
-        foreach ($existing_rows_like_that as $existing_row) {
2817
-            $existing_row->save(array('EXM_value' => $meta_value));
2818
-        }
2819
-        return count($existing_rows_like_that);
2820
-    }
2821
-
2822
-
2823
-    /**
2824
-     * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
2825
-     * no other extra meta for this model object have the same key. Returns TRUE if the
2826
-     * extra meta row was entered, false if not
2827
-     *
2828
-     * @param string  $meta_key
2829
-     * @param mixed   $meta_value
2830
-     * @param boolean $unique
2831
-     * @return boolean
2832
-     * @throws InvalidArgumentException
2833
-     * @throws InvalidInterfaceException
2834
-     * @throws InvalidDataTypeException
2835
-     * @throws EE_Error
2836
-     * @throws ReflectionException
2837
-     * @throws ReflectionException
2838
-     */
2839
-    public function add_extra_meta($meta_key, $meta_value, $unique = false)
2840
-    {
2841
-        if ($unique) {
2842
-            $existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
2843
-                array(
2844
-                    array(
2845
-                        'EXM_key'  => $meta_key,
2846
-                        'OBJ_ID'   => $this->ID(),
2847
-                        'EXM_type' => $this->get_model()->get_this_model_name(),
2848
-                    ),
2849
-                )
2850
-            );
2851
-            if ($existing_extra_meta) {
2852
-                return false;
2853
-            }
2854
-        }
2855
-        $new_extra_meta = EE_Extra_Meta::new_instance(
2856
-            array(
2857
-                'EXM_key'   => $meta_key,
2858
-                'EXM_value' => $meta_value,
2859
-                'OBJ_ID'    => $this->ID(),
2860
-                'EXM_type'  => $this->get_model()->get_this_model_name(),
2861
-            )
2862
-        );
2863
-        $new_extra_meta->save();
2864
-        return true;
2865
-    }
2866
-
2867
-
2868
-    /**
2869
-     * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2870
-     * is specified, only deletes extra meta records with that value.
2871
-     *
2872
-     * @param string $meta_key
2873
-     * @param mixed  $meta_value
2874
-     * @return int number of extra meta rows deleted
2875
-     * @throws InvalidArgumentException
2876
-     * @throws InvalidInterfaceException
2877
-     * @throws InvalidDataTypeException
2878
-     * @throws EE_Error
2879
-     * @throws ReflectionException
2880
-     */
2881
-    public function delete_extra_meta($meta_key, $meta_value = null)
2882
-    {
2883
-        $query_params = array(
2884
-            array(
2885
-                'EXM_key'  => $meta_key,
2886
-                'OBJ_ID'   => $this->ID(),
2887
-                'EXM_type' => $this->get_model()->get_this_model_name(),
2888
-            ),
2889
-        );
2890
-        if ($meta_value !== null) {
2891
-            $query_params[0]['EXM_value'] = $meta_value;
2892
-        }
2893
-        return EEM_Extra_Meta::instance()->delete($query_params);
2894
-    }
2895
-
2896
-
2897
-    /**
2898
-     * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
2899
-     * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
2900
-     * You can specify $default is case you haven't found the extra meta
2901
-     *
2902
-     * @param string  $meta_key
2903
-     * @param boolean $single
2904
-     * @param mixed   $default if we don't find anything, what should we return?
2905
-     * @return mixed single value if $single; array if ! $single
2906
-     * @throws ReflectionException
2907
-     * @throws InvalidArgumentException
2908
-     * @throws InvalidInterfaceException
2909
-     * @throws InvalidDataTypeException
2910
-     * @throws EE_Error
2911
-     */
2912
-    public function get_extra_meta($meta_key, $single = false, $default = null)
2913
-    {
2914
-        if ($single) {
2915
-            $result = $this->get_first_related(
2916
-                'Extra_Meta',
2917
-                array(array('EXM_key' => $meta_key))
2918
-            );
2919
-            if ($result instanceof EE_Extra_Meta) {
2920
-                return $result->value();
2921
-            }
2922
-        } else {
2923
-            $results = $this->get_many_related(
2924
-                'Extra_Meta',
2925
-                array(array('EXM_key' => $meta_key))
2926
-            );
2927
-            if ($results) {
2928
-                $values = array();
2929
-                foreach ($results as $result) {
2930
-                    if ($result instanceof EE_Extra_Meta) {
2931
-                        $values[ $result->ID() ] = $result->value();
2932
-                    }
2933
-                }
2934
-                return $values;
2935
-            }
2936
-        }
2937
-        // if nothing discovered yet return default.
2938
-        return apply_filters(
2939
-            'FHEE__EE_Base_Class__get_extra_meta__default_value',
2940
-            $default,
2941
-            $meta_key,
2942
-            $single,
2943
-            $this
2944
-        );
2945
-    }
2946
-
2947
-
2948
-    /**
2949
-     * Returns a simple array of all the extra meta associated with this model object.
2950
-     * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2951
-     * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2952
-     * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2953
-     * If $one_of_each_key is false, it will return an array with the top-level keys being
2954
-     * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2955
-     * finally the extra meta's value as each sub-value. (eg
2956
-     * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2957
-     *
2958
-     * @param boolean $one_of_each_key
2959
-     * @return array
2960
-     * @throws ReflectionException
2961
-     * @throws InvalidArgumentException
2962
-     * @throws InvalidInterfaceException
2963
-     * @throws InvalidDataTypeException
2964
-     * @throws EE_Error
2965
-     */
2966
-    public function all_extra_meta_array($one_of_each_key = true)
2967
-    {
2968
-        $return_array = array();
2969
-        if ($one_of_each_key) {
2970
-            $extra_meta_objs = $this->get_many_related(
2971
-                'Extra_Meta',
2972
-                array('group_by' => 'EXM_key')
2973
-            );
2974
-            foreach ($extra_meta_objs as $extra_meta_obj) {
2975
-                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2976
-                    $return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2977
-                }
2978
-            }
2979
-        } else {
2980
-            $extra_meta_objs = $this->get_many_related('Extra_Meta');
2981
-            foreach ($extra_meta_objs as $extra_meta_obj) {
2982
-                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2983
-                    if (! isset($return_array[ $extra_meta_obj->key() ])) {
2984
-                        $return_array[ $extra_meta_obj->key() ] = array();
2985
-                    }
2986
-                    $return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
2987
-                }
2988
-            }
2989
-        }
2990
-        return $return_array;
2991
-    }
2992
-
2993
-
2994
-    /**
2995
-     * Gets a pretty nice displayable nice for this model object. Often overridden
2996
-     *
2997
-     * @return string
2998
-     * @throws ReflectionException
2999
-     * @throws InvalidArgumentException
3000
-     * @throws InvalidInterfaceException
3001
-     * @throws InvalidDataTypeException
3002
-     * @throws EE_Error
3003
-     */
3004
-    public function name()
3005
-    {
3006
-        // find a field that's not a text field
3007
-        $field_we_can_use = $this->get_model()->get_a_field_of_type('EE_Text_Field_Base');
3008
-        if ($field_we_can_use) {
3009
-            return $this->get($field_we_can_use->get_name());
3010
-        }
3011
-        $first_few_properties = $this->model_field_array();
3012
-        $first_few_properties = array_slice($first_few_properties, 0, 3);
3013
-        $name_parts = array();
3014
-        foreach ($first_few_properties as $name => $value) {
3015
-            $name_parts[] = "$name:$value";
3016
-        }
3017
-        return implode(',', $name_parts);
3018
-    }
3019
-
3020
-
3021
-    /**
3022
-     * in_entity_map
3023
-     * Checks if this model object has been proven to already be in the entity map
3024
-     *
3025
-     * @return boolean
3026
-     * @throws ReflectionException
3027
-     * @throws InvalidArgumentException
3028
-     * @throws InvalidInterfaceException
3029
-     * @throws InvalidDataTypeException
3030
-     * @throws EE_Error
3031
-     */
3032
-    public function in_entity_map()
3033
-    {
3034
-        // well, if we looked, did we find it in the entity map?
3035
-        return $this->ID() && $this->get_model()->get_from_entity_map($this->ID()) === $this;
3036
-    }
3037
-
3038
-
3039
-    /**
3040
-     * refresh_from_db
3041
-     * Makes sure the fields and values on this model object are in-sync with what's in the database.
3042
-     *
3043
-     * @throws ReflectionException
3044
-     * @throws InvalidArgumentException
3045
-     * @throws InvalidInterfaceException
3046
-     * @throws InvalidDataTypeException
3047
-     * @throws EE_Error if this model object isn't in the entity mapper (because then you should
3048
-     * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
3049
-     */
3050
-    public function refresh_from_db()
3051
-    {
3052
-        if ($this->ID() && $this->in_entity_map()) {
3053
-            $this->get_model()->refresh_entity_map_from_db($this->ID());
3054
-        } else {
3055
-            // if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
3056
-            // if it has an ID but it's not in the map, and you're asking me to refresh it
3057
-            // that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
3058
-            // absolutely nothing in it for this ID
3059
-            if (WP_DEBUG) {
3060
-                throw new EE_Error(
3061
-                    sprintf(
3062
-                        esc_html__(
3063
-                            'Trying to refresh a model object with ID "%1$s" that\'s not in the entity map? First off: you should put it in the entity map by calling %2$s. Second off, if you want what\'s in the database right now, you should just call %3$s yourself and discard this model object.',
3064
-                            'event_espresso'
3065
-                        ),
3066
-                        $this->ID(),
3067
-                        get_class($this->get_model()) . '::instance()->add_to_entity_map()',
3068
-                        get_class($this->get_model()) . '::instance()->refresh_entity_map()'
3069
-                    )
3070
-                );
3071
-            }
3072
-        }
3073
-    }
3074
-
3075
-
3076
-    /**
3077
-     * Change $fields' values to $new_value_sql (which is a string of raw SQL)
3078
-     *
3079
-     * @since 4.9.80.p
3080
-     * @param EE_Model_Field_Base[] $fields
3081
-     * @param string $new_value_sql
3082
-     *      example: 'column_name=123',
3083
-     *      or 'column_name=column_name+1',
3084
-     *      or 'column_name= CASE
3085
-     *          WHEN (`column_name` + `other_column` + 5) <= `yet_another_column`
3086
-     *          THEN `column_name` + 5
3087
-     *          ELSE `column_name`
3088
-     *      END'
3089
-     *      Also updates $field on this model object with the latest value from the database.
3090
-     * @return bool
3091
-     * @throws EE_Error
3092
-     * @throws InvalidArgumentException
3093
-     * @throws InvalidDataTypeException
3094
-     * @throws InvalidInterfaceException
3095
-     * @throws ReflectionException
3096
-     */
3097
-    protected function updateFieldsInDB($fields, $new_value_sql)
3098
-    {
3099
-        // First make sure this model object actually exists in the DB. It would be silly to try to update it in the DB
3100
-        // if it wasn't even there to start off.
3101
-        if (! $this->ID()) {
3102
-            $this->save();
3103
-        }
3104
-        global $wpdb;
3105
-        if (empty($fields)) {
3106
-            throw new InvalidArgumentException(
3107
-                esc_html__(
3108
-                    'EE_Base_Class::updateFieldsInDB was passed an empty array of fields.',
3109
-                    'event_espresso'
3110
-                )
3111
-            );
3112
-        }
3113
-        $first_field = reset($fields);
3114
-        $table_alias = $first_field->get_table_alias();
3115
-        foreach ($fields as $field) {
3116
-            if ($table_alias !== $field->get_table_alias()) {
3117
-                throw new InvalidArgumentException(
3118
-                    sprintf(
3119
-                        esc_html__(
3120
-                            // @codingStandardsIgnoreStart
3121
-                            'EE_Base_Class::updateFieldsInDB was passed fields for different tables ("%1$s" and "%2$s"), which is not supported. Instead, please call the method multiple times.',
3122
-                            // @codingStandardsIgnoreEnd
3123
-                            'event_espresso'
3124
-                        ),
3125
-                        $table_alias,
3126
-                        $field->get_table_alias()
3127
-                    )
3128
-                );
3129
-            }
3130
-        }
3131
-        // Ok the fields are now known to all be for the same table. Proceed with creating the SQL to update it.
3132
-        $table_obj = $this->get_model()->get_table_obj_by_alias($table_alias);
3133
-        $table_pk_value = $this->ID();
3134
-        $table_name = $table_obj->get_table_name();
3135
-        if ($table_obj instanceof EE_Secondary_Table) {
3136
-            $table_pk_field_name = $table_obj->get_fk_on_table();
3137
-        } else {
3138
-            $table_pk_field_name = $table_obj->get_pk_column();
3139
-        }
3140
-
3141
-        $query =
3142
-            "UPDATE `{$table_name}`
338
+				$this->_props_n_values_provided_in_constructor
339
+				&& $field_value
340
+				&& $field_name === $model->primary_key_name()
341
+			) {
342
+				// if so, we want all this object's fields to be filled either with
343
+				// what we've explicitly set on this model
344
+				// or what we have in the db
345
+				// echo "setting primary key!";
346
+				$fields_on_model = self::_get_model(get_class($this))->field_settings();
347
+				$obj_in_db = self::_get_model(get_class($this))->get_one_by_ID($field_value);
348
+				foreach ($fields_on_model as $field_obj) {
349
+					if (
350
+						! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
351
+						&& $field_obj->get_name() !== $field_name
352
+					) {
353
+						$this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
354
+					}
355
+				}
356
+				// oh this model object has an ID? well make sure its in the entity mapper
357
+				$model->add_to_entity_map($this);
358
+			}
359
+			// let's unset any cache for this field_name from the $_cached_properties property.
360
+			$this->_clear_cached_property($field_name);
361
+		} else {
362
+			throw new EE_Error(
363
+				sprintf(
364
+					esc_html__(
365
+						'A valid EE_Model_Field_Base could not be found for the given field name: %s',
366
+						'event_espresso'
367
+					),
368
+					$field_name
369
+				)
370
+			);
371
+		}
372
+	}
373
+
374
+
375
+	/**
376
+	 * Set custom select values for model.
377
+	 *
378
+	 * @param array $custom_select_values
379
+	 */
380
+	public function setCustomSelectsValues(array $custom_select_values)
381
+	{
382
+		$this->custom_selection_results = $custom_select_values;
383
+	}
384
+
385
+
386
+	/**
387
+	 * Returns the custom select value for the provided alias if its set.
388
+	 * If not set, returns null.
389
+	 *
390
+	 * @param string $alias
391
+	 * @return string|int|float|null
392
+	 */
393
+	public function getCustomSelect($alias)
394
+	{
395
+		return isset($this->custom_selection_results[ $alias ])
396
+			? $this->custom_selection_results[ $alias ]
397
+			: null;
398
+	}
399
+
400
+
401
+	/**
402
+	 * This sets the field value on the db column if it exists for the given $column_name or
403
+	 * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
404
+	 *
405
+	 * @see EE_message::get_column_value for related documentation on the necessity of this method.
406
+	 * @param string $field_name  Must be the exact column name.
407
+	 * @param mixed  $field_value The value to set.
408
+	 * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
409
+	 * @throws InvalidArgumentException
410
+	 * @throws InvalidInterfaceException
411
+	 * @throws InvalidDataTypeException
412
+	 * @throws EE_Error
413
+	 * @throws ReflectionException
414
+	 */
415
+	public function set_field_or_extra_meta($field_name, $field_value)
416
+	{
417
+		if ($this->get_model()->has_field($field_name)) {
418
+			$this->set($field_name, $field_value);
419
+			return true;
420
+		}
421
+		// ensure this object is saved first so that extra meta can be properly related.
422
+		$this->save();
423
+		return $this->update_extra_meta($field_name, $field_value);
424
+	}
425
+
426
+
427
+	/**
428
+	 * This retrieves the value of the db column set on this class or if that's not present
429
+	 * it will attempt to retrieve from extra_meta if found.
430
+	 * Example Usage:
431
+	 * Via EE_Message child class:
432
+	 * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
433
+	 * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
434
+	 * also have additional main fields specific to the messenger.  The system accommodates those extra
435
+	 * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
436
+	 * value for those extra fields dynamically via the EE_message object.
437
+	 *
438
+	 * @param  string $field_name expecting the fully qualified field name.
439
+	 * @return mixed|null  value for the field if found.  null if not found.
440
+	 * @throws ReflectionException
441
+	 * @throws InvalidArgumentException
442
+	 * @throws InvalidInterfaceException
443
+	 * @throws InvalidDataTypeException
444
+	 * @throws EE_Error
445
+	 */
446
+	public function get_field_or_extra_meta($field_name)
447
+	{
448
+		if ($this->get_model()->has_field($field_name)) {
449
+			$column_value = $this->get($field_name);
450
+		} else {
451
+			// This isn't a column in the main table, let's see if it is in the extra meta.
452
+			$column_value = $this->get_extra_meta($field_name, true, null);
453
+		}
454
+		return $column_value;
455
+	}
456
+
457
+
458
+	/**
459
+	 * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
460
+	 * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
461
+	 * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
462
+	 * available to all child classes that may be using the EE_Datetime_Field for a field data type.
463
+	 *
464
+	 * @access public
465
+	 * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
466
+	 * @return void
467
+	 * @throws InvalidArgumentException
468
+	 * @throws InvalidInterfaceException
469
+	 * @throws InvalidDataTypeException
470
+	 * @throws EE_Error
471
+	 * @throws ReflectionException
472
+	 */
473
+	public function set_timezone($timezone = '')
474
+	{
475
+		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
476
+		// make sure we clear all cached properties because they won't be relevant now
477
+		$this->_clear_cached_properties();
478
+		// make sure we update field settings and the date for all EE_Datetime_Fields
479
+		$model_fields = $this->get_model()->field_settings(false);
480
+		foreach ($model_fields as $field_name => $field_obj) {
481
+			if ($field_obj instanceof EE_Datetime_Field) {
482
+				$field_obj->set_timezone($this->_timezone);
483
+				if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
484
+					EEH_DTT_Helper::setTimezone($this->_fields[ $field_name ], new DateTimeZone($this->_timezone));
485
+				}
486
+			}
487
+		}
488
+	}
489
+
490
+
491
+	/**
492
+	 * This just returns whatever is set for the current timezone.
493
+	 *
494
+	 * @access public
495
+	 * @return string timezone string
496
+	 */
497
+	public function get_timezone()
498
+	{
499
+		return $this->_timezone;
500
+	}
501
+
502
+
503
+	/**
504
+	 * This sets the internal date format to what is sent in to be used as the new default for the class
505
+	 * internally instead of wp set date format options
506
+	 *
507
+	 * @since 4.6
508
+	 * @param string $format should be a format recognizable by PHP date() functions.
509
+	 */
510
+	public function set_date_format($format)
511
+	{
512
+		$this->_dt_frmt = $format;
513
+		// clear cached_properties because they won't be relevant now.
514
+		$this->_clear_cached_properties();
515
+	}
516
+
517
+
518
+	/**
519
+	 * This sets the internal time format string to what is sent in to be used as the new default for the
520
+	 * class internally instead of wp set time format options.
521
+	 *
522
+	 * @since 4.6
523
+	 * @param string $format should be a format recognizable by PHP date() functions.
524
+	 */
525
+	public function set_time_format($format)
526
+	{
527
+		$this->_tm_frmt = $format;
528
+		// clear cached_properties because they won't be relevant now.
529
+		$this->_clear_cached_properties();
530
+	}
531
+
532
+
533
+	/**
534
+	 * This returns the current internal set format for the date and time formats.
535
+	 *
536
+	 * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
537
+	 *                             where the first value is the date format and the second value is the time format.
538
+	 * @return mixed string|array
539
+	 */
540
+	public function get_format($full = true)
541
+	{
542
+		return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : array($this->_dt_frmt, $this->_tm_frmt);
543
+	}
544
+
545
+
546
+	/**
547
+	 * cache
548
+	 * stores the passed model object on the current model object.
549
+	 * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
550
+	 *
551
+	 * @param string        $relationName    one of the keys in the _model_relations array on the model. Eg
552
+	 *                                       'Registration' associated with this model object
553
+	 * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
554
+	 *                                       that could be a payment or a registration)
555
+	 * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
556
+	 *                                       items which will be stored in an array on this object
557
+	 * @throws ReflectionException
558
+	 * @throws InvalidArgumentException
559
+	 * @throws InvalidInterfaceException
560
+	 * @throws InvalidDataTypeException
561
+	 * @throws EE_Error
562
+	 * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
563
+	 *                                       related thing, no array)
564
+	 */
565
+	public function cache($relationName = '', $object_to_cache = null, $cache_id = null)
566
+	{
567
+		// its entirely possible that there IS no related object yet in which case there is nothing to cache.
568
+		if (! $object_to_cache instanceof EE_Base_Class) {
569
+			return false;
570
+		}
571
+		// also get "how" the object is related, or throw an error
572
+		if (! $relationship_to_model = $this->get_model()->related_settings_for($relationName)) {
573
+			throw new EE_Error(
574
+				sprintf(
575
+					esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
576
+					$relationName,
577
+					get_class($this)
578
+				)
579
+			);
580
+		}
581
+		// how many things are related ?
582
+		if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
583
+			// if it's a "belongs to" relationship, then there's only one related model object
584
+			// eg, if this is a registration, there's only 1 attendee for it
585
+			// so for these model objects just set it to be cached
586
+			$this->_model_relations[ $relationName ] = $object_to_cache;
587
+			$return = true;
588
+		} else {
589
+			// otherwise, this is the "many" side of a one to many relationship,
590
+			// so we'll add the object to the array of related objects for that type.
591
+			// eg: if this is an event, there are many registrations for that event,
592
+			// so we cache the registrations in an array
593
+			if (! is_array($this->_model_relations[ $relationName ])) {
594
+				// if for some reason, the cached item is a model object,
595
+				// then stick that in the array, otherwise start with an empty array
596
+				$this->_model_relations[ $relationName ] = $this->_model_relations[ $relationName ]
597
+														   instanceof
598
+														   EE_Base_Class
599
+					? array($this->_model_relations[ $relationName ]) : array();
600
+			}
601
+			// first check for a cache_id which is normally empty
602
+			if (! empty($cache_id)) {
603
+				// if the cache_id exists, then it means we are purposely trying to cache this
604
+				// with a known key that can then be used to retrieve the object later on
605
+				$this->_model_relations[ $relationName ][ $cache_id ] = $object_to_cache;
606
+				$return = $cache_id;
607
+			} elseif ($object_to_cache->ID()) {
608
+				// OR the cached object originally came from the db, so let's just use it's PK for an ID
609
+				$this->_model_relations[ $relationName ][ $object_to_cache->ID() ] = $object_to_cache;
610
+				$return = $object_to_cache->ID();
611
+			} else {
612
+				// OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
613
+				$this->_model_relations[ $relationName ][] = $object_to_cache;
614
+				// move the internal pointer to the end of the array
615
+				end($this->_model_relations[ $relationName ]);
616
+				// and grab the key so that we can return it
617
+				$return = key($this->_model_relations[ $relationName ]);
618
+			}
619
+		}
620
+		return $return;
621
+	}
622
+
623
+
624
+	/**
625
+	 * For adding an item to the cached_properties property.
626
+	 *
627
+	 * @access protected
628
+	 * @param string      $fieldname the property item the corresponding value is for.
629
+	 * @param mixed       $value     The value we are caching.
630
+	 * @param string|null $cache_type
631
+	 * @return void
632
+	 * @throws ReflectionException
633
+	 * @throws InvalidArgumentException
634
+	 * @throws InvalidInterfaceException
635
+	 * @throws InvalidDataTypeException
636
+	 * @throws EE_Error
637
+	 */
638
+	protected function _set_cached_property($fieldname, $value, $cache_type = null)
639
+	{
640
+		// first make sure this property exists
641
+		$this->get_model()->field_settings_for($fieldname);
642
+		$cache_type = empty($cache_type) ? 'standard' : $cache_type;
643
+		$this->_cached_properties[ $fieldname ][ $cache_type ] = $value;
644
+	}
645
+
646
+
647
+	/**
648
+	 * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
649
+	 * This also SETS the cache if we return the actual property!
650
+	 *
651
+	 * @param string $fieldname        the name of the property we're trying to retrieve
652
+	 * @param bool   $pretty
653
+	 * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
654
+	 *                                 (in cases where the same property may be used for different outputs
655
+	 *                                 - i.e. datetime, money etc.)
656
+	 *                                 It can also accept certain pre-defined "schema" strings
657
+	 *                                 to define how to output the property.
658
+	 *                                 see the field's prepare_for_pretty_echoing for what strings can be used
659
+	 * @return mixed                   whatever the value for the property is we're retrieving
660
+	 * @throws ReflectionException
661
+	 * @throws InvalidArgumentException
662
+	 * @throws InvalidInterfaceException
663
+	 * @throws InvalidDataTypeException
664
+	 * @throws EE_Error
665
+	 */
666
+	protected function _get_cached_property($fieldname, $pretty = false, $extra_cache_ref = null)
667
+	{
668
+		// verify the field exists
669
+		$model = $this->get_model();
670
+		$model->field_settings_for($fieldname);
671
+		$cache_type = $pretty ? 'pretty' : 'standard';
672
+		$cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
673
+		if (isset($this->_cached_properties[ $fieldname ][ $cache_type ])) {
674
+			return $this->_cached_properties[ $fieldname ][ $cache_type ];
675
+		}
676
+		$value = $this->_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
677
+		$this->_set_cached_property($fieldname, $value, $cache_type);
678
+		return $value;
679
+	}
680
+
681
+
682
+	/**
683
+	 * If the cache didn't fetch the needed item, this fetches it.
684
+	 *
685
+	 * @param string $fieldname
686
+	 * @param bool   $pretty
687
+	 * @param string $extra_cache_ref
688
+	 * @return mixed
689
+	 * @throws InvalidArgumentException
690
+	 * @throws InvalidInterfaceException
691
+	 * @throws InvalidDataTypeException
692
+	 * @throws EE_Error
693
+	 * @throws ReflectionException
694
+	 */
695
+	protected function _get_fresh_property($fieldname, $pretty = false, $extra_cache_ref = null)
696
+	{
697
+		$field_obj = $this->get_model()->field_settings_for($fieldname);
698
+		// If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
699
+		if ($field_obj instanceof EE_Datetime_Field) {
700
+			$this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
701
+		}
702
+		if (! isset($this->_fields[ $fieldname ])) {
703
+			$this->_fields[ $fieldname ] = null;
704
+		}
705
+		$value = $pretty
706
+			? $field_obj->prepare_for_pretty_echoing($this->_fields[ $fieldname ], $extra_cache_ref)
707
+			: $field_obj->prepare_for_get($this->_fields[ $fieldname ]);
708
+		return $value;
709
+	}
710
+
711
+
712
+	/**
713
+	 * set timezone, formats, and output for EE_Datetime_Field objects
714
+	 *
715
+	 * @param \EE_Datetime_Field $datetime_field
716
+	 * @param bool               $pretty
717
+	 * @param null               $date_or_time
718
+	 * @return void
719
+	 * @throws InvalidArgumentException
720
+	 * @throws InvalidInterfaceException
721
+	 * @throws InvalidDataTypeException
722
+	 * @throws EE_Error
723
+	 */
724
+	protected function _prepare_datetime_field(
725
+		EE_Datetime_Field $datetime_field,
726
+		$pretty = false,
727
+		$date_or_time = null
728
+	) {
729
+		$datetime_field->set_timezone($this->_timezone);
730
+		$datetime_field->set_date_format($this->_dt_frmt, $pretty);
731
+		$datetime_field->set_time_format($this->_tm_frmt, $pretty);
732
+		// set the output returned
733
+		switch ($date_or_time) {
734
+			case 'D':
735
+				$datetime_field->set_date_time_output('date');
736
+				break;
737
+			case 'T':
738
+				$datetime_field->set_date_time_output('time');
739
+				break;
740
+			default:
741
+				$datetime_field->set_date_time_output();
742
+		}
743
+	}
744
+
745
+
746
+	/**
747
+	 * This just takes care of clearing out the cached_properties
748
+	 *
749
+	 * @return void
750
+	 */
751
+	protected function _clear_cached_properties()
752
+	{
753
+		$this->_cached_properties = array();
754
+	}
755
+
756
+
757
+	/**
758
+	 * This just clears out ONE property if it exists in the cache
759
+	 *
760
+	 * @param  string $property_name the property to remove if it exists (from the _cached_properties array)
761
+	 * @return void
762
+	 */
763
+	protected function _clear_cached_property($property_name)
764
+	{
765
+		if (isset($this->_cached_properties[ $property_name ])) {
766
+			unset($this->_cached_properties[ $property_name ]);
767
+		}
768
+	}
769
+
770
+
771
+	/**
772
+	 * Ensures that this related thing is a model object.
773
+	 *
774
+	 * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
775
+	 * @param string $model_name   name of the related thing, eg 'Attendee',
776
+	 * @return EE_Base_Class
777
+	 * @throws ReflectionException
778
+	 * @throws InvalidArgumentException
779
+	 * @throws InvalidInterfaceException
780
+	 * @throws InvalidDataTypeException
781
+	 * @throws EE_Error
782
+	 */
783
+	protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
784
+	{
785
+		$other_model_instance = self::_get_model_instance_with_name(
786
+			self::_get_model_classname($model_name),
787
+			$this->_timezone
788
+		);
789
+		return $other_model_instance->ensure_is_obj($object_or_id);
790
+	}
791
+
792
+
793
+	/**
794
+	 * Forgets the cached model of the given relation Name. So the next time we request it,
795
+	 * we will fetch it again from the database. (Handy if you know it's changed somehow).
796
+	 * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
797
+	 * then only remove that one object from our cached array. Otherwise, clear the entire list
798
+	 *
799
+	 * @param string $relationName                         one of the keys in the _model_relations array on the model.
800
+	 *                                                     Eg 'Registration'
801
+	 * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
802
+	 *                                                     if you intend to use $clear_all = TRUE, or the relation only
803
+	 *                                                     has 1 object anyways (ie, it's a BelongsToRelation)
804
+	 * @param bool   $clear_all                            This flags clearing the entire cache relation property if
805
+	 *                                                     this is HasMany or HABTM.
806
+	 * @throws ReflectionException
807
+	 * @throws InvalidArgumentException
808
+	 * @throws InvalidInterfaceException
809
+	 * @throws InvalidDataTypeException
810
+	 * @throws EE_Error
811
+	 * @return EE_Base_Class | boolean from which was cleared from the cache, or true if we requested to remove a
812
+	 *                                                     relation from all
813
+	 */
814
+	public function clear_cache($relationName, $object_to_remove_or_index_into_array = null, $clear_all = false)
815
+	{
816
+		$relationship_to_model = $this->get_model()->related_settings_for($relationName);
817
+		$index_in_cache = '';
818
+		if (! $relationship_to_model) {
819
+			throw new EE_Error(
820
+				sprintf(
821
+					esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
822
+					$relationName,
823
+					get_class($this)
824
+				)
825
+			);
826
+		}
827
+		if ($clear_all) {
828
+			$obj_removed = true;
829
+			$this->_model_relations[ $relationName ] = null;
830
+		} elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
831
+			$obj_removed = $this->_model_relations[ $relationName ];
832
+			$this->_model_relations[ $relationName ] = null;
833
+		} else {
834
+			if (
835
+				$object_to_remove_or_index_into_array instanceof EE_Base_Class
836
+				&& $object_to_remove_or_index_into_array->ID()
837
+			) {
838
+				$index_in_cache = $object_to_remove_or_index_into_array->ID();
839
+				if (
840
+					is_array($this->_model_relations[ $relationName ])
841
+					&& ! isset($this->_model_relations[ $relationName ][ $index_in_cache ])
842
+				) {
843
+					$index_found_at = null;
844
+					// find this object in the array even though it has a different key
845
+					foreach ($this->_model_relations[ $relationName ] as $index => $obj) {
846
+						/** @noinspection TypeUnsafeComparisonInspection */
847
+						if (
848
+							$obj instanceof EE_Base_Class
849
+							&& (
850
+								$obj == $object_to_remove_or_index_into_array
851
+								|| $obj->ID() === $object_to_remove_or_index_into_array->ID()
852
+							)
853
+						) {
854
+							$index_found_at = $index;
855
+							break;
856
+						}
857
+					}
858
+					if ($index_found_at) {
859
+						$index_in_cache = $index_found_at;
860
+					} else {
861
+						// it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
862
+						// if it wasn't in it to begin with. So we're done
863
+						return $object_to_remove_or_index_into_array;
864
+					}
865
+				}
866
+			} elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
867
+				// so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
868
+				foreach ($this->get_all_from_cache($relationName) as $index => $potentially_obj_we_want) {
869
+					/** @noinspection TypeUnsafeComparisonInspection */
870
+					if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
871
+						$index_in_cache = $index;
872
+					}
873
+				}
874
+			} else {
875
+				$index_in_cache = $object_to_remove_or_index_into_array;
876
+			}
877
+			// supposedly we've found it. But it could just be that the client code
878
+			// provided a bad index/object
879
+			if (isset($this->_model_relations[ $relationName ][ $index_in_cache ])) {
880
+				$obj_removed = $this->_model_relations[ $relationName ][ $index_in_cache ];
881
+				unset($this->_model_relations[ $relationName ][ $index_in_cache ]);
882
+			} else {
883
+				// that thing was never cached anyways.
884
+				$obj_removed = null;
885
+			}
886
+		}
887
+		return $obj_removed;
888
+	}
889
+
890
+
891
+	/**
892
+	 * update_cache_after_object_save
893
+	 * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
894
+	 * obtained after being saved to the db
895
+	 *
896
+	 * @param string        $relationName       - the type of object that is cached
897
+	 * @param EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
898
+	 * @param string        $current_cache_id   - the ID that was used when originally caching the object
899
+	 * @return boolean TRUE on success, FALSE on fail
900
+	 * @throws ReflectionException
901
+	 * @throws InvalidArgumentException
902
+	 * @throws InvalidInterfaceException
903
+	 * @throws InvalidDataTypeException
904
+	 * @throws EE_Error
905
+	 */
906
+	public function update_cache_after_object_save(
907
+		$relationName,
908
+		EE_Base_Class $newly_saved_object,
909
+		$current_cache_id = ''
910
+	) {
911
+		// verify that incoming object is of the correct type
912
+		$obj_class = 'EE_' . $relationName;
913
+		if ($newly_saved_object instanceof $obj_class) {
914
+			/* @type EE_Base_Class $newly_saved_object */
915
+			// now get the type of relation
916
+			$relationship_to_model = $this->get_model()->related_settings_for($relationName);
917
+			// if this is a 1:1 relationship
918
+			if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
919
+				// then just replace the cached object with the newly saved object
920
+				$this->_model_relations[ $relationName ] = $newly_saved_object;
921
+				return true;
922
+				// or if it's some kind of sordid feral polyamorous relationship...
923
+			}
924
+			if (
925
+				is_array($this->_model_relations[ $relationName ])
926
+				&& isset($this->_model_relations[ $relationName ][ $current_cache_id ])
927
+			) {
928
+				// then remove the current cached item
929
+				unset($this->_model_relations[ $relationName ][ $current_cache_id ]);
930
+				// and cache the newly saved object using it's new ID
931
+				$this->_model_relations[ $relationName ][ $newly_saved_object->ID() ] = $newly_saved_object;
932
+				return true;
933
+			}
934
+		}
935
+		return false;
936
+	}
937
+
938
+
939
+	/**
940
+	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
941
+	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
942
+	 *
943
+	 * @param string $relationName
944
+	 * @return EE_Base_Class
945
+	 */
946
+	public function get_one_from_cache($relationName)
947
+	{
948
+		$cached_array_or_object = isset($this->_model_relations[ $relationName ])
949
+			? $this->_model_relations[ $relationName ]
950
+			: null;
951
+		if (is_array($cached_array_or_object)) {
952
+			return array_shift($cached_array_or_object);
953
+		}
954
+		return $cached_array_or_object;
955
+	}
956
+
957
+
958
+	/**
959
+	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
960
+	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
961
+	 *
962
+	 * @param string $relationName
963
+	 * @throws ReflectionException
964
+	 * @throws InvalidArgumentException
965
+	 * @throws InvalidInterfaceException
966
+	 * @throws InvalidDataTypeException
967
+	 * @throws EE_Error
968
+	 * @return EE_Base_Class[] NOT necessarily indexed by primary keys
969
+	 */
970
+	public function get_all_from_cache($relationName)
971
+	{
972
+		$objects = isset($this->_model_relations[ $relationName ]) ? $this->_model_relations[ $relationName ] : array();
973
+		// if the result is not an array, but exists, make it an array
974
+		$objects = is_array($objects) ? $objects : array($objects);
975
+		// bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
976
+		// basically, if this model object was stored in the session, and these cached model objects
977
+		// already have IDs, let's make sure they're in their model's entity mapper
978
+		// otherwise we will have duplicates next time we call
979
+		// EE_Registry::instance()->load_model( $relationName )->get_one_by_ID( $result->ID() );
980
+		$model = EE_Registry::instance()->load_model($relationName);
981
+		foreach ($objects as $model_object) {
982
+			if ($model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
983
+				// ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
984
+				if ($model_object->ID()) {
985
+					$model->add_to_entity_map($model_object);
986
+				}
987
+			} else {
988
+				throw new EE_Error(
989
+					sprintf(
990
+						esc_html__(
991
+							'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
992
+							'event_espresso'
993
+						),
994
+						$relationName,
995
+						gettype($model_object)
996
+					)
997
+				);
998
+			}
999
+		}
1000
+		return $objects;
1001
+	}
1002
+
1003
+
1004
+	/**
1005
+	 * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
1006
+	 * matching the given query conditions.
1007
+	 *
1008
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1009
+	 * @param int   $limit              How many objects to return.
1010
+	 * @param array $query_params       Any additional conditions on the query.
1011
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1012
+	 *                                  you can indicate just the columns you want returned
1013
+	 * @return array|EE_Base_Class[]
1014
+	 * @throws ReflectionException
1015
+	 * @throws InvalidArgumentException
1016
+	 * @throws InvalidInterfaceException
1017
+	 * @throws InvalidDataTypeException
1018
+	 * @throws EE_Error
1019
+	 */
1020
+	public function next_x($field_to_order_by = null, $limit = 1, $query_params = array(), $columns_to_select = null)
1021
+	{
1022
+		$model = $this->get_model();
1023
+		$field = empty($field_to_order_by) && $model->has_primary_key_field()
1024
+			? $model->get_primary_key_field()->get_name()
1025
+			: $field_to_order_by;
1026
+		$current_value = ! empty($field) ? $this->get($field) : null;
1027
+		if (empty($field) || empty($current_value)) {
1028
+			return array();
1029
+		}
1030
+		return $model->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
1031
+	}
1032
+
1033
+
1034
+	/**
1035
+	 * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
1036
+	 * matching the given query conditions.
1037
+	 *
1038
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1039
+	 * @param int   $limit              How many objects to return.
1040
+	 * @param array $query_params       Any additional conditions on the query.
1041
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1042
+	 *                                  you can indicate just the columns you want returned
1043
+	 * @return array|EE_Base_Class[]
1044
+	 * @throws ReflectionException
1045
+	 * @throws InvalidArgumentException
1046
+	 * @throws InvalidInterfaceException
1047
+	 * @throws InvalidDataTypeException
1048
+	 * @throws EE_Error
1049
+	 */
1050
+	public function previous_x(
1051
+		$field_to_order_by = null,
1052
+		$limit = 1,
1053
+		$query_params = array(),
1054
+		$columns_to_select = null
1055
+	) {
1056
+		$model = $this->get_model();
1057
+		$field = empty($field_to_order_by) && $model->has_primary_key_field()
1058
+			? $model->get_primary_key_field()->get_name()
1059
+			: $field_to_order_by;
1060
+		$current_value = ! empty($field) ? $this->get($field) : null;
1061
+		if (empty($field) || empty($current_value)) {
1062
+			return array();
1063
+		}
1064
+		return $model->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
1065
+	}
1066
+
1067
+
1068
+	/**
1069
+	 * Returns the next EE_Base_Class object in sequence from this object as found in the database
1070
+	 * matching the given query conditions.
1071
+	 *
1072
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1073
+	 * @param array $query_params       Any additional conditions on the query.
1074
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1075
+	 *                                  you can indicate just the columns you want returned
1076
+	 * @return array|EE_Base_Class
1077
+	 * @throws ReflectionException
1078
+	 * @throws InvalidArgumentException
1079
+	 * @throws InvalidInterfaceException
1080
+	 * @throws InvalidDataTypeException
1081
+	 * @throws EE_Error
1082
+	 */
1083
+	public function next($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1084
+	{
1085
+		$model = $this->get_model();
1086
+		$field = empty($field_to_order_by) && $model->has_primary_key_field()
1087
+			? $model->get_primary_key_field()->get_name()
1088
+			: $field_to_order_by;
1089
+		$current_value = ! empty($field) ? $this->get($field) : null;
1090
+		if (empty($field) || empty($current_value)) {
1091
+			return array();
1092
+		}
1093
+		return $model->next($current_value, $field, $query_params, $columns_to_select);
1094
+	}
1095
+
1096
+
1097
+	/**
1098
+	 * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1099
+	 * matching the given query conditions.
1100
+	 *
1101
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1102
+	 * @param array $query_params       Any additional conditions on the query.
1103
+	 * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1104
+	 *                                  you can indicate just the column you want returned
1105
+	 * @return array|EE_Base_Class
1106
+	 * @throws ReflectionException
1107
+	 * @throws InvalidArgumentException
1108
+	 * @throws InvalidInterfaceException
1109
+	 * @throws InvalidDataTypeException
1110
+	 * @throws EE_Error
1111
+	 */
1112
+	public function previous($field_to_order_by = null, $query_params = array(), $columns_to_select = null)
1113
+	{
1114
+		$model = $this->get_model();
1115
+		$field = empty($field_to_order_by) && $model->has_primary_key_field()
1116
+			? $model->get_primary_key_field()->get_name()
1117
+			: $field_to_order_by;
1118
+		$current_value = ! empty($field) ? $this->get($field) : null;
1119
+		if (empty($field) || empty($current_value)) {
1120
+			return array();
1121
+		}
1122
+		return $model->previous($current_value, $field, $query_params, $columns_to_select);
1123
+	}
1124
+
1125
+
1126
+	/**
1127
+	 * Overrides parent because parent expects old models.
1128
+	 * This also doesn't do any validation, and won't work for serialized arrays
1129
+	 *
1130
+	 * @param string $field_name
1131
+	 * @param mixed  $field_value_from_db
1132
+	 * @throws ReflectionException
1133
+	 * @throws InvalidArgumentException
1134
+	 * @throws InvalidInterfaceException
1135
+	 * @throws InvalidDataTypeException
1136
+	 * @throws EE_Error
1137
+	 */
1138
+	public function set_from_db($field_name, $field_value_from_db)
1139
+	{
1140
+		$field_obj = $this->get_model()->field_settings_for($field_name);
1141
+		if ($field_obj instanceof EE_Model_Field_Base) {
1142
+			// you would think the DB has no NULLs for non-null label fields right? wrong!
1143
+			// eg, a CPT model object could have an entry in the posts table, but no
1144
+			// entry in the meta table. Meaning that all its columns in the meta table
1145
+			// are null! yikes! so when we find one like that, use defaults for its meta columns
1146
+			if ($field_value_from_db === null) {
1147
+				if ($field_obj->is_nullable()) {
1148
+					// if the field allows nulls, then let it be null
1149
+					$field_value = null;
1150
+				} else {
1151
+					$field_value = $field_obj->get_default_value();
1152
+				}
1153
+			} else {
1154
+				$field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
1155
+			}
1156
+			$this->_fields[ $field_name ] = $field_value;
1157
+			$this->_clear_cached_property($field_name);
1158
+		}
1159
+	}
1160
+
1161
+
1162
+	/**
1163
+	 * verifies that the specified field is of the correct type
1164
+	 *
1165
+	 * @param string $field_name
1166
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1167
+	 *                                (in cases where the same property may be used for different outputs
1168
+	 *                                - i.e. datetime, money etc.)
1169
+	 * @return mixed
1170
+	 * @throws ReflectionException
1171
+	 * @throws InvalidArgumentException
1172
+	 * @throws InvalidInterfaceException
1173
+	 * @throws InvalidDataTypeException
1174
+	 * @throws EE_Error
1175
+	 */
1176
+	public function get($field_name, $extra_cache_ref = null)
1177
+	{
1178
+		return $this->_get_cached_property($field_name, false, $extra_cache_ref);
1179
+	}
1180
+
1181
+
1182
+	/**
1183
+	 * This method simply returns the RAW unprocessed value for the given property in this class
1184
+	 *
1185
+	 * @param  string $field_name A valid fieldname
1186
+	 * @return mixed              Whatever the raw value stored on the property is.
1187
+	 * @throws ReflectionException
1188
+	 * @throws InvalidArgumentException
1189
+	 * @throws InvalidInterfaceException
1190
+	 * @throws InvalidDataTypeException
1191
+	 * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
1192
+	 */
1193
+	public function get_raw($field_name)
1194
+	{
1195
+		$field_settings = $this->get_model()->field_settings_for($field_name);
1196
+		return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
1197
+			? $this->_fields[ $field_name ]->format('U')
1198
+			: $this->_fields[ $field_name ];
1199
+	}
1200
+
1201
+
1202
+	/**
1203
+	 * This is used to return the internal DateTime object used for a field that is a
1204
+	 * EE_Datetime_Field.
1205
+	 *
1206
+	 * @param string $field_name               The field name retrieving the DateTime object.
1207
+	 * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1208
+	 * @throws EE_Error an error is set and false returned.  If the field IS an
1209
+	 *                                         EE_Datetime_Field and but the field value is null, then
1210
+	 *                                         just null is returned (because that indicates that likely
1211
+	 *                                         this field is nullable).
1212
+	 * @throws InvalidArgumentException
1213
+	 * @throws InvalidDataTypeException
1214
+	 * @throws InvalidInterfaceException
1215
+	 * @throws ReflectionException
1216
+	 */
1217
+	public function get_DateTime_object($field_name)
1218
+	{
1219
+		$field_settings = $this->get_model()->field_settings_for($field_name);
1220
+		if (! $field_settings instanceof EE_Datetime_Field) {
1221
+			EE_Error::add_error(
1222
+				sprintf(
1223
+					esc_html__(
1224
+						'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1225
+						'event_espresso'
1226
+					),
1227
+					$field_name
1228
+				),
1229
+				__FILE__,
1230
+				__FUNCTION__,
1231
+				__LINE__
1232
+			);
1233
+			return false;
1234
+		}
1235
+		return isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime
1236
+			? clone $this->_fields[ $field_name ]
1237
+			: null;
1238
+	}
1239
+
1240
+
1241
+	/**
1242
+	 * To be used in template to immediately echo out the value, and format it for output.
1243
+	 * Eg, should call stripslashes and whatnot before echoing
1244
+	 *
1245
+	 * @param string $field_name      the name of the field as it appears in the DB
1246
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1247
+	 *                                (in cases where the same property may be used for different outputs
1248
+	 *                                - i.e. datetime, money etc.)
1249
+	 * @return void
1250
+	 * @throws ReflectionException
1251
+	 * @throws InvalidArgumentException
1252
+	 * @throws InvalidInterfaceException
1253
+	 * @throws InvalidDataTypeException
1254
+	 * @throws EE_Error
1255
+	 */
1256
+	public function e($field_name, $extra_cache_ref = null)
1257
+	{
1258
+		echo wp_kses($this->get_pretty($field_name, $extra_cache_ref), AllowedTags::getWithFormTags());
1259
+	}
1260
+
1261
+
1262
+	/**
1263
+	 * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1264
+	 * can be easily used as the value of form input.
1265
+	 *
1266
+	 * @param string $field_name
1267
+	 * @return void
1268
+	 * @throws ReflectionException
1269
+	 * @throws InvalidArgumentException
1270
+	 * @throws InvalidInterfaceException
1271
+	 * @throws InvalidDataTypeException
1272
+	 * @throws EE_Error
1273
+	 */
1274
+	public function f($field_name)
1275
+	{
1276
+		$this->e($field_name, 'form_input');
1277
+	}
1278
+
1279
+
1280
+	/**
1281
+	 * Same as `f()` but just returns the value instead of echoing it
1282
+	 *
1283
+	 * @param string $field_name
1284
+	 * @return string
1285
+	 * @throws ReflectionException
1286
+	 * @throws InvalidArgumentException
1287
+	 * @throws InvalidInterfaceException
1288
+	 * @throws InvalidDataTypeException
1289
+	 * @throws EE_Error
1290
+	 */
1291
+	public function get_f($field_name)
1292
+	{
1293
+		return (string) $this->get_pretty($field_name, 'form_input');
1294
+	}
1295
+
1296
+
1297
+	/**
1298
+	 * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1299
+	 * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1300
+	 * to see what options are available.
1301
+	 *
1302
+	 * @param string $field_name
1303
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1304
+	 *                                (in cases where the same property may be used for different outputs
1305
+	 *                                - i.e. datetime, money etc.)
1306
+	 * @return mixed
1307
+	 * @throws ReflectionException
1308
+	 * @throws InvalidArgumentException
1309
+	 * @throws InvalidInterfaceException
1310
+	 * @throws InvalidDataTypeException
1311
+	 * @throws EE_Error
1312
+	 */
1313
+	public function get_pretty($field_name, $extra_cache_ref = null)
1314
+	{
1315
+		return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1316
+	}
1317
+
1318
+
1319
+	/**
1320
+	 * This simply returns the datetime for the given field name
1321
+	 * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1322
+	 * (and the equivalent e_date, e_time, e_datetime).
1323
+	 *
1324
+	 * @access   protected
1325
+	 * @param string   $field_name   Field on the instantiated EE_Base_Class child object
1326
+	 * @param string   $dt_frmt      valid datetime format used for date
1327
+	 *                               (if '' then we just use the default on the field,
1328
+	 *                               if NULL we use the last-used format)
1329
+	 * @param string   $tm_frmt      Same as above except this is for time format
1330
+	 * @param string   $date_or_time if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1331
+	 * @param  boolean $echo         Whether the dtt is echoing using pretty echoing or just returned using vanilla get
1332
+	 * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1333
+	 *                               if field is not a valid dtt field, or void if echoing
1334
+	 * @throws ReflectionException
1335
+	 * @throws InvalidArgumentException
1336
+	 * @throws InvalidInterfaceException
1337
+	 * @throws InvalidDataTypeException
1338
+	 * @throws EE_Error
1339
+	 */
1340
+	protected function _get_datetime($field_name, $dt_frmt = '', $tm_frmt = '', $date_or_time = '', $echo = false)
1341
+	{
1342
+		// clear cached property
1343
+		$this->_clear_cached_property($field_name);
1344
+		// reset format properties because they are used in get()
1345
+		$this->_dt_frmt = $dt_frmt !== '' ? $dt_frmt : $this->_dt_frmt;
1346
+		$this->_tm_frmt = $tm_frmt !== '' ? $tm_frmt : $this->_tm_frmt;
1347
+		if ($echo) {
1348
+			$this->e($field_name, $date_or_time);
1349
+			return '';
1350
+		}
1351
+		return $this->get($field_name, $date_or_time);
1352
+	}
1353
+
1354
+
1355
+	/**
1356
+	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1357
+	 * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1358
+	 * other echoes the pretty value for dtt)
1359
+	 *
1360
+	 * @param  string $field_name name of model object datetime field holding the value
1361
+	 * @param  string $format     format for the date returned (if NULL we use default in dt_frmt property)
1362
+	 * @return string            datetime value formatted
1363
+	 * @throws ReflectionException
1364
+	 * @throws InvalidArgumentException
1365
+	 * @throws InvalidInterfaceException
1366
+	 * @throws InvalidDataTypeException
1367
+	 * @throws EE_Error
1368
+	 */
1369
+	public function get_date($field_name, $format = '')
1370
+	{
1371
+		return $this->_get_datetime($field_name, $format, null, 'D');
1372
+	}
1373
+
1374
+
1375
+	/**
1376
+	 * @param        $field_name
1377
+	 * @param string $format
1378
+	 * @throws ReflectionException
1379
+	 * @throws InvalidArgumentException
1380
+	 * @throws InvalidInterfaceException
1381
+	 * @throws InvalidDataTypeException
1382
+	 * @throws EE_Error
1383
+	 */
1384
+	public function e_date($field_name, $format = '')
1385
+	{
1386
+		$this->_get_datetime($field_name, $format, null, 'D', true);
1387
+	}
1388
+
1389
+
1390
+	/**
1391
+	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1392
+	 * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1393
+	 * other echoes the pretty value for dtt)
1394
+	 *
1395
+	 * @param  string $field_name name of model object datetime field holding the value
1396
+	 * @param  string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1397
+	 * @return string             datetime value formatted
1398
+	 * @throws ReflectionException
1399
+	 * @throws InvalidArgumentException
1400
+	 * @throws InvalidInterfaceException
1401
+	 * @throws InvalidDataTypeException
1402
+	 * @throws EE_Error
1403
+	 */
1404
+	public function get_time($field_name, $format = '')
1405
+	{
1406
+		return $this->_get_datetime($field_name, null, $format, 'T');
1407
+	}
1408
+
1409
+
1410
+	/**
1411
+	 * @param        $field_name
1412
+	 * @param string $format
1413
+	 * @throws ReflectionException
1414
+	 * @throws InvalidArgumentException
1415
+	 * @throws InvalidInterfaceException
1416
+	 * @throws InvalidDataTypeException
1417
+	 * @throws EE_Error
1418
+	 */
1419
+	public function e_time($field_name, $format = '')
1420
+	{
1421
+		$this->_get_datetime($field_name, null, $format, 'T', true);
1422
+	}
1423
+
1424
+
1425
+	/**
1426
+	 * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1427
+	 * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1428
+	 * other echoes the pretty value for dtt)
1429
+	 *
1430
+	 * @param  string $field_name name of model object datetime field holding the value
1431
+	 * @param  string $dt_frmt    format for the date returned (if NULL we use default in dt_frmt property)
1432
+	 * @param  string $tm_frmt    format for the time returned (if NULL we use default in tm_frmt property)
1433
+	 * @return string             datetime value formatted
1434
+	 * @throws ReflectionException
1435
+	 * @throws InvalidArgumentException
1436
+	 * @throws InvalidInterfaceException
1437
+	 * @throws InvalidDataTypeException
1438
+	 * @throws EE_Error
1439
+	 */
1440
+	public function get_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1441
+	{
1442
+		return $this->_get_datetime($field_name, $dt_frmt, $tm_frmt);
1443
+	}
1444
+
1445
+
1446
+	/**
1447
+	 * @param string $field_name
1448
+	 * @param string $dt_frmt
1449
+	 * @param string $tm_frmt
1450
+	 * @throws ReflectionException
1451
+	 * @throws InvalidArgumentException
1452
+	 * @throws InvalidInterfaceException
1453
+	 * @throws InvalidDataTypeException
1454
+	 * @throws EE_Error
1455
+	 */
1456
+	public function e_datetime($field_name, $dt_frmt = '', $tm_frmt = '')
1457
+	{
1458
+		$this->_get_datetime($field_name, $dt_frmt, $tm_frmt, null, true);
1459
+	}
1460
+
1461
+
1462
+	/**
1463
+	 * Get the i8ln value for a date using the WordPress @see date_i18n function.
1464
+	 *
1465
+	 * @param string $field_name The EE_Datetime_Field reference for the date being retrieved.
1466
+	 * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format
1467
+	 *                           on the object will be used.
1468
+	 * @return string Date and time string in set locale or false if no field exists for the given
1469
+	 * @throws ReflectionException
1470
+	 * @throws InvalidArgumentException
1471
+	 * @throws InvalidInterfaceException
1472
+	 * @throws InvalidDataTypeException
1473
+	 * @throws EE_Error
1474
+	 *                           field name.
1475
+	 */
1476
+	public function get_i18n_datetime($field_name, $format = '')
1477
+	{
1478
+		$format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1479
+		return date_i18n(
1480
+			$format,
1481
+			EEH_DTT_Helper::get_timestamp_with_offset(
1482
+				$this->get_raw($field_name),
1483
+				$this->_timezone
1484
+			)
1485
+		);
1486
+	}
1487
+
1488
+
1489
+	/**
1490
+	 * This method validates whether the given field name is a valid field on the model object as well as it is of a
1491
+	 * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
1492
+	 * thrown.
1493
+	 *
1494
+	 * @param  string $field_name The field name being checked
1495
+	 * @throws ReflectionException
1496
+	 * @throws InvalidArgumentException
1497
+	 * @throws InvalidInterfaceException
1498
+	 * @throws InvalidDataTypeException
1499
+	 * @throws EE_Error
1500
+	 * @return EE_Datetime_Field
1501
+	 */
1502
+	protected function _get_dtt_field_settings($field_name)
1503
+	{
1504
+		$field = $this->get_model()->field_settings_for($field_name);
1505
+		// check if field is dtt
1506
+		if ($field instanceof EE_Datetime_Field) {
1507
+			return $field;
1508
+		}
1509
+		throw new EE_Error(
1510
+			sprintf(
1511
+				esc_html__(
1512
+					'The field name "%s" has been requested for the EE_Base_Class datetime functions and it is not a valid EE_Datetime_Field.  Please check the spelling of the field and make sure it has been setup as a EE_Datetime_Field in the %s model constructor',
1513
+					'event_espresso'
1514
+				),
1515
+				$field_name,
1516
+				self::_get_model_classname(get_class($this))
1517
+			)
1518
+		);
1519
+	}
1520
+
1521
+
1522
+
1523
+
1524
+	/**
1525
+	 * NOTE ABOUT BELOW:
1526
+	 * These convenience date and time setters are for setting date and time independently.  In other words you might
1527
+	 * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1528
+	 * you want to set both date and time at the same time, you can just use the models default set($fieldname,$value)
1529
+	 * method and make sure you send the entire datetime value for setting.
1530
+	 */
1531
+	/**
1532
+	 * sets the time on a datetime property
1533
+	 *
1534
+	 * @access protected
1535
+	 * @param string|Datetime $time      a valid time string for php datetime functions (or DateTime object)
1536
+	 * @param string          $fieldname the name of the field the time is being set on (must match a EE_Datetime_Field)
1537
+	 * @throws ReflectionException
1538
+	 * @throws InvalidArgumentException
1539
+	 * @throws InvalidInterfaceException
1540
+	 * @throws InvalidDataTypeException
1541
+	 * @throws EE_Error
1542
+	 */
1543
+	protected function _set_time_for($time, $fieldname)
1544
+	{
1545
+		$this->_set_date_time('T', $time, $fieldname);
1546
+	}
1547
+
1548
+
1549
+	/**
1550
+	 * sets the date on a datetime property
1551
+	 *
1552
+	 * @access protected
1553
+	 * @param string|DateTime $date      a valid date string for php datetime functions ( or DateTime object)
1554
+	 * @param string          $fieldname the name of the field the date is being set on (must match a EE_Datetime_Field)
1555
+	 * @throws ReflectionException
1556
+	 * @throws InvalidArgumentException
1557
+	 * @throws InvalidInterfaceException
1558
+	 * @throws InvalidDataTypeException
1559
+	 * @throws EE_Error
1560
+	 */
1561
+	protected function _set_date_for($date, $fieldname)
1562
+	{
1563
+		$this->_set_date_time('D', $date, $fieldname);
1564
+	}
1565
+
1566
+
1567
+	/**
1568
+	 * This takes care of setting a date or time independently on a given model object property. This method also
1569
+	 * verifies that the given fieldname matches a model object property and is for a EE_Datetime_Field field
1570
+	 *
1571
+	 * @access protected
1572
+	 * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
1573
+	 * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
1574
+	 * @param string          $fieldname      the name of the field the date OR time is being set on (must match a
1575
+	 *                                        EE_Datetime_Field property)
1576
+	 * @throws ReflectionException
1577
+	 * @throws InvalidArgumentException
1578
+	 * @throws InvalidInterfaceException
1579
+	 * @throws InvalidDataTypeException
1580
+	 * @throws EE_Error
1581
+	 */
1582
+	protected function _set_date_time($what = 'T', $datetime_value, $fieldname)
1583
+	{
1584
+		$field = $this->_get_dtt_field_settings($fieldname);
1585
+		$field->set_timezone($this->_timezone);
1586
+		$field->set_date_format($this->_dt_frmt);
1587
+		$field->set_time_format($this->_tm_frmt);
1588
+		switch ($what) {
1589
+			case 'T':
1590
+				$this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_time(
1591
+					$datetime_value,
1592
+					$this->_fields[ $fieldname ]
1593
+				);
1594
+				$this->_has_changes = true;
1595
+				break;
1596
+			case 'D':
1597
+				$this->_fields[ $fieldname ] = $field->prepare_for_set_with_new_date(
1598
+					$datetime_value,
1599
+					$this->_fields[ $fieldname ]
1600
+				);
1601
+				$this->_has_changes = true;
1602
+				break;
1603
+			case 'B':
1604
+				$this->_fields[ $fieldname ] = $field->prepare_for_set($datetime_value);
1605
+				$this->_has_changes = true;
1606
+				break;
1607
+		}
1608
+		$this->_clear_cached_property($fieldname);
1609
+	}
1610
+
1611
+
1612
+	/**
1613
+	 * This will return a timestamp for the website timezone but ONLY when the current website timezone is different
1614
+	 * than the timezone set for the website. NOTE, this currently only works well with methods that return values.  If
1615
+	 * you use it with methods that echo values the $_timestamp property may not get reset to its original value and
1616
+	 * that could lead to some unexpected results!
1617
+	 *
1618
+	 * @access public
1619
+	 * @param string $field_name               This is the name of the field on the object that contains the date/time
1620
+	 *                                         value being returned.
1621
+	 * @param string $callback                 must match a valid method in this class (defaults to get_datetime)
1622
+	 * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
1623
+	 * @param string $prepend                  You can include something to prepend on the timestamp
1624
+	 * @param string $append                   You can include something to append on the timestamp
1625
+	 * @throws ReflectionException
1626
+	 * @throws InvalidArgumentException
1627
+	 * @throws InvalidInterfaceException
1628
+	 * @throws InvalidDataTypeException
1629
+	 * @throws EE_Error
1630
+	 * @return string timestamp
1631
+	 */
1632
+	public function display_in_my_timezone(
1633
+		$field_name,
1634
+		$callback = 'get_datetime',
1635
+		$args = null,
1636
+		$prepend = '',
1637
+		$append = ''
1638
+	) {
1639
+		$timezone = EEH_DTT_Helper::get_timezone();
1640
+		if ($timezone === $this->_timezone) {
1641
+			return '';
1642
+		}
1643
+		$original_timezone = $this->_timezone;
1644
+		$this->set_timezone($timezone);
1645
+		$fn = (array) $field_name;
1646
+		$args = array_merge($fn, (array) $args);
1647
+		if (! method_exists($this, $callback)) {
1648
+			throw new EE_Error(
1649
+				sprintf(
1650
+					esc_html__(
1651
+						'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
1652
+						'event_espresso'
1653
+					),
1654
+					$callback
1655
+				)
1656
+			);
1657
+		}
1658
+		$args = (array) $args;
1659
+		$return = $prepend . call_user_func_array(array($this, $callback), $args) . $append;
1660
+		$this->set_timezone($original_timezone);
1661
+		return $return;
1662
+	}
1663
+
1664
+
1665
+	/**
1666
+	 * Deletes this model object.
1667
+	 * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
1668
+	 * override
1669
+	 * `EE_Base_Class::_delete` NOT this class.
1670
+	 *
1671
+	 * @return boolean | int
1672
+	 * @throws ReflectionException
1673
+	 * @throws InvalidArgumentException
1674
+	 * @throws InvalidInterfaceException
1675
+	 * @throws InvalidDataTypeException
1676
+	 * @throws EE_Error
1677
+	 */
1678
+	public function delete()
1679
+	{
1680
+		/**
1681
+		 * Called just before the `EE_Base_Class::_delete` method call.
1682
+		 * Note:
1683
+		 * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1684
+		 * should be aware that `_delete` may not always result in a permanent delete.
1685
+		 * For example, `EE_Soft_Delete_Base_Class::_delete`
1686
+		 * soft deletes (trash) the object and does not permanently delete it.
1687
+		 *
1688
+		 * @param EE_Base_Class $model_object about to be 'deleted'
1689
+		 */
1690
+		do_action('AHEE__EE_Base_Class__delete__before', $this);
1691
+		$result = $this->_delete();
1692
+		/**
1693
+		 * Called just after the `EE_Base_Class::_delete` method call.
1694
+		 * Note:
1695
+		 * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1696
+		 * should be aware that `_delete` may not always result in a permanent delete.
1697
+		 * For example `EE_Soft_Base_Class::_delete`
1698
+		 * soft deletes (trash) the object and does not permanently delete it.
1699
+		 *
1700
+		 * @param EE_Base_Class $model_object that was just 'deleted'
1701
+		 * @param boolean       $result
1702
+		 */
1703
+		do_action('AHEE__EE_Base_Class__delete__end', $this, $result);
1704
+		return $result;
1705
+	}
1706
+
1707
+
1708
+	/**
1709
+	 * Calls the specific delete method for the instantiated class.
1710
+	 * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
1711
+	 * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
1712
+	 * `EE_Base_Class::delete`
1713
+	 *
1714
+	 * @return bool|int
1715
+	 * @throws ReflectionException
1716
+	 * @throws InvalidArgumentException
1717
+	 * @throws InvalidInterfaceException
1718
+	 * @throws InvalidDataTypeException
1719
+	 * @throws EE_Error
1720
+	 */
1721
+	protected function _delete()
1722
+	{
1723
+		return $this->delete_permanently();
1724
+	}
1725
+
1726
+
1727
+	/**
1728
+	 * Deletes this model object permanently from db
1729
+	 * (but keep in mind related models may block the delete and return an error)
1730
+	 *
1731
+	 * @return bool | int
1732
+	 * @throws ReflectionException
1733
+	 * @throws InvalidArgumentException
1734
+	 * @throws InvalidInterfaceException
1735
+	 * @throws InvalidDataTypeException
1736
+	 * @throws EE_Error
1737
+	 */
1738
+	public function delete_permanently()
1739
+	{
1740
+		/**
1741
+		 * Called just before HARD deleting a model object
1742
+		 *
1743
+		 * @param EE_Base_Class $model_object about to be 'deleted'
1744
+		 */
1745
+		do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
1746
+		$model = $this->get_model();
1747
+		$result = $model->delete_permanently_by_ID($this->ID());
1748
+		$this->refresh_cache_of_related_objects();
1749
+		/**
1750
+		 * Called just after HARD deleting a model object
1751
+		 *
1752
+		 * @param EE_Base_Class $model_object that was just 'deleted'
1753
+		 * @param boolean       $result
1754
+		 */
1755
+		do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
1756
+		return $result;
1757
+	}
1758
+
1759
+
1760
+	/**
1761
+	 * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
1762
+	 * related model objects
1763
+	 *
1764
+	 * @throws ReflectionException
1765
+	 * @throws InvalidArgumentException
1766
+	 * @throws InvalidInterfaceException
1767
+	 * @throws InvalidDataTypeException
1768
+	 * @throws EE_Error
1769
+	 */
1770
+	public function refresh_cache_of_related_objects()
1771
+	{
1772
+		$model = $this->get_model();
1773
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1774
+			if (! empty($this->_model_relations[ $relation_name ])) {
1775
+				$related_objects = $this->_model_relations[ $relation_name ];
1776
+				if ($relation_obj instanceof EE_Belongs_To_Relation) {
1777
+					// this relation only stores a single model object, not an array
1778
+					// but let's make it consistent
1779
+					$related_objects = array($related_objects);
1780
+				}
1781
+				foreach ($related_objects as $related_object) {
1782
+					// only refresh their cache if they're in memory
1783
+					if ($related_object instanceof EE_Base_Class) {
1784
+						$related_object->clear_cache(
1785
+							$model->get_this_model_name(),
1786
+							$this
1787
+						);
1788
+					}
1789
+				}
1790
+			}
1791
+		}
1792
+	}
1793
+
1794
+
1795
+	/**
1796
+	 *        Saves this object to the database. An array may be supplied to set some values on this
1797
+	 * object just before saving.
1798
+	 *
1799
+	 * @access public
1800
+	 * @param array $set_cols_n_values  keys are field names, values are their new values,
1801
+	 *                                  if provided during the save() method
1802
+	 *                                  (often client code will change the fields' values before calling save)
1803
+	 * @return false|int|string         1 on a successful update;
1804
+	 *                                  the ID of the new entry on insert;
1805
+	 *                                  0 on failure, or if the model object isn't allowed to persist
1806
+	 *                                  (as determined by EE_Base_Class::allow_persist())
1807
+	 * @throws InvalidInterfaceException
1808
+	 * @throws InvalidDataTypeException
1809
+	 * @throws EE_Error
1810
+	 * @throws InvalidArgumentException
1811
+	 * @throws ReflectionException
1812
+	 * @throws ReflectionException
1813
+	 * @throws ReflectionException
1814
+	 */
1815
+	public function save($set_cols_n_values = array())
1816
+	{
1817
+		$model = $this->get_model();
1818
+		/**
1819
+		 * Filters the fields we're about to save on the model object
1820
+		 *
1821
+		 * @param array         $set_cols_n_values
1822
+		 * @param EE_Base_Class $model_object
1823
+		 */
1824
+		$set_cols_n_values = (array) apply_filters(
1825
+			'FHEE__EE_Base_Class__save__set_cols_n_values',
1826
+			$set_cols_n_values,
1827
+			$this
1828
+		);
1829
+		// set attributes as provided in $set_cols_n_values
1830
+		foreach ($set_cols_n_values as $column => $value) {
1831
+			$this->set($column, $value);
1832
+		}
1833
+		// no changes ? then don't do anything
1834
+		if (! $this->_has_changes && $this->ID() && $model->get_primary_key_field()->is_auto_increment()) {
1835
+			return 0;
1836
+		}
1837
+		/**
1838
+		 * Saving a model object.
1839
+		 * Before we perform a save, this action is fired.
1840
+		 *
1841
+		 * @param EE_Base_Class $model_object the model object about to be saved.
1842
+		 */
1843
+		do_action('AHEE__EE_Base_Class__save__begin', $this);
1844
+		if (! $this->allow_persist()) {
1845
+			return 0;
1846
+		}
1847
+		// now get current attribute values
1848
+		$save_cols_n_values = $this->_fields;
1849
+		// if the object already has an ID, update it. Otherwise, insert it
1850
+		// also: change the assumption about values passed to the model NOT being prepare dby the model object.
1851
+		// They have been
1852
+		$old_assumption_concerning_value_preparation = $model
1853
+			->get_assumption_concerning_values_already_prepared_by_model_object();
1854
+		$model->assume_values_already_prepared_by_model_object(true);
1855
+		// does this model have an autoincrement PK?
1856
+		if ($model->has_primary_key_field()) {
1857
+			if ($model->get_primary_key_field()->is_auto_increment()) {
1858
+				// ok check if it's set, if so: update; if not, insert
1859
+				if (! empty($save_cols_n_values[ $model->primary_key_name() ])) {
1860
+					$results = $model->update_by_ID($save_cols_n_values, $this->ID());
1861
+				} else {
1862
+					unset($save_cols_n_values[ $model->primary_key_name() ]);
1863
+					$results = $model->insert($save_cols_n_values);
1864
+					if ($results) {
1865
+						// if successful, set the primary key
1866
+						// but don't use the normal SET method, because it will check if
1867
+						// an item with the same ID exists in the mapper & db, then
1868
+						// will find it in the db (because we just added it) and THAT object
1869
+						// will get added to the mapper before we can add this one!
1870
+						// but if we just avoid using the SET method, all that headache can be avoided
1871
+						$pk_field_name = $model->primary_key_name();
1872
+						$this->_fields[ $pk_field_name ] = $results;
1873
+						$this->_clear_cached_property($pk_field_name);
1874
+						$model->add_to_entity_map($this);
1875
+						$this->_update_cached_related_model_objs_fks();
1876
+					}
1877
+				}
1878
+			} else {// PK is NOT auto-increment
1879
+				// so check if one like it already exists in the db
1880
+				if ($model->exists_by_ID($this->ID())) {
1881
+					if (WP_DEBUG && ! $this->in_entity_map()) {
1882
+						throw new EE_Error(
1883
+							sprintf(
1884
+								esc_html__(
1885
+									'Using a model object %1$s that is NOT in the entity map, can lead to unexpected errors. You should either: %4$s 1. Put it in the entity mapper by calling %2$s %4$s 2. Discard this model object and use what is in the entity mapper %4$s 3. Fetch from the database using %3$s',
1886
+									'event_espresso'
1887
+								),
1888
+								get_class($this),
1889
+								get_class($model) . '::instance()->add_to_entity_map()',
1890
+								get_class($model) . '::instance()->get_one_by_ID()',
1891
+								'<br />'
1892
+							)
1893
+						);
1894
+					}
1895
+					$results = $model->update_by_ID($save_cols_n_values, $this->ID());
1896
+				} else {
1897
+					$results = $model->insert($save_cols_n_values);
1898
+					$this->_update_cached_related_model_objs_fks();
1899
+				}
1900
+			}
1901
+		} else {// there is NO primary key
1902
+			$already_in_db = false;
1903
+			foreach ($model->unique_indexes() as $index) {
1904
+				$uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
1905
+				if ($model->exists(array($uniqueness_where_params))) {
1906
+					$already_in_db = true;
1907
+				}
1908
+			}
1909
+			if ($already_in_db) {
1910
+				$combined_pk_fields_n_values = array_intersect_key(
1911
+					$save_cols_n_values,
1912
+					$model->get_combined_primary_key_fields()
1913
+				);
1914
+				$results = $model->update(
1915
+					$save_cols_n_values,
1916
+					$combined_pk_fields_n_values
1917
+				);
1918
+			} else {
1919
+				$results = $model->insert($save_cols_n_values);
1920
+			}
1921
+		}
1922
+		// restore the old assumption about values being prepared by the model object
1923
+		$model->assume_values_already_prepared_by_model_object(
1924
+			$old_assumption_concerning_value_preparation
1925
+		);
1926
+		/**
1927
+		 * After saving the model object this action is called
1928
+		 *
1929
+		 * @param EE_Base_Class $model_object which was just saved
1930
+		 * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
1931
+		 *                                    the new ID (or 0 if an error occurred and it wasn't updated)
1932
+		 */
1933
+		do_action('AHEE__EE_Base_Class__save__end', $this, $results);
1934
+		$this->_has_changes = false;
1935
+		return $results;
1936
+	}
1937
+
1938
+
1939
+	/**
1940
+	 * Updates the foreign key on related models objects pointing to this to have this model object's ID
1941
+	 * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1942
+	 * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1943
+	 * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1944
+	 * saved it to the db. We also create a registration and don't save it to the DB, but we DO cache it on the
1945
+	 * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1946
+	 * or not they exist in the DB (if they do, their DB records will be automatically updated)
1947
+	 *
1948
+	 * @return void
1949
+	 * @throws ReflectionException
1950
+	 * @throws InvalidArgumentException
1951
+	 * @throws InvalidInterfaceException
1952
+	 * @throws InvalidDataTypeException
1953
+	 * @throws EE_Error
1954
+	 */
1955
+	protected function _update_cached_related_model_objs_fks()
1956
+	{
1957
+		$model = $this->get_model();
1958
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1959
+			if ($relation_obj instanceof EE_Has_Many_Relation) {
1960
+				foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1961
+					$fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1962
+						$model->get_this_model_name()
1963
+					);
1964
+					$related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1965
+					if ($related_model_obj_in_cache->ID()) {
1966
+						$related_model_obj_in_cache->save();
1967
+					}
1968
+				}
1969
+			}
1970
+		}
1971
+	}
1972
+
1973
+
1974
+	/**
1975
+	 * Saves this model object and its NEW cached relations to the database.
1976
+	 * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
1977
+	 * In order for that to work, we would need to mark model objects as dirty/clean...
1978
+	 * because otherwise, there's a potential for infinite looping of saving
1979
+	 * Saves the cached related model objects, and ensures the relation between them
1980
+	 * and this object and properly setup
1981
+	 *
1982
+	 * @return int ID of new model object on save; 0 on failure+
1983
+	 * @throws ReflectionException
1984
+	 * @throws InvalidArgumentException
1985
+	 * @throws InvalidInterfaceException
1986
+	 * @throws InvalidDataTypeException
1987
+	 * @throws EE_Error
1988
+	 */
1989
+	public function save_new_cached_related_model_objs()
1990
+	{
1991
+		// make sure this has been saved
1992
+		if (! $this->ID()) {
1993
+			$id = $this->save();
1994
+		} else {
1995
+			$id = $this->ID();
1996
+		}
1997
+		// now save all the NEW cached model objects  (ie they don't exist in the DB)
1998
+		foreach ($this->get_model()->relation_settings() as $relationName => $relationObj) {
1999
+			if ($this->_model_relations[ $relationName ]) {
2000
+				// is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
2001
+				// or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
2002
+				/* @var $related_model_obj EE_Base_Class */
2003
+				if ($relationObj instanceof EE_Belongs_To_Relation) {
2004
+					// add a relation to that relation type (which saves the appropriate thing in the process)
2005
+					// but ONLY if it DOES NOT exist in the DB
2006
+					$related_model_obj = $this->_model_relations[ $relationName ];
2007
+					// if( ! $related_model_obj->ID()){
2008
+					$this->_add_relation_to($related_model_obj, $relationName);
2009
+					$related_model_obj->save_new_cached_related_model_objs();
2010
+					// }
2011
+				} else {
2012
+					foreach ($this->_model_relations[ $relationName ] as $related_model_obj) {
2013
+						// add a relation to that relation type (which saves the appropriate thing in the process)
2014
+						// but ONLY if it DOES NOT exist in the DB
2015
+						// if( ! $related_model_obj->ID()){
2016
+						$this->_add_relation_to($related_model_obj, $relationName);
2017
+						$related_model_obj->save_new_cached_related_model_objs();
2018
+						// }
2019
+					}
2020
+				}
2021
+			}
2022
+		}
2023
+		return $id;
2024
+	}
2025
+
2026
+
2027
+	/**
2028
+	 * for getting a model while instantiated.
2029
+	 *
2030
+	 * @return EEM_Base | EEM_CPT_Base
2031
+	 * @throws ReflectionException
2032
+	 * @throws InvalidArgumentException
2033
+	 * @throws InvalidInterfaceException
2034
+	 * @throws InvalidDataTypeException
2035
+	 * @throws EE_Error
2036
+	 */
2037
+	public function get_model()
2038
+	{
2039
+		if (! $this->_model) {
2040
+			$modelName = self::_get_model_classname(get_class($this));
2041
+			$this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
2042
+		} else {
2043
+			$this->_model->set_timezone($this->_timezone);
2044
+		}
2045
+		return $this->_model;
2046
+	}
2047
+
2048
+
2049
+	/**
2050
+	 * @param $props_n_values
2051
+	 * @param $classname
2052
+	 * @return mixed bool|EE_Base_Class|EEM_CPT_Base
2053
+	 * @throws ReflectionException
2054
+	 * @throws InvalidArgumentException
2055
+	 * @throws InvalidInterfaceException
2056
+	 * @throws InvalidDataTypeException
2057
+	 * @throws EE_Error
2058
+	 */
2059
+	protected static function _get_object_from_entity_mapper($props_n_values, $classname)
2060
+	{
2061
+		// TODO: will not work for Term_Relationships because they have no PK!
2062
+		$primary_id_ref = self::_get_primary_key_name($classname);
2063
+		if (
2064
+			array_key_exists($primary_id_ref, $props_n_values)
2065
+			&& ! empty($props_n_values[ $primary_id_ref ])
2066
+		) {
2067
+			$id = $props_n_values[ $primary_id_ref ];
2068
+			return self::_get_model($classname)->get_from_entity_map($id);
2069
+		}
2070
+		return false;
2071
+	}
2072
+
2073
+
2074
+	/**
2075
+	 * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
2076
+	 * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
2077
+	 * primary key for the model AND it is not null, then we check the db. If there's a an object we return it.  If not
2078
+	 * we return false.
2079
+	 *
2080
+	 * @param  array  $props_n_values   incoming array of properties and their values
2081
+	 * @param  string $classname        the classname of the child class
2082
+	 * @param null    $timezone
2083
+	 * @param array   $date_formats     incoming date_formats in an array where the first value is the
2084
+	 *                                  date_format and the second value is the time format
2085
+	 * @return mixed (EE_Base_Class|bool)
2086
+	 * @throws InvalidArgumentException
2087
+	 * @throws InvalidInterfaceException
2088
+	 * @throws InvalidDataTypeException
2089
+	 * @throws EE_Error
2090
+	 * @throws ReflectionException
2091
+	 * @throws ReflectionException
2092
+	 * @throws ReflectionException
2093
+	 */
2094
+	protected static function _check_for_object($props_n_values, $classname, $timezone = null, $date_formats = array())
2095
+	{
2096
+		$existing = null;
2097
+		$model = self::_get_model($classname, $timezone);
2098
+		if ($model->has_primary_key_field()) {
2099
+			$primary_id_ref = self::_get_primary_key_name($classname);
2100
+			if (
2101
+				array_key_exists($primary_id_ref, $props_n_values)
2102
+				&& ! empty($props_n_values[ $primary_id_ref ])
2103
+			) {
2104
+				$existing = $model->get_one_by_ID(
2105
+					$props_n_values[ $primary_id_ref ]
2106
+				);
2107
+			}
2108
+		} elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
2109
+			// no primary key on this model, but there's still a matching item in the DB
2110
+			$existing = self::_get_model($classname, $timezone)->get_one_by_ID(
2111
+				self::_get_model($classname, $timezone)
2112
+					->get_index_primary_key_string($props_n_values)
2113
+			);
2114
+		}
2115
+		if ($existing) {
2116
+			// set date formats if present before setting values
2117
+			if (! empty($date_formats) && is_array($date_formats)) {
2118
+				$existing->set_date_format($date_formats[0]);
2119
+				$existing->set_time_format($date_formats[1]);
2120
+			} else {
2121
+				// set default formats for date and time
2122
+				$existing->set_date_format(get_option('date_format'));
2123
+				$existing->set_time_format(get_option('time_format'));
2124
+			}
2125
+			foreach ($props_n_values as $property => $field_value) {
2126
+				$existing->set($property, $field_value);
2127
+			}
2128
+			return $existing;
2129
+		}
2130
+		return false;
2131
+	}
2132
+
2133
+
2134
+	/**
2135
+	 * Gets the EEM_*_Model for this class
2136
+	 *
2137
+	 * @access public now, as this is more convenient
2138
+	 * @param      $classname
2139
+	 * @param null $timezone
2140
+	 * @throws ReflectionException
2141
+	 * @throws InvalidArgumentException
2142
+	 * @throws InvalidInterfaceException
2143
+	 * @throws InvalidDataTypeException
2144
+	 * @throws EE_Error
2145
+	 * @return EEM_Base
2146
+	 */
2147
+	protected static function _get_model($classname, $timezone = null)
2148
+	{
2149
+		// find model for this class
2150
+		if (! $classname) {
2151
+			throw new EE_Error(
2152
+				sprintf(
2153
+					esc_html__(
2154
+						'What were you thinking calling _get_model(%s)?? You need to specify the class name',
2155
+						'event_espresso'
2156
+					),
2157
+					$classname
2158
+				)
2159
+			);
2160
+		}
2161
+		$modelName = self::_get_model_classname($classname);
2162
+		return self::_get_model_instance_with_name($modelName, $timezone);
2163
+	}
2164
+
2165
+
2166
+	/**
2167
+	 * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
2168
+	 *
2169
+	 * @param string $model_classname
2170
+	 * @param null   $timezone
2171
+	 * @return EEM_Base
2172
+	 * @throws ReflectionException
2173
+	 * @throws InvalidArgumentException
2174
+	 * @throws InvalidInterfaceException
2175
+	 * @throws InvalidDataTypeException
2176
+	 * @throws EE_Error
2177
+	 */
2178
+	protected static function _get_model_instance_with_name($model_classname, $timezone = null)
2179
+	{
2180
+		$model_classname = str_replace('EEM_', '', $model_classname);
2181
+		$model = EE_Registry::instance()->load_model($model_classname);
2182
+		$model->set_timezone($timezone);
2183
+		return $model;
2184
+	}
2185
+
2186
+
2187
+	/**
2188
+	 * If a model name is provided (eg Registration), gets the model classname for that model.
2189
+	 * Also works if a model class's classname is provided (eg EE_Registration).
2190
+	 *
2191
+	 * @param null $model_name
2192
+	 * @return string like EEM_Attendee
2193
+	 */
2194
+	private static function _get_model_classname($model_name = null)
2195
+	{
2196
+		if (strpos($model_name, 'EE_') === 0) {
2197
+			$model_classname = str_replace('EE_', 'EEM_', $model_name);
2198
+		} else {
2199
+			$model_classname = 'EEM_' . $model_name;
2200
+		}
2201
+		return $model_classname;
2202
+	}
2203
+
2204
+
2205
+	/**
2206
+	 * returns the name of the primary key attribute
2207
+	 *
2208
+	 * @param null $classname
2209
+	 * @throws ReflectionException
2210
+	 * @throws InvalidArgumentException
2211
+	 * @throws InvalidInterfaceException
2212
+	 * @throws InvalidDataTypeException
2213
+	 * @throws EE_Error
2214
+	 * @return string
2215
+	 */
2216
+	protected static function _get_primary_key_name($classname = null)
2217
+	{
2218
+		if (! $classname) {
2219
+			throw new EE_Error(
2220
+				sprintf(
2221
+					esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
2222
+					$classname
2223
+				)
2224
+			);
2225
+		}
2226
+		return self::_get_model($classname)->get_primary_key_field()->get_name();
2227
+	}
2228
+
2229
+
2230
+	/**
2231
+	 * Gets the value of the primary key.
2232
+	 * If the object hasn't yet been saved, it should be whatever the model field's default was
2233
+	 * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
2234
+	 * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
2235
+	 *
2236
+	 * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
2237
+	 * @throws ReflectionException
2238
+	 * @throws InvalidArgumentException
2239
+	 * @throws InvalidInterfaceException
2240
+	 * @throws InvalidDataTypeException
2241
+	 * @throws EE_Error
2242
+	 */
2243
+	public function ID()
2244
+	{
2245
+		$model = $this->get_model();
2246
+		// now that we know the name of the variable, use a variable variable to get its value and return its
2247
+		if ($model->has_primary_key_field()) {
2248
+			return $this->_fields[ $model->primary_key_name() ];
2249
+		}
2250
+		return $model->get_index_primary_key_string($this->_fields);
2251
+	}
2252
+
2253
+
2254
+	/**
2255
+	 * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2256
+	 * model is related to a group of events, the $relationName should be 'Event', and should be a key in the EE
2257
+	 * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2258
+	 *
2259
+	 * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2260
+	 * @param string $relationName                     eg 'Events','Question',etc.
2261
+	 *                                                 an attendee to a group, you also want to specify which role they
2262
+	 *                                                 will have in that group. So you would use this parameter to
2263
+	 *                                                 specify array('role-column-name'=>'role-id')
2264
+	 * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2265
+	 *                                                 allow you to further constrict the relation to being added.
2266
+	 *                                                 However, keep in mind that the columns (keys) given must match a
2267
+	 *                                                 column on the JOIN table and currently only the HABTM models
2268
+	 *                                                 accept these additional conditions.  Also remember that if an
2269
+	 *                                                 exact match isn't found for these extra cols/val pairs, then a
2270
+	 *                                                 NEW row is created in the join table.
2271
+	 * @param null   $cache_id
2272
+	 * @throws ReflectionException
2273
+	 * @throws InvalidArgumentException
2274
+	 * @throws InvalidInterfaceException
2275
+	 * @throws InvalidDataTypeException
2276
+	 * @throws EE_Error
2277
+	 * @return EE_Base_Class the object the relation was added to
2278
+	 */
2279
+	public function _add_relation_to(
2280
+		$otherObjectModelObjectOrID,
2281
+		$relationName,
2282
+		$extra_join_model_fields_n_values = array(),
2283
+		$cache_id = null
2284
+	) {
2285
+		$model = $this->get_model();
2286
+		// if this thing exists in the DB, save the relation to the DB
2287
+		if ($this->ID()) {
2288
+			$otherObject = $model->add_relationship_to(
2289
+				$this,
2290
+				$otherObjectModelObjectOrID,
2291
+				$relationName,
2292
+				$extra_join_model_fields_n_values
2293
+			);
2294
+			// clear cache so future get_many_related and get_first_related() return new results.
2295
+			$this->clear_cache($relationName, $otherObject, true);
2296
+			if ($otherObject instanceof EE_Base_Class) {
2297
+				$otherObject->clear_cache($model->get_this_model_name(), $this);
2298
+			}
2299
+		} else {
2300
+			// this thing doesn't exist in the DB,  so just cache it
2301
+			if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2302
+				throw new EE_Error(
2303
+					sprintf(
2304
+						esc_html__(
2305
+							'Before a model object is saved to the database, calls to _add_relation_to must be passed an actual object, not just an ID. You provided %s as the model object to a %s',
2306
+							'event_espresso'
2307
+						),
2308
+						$otherObjectModelObjectOrID,
2309
+						get_class($this)
2310
+					)
2311
+				);
2312
+			}
2313
+			$otherObject = $otherObjectModelObjectOrID;
2314
+			$this->cache($relationName, $otherObjectModelObjectOrID, $cache_id);
2315
+		}
2316
+		if ($otherObject instanceof EE_Base_Class) {
2317
+			// fix the reciprocal relation too
2318
+			if ($otherObject->ID()) {
2319
+				// its saved so assumed relations exist in the DB, so we can just
2320
+				// clear the cache so future queries use the updated info in the DB
2321
+				$otherObject->clear_cache(
2322
+					$model->get_this_model_name(),
2323
+					null,
2324
+					true
2325
+				);
2326
+			} else {
2327
+				// it's not saved, so it caches relations like this
2328
+				$otherObject->cache($model->get_this_model_name(), $this);
2329
+			}
2330
+		}
2331
+		return $otherObject;
2332
+	}
2333
+
2334
+
2335
+	/**
2336
+	 * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2337
+	 * model is related to a group of events, the $relationName should be 'Events', and should be a key in the EE
2338
+	 * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2339
+	 * from the cache
2340
+	 *
2341
+	 * @param mixed  $otherObjectModelObjectOrID
2342
+	 *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2343
+	 *                to the DB yet
2344
+	 * @param string $relationName
2345
+	 * @param array  $where_query
2346
+	 *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2347
+	 *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2348
+	 *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2349
+	 *                remember that if an exact match isn't found for these extra cols/val pairs, then no row is
2350
+	 *                deleted.
2351
+	 * @return EE_Base_Class the relation was removed from
2352
+	 * @throws ReflectionException
2353
+	 * @throws InvalidArgumentException
2354
+	 * @throws InvalidInterfaceException
2355
+	 * @throws InvalidDataTypeException
2356
+	 * @throws EE_Error
2357
+	 */
2358
+	public function _remove_relation_to($otherObjectModelObjectOrID, $relationName, $where_query = array())
2359
+	{
2360
+		if ($this->ID()) {
2361
+			// if this exists in the DB, save the relation change to the DB too
2362
+			$otherObject = $this->get_model()->remove_relationship_to(
2363
+				$this,
2364
+				$otherObjectModelObjectOrID,
2365
+				$relationName,
2366
+				$where_query
2367
+			);
2368
+			$this->clear_cache(
2369
+				$relationName,
2370
+				$otherObject
2371
+			);
2372
+		} else {
2373
+			// this doesn't exist in the DB, just remove it from the cache
2374
+			$otherObject = $this->clear_cache(
2375
+				$relationName,
2376
+				$otherObjectModelObjectOrID
2377
+			);
2378
+		}
2379
+		if ($otherObject instanceof EE_Base_Class) {
2380
+			$otherObject->clear_cache(
2381
+				$this->get_model()->get_this_model_name(),
2382
+				$this
2383
+			);
2384
+		}
2385
+		return $otherObject;
2386
+	}
2387
+
2388
+
2389
+	/**
2390
+	 * Removes ALL the related things for the $relationName.
2391
+	 *
2392
+	 * @param string $relationName
2393
+	 * @param array  $where_query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2394
+	 * @return EE_Base_Class
2395
+	 * @throws ReflectionException
2396
+	 * @throws InvalidArgumentException
2397
+	 * @throws InvalidInterfaceException
2398
+	 * @throws InvalidDataTypeException
2399
+	 * @throws EE_Error
2400
+	 */
2401
+	public function _remove_relations($relationName, $where_query_params = array())
2402
+	{
2403
+		if ($this->ID()) {
2404
+			// if this exists in the DB, save the relation change to the DB too
2405
+			$otherObjects = $this->get_model()->remove_relations(
2406
+				$this,
2407
+				$relationName,
2408
+				$where_query_params
2409
+			);
2410
+			$this->clear_cache(
2411
+				$relationName,
2412
+				null,
2413
+				true
2414
+			);
2415
+		} else {
2416
+			// this doesn't exist in the DB, just remove it from the cache
2417
+			$otherObjects = $this->clear_cache(
2418
+				$relationName,
2419
+				null,
2420
+				true
2421
+			);
2422
+		}
2423
+		if (is_array($otherObjects)) {
2424
+			foreach ($otherObjects as $otherObject) {
2425
+				$otherObject->clear_cache(
2426
+					$this->get_model()->get_this_model_name(),
2427
+					$this
2428
+				);
2429
+			}
2430
+		}
2431
+		return $otherObjects;
2432
+	}
2433
+
2434
+
2435
+	/**
2436
+	 * Gets all the related model objects of the specified type. Eg, if the current class if
2437
+	 * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
2438
+	 * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
2439
+	 * because we want to get even deleted items etc.
2440
+	 *
2441
+	 * @param string $relationName key in the model's _model_relations array
2442
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2443
+	 * @return EE_Base_Class[]     Results not necessarily indexed by IDs, because some results might not have primary
2444
+	 *                             keys or might not be saved yet. Consider using EEM_Base::get_IDs() on these
2445
+	 *                             results if you want IDs
2446
+	 * @throws ReflectionException
2447
+	 * @throws InvalidArgumentException
2448
+	 * @throws InvalidInterfaceException
2449
+	 * @throws InvalidDataTypeException
2450
+	 * @throws EE_Error
2451
+	 */
2452
+	public function get_many_related($relationName, $query_params = array())
2453
+	{
2454
+		if ($this->ID()) {
2455
+			// this exists in the DB, so get the related things from either the cache or the DB
2456
+			// if there are query parameters, forget about caching the related model objects.
2457
+			if ($query_params) {
2458
+				$related_model_objects = $this->get_model()->get_all_related(
2459
+					$this,
2460
+					$relationName,
2461
+					$query_params
2462
+				);
2463
+			} else {
2464
+				// did we already cache the result of this query?
2465
+				$cached_results = $this->get_all_from_cache($relationName);
2466
+				if (! $cached_results) {
2467
+					$related_model_objects = $this->get_model()->get_all_related(
2468
+						$this,
2469
+						$relationName,
2470
+						$query_params
2471
+					);
2472
+					// if no query parameters were passed, then we got all the related model objects
2473
+					// for that relation. We can cache them then.
2474
+					foreach ($related_model_objects as $related_model_object) {
2475
+						$this->cache($relationName, $related_model_object);
2476
+					}
2477
+				} else {
2478
+					$related_model_objects = $cached_results;
2479
+				}
2480
+			}
2481
+		} else {
2482
+			// this doesn't exist in the DB, so just get the related things from the cache
2483
+			$related_model_objects = $this->get_all_from_cache($relationName);
2484
+		}
2485
+		return $related_model_objects;
2486
+	}
2487
+
2488
+
2489
+	/**
2490
+	 * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2491
+	 * unless otherwise specified in the $query_params
2492
+	 *
2493
+	 * @param string $relation_name  model_name like 'Event', or 'Registration'
2494
+	 * @param array  $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2495
+	 * @param string $field_to_count name of field to count by. By default, uses primary key
2496
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2497
+	 *                               that by the setting $distinct to TRUE;
2498
+	 * @return int
2499
+	 * @throws ReflectionException
2500
+	 * @throws InvalidArgumentException
2501
+	 * @throws InvalidInterfaceException
2502
+	 * @throws InvalidDataTypeException
2503
+	 * @throws EE_Error
2504
+	 */
2505
+	public function count_related($relation_name, $query_params = array(), $field_to_count = null, $distinct = false)
2506
+	{
2507
+		return $this->get_model()->count_related(
2508
+			$this,
2509
+			$relation_name,
2510
+			$query_params,
2511
+			$field_to_count,
2512
+			$distinct
2513
+		);
2514
+	}
2515
+
2516
+
2517
+	/**
2518
+	 * Instead of getting the related model objects, simply sums up the values of the specified field.
2519
+	 * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2520
+	 *
2521
+	 * @param string $relation_name model_name like 'Event', or 'Registration'
2522
+	 * @param array  $query_params  @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2523
+	 * @param string $field_to_sum  name of field to count by.
2524
+	 *                              By default, uses primary key
2525
+	 *                              (which doesn't make much sense, so you should probably change it)
2526
+	 * @return int
2527
+	 * @throws ReflectionException
2528
+	 * @throws InvalidArgumentException
2529
+	 * @throws InvalidInterfaceException
2530
+	 * @throws InvalidDataTypeException
2531
+	 * @throws EE_Error
2532
+	 */
2533
+	public function sum_related($relation_name, $query_params = array(), $field_to_sum = null)
2534
+	{
2535
+		return $this->get_model()->sum_related(
2536
+			$this,
2537
+			$relation_name,
2538
+			$query_params,
2539
+			$field_to_sum
2540
+		);
2541
+	}
2542
+
2543
+
2544
+	/**
2545
+	 * Gets the first (ie, one) related model object of the specified type.
2546
+	 *
2547
+	 * @param string $relationName key in the model's _model_relations array
2548
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2549
+	 * @return EE_Base_Class (not an array, a single object)
2550
+	 * @throws ReflectionException
2551
+	 * @throws InvalidArgumentException
2552
+	 * @throws InvalidInterfaceException
2553
+	 * @throws InvalidDataTypeException
2554
+	 * @throws EE_Error
2555
+	 */
2556
+	public function get_first_related($relationName, $query_params = array())
2557
+	{
2558
+		$model = $this->get_model();
2559
+		if ($this->ID()) {// this exists in the DB, get from the cache OR the DB
2560
+			// if they've provided some query parameters, don't bother trying to cache the result
2561
+			// also make sure we're not caching the result of get_first_related
2562
+			// on a relation which should have an array of objects (because the cache might have an array of objects)
2563
+			if (
2564
+				$query_params
2565
+				|| ! $model->related_settings_for($relationName)
2566
+					 instanceof
2567
+					 EE_Belongs_To_Relation
2568
+			) {
2569
+				$related_model_object = $model->get_first_related(
2570
+					$this,
2571
+					$relationName,
2572
+					$query_params
2573
+				);
2574
+			} else {
2575
+				// first, check if we've already cached the result of this query
2576
+				$cached_result = $this->get_one_from_cache($relationName);
2577
+				if (! $cached_result) {
2578
+					$related_model_object = $model->get_first_related(
2579
+						$this,
2580
+						$relationName,
2581
+						$query_params
2582
+					);
2583
+					$this->cache($relationName, $related_model_object);
2584
+				} else {
2585
+					$related_model_object = $cached_result;
2586
+				}
2587
+			}
2588
+		} else {
2589
+			$related_model_object = null;
2590
+			// this doesn't exist in the Db,
2591
+			// but maybe the relation is of type belongs to, and so the related thing might
2592
+			if ($model->related_settings_for($relationName) instanceof EE_Belongs_To_Relation) {
2593
+				$related_model_object = $model->get_first_related(
2594
+					$this,
2595
+					$relationName,
2596
+					$query_params
2597
+				);
2598
+			}
2599
+			// this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
2600
+			// just get what's cached on this object
2601
+			if (! $related_model_object) {
2602
+				$related_model_object = $this->get_one_from_cache($relationName);
2603
+			}
2604
+		}
2605
+		return $related_model_object;
2606
+	}
2607
+
2608
+
2609
+	/**
2610
+	 * Does a delete on all related objects of type $relationName and removes
2611
+	 * the current model object's relation to them. If they can't be deleted (because
2612
+	 * of blocking related model objects) does nothing. If the related model objects are
2613
+	 * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2614
+	 * If this model object doesn't exist yet in the DB, just removes its related things
2615
+	 *
2616
+	 * @param string $relationName
2617
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2618
+	 * @return int how many deleted
2619
+	 * @throws ReflectionException
2620
+	 * @throws InvalidArgumentException
2621
+	 * @throws InvalidInterfaceException
2622
+	 * @throws InvalidDataTypeException
2623
+	 * @throws EE_Error
2624
+	 */
2625
+	public function delete_related($relationName, $query_params = array())
2626
+	{
2627
+		if ($this->ID()) {
2628
+			$count = $this->get_model()->delete_related(
2629
+				$this,
2630
+				$relationName,
2631
+				$query_params
2632
+			);
2633
+		} else {
2634
+			$count = count($this->get_all_from_cache($relationName));
2635
+			$this->clear_cache($relationName, null, true);
2636
+		}
2637
+		return $count;
2638
+	}
2639
+
2640
+
2641
+	/**
2642
+	 * Does a hard delete (ie, removes the DB row) on all related objects of type $relationName and removes
2643
+	 * the current model object's relation to them. If they can't be deleted (because
2644
+	 * of blocking related model objects) just does a soft delete on it instead, if possible.
2645
+	 * If the related thing isn't a soft-deletable model object, this function is identical
2646
+	 * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2647
+	 *
2648
+	 * @param string $relationName
2649
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2650
+	 * @return int how many deleted (including those soft deleted)
2651
+	 * @throws ReflectionException
2652
+	 * @throws InvalidArgumentException
2653
+	 * @throws InvalidInterfaceException
2654
+	 * @throws InvalidDataTypeException
2655
+	 * @throws EE_Error
2656
+	 */
2657
+	public function delete_related_permanently($relationName, $query_params = array())
2658
+	{
2659
+		if ($this->ID()) {
2660
+			$count = $this->get_model()->delete_related_permanently(
2661
+				$this,
2662
+				$relationName,
2663
+				$query_params
2664
+			);
2665
+		} else {
2666
+			$count = count($this->get_all_from_cache($relationName));
2667
+		}
2668
+		$this->clear_cache($relationName, null, true);
2669
+		return $count;
2670
+	}
2671
+
2672
+
2673
+	/**
2674
+	 * is_set
2675
+	 * Just a simple utility function children can use for checking if property exists
2676
+	 *
2677
+	 * @access  public
2678
+	 * @param  string $field_name property to check
2679
+	 * @return bool                              TRUE if existing,FALSE if not.
2680
+	 */
2681
+	public function is_set($field_name)
2682
+	{
2683
+		return isset($this->_fields[ $field_name ]);
2684
+	}
2685
+
2686
+
2687
+	/**
2688
+	 * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
2689
+	 * EE_Error exception if they don't
2690
+	 *
2691
+	 * @param  mixed (string|array) $properties properties to check
2692
+	 * @throws EE_Error
2693
+	 * @return bool                              TRUE if existing, throw EE_Error if not.
2694
+	 */
2695
+	protected function _property_exists($properties)
2696
+	{
2697
+		foreach ((array) $properties as $property_name) {
2698
+			// first make sure this property exists
2699
+			if (! $this->_fields[ $property_name ]) {
2700
+				throw new EE_Error(
2701
+					sprintf(
2702
+						esc_html__(
2703
+							'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
2704
+							'event_espresso'
2705
+						),
2706
+						$property_name
2707
+					)
2708
+				);
2709
+			}
2710
+		}
2711
+		return true;
2712
+	}
2713
+
2714
+
2715
+	/**
2716
+	 * This simply returns an array of model fields for this object
2717
+	 *
2718
+	 * @return array
2719
+	 * @throws ReflectionException
2720
+	 * @throws InvalidArgumentException
2721
+	 * @throws InvalidInterfaceException
2722
+	 * @throws InvalidDataTypeException
2723
+	 * @throws EE_Error
2724
+	 */
2725
+	public function model_field_array()
2726
+	{
2727
+		$fields = $this->get_model()->field_settings(false);
2728
+		$properties = array();
2729
+		// remove prepended underscore
2730
+		foreach ($fields as $field_name => $settings) {
2731
+			$properties[ $field_name ] = $this->get($field_name);
2732
+		}
2733
+		return $properties;
2734
+	}
2735
+
2736
+
2737
+	/**
2738
+	 * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2739
+	 * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2740
+	 * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments.
2741
+	 * Instead of requiring a plugin to extend the EE_Base_Class
2742
+	 * (which works fine is there's only 1 plugin, but when will that happen?)
2743
+	 * they can add a hook onto 'filters_hook_espresso__{className}__{methodName}'
2744
+	 * (eg, filters_hook_espresso__EE_Answer__my_great_function)
2745
+	 * and accepts 2 arguments: the object on which the function was called,
2746
+	 * and an array of the original arguments passed to the function.
2747
+	 * Whatever their callback function returns will be returned by this function.
2748
+	 * Example: in functions.php (or in a plugin):
2749
+	 *      add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3);
2750
+	 *      function my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2751
+	 *          $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2752
+	 *          return $previousReturnValue.$returnString;
2753
+	 *      }
2754
+	 * require('EE_Answer.class.php');
2755
+	 * $answer= EE_Answer::new_instance(array('REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'));
2756
+	 * echo $answer->my_callback('monkeys',100);
2757
+	 * //will output "you called my_callback! and passed args:monkeys,100"
2758
+	 *
2759
+	 * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2760
+	 * @param array  $args       array of original arguments passed to the function
2761
+	 * @throws EE_Error
2762
+	 * @return mixed whatever the plugin which calls add_filter decides
2763
+	 */
2764
+	public function __call($methodName, $args)
2765
+	{
2766
+		$className = get_class($this);
2767
+		$tagName = "FHEE__{$className}__{$methodName}";
2768
+		if (! has_filter($tagName)) {
2769
+			throw new EE_Error(
2770
+				sprintf(
2771
+					esc_html__(
2772
+						"Method %s on class %s does not exist! You can create one with the following code in functions.php or in a plugin: add_filter('%s','my_callback',10,3);function my_callback(\$previousReturnValue,EE_Base_Class \$object, \$argsArray){/*function body*/return \$whatever;}",
2773
+						'event_espresso'
2774
+					),
2775
+					$methodName,
2776
+					$className,
2777
+					$tagName
2778
+				)
2779
+			);
2780
+		}
2781
+		return apply_filters($tagName, null, $this, $args);
2782
+	}
2783
+
2784
+
2785
+	/**
2786
+	 * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
2787
+	 * A $previous_value can be specified in case there are many meta rows with the same key
2788
+	 *
2789
+	 * @param string $meta_key
2790
+	 * @param mixed  $meta_value
2791
+	 * @param mixed  $previous_value
2792
+	 * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
2793
+	 *                  NOTE: if the values haven't changed, returns 0
2794
+	 * @throws InvalidArgumentException
2795
+	 * @throws InvalidInterfaceException
2796
+	 * @throws InvalidDataTypeException
2797
+	 * @throws EE_Error
2798
+	 * @throws ReflectionException
2799
+	 */
2800
+	public function update_extra_meta($meta_key, $meta_value, $previous_value = null)
2801
+	{
2802
+		$query_params = array(
2803
+			array(
2804
+				'EXM_key'  => $meta_key,
2805
+				'OBJ_ID'   => $this->ID(),
2806
+				'EXM_type' => $this->get_model()->get_this_model_name(),
2807
+			),
2808
+		);
2809
+		if ($previous_value !== null) {
2810
+			$query_params[0]['EXM_value'] = $meta_value;
2811
+		}
2812
+		$existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2813
+		if (! $existing_rows_like_that) {
2814
+			return $this->add_extra_meta($meta_key, $meta_value);
2815
+		}
2816
+		foreach ($existing_rows_like_that as $existing_row) {
2817
+			$existing_row->save(array('EXM_value' => $meta_value));
2818
+		}
2819
+		return count($existing_rows_like_that);
2820
+	}
2821
+
2822
+
2823
+	/**
2824
+	 * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
2825
+	 * no other extra meta for this model object have the same key. Returns TRUE if the
2826
+	 * extra meta row was entered, false if not
2827
+	 *
2828
+	 * @param string  $meta_key
2829
+	 * @param mixed   $meta_value
2830
+	 * @param boolean $unique
2831
+	 * @return boolean
2832
+	 * @throws InvalidArgumentException
2833
+	 * @throws InvalidInterfaceException
2834
+	 * @throws InvalidDataTypeException
2835
+	 * @throws EE_Error
2836
+	 * @throws ReflectionException
2837
+	 * @throws ReflectionException
2838
+	 */
2839
+	public function add_extra_meta($meta_key, $meta_value, $unique = false)
2840
+	{
2841
+		if ($unique) {
2842
+			$existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
2843
+				array(
2844
+					array(
2845
+						'EXM_key'  => $meta_key,
2846
+						'OBJ_ID'   => $this->ID(),
2847
+						'EXM_type' => $this->get_model()->get_this_model_name(),
2848
+					),
2849
+				)
2850
+			);
2851
+			if ($existing_extra_meta) {
2852
+				return false;
2853
+			}
2854
+		}
2855
+		$new_extra_meta = EE_Extra_Meta::new_instance(
2856
+			array(
2857
+				'EXM_key'   => $meta_key,
2858
+				'EXM_value' => $meta_value,
2859
+				'OBJ_ID'    => $this->ID(),
2860
+				'EXM_type'  => $this->get_model()->get_this_model_name(),
2861
+			)
2862
+		);
2863
+		$new_extra_meta->save();
2864
+		return true;
2865
+	}
2866
+
2867
+
2868
+	/**
2869
+	 * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2870
+	 * is specified, only deletes extra meta records with that value.
2871
+	 *
2872
+	 * @param string $meta_key
2873
+	 * @param mixed  $meta_value
2874
+	 * @return int number of extra meta rows deleted
2875
+	 * @throws InvalidArgumentException
2876
+	 * @throws InvalidInterfaceException
2877
+	 * @throws InvalidDataTypeException
2878
+	 * @throws EE_Error
2879
+	 * @throws ReflectionException
2880
+	 */
2881
+	public function delete_extra_meta($meta_key, $meta_value = null)
2882
+	{
2883
+		$query_params = array(
2884
+			array(
2885
+				'EXM_key'  => $meta_key,
2886
+				'OBJ_ID'   => $this->ID(),
2887
+				'EXM_type' => $this->get_model()->get_this_model_name(),
2888
+			),
2889
+		);
2890
+		if ($meta_value !== null) {
2891
+			$query_params[0]['EXM_value'] = $meta_value;
2892
+		}
2893
+		return EEM_Extra_Meta::instance()->delete($query_params);
2894
+	}
2895
+
2896
+
2897
+	/**
2898
+	 * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
2899
+	 * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
2900
+	 * You can specify $default is case you haven't found the extra meta
2901
+	 *
2902
+	 * @param string  $meta_key
2903
+	 * @param boolean $single
2904
+	 * @param mixed   $default if we don't find anything, what should we return?
2905
+	 * @return mixed single value if $single; array if ! $single
2906
+	 * @throws ReflectionException
2907
+	 * @throws InvalidArgumentException
2908
+	 * @throws InvalidInterfaceException
2909
+	 * @throws InvalidDataTypeException
2910
+	 * @throws EE_Error
2911
+	 */
2912
+	public function get_extra_meta($meta_key, $single = false, $default = null)
2913
+	{
2914
+		if ($single) {
2915
+			$result = $this->get_first_related(
2916
+				'Extra_Meta',
2917
+				array(array('EXM_key' => $meta_key))
2918
+			);
2919
+			if ($result instanceof EE_Extra_Meta) {
2920
+				return $result->value();
2921
+			}
2922
+		} else {
2923
+			$results = $this->get_many_related(
2924
+				'Extra_Meta',
2925
+				array(array('EXM_key' => $meta_key))
2926
+			);
2927
+			if ($results) {
2928
+				$values = array();
2929
+				foreach ($results as $result) {
2930
+					if ($result instanceof EE_Extra_Meta) {
2931
+						$values[ $result->ID() ] = $result->value();
2932
+					}
2933
+				}
2934
+				return $values;
2935
+			}
2936
+		}
2937
+		// if nothing discovered yet return default.
2938
+		return apply_filters(
2939
+			'FHEE__EE_Base_Class__get_extra_meta__default_value',
2940
+			$default,
2941
+			$meta_key,
2942
+			$single,
2943
+			$this
2944
+		);
2945
+	}
2946
+
2947
+
2948
+	/**
2949
+	 * Returns a simple array of all the extra meta associated with this model object.
2950
+	 * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2951
+	 * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2952
+	 * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2953
+	 * If $one_of_each_key is false, it will return an array with the top-level keys being
2954
+	 * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2955
+	 * finally the extra meta's value as each sub-value. (eg
2956
+	 * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2957
+	 *
2958
+	 * @param boolean $one_of_each_key
2959
+	 * @return array
2960
+	 * @throws ReflectionException
2961
+	 * @throws InvalidArgumentException
2962
+	 * @throws InvalidInterfaceException
2963
+	 * @throws InvalidDataTypeException
2964
+	 * @throws EE_Error
2965
+	 */
2966
+	public function all_extra_meta_array($one_of_each_key = true)
2967
+	{
2968
+		$return_array = array();
2969
+		if ($one_of_each_key) {
2970
+			$extra_meta_objs = $this->get_many_related(
2971
+				'Extra_Meta',
2972
+				array('group_by' => 'EXM_key')
2973
+			);
2974
+			foreach ($extra_meta_objs as $extra_meta_obj) {
2975
+				if ($extra_meta_obj instanceof EE_Extra_Meta) {
2976
+					$return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2977
+				}
2978
+			}
2979
+		} else {
2980
+			$extra_meta_objs = $this->get_many_related('Extra_Meta');
2981
+			foreach ($extra_meta_objs as $extra_meta_obj) {
2982
+				if ($extra_meta_obj instanceof EE_Extra_Meta) {
2983
+					if (! isset($return_array[ $extra_meta_obj->key() ])) {
2984
+						$return_array[ $extra_meta_obj->key() ] = array();
2985
+					}
2986
+					$return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
2987
+				}
2988
+			}
2989
+		}
2990
+		return $return_array;
2991
+	}
2992
+
2993
+
2994
+	/**
2995
+	 * Gets a pretty nice displayable nice for this model object. Often overridden
2996
+	 *
2997
+	 * @return string
2998
+	 * @throws ReflectionException
2999
+	 * @throws InvalidArgumentException
3000
+	 * @throws InvalidInterfaceException
3001
+	 * @throws InvalidDataTypeException
3002
+	 * @throws EE_Error
3003
+	 */
3004
+	public function name()
3005
+	{
3006
+		// find a field that's not a text field
3007
+		$field_we_can_use = $this->get_model()->get_a_field_of_type('EE_Text_Field_Base');
3008
+		if ($field_we_can_use) {
3009
+			return $this->get($field_we_can_use->get_name());
3010
+		}
3011
+		$first_few_properties = $this->model_field_array();
3012
+		$first_few_properties = array_slice($first_few_properties, 0, 3);
3013
+		$name_parts = array();
3014
+		foreach ($first_few_properties as $name => $value) {
3015
+			$name_parts[] = "$name:$value";
3016
+		}
3017
+		return implode(',', $name_parts);
3018
+	}
3019
+
3020
+
3021
+	/**
3022
+	 * in_entity_map
3023
+	 * Checks if this model object has been proven to already be in the entity map
3024
+	 *
3025
+	 * @return boolean
3026
+	 * @throws ReflectionException
3027
+	 * @throws InvalidArgumentException
3028
+	 * @throws InvalidInterfaceException
3029
+	 * @throws InvalidDataTypeException
3030
+	 * @throws EE_Error
3031
+	 */
3032
+	public function in_entity_map()
3033
+	{
3034
+		// well, if we looked, did we find it in the entity map?
3035
+		return $this->ID() && $this->get_model()->get_from_entity_map($this->ID()) === $this;
3036
+	}
3037
+
3038
+
3039
+	/**
3040
+	 * refresh_from_db
3041
+	 * Makes sure the fields and values on this model object are in-sync with what's in the database.
3042
+	 *
3043
+	 * @throws ReflectionException
3044
+	 * @throws InvalidArgumentException
3045
+	 * @throws InvalidInterfaceException
3046
+	 * @throws InvalidDataTypeException
3047
+	 * @throws EE_Error if this model object isn't in the entity mapper (because then you should
3048
+	 * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
3049
+	 */
3050
+	public function refresh_from_db()
3051
+	{
3052
+		if ($this->ID() && $this->in_entity_map()) {
3053
+			$this->get_model()->refresh_entity_map_from_db($this->ID());
3054
+		} else {
3055
+			// if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
3056
+			// if it has an ID but it's not in the map, and you're asking me to refresh it
3057
+			// that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
3058
+			// absolutely nothing in it for this ID
3059
+			if (WP_DEBUG) {
3060
+				throw new EE_Error(
3061
+					sprintf(
3062
+						esc_html__(
3063
+							'Trying to refresh a model object with ID "%1$s" that\'s not in the entity map? First off: you should put it in the entity map by calling %2$s. Second off, if you want what\'s in the database right now, you should just call %3$s yourself and discard this model object.',
3064
+							'event_espresso'
3065
+						),
3066
+						$this->ID(),
3067
+						get_class($this->get_model()) . '::instance()->add_to_entity_map()',
3068
+						get_class($this->get_model()) . '::instance()->refresh_entity_map()'
3069
+					)
3070
+				);
3071
+			}
3072
+		}
3073
+	}
3074
+
3075
+
3076
+	/**
3077
+	 * Change $fields' values to $new_value_sql (which is a string of raw SQL)
3078
+	 *
3079
+	 * @since 4.9.80.p
3080
+	 * @param EE_Model_Field_Base[] $fields
3081
+	 * @param string $new_value_sql
3082
+	 *      example: 'column_name=123',
3083
+	 *      or 'column_name=column_name+1',
3084
+	 *      or 'column_name= CASE
3085
+	 *          WHEN (`column_name` + `other_column` + 5) <= `yet_another_column`
3086
+	 *          THEN `column_name` + 5
3087
+	 *          ELSE `column_name`
3088
+	 *      END'
3089
+	 *      Also updates $field on this model object with the latest value from the database.
3090
+	 * @return bool
3091
+	 * @throws EE_Error
3092
+	 * @throws InvalidArgumentException
3093
+	 * @throws InvalidDataTypeException
3094
+	 * @throws InvalidInterfaceException
3095
+	 * @throws ReflectionException
3096
+	 */
3097
+	protected function updateFieldsInDB($fields, $new_value_sql)
3098
+	{
3099
+		// First make sure this model object actually exists in the DB. It would be silly to try to update it in the DB
3100
+		// if it wasn't even there to start off.
3101
+		if (! $this->ID()) {
3102
+			$this->save();
3103
+		}
3104
+		global $wpdb;
3105
+		if (empty($fields)) {
3106
+			throw new InvalidArgumentException(
3107
+				esc_html__(
3108
+					'EE_Base_Class::updateFieldsInDB was passed an empty array of fields.',
3109
+					'event_espresso'
3110
+				)
3111
+			);
3112
+		}
3113
+		$first_field = reset($fields);
3114
+		$table_alias = $first_field->get_table_alias();
3115
+		foreach ($fields as $field) {
3116
+			if ($table_alias !== $field->get_table_alias()) {
3117
+				throw new InvalidArgumentException(
3118
+					sprintf(
3119
+						esc_html__(
3120
+							// @codingStandardsIgnoreStart
3121
+							'EE_Base_Class::updateFieldsInDB was passed fields for different tables ("%1$s" and "%2$s"), which is not supported. Instead, please call the method multiple times.',
3122
+							// @codingStandardsIgnoreEnd
3123
+							'event_espresso'
3124
+						),
3125
+						$table_alias,
3126
+						$field->get_table_alias()
3127
+					)
3128
+				);
3129
+			}
3130
+		}
3131
+		// Ok the fields are now known to all be for the same table. Proceed with creating the SQL to update it.
3132
+		$table_obj = $this->get_model()->get_table_obj_by_alias($table_alias);
3133
+		$table_pk_value = $this->ID();
3134
+		$table_name = $table_obj->get_table_name();
3135
+		if ($table_obj instanceof EE_Secondary_Table) {
3136
+			$table_pk_field_name = $table_obj->get_fk_on_table();
3137
+		} else {
3138
+			$table_pk_field_name = $table_obj->get_pk_column();
3139
+		}
3140
+
3141
+		$query =
3142
+			"UPDATE `{$table_name}`
3143 3143
             SET "
3144
-            . $new_value_sql
3145
-            . $wpdb->prepare(
3146
-                "
3144
+			. $new_value_sql
3145
+			. $wpdb->prepare(
3146
+				"
3147 3147
             WHERE `{$table_pk_field_name}` = %d;",
3148
-                $table_pk_value
3149
-            );
3150
-        $result = $wpdb->query($query);
3151
-        foreach ($fields as $field) {
3152
-            // If it was successful, we'd like to know the new value.
3153
-            // If it failed, we'd also like to know the new value.
3154
-            $new_value = $this->get_model()->get_var(
3155
-                $this->get_model()->alter_query_params_to_restrict_by_ID(
3156
-                    $this->get_model()->get_index_primary_key_string(
3157
-                        $this->model_field_array()
3158
-                    ),
3159
-                    array(
3160
-                        'default_where_conditions' => 'minimum',
3161
-                    )
3162
-                ),
3163
-                $field->get_name()
3164
-            );
3165
-            $this->set_from_db(
3166
-                $field->get_name(),
3167
-                $new_value
3168
-            );
3169
-        }
3170
-        return (bool) $result;
3171
-    }
3172
-
3173
-
3174
-    /**
3175
-     * Nudges $field_name's value by $quantity, without any conditionals (in comparison to bumpConditionally()).
3176
-     * Does not allow negative values, however.
3177
-     *
3178
-     * @since 4.9.80.p
3179
-     * @param array $fields_n_quantities keys are the field names, and values are the amount by which to bump them
3180
-     *                                   (positive or negative). One important gotcha: all these values must be
3181
-     *                                   on the same table (eg don't pass in one field for the posts table and
3182
-     *                                   another for the event meta table.)
3183
-     * @return bool
3184
-     * @throws EE_Error
3185
-     * @throws InvalidArgumentException
3186
-     * @throws InvalidDataTypeException
3187
-     * @throws InvalidInterfaceException
3188
-     * @throws ReflectionException
3189
-     */
3190
-    public function adjustNumericFieldsInDb(array $fields_n_quantities)
3191
-    {
3192
-        global $wpdb;
3193
-        if (empty($fields_n_quantities)) {
3194
-            // No fields to update? Well sure, we updated them to that value just fine.
3195
-            return true;
3196
-        }
3197
-        $fields = [];
3198
-        $set_sql_statements = [];
3199
-        foreach ($fields_n_quantities as $field_name => $quantity) {
3200
-            $field = $this->get_model()->field_settings_for($field_name, true);
3201
-            $fields[] = $field;
3202
-            $column_name = $field->get_table_column();
3203
-
3204
-            $abs_qty = absint($quantity);
3205
-            if ($quantity > 0) {
3206
-                // don't let the value be negative as often these fields are unsigned
3207
-                $set_sql_statements[] = $wpdb->prepare(
3208
-                    "`{$column_name}` = `{$column_name}` + %d",
3209
-                    $abs_qty
3210
-                );
3211
-            } else {
3212
-                $set_sql_statements[] = $wpdb->prepare(
3213
-                    "`{$column_name}` = CASE
3148
+				$table_pk_value
3149
+			);
3150
+		$result = $wpdb->query($query);
3151
+		foreach ($fields as $field) {
3152
+			// If it was successful, we'd like to know the new value.
3153
+			// If it failed, we'd also like to know the new value.
3154
+			$new_value = $this->get_model()->get_var(
3155
+				$this->get_model()->alter_query_params_to_restrict_by_ID(
3156
+					$this->get_model()->get_index_primary_key_string(
3157
+						$this->model_field_array()
3158
+					),
3159
+					array(
3160
+						'default_where_conditions' => 'minimum',
3161
+					)
3162
+				),
3163
+				$field->get_name()
3164
+			);
3165
+			$this->set_from_db(
3166
+				$field->get_name(),
3167
+				$new_value
3168
+			);
3169
+		}
3170
+		return (bool) $result;
3171
+	}
3172
+
3173
+
3174
+	/**
3175
+	 * Nudges $field_name's value by $quantity, without any conditionals (in comparison to bumpConditionally()).
3176
+	 * Does not allow negative values, however.
3177
+	 *
3178
+	 * @since 4.9.80.p
3179
+	 * @param array $fields_n_quantities keys are the field names, and values are the amount by which to bump them
3180
+	 *                                   (positive or negative). One important gotcha: all these values must be
3181
+	 *                                   on the same table (eg don't pass in one field for the posts table and
3182
+	 *                                   another for the event meta table.)
3183
+	 * @return bool
3184
+	 * @throws EE_Error
3185
+	 * @throws InvalidArgumentException
3186
+	 * @throws InvalidDataTypeException
3187
+	 * @throws InvalidInterfaceException
3188
+	 * @throws ReflectionException
3189
+	 */
3190
+	public function adjustNumericFieldsInDb(array $fields_n_quantities)
3191
+	{
3192
+		global $wpdb;
3193
+		if (empty($fields_n_quantities)) {
3194
+			// No fields to update? Well sure, we updated them to that value just fine.
3195
+			return true;
3196
+		}
3197
+		$fields = [];
3198
+		$set_sql_statements = [];
3199
+		foreach ($fields_n_quantities as $field_name => $quantity) {
3200
+			$field = $this->get_model()->field_settings_for($field_name, true);
3201
+			$fields[] = $field;
3202
+			$column_name = $field->get_table_column();
3203
+
3204
+			$abs_qty = absint($quantity);
3205
+			if ($quantity > 0) {
3206
+				// don't let the value be negative as often these fields are unsigned
3207
+				$set_sql_statements[] = $wpdb->prepare(
3208
+					"`{$column_name}` = `{$column_name}` + %d",
3209
+					$abs_qty
3210
+				);
3211
+			} else {
3212
+				$set_sql_statements[] = $wpdb->prepare(
3213
+					"`{$column_name}` = CASE
3214 3214
                        WHEN (`{$column_name}` >= %d)
3215 3215
                        THEN `{$column_name}` - %d
3216 3216
                        ELSE 0
3217 3217
                     END",
3218
-                    $abs_qty,
3219
-                    $abs_qty
3220
-                );
3221
-            }
3222
-        }
3223
-        return $this->updateFieldsInDB(
3224
-            $fields,
3225
-            implode(', ', $set_sql_statements)
3226
-        );
3227
-    }
3228
-
3229
-
3230
-    /**
3231
-     * Increases the value of the field $field_name_to_bump by $quantity, but only if the values of
3232
-     * $field_name_to_bump plus $field_name_affecting_total and $quantity won't exceed $limit_field_name's value.
3233
-     * For example, this is useful when bumping the value of TKT_reserved, TKT_sold, DTT_reserved or DTT_sold.
3234
-     * Returns true if the value was successfully bumped, and updates the value on this model object.
3235
-     * Otherwise returns false.
3236
-     *
3237
-     * @since 4.9.80.p
3238
-     * @param string $field_name_to_bump
3239
-     * @param string $field_name_affecting_total
3240
-     * @param string $limit_field_name
3241
-     * @param int    $quantity
3242
-     * @return bool
3243
-     * @throws EE_Error
3244
-     * @throws InvalidArgumentException
3245
-     * @throws InvalidDataTypeException
3246
-     * @throws InvalidInterfaceException
3247
-     * @throws ReflectionException
3248
-     */
3249
-    public function incrementFieldConditionallyInDb($field_name_to_bump, $field_name_affecting_total, $limit_field_name, $quantity)
3250
-    {
3251
-        global $wpdb;
3252
-        $field = $this->get_model()->field_settings_for($field_name_to_bump, true);
3253
-        $column_name = $field->get_table_column();
3254
-
3255
-        $field_affecting_total = $this->get_model()->field_settings_for($field_name_affecting_total, true);
3256
-        $column_affecting_total = $field_affecting_total->get_table_column();
3257
-
3258
-        $limiting_field = $this->get_model()->field_settings_for($limit_field_name, true);
3259
-        $limiting_column = $limiting_field->get_table_column();
3260
-        return $this->updateFieldsInDB(
3261
-            [$field],
3262
-            $wpdb->prepare(
3263
-                "`{$column_name}` =
3218
+					$abs_qty,
3219
+					$abs_qty
3220
+				);
3221
+			}
3222
+		}
3223
+		return $this->updateFieldsInDB(
3224
+			$fields,
3225
+			implode(', ', $set_sql_statements)
3226
+		);
3227
+	}
3228
+
3229
+
3230
+	/**
3231
+	 * Increases the value of the field $field_name_to_bump by $quantity, but only if the values of
3232
+	 * $field_name_to_bump plus $field_name_affecting_total and $quantity won't exceed $limit_field_name's value.
3233
+	 * For example, this is useful when bumping the value of TKT_reserved, TKT_sold, DTT_reserved or DTT_sold.
3234
+	 * Returns true if the value was successfully bumped, and updates the value on this model object.
3235
+	 * Otherwise returns false.
3236
+	 *
3237
+	 * @since 4.9.80.p
3238
+	 * @param string $field_name_to_bump
3239
+	 * @param string $field_name_affecting_total
3240
+	 * @param string $limit_field_name
3241
+	 * @param int    $quantity
3242
+	 * @return bool
3243
+	 * @throws EE_Error
3244
+	 * @throws InvalidArgumentException
3245
+	 * @throws InvalidDataTypeException
3246
+	 * @throws InvalidInterfaceException
3247
+	 * @throws ReflectionException
3248
+	 */
3249
+	public function incrementFieldConditionallyInDb($field_name_to_bump, $field_name_affecting_total, $limit_field_name, $quantity)
3250
+	{
3251
+		global $wpdb;
3252
+		$field = $this->get_model()->field_settings_for($field_name_to_bump, true);
3253
+		$column_name = $field->get_table_column();
3254
+
3255
+		$field_affecting_total = $this->get_model()->field_settings_for($field_name_affecting_total, true);
3256
+		$column_affecting_total = $field_affecting_total->get_table_column();
3257
+
3258
+		$limiting_field = $this->get_model()->field_settings_for($limit_field_name, true);
3259
+		$limiting_column = $limiting_field->get_table_column();
3260
+		return $this->updateFieldsInDB(
3261
+			[$field],
3262
+			$wpdb->prepare(
3263
+				"`{$column_name}` =
3264 3264
             CASE
3265 3265
                WHEN ((`{$column_name}` + `{$column_affecting_total}` + %d) <= `{$limiting_column}`) OR `{$limiting_column}` = %d
3266 3266
                THEN `{$column_name}` + %d
3267 3267
                ELSE `{$column_name}`
3268 3268
             END",
3269
-                $quantity,
3270
-                EE_INF_IN_DB,
3271
-                $quantity
3272
-            )
3273
-        );
3274
-    }
3275
-
3276
-
3277
-    /**
3278
-     * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
3279
-     * (probably a bad assumption they have made, oh well)
3280
-     *
3281
-     * @return string
3282
-     */
3283
-    public function __toString()
3284
-    {
3285
-        try {
3286
-            return sprintf('%s (%s)', $this->name(), $this->ID());
3287
-        } catch (Exception $e) {
3288
-            EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
3289
-            return '';
3290
-        }
3291
-    }
3292
-
3293
-
3294
-    /**
3295
-     * Clear related model objects if they're already in the DB, because otherwise when we
3296
-     * UN-serialize this model object we'll need to be careful to add them to the entity map.
3297
-     * This means if we have made changes to those related model objects, and want to unserialize
3298
-     * the this model object on a subsequent request, changes to those related model objects will be lost.
3299
-     * Instead, those related model objects should be directly serialized and stored.
3300
-     * Eg, the following won't work:
3301
-     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3302
-     * $att = $reg->attendee();
3303
-     * $att->set( 'ATT_fname', 'Dirk' );
3304
-     * update_option( 'my_option', serialize( $reg ) );
3305
-     * //END REQUEST
3306
-     * //START NEXT REQUEST
3307
-     * $reg = get_option( 'my_option' );
3308
-     * $reg->attendee()->save();
3309
-     * And would need to be replace with:
3310
-     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3311
-     * $att = $reg->attendee();
3312
-     * $att->set( 'ATT_fname', 'Dirk' );
3313
-     * update_option( 'my_option', serialize( $reg ) );
3314
-     * //END REQUEST
3315
-     * //START NEXT REQUEST
3316
-     * $att = get_option( 'my_option' );
3317
-     * $att->save();
3318
-     *
3319
-     * @return array
3320
-     * @throws ReflectionException
3321
-     * @throws InvalidArgumentException
3322
-     * @throws InvalidInterfaceException
3323
-     * @throws InvalidDataTypeException
3324
-     * @throws EE_Error
3325
-     */
3326
-    public function __sleep()
3327
-    {
3328
-        $model = $this->get_model();
3329
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
3330
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
3331
-                $classname = 'EE_' . $model->get_this_model_name();
3332
-                if (
3333
-                    $this->get_one_from_cache($relation_name) instanceof $classname
3334
-                    && $this->get_one_from_cache($relation_name)->ID()
3335
-                ) {
3336
-                    $this->clear_cache(
3337
-                        $relation_name,
3338
-                        $this->get_one_from_cache($relation_name)->ID()
3339
-                    );
3340
-                }
3341
-            }
3342
-        }
3343
-        $this->_props_n_values_provided_in_constructor = array();
3344
-        $properties_to_serialize = get_object_vars($this);
3345
-        // don't serialize the model. It's big and that risks recursion
3346
-        unset($properties_to_serialize['_model']);
3347
-        return array_keys($properties_to_serialize);
3348
-    }
3349
-
3350
-
3351
-    /**
3352
-     * restore _props_n_values_provided_in_constructor
3353
-     * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
3354
-     * and therefore should NOT be used to determine if state change has occurred since initial construction.
3355
-     * At best, you would only be able to detect if state change has occurred during THIS request.
3356
-     */
3357
-    public function __wakeup()
3358
-    {
3359
-        $this->_props_n_values_provided_in_constructor = $this->_fields;
3360
-    }
3361
-
3362
-
3363
-    /**
3364
-     * Usage of this magic method is to ensure any internally cached references to object instances that must remain
3365
-     * distinct with the clone host instance are also cloned.
3366
-     */
3367
-    public function __clone()
3368
-    {
3369
-        // handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3370
-        foreach ($this->_fields as $field => $value) {
3371
-            if ($value instanceof DateTime) {
3372
-                $this->_fields[ $field ] = clone $value;
3373
-            }
3374
-        }
3375
-    }
3269
+				$quantity,
3270
+				EE_INF_IN_DB,
3271
+				$quantity
3272
+			)
3273
+		);
3274
+	}
3275
+
3276
+
3277
+	/**
3278
+	 * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
3279
+	 * (probably a bad assumption they have made, oh well)
3280
+	 *
3281
+	 * @return string
3282
+	 */
3283
+	public function __toString()
3284
+	{
3285
+		try {
3286
+			return sprintf('%s (%s)', $this->name(), $this->ID());
3287
+		} catch (Exception $e) {
3288
+			EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
3289
+			return '';
3290
+		}
3291
+	}
3292
+
3293
+
3294
+	/**
3295
+	 * Clear related model objects if they're already in the DB, because otherwise when we
3296
+	 * UN-serialize this model object we'll need to be careful to add them to the entity map.
3297
+	 * This means if we have made changes to those related model objects, and want to unserialize
3298
+	 * the this model object on a subsequent request, changes to those related model objects will be lost.
3299
+	 * Instead, those related model objects should be directly serialized and stored.
3300
+	 * Eg, the following won't work:
3301
+	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3302
+	 * $att = $reg->attendee();
3303
+	 * $att->set( 'ATT_fname', 'Dirk' );
3304
+	 * update_option( 'my_option', serialize( $reg ) );
3305
+	 * //END REQUEST
3306
+	 * //START NEXT REQUEST
3307
+	 * $reg = get_option( 'my_option' );
3308
+	 * $reg->attendee()->save();
3309
+	 * And would need to be replace with:
3310
+	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3311
+	 * $att = $reg->attendee();
3312
+	 * $att->set( 'ATT_fname', 'Dirk' );
3313
+	 * update_option( 'my_option', serialize( $reg ) );
3314
+	 * //END REQUEST
3315
+	 * //START NEXT REQUEST
3316
+	 * $att = get_option( 'my_option' );
3317
+	 * $att->save();
3318
+	 *
3319
+	 * @return array
3320
+	 * @throws ReflectionException
3321
+	 * @throws InvalidArgumentException
3322
+	 * @throws InvalidInterfaceException
3323
+	 * @throws InvalidDataTypeException
3324
+	 * @throws EE_Error
3325
+	 */
3326
+	public function __sleep()
3327
+	{
3328
+		$model = $this->get_model();
3329
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
3330
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
3331
+				$classname = 'EE_' . $model->get_this_model_name();
3332
+				if (
3333
+					$this->get_one_from_cache($relation_name) instanceof $classname
3334
+					&& $this->get_one_from_cache($relation_name)->ID()
3335
+				) {
3336
+					$this->clear_cache(
3337
+						$relation_name,
3338
+						$this->get_one_from_cache($relation_name)->ID()
3339
+					);
3340
+				}
3341
+			}
3342
+		}
3343
+		$this->_props_n_values_provided_in_constructor = array();
3344
+		$properties_to_serialize = get_object_vars($this);
3345
+		// don't serialize the model. It's big and that risks recursion
3346
+		unset($properties_to_serialize['_model']);
3347
+		return array_keys($properties_to_serialize);
3348
+	}
3349
+
3350
+
3351
+	/**
3352
+	 * restore _props_n_values_provided_in_constructor
3353
+	 * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
3354
+	 * and therefore should NOT be used to determine if state change has occurred since initial construction.
3355
+	 * At best, you would only be able to detect if state change has occurred during THIS request.
3356
+	 */
3357
+	public function __wakeup()
3358
+	{
3359
+		$this->_props_n_values_provided_in_constructor = $this->_fields;
3360
+	}
3361
+
3362
+
3363
+	/**
3364
+	 * Usage of this magic method is to ensure any internally cached references to object instances that must remain
3365
+	 * distinct with the clone host instance are also cloned.
3366
+	 */
3367
+	public function __clone()
3368
+	{
3369
+		// handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3370
+		foreach ($this->_fields as $field => $value) {
3371
+			if ($value instanceof DateTime) {
3372
+				$this->_fields[ $field ] = clone $value;
3373
+			}
3374
+		}
3375
+	}
3376 3376
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Attendee.class.php 1 patch
Indentation   +742 added lines, -742 removed lines patch added patch discarded remove patch
@@ -25,746 +25,746 @@
 block discarded – undo
25 25
 class EE_Attendee extends EE_CPT_Base implements EEI_Contact, EEI_Address, EEI_Admin_Links, EEI_Attendee
26 26
 {
27 27
 
28
-    /**
29
-     * Sets some dynamic defaults
30
-     *
31
-     * @param array  $fieldValues
32
-     * @param bool   $bydb
33
-     * @param string $timezone
34
-     * @param array  $date_formats
35
-     * @throws EE_Error
36
-     */
37
-    protected function __construct($fieldValues = null, $bydb = false, $timezone = null, $date_formats = array())
38
-    {
39
-        if (! isset($fieldValues['ATT_full_name'])) {
40
-            $fname = isset($fieldValues['ATT_fname']) ? $fieldValues['ATT_fname'] . ' ' : '';
41
-            $lname = isset($fieldValues['ATT_lname']) ? $fieldValues['ATT_lname'] : '';
42
-            $fieldValues['ATT_full_name'] = $fname . $lname;
43
-        }
44
-        if (! isset($fieldValues['ATT_slug'])) {
45
-            // $fieldValues['ATT_slug'] = sanitize_key(wp_generate_password(20));
46
-            $fieldValues['ATT_slug'] = sanitize_title($fieldValues['ATT_full_name']);
47
-        }
48
-        if (! isset($fieldValues['ATT_short_bio']) && isset($fieldValues['ATT_bio'])) {
49
-            $fieldValues['ATT_short_bio'] = substr($fieldValues['ATT_bio'], 0, 50);
50
-        }
51
-        parent::__construct($fieldValues, $bydb, $timezone, $date_formats);
52
-    }
53
-
54
-
55
-    /**
56
-     * @param array  $props_n_values          incoming values
57
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
58
-     *                                        used.)
59
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
60
-     *                                        date_format and the second value is the time format
61
-     * @return EE_Attendee
62
-     * @throws EE_Error
63
-     */
64
-    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
65
-    {
66
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
67
-        return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
68
-    }
69
-
70
-
71
-    /**
72
-     * @param array  $props_n_values  incoming values from the database
73
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
74
-     *                                the website will be used.
75
-     * @return EE_Attendee
76
-     */
77
-    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
78
-    {
79
-        return new self($props_n_values, true, $timezone);
80
-    }
81
-
82
-
83
-    /**
84
-     *        Set Attendee First Name
85
-     *
86
-     * @access        public
87
-     * @param string $fname
88
-     * @throws EE_Error
89
-     */
90
-    public function set_fname($fname = '')
91
-    {
92
-        $this->set('ATT_fname', $fname);
93
-    }
94
-
95
-
96
-    /**
97
-     *        Set Attendee Last Name
98
-     *
99
-     * @access        public
100
-     * @param string $lname
101
-     * @throws EE_Error
102
-     */
103
-    public function set_lname($lname = '')
104
-    {
105
-        $this->set('ATT_lname', $lname);
106
-    }
107
-
108
-
109
-    /**
110
-     *        Set Attendee Address
111
-     *
112
-     * @access        public
113
-     * @param string $address
114
-     * @throws EE_Error
115
-     */
116
-    public function set_address($address = '')
117
-    {
118
-        $this->set('ATT_address', $address);
119
-    }
120
-
121
-
122
-    /**
123
-     *        Set Attendee Address2
124
-     *
125
-     * @access        public
126
-     * @param        string $address2
127
-     * @throws EE_Error
128
-     */
129
-    public function set_address2($address2 = '')
130
-    {
131
-        $this->set('ATT_address2', $address2);
132
-    }
133
-
134
-
135
-    /**
136
-     *        Set Attendee City
137
-     *
138
-     * @access        public
139
-     * @param        string $city
140
-     * @throws EE_Error
141
-     */
142
-    public function set_city($city = '')
143
-    {
144
-        $this->set('ATT_city', $city);
145
-    }
146
-
147
-
148
-    /**
149
-     *        Set Attendee State ID
150
-     *
151
-     * @access        public
152
-     * @param        int $STA_ID
153
-     * @throws EE_Error
154
-     */
155
-    public function set_state($STA_ID = 0)
156
-    {
157
-        $this->set('STA_ID', $STA_ID);
158
-    }
159
-
160
-
161
-    /**
162
-     *        Set Attendee Country ISO Code
163
-     *
164
-     * @access        public
165
-     * @param        string $CNT_ISO
166
-     * @throws EE_Error
167
-     */
168
-    public function set_country($CNT_ISO = '')
169
-    {
170
-        $this->set('CNT_ISO', $CNT_ISO);
171
-    }
172
-
173
-
174
-    /**
175
-     *        Set Attendee Zip/Postal Code
176
-     *
177
-     * @access        public
178
-     * @param        string $zip
179
-     * @throws EE_Error
180
-     */
181
-    public function set_zip($zip = '')
182
-    {
183
-        $this->set('ATT_zip', $zip);
184
-    }
185
-
186
-
187
-    /**
188
-     *        Set Attendee Email Address
189
-     *
190
-     * @access        public
191
-     * @param        string $email
192
-     * @throws EE_Error
193
-     */
194
-    public function set_email($email = '')
195
-    {
196
-        $this->set('ATT_email', $email);
197
-    }
198
-
199
-
200
-    /**
201
-     *        Set Attendee Phone
202
-     *
203
-     * @access        public
204
-     * @param        string $phone
205
-     * @throws EE_Error
206
-     */
207
-    public function set_phone($phone = '')
208
-    {
209
-        $this->set('ATT_phone', $phone);
210
-    }
211
-
212
-
213
-    /**
214
-     *        set deleted
215
-     *
216
-     * @access        public
217
-     * @param        bool $ATT_deleted
218
-     * @throws EE_Error
219
-     */
220
-    public function set_deleted($ATT_deleted = false)
221
-    {
222
-        $this->set('ATT_deleted', $ATT_deleted);
223
-    }
224
-
225
-
226
-    /**
227
-     * Returns the value for the post_author id saved with the cpt
228
-     *
229
-     * @since 4.5.0
230
-     * @return int
231
-     * @throws EE_Error
232
-     */
233
-    public function wp_user()
234
-    {
235
-        return $this->get('ATT_author');
236
-    }
237
-
238
-
239
-    /**
240
-     *        get Attendee First Name
241
-     *
242
-     * @access        public
243
-     * @return string
244
-     * @throws EE_Error
245
-     */
246
-    public function fname()
247
-    {
248
-        return $this->get('ATT_fname');
249
-    }
250
-
251
-
252
-    /**
253
-     * echoes out the attendee's first name
254
-     *
255
-     * @return void
256
-     * @throws EE_Error
257
-     */
258
-    public function e_full_name()
259
-    {
260
-        echo esc_html($this->full_name());
261
-    }
262
-
263
-
264
-    /**
265
-     * Returns the first and last name concatenated together with a space.
266
-     *
267
-     * @param bool $apply_html_entities
268
-     * @return string
269
-     * @throws EE_Error
270
-     */
271
-    public function full_name($apply_html_entities = false)
272
-    {
273
-        $full_name = array(
274
-            $this->fname(),
275
-            $this->lname(),
276
-        );
277
-        $full_name = array_filter($full_name);
278
-        $full_name = implode(' ', $full_name);
279
-        return $apply_html_entities ? htmlentities($full_name, ENT_QUOTES, 'UTF-8') : $full_name;
280
-    }
281
-
282
-
283
-    /**
284
-     * This returns the value of the `ATT_full_name` field which is usually equivalent to calling `full_name()` unless
285
-     * the post_title field has been directly modified in the db for the post (espresso_attendees post type) for this
286
-     * attendee.
287
-     *
288
-     * @param bool $apply_html_entities
289
-     * @return string
290
-     * @throws EE_Error
291
-     */
292
-    public function ATT_full_name($apply_html_entities = false)
293
-    {
294
-        return $apply_html_entities
295
-            ? htmlentities($this->get('ATT_full_name'), ENT_QUOTES, 'UTF-8')
296
-            : $this->get('ATT_full_name');
297
-    }
298
-
299
-
300
-    /**
301
-     *        get Attendee Last Name
302
-     *
303
-     * @access        public
304
-     * @return string
305
-     * @throws EE_Error
306
-     */
307
-    public function lname()
308
-    {
309
-        return $this->get('ATT_lname');
310
-    }
311
-
312
-
313
-    /**
314
-     * Gets the attendee's full address as an array so client code can decide hwo to display it
315
-     *
316
-     * @return array numerically indexed, with each part of the address that is known.
317
-     * Eg, if the user only responded to state and country,
318
-     * it would be array(0=>'Alabama',1=>'USA')
319
-     * @return array
320
-     * @throws EE_Error
321
-     */
322
-    public function full_address_as_array()
323
-    {
324
-        $full_address_array = array();
325
-        $initial_address_fields = array('ATT_address', 'ATT_address2', 'ATT_city',);
326
-        foreach ($initial_address_fields as $address_field_name) {
327
-            $address_fields_value = $this->get($address_field_name);
328
-            if (! empty($address_fields_value)) {
329
-                $full_address_array[] = $address_fields_value;
330
-            }
331
-        }
332
-        // now handle state and country
333
-        $state_obj = $this->state_obj();
334
-        if ($state_obj instanceof EE_State) {
335
-            $full_address_array[] = $state_obj->name();
336
-        }
337
-        $country_obj = $this->country_obj();
338
-        if ($country_obj instanceof EE_Country) {
339
-            $full_address_array[] = $country_obj->name();
340
-        }
341
-        // lastly get the xip
342
-        $zip_value = $this->zip();
343
-        if (! empty($zip_value)) {
344
-            $full_address_array[] = $zip_value;
345
-        }
346
-        return $full_address_array;
347
-    }
348
-
349
-
350
-    /**
351
-     *        get Attendee Address
352
-     *
353
-     * @return string
354
-     * @throws EE_Error
355
-     */
356
-    public function address()
357
-    {
358
-        return $this->get('ATT_address');
359
-    }
360
-
361
-
362
-    /**
363
-     *        get Attendee Address2
364
-     *
365
-     * @return string
366
-     * @throws EE_Error
367
-     */
368
-    public function address2()
369
-    {
370
-        return $this->get('ATT_address2');
371
-    }
372
-
373
-
374
-    /**
375
-     *        get Attendee City
376
-     *
377
-     * @return string
378
-     * @throws EE_Error
379
-     */
380
-    public function city()
381
-    {
382
-        return $this->get('ATT_city');
383
-    }
384
-
385
-
386
-    /**
387
-     *        get Attendee State ID
388
-     *
389
-     * @return string
390
-     * @throws EE_Error
391
-     */
392
-    public function state_ID()
393
-    {
394
-        return $this->get('STA_ID');
395
-    }
396
-
397
-
398
-    /**
399
-     * @return string
400
-     * @throws EE_Error
401
-     */
402
-    public function state_abbrev()
403
-    {
404
-        return $this->state_obj() instanceof EE_State ? $this->state_obj()->abbrev() : '';
405
-    }
406
-
407
-
408
-    /**
409
-     * Gets the state set to this attendee
410
-     *
411
-     * @return EE_State
412
-     * @throws EE_Error
413
-     */
414
-    public function state_obj()
415
-    {
416
-        return $this->get_first_related('State');
417
-    }
418
-
419
-
420
-    /**
421
-     * Returns the state's name, otherwise 'Unknown'
422
-     *
423
-     * @return string
424
-     * @throws EE_Error
425
-     */
426
-    public function state_name()
427
-    {
428
-        if ($this->state_obj()) {
429
-            return $this->state_obj()->name();
430
-        } else {
431
-            return '';
432
-        }
433
-    }
434
-
435
-
436
-    /**
437
-     * either displays the state abbreviation or the state name, as determined
438
-     * by the "FHEE__EEI_Address__state__use_abbreviation" filter.
439
-     * defaults to abbreviation
440
-     *
441
-     * @return string
442
-     * @throws EE_Error
443
-     */
444
-    public function state()
445
-    {
446
-        if (apply_filters('FHEE__EEI_Address__state__use_abbreviation', true, $this->state_obj())) {
447
-            return $this->state_abbrev();
448
-        }
449
-        return $this->state_name();
450
-    }
451
-
452
-
453
-    /**
454
-     *    get Attendee Country ISO Code
455
-     *
456
-     * @return string
457
-     * @throws EE_Error
458
-     */
459
-    public function country_ID()
460
-    {
461
-        return $this->get('CNT_ISO');
462
-    }
463
-
464
-
465
-    /**
466
-     * Gets country set for this attendee
467
-     *
468
-     * @return EE_Country
469
-     * @throws EE_Error
470
-     */
471
-    public function country_obj()
472
-    {
473
-        return $this->get_first_related('Country');
474
-    }
475
-
476
-
477
-    /**
478
-     * Returns the country's name if known, otherwise 'Unknown'
479
-     *
480
-     * @return string
481
-     * @throws EE_Error
482
-     */
483
-    public function country_name()
484
-    {
485
-        if ($this->country_obj()) {
486
-            return $this->country_obj()->name();
487
-        }
488
-        return '';
489
-    }
490
-
491
-
492
-    /**
493
-     * either displays the country ISO2 code or the country name, as determined
494
-     * by the "FHEE__EEI_Address__country__use_abbreviation" filter.
495
-     * defaults to abbreviation
496
-     *
497
-     * @return string
498
-     * @throws EE_Error
499
-     */
500
-    public function country()
501
-    {
502
-        if (apply_filters('FHEE__EEI_Address__country__use_abbreviation', true, $this->country_obj())) {
503
-            return $this->country_ID();
504
-        }
505
-        return $this->country_name();
506
-    }
507
-
508
-
509
-    /**
510
-     *        get Attendee Zip/Postal Code
511
-     *
512
-     * @return string
513
-     * @throws EE_Error
514
-     */
515
-    public function zip()
516
-    {
517
-        return $this->get('ATT_zip');
518
-    }
519
-
520
-
521
-    /**
522
-     *        get Attendee Email Address
523
-     *
524
-     * @return string
525
-     * @throws EE_Error
526
-     */
527
-    public function email()
528
-    {
529
-        return $this->get('ATT_email');
530
-    }
531
-
532
-
533
-    /**
534
-     *        get Attendee Phone #
535
-     *
536
-     * @return string
537
-     * @throws EE_Error
538
-     */
539
-    public function phone()
540
-    {
541
-        return $this->get('ATT_phone');
542
-    }
543
-
544
-
545
-    /**
546
-     *    get deleted
547
-     *
548
-     * @return        bool
549
-     * @throws EE_Error
550
-     */
551
-    public function deleted()
552
-    {
553
-        return $this->get('ATT_deleted');
554
-    }
555
-
556
-
557
-    /**
558
-     * Gets registrations of this attendee
559
-     *
560
-     * @param array $query_params
561
-     * @return EE_Registration[]
562
-     * @throws EE_Error
563
-     */
564
-    public function get_registrations($query_params = array())
565
-    {
566
-        return $this->get_many_related('Registration', $query_params);
567
-    }
568
-
569
-
570
-    /**
571
-     * Gets the most recent registration of this attendee
572
-     *
573
-     * @return EE_Registration
574
-     * @throws EE_Error
575
-     */
576
-    public function get_most_recent_registration()
577
-    {
578
-        return $this->get_first_related(
579
-            'Registration',
580
-            array('order_by' => array('REG_date' => 'DESC'))
581
-        ); // null, 'REG_date', 'DESC', '=', 'OBJECT_K');
582
-    }
583
-
584
-
585
-    /**
586
-     * Gets the most recent registration for this attend at this event
587
-     *
588
-     * @param int $event_id
589
-     * @return EE_Registration
590
-     * @throws EE_Error
591
-     */
592
-    public function get_most_recent_registration_for_event($event_id)
593
-    {
594
-        return $this->get_first_related(
595
-            'Registration',
596
-            array(array('EVT_ID' => $event_id), 'order_by' => array('REG_date' => 'DESC'))
597
-        );
598
-    }
599
-
600
-
601
-    /**
602
-     * returns any events attached to this attendee ($_Event property);
603
-     *
604
-     * @return array
605
-     * @throws EE_Error
606
-     */
607
-    public function events()
608
-    {
609
-        return $this->get_many_related('Event');
610
-    }
611
-
612
-
613
-    /**
614
-     * Gets the billing info array where keys match espresso_reg_page_billing_inputs(),
615
-     * and keys are their cleaned values. @see EE_Attendee::save_and_clean_billing_info_for_payment_method() which was
616
-     * used to save the billing info
617
-     *
618
-     * @param EE_Payment_Method $payment_method the _gateway_name property on the gateway class
619
-     * @return EE_Form_Section_Proper|null
620
-     * @throws EE_Error
621
-     */
622
-    public function billing_info_for_payment_method($payment_method)
623
-    {
624
-        $pm_type = $payment_method->type_obj();
625
-        if (! $pm_type instanceof EE_PMT_Base) {
626
-            return null;
627
-        }
628
-        $billing_info = $this->get_post_meta($this->get_billing_info_postmeta_name($payment_method), true);
629
-        if (! $billing_info) {
630
-            return null;
631
-        }
632
-        $billing_form = $pm_type->billing_form();
633
-        // double-check the form isn't totally hidden, in which case pretend there is no form
634
-        $form_totally_hidden = true;
635
-        foreach ($billing_form->inputs_in_subsections() as $input) {
636
-            if (! $input->get_display_strategy() instanceof EE_Hidden_Display_Strategy) {
637
-                $form_totally_hidden = false;
638
-                break;
639
-            }
640
-        }
641
-        if ($form_totally_hidden) {
642
-            return null;
643
-        }
644
-        if ($billing_form instanceof EE_Form_Section_Proper) {
645
-            $billing_form->receive_form_submission(array($billing_form->name() => $billing_info), false);
646
-        }
647
-
648
-        return $billing_form;
649
-    }
650
-
651
-
652
-    /**
653
-     * Gets the postmeta key that holds this attendee's billing info for the
654
-     * specified payment method
655
-     *
656
-     * @param EE_Payment_Method $payment_method
657
-     * @return string
658
-     * @throws EE_Error
659
-     */
660
-    public function get_billing_info_postmeta_name($payment_method)
661
-    {
662
-        if ($payment_method->type_obj() instanceof EE_PMT_Base) {
663
-            return 'billing_info_' . $payment_method->type_obj()->system_name();
664
-        }
665
-        return null;
666
-    }
667
-
668
-
669
-    /**
670
-     * Saves the billing info to the attendee. @see EE_Attendee::billing_info_for_payment_method() which is used to
671
-     * retrieve it
672
-     *
673
-     * @param EE_Billing_Attendee_Info_Form $billing_form
674
-     * @param EE_Payment_Method             $payment_method
675
-     * @return boolean
676
-     * @throws EE_Error
677
-     */
678
-    public function save_and_clean_billing_info_for_payment_method($billing_form, $payment_method)
679
-    {
680
-        if (! $billing_form instanceof EE_Billing_Attendee_Info_Form) {
681
-            EE_Error::add_error(esc_html__('Cannot save billing info because there is none.', 'event_espresso'));
682
-            return false;
683
-        }
684
-        $billing_form->clean_sensitive_data();
685
-        return update_post_meta(
686
-            $this->ID(),
687
-            $this->get_billing_info_postmeta_name($payment_method),
688
-            $billing_form->input_values(true)
689
-        );
690
-    }
691
-
692
-
693
-    /**
694
-     * Return the link to the admin details for the object.
695
-     *
696
-     * @return string
697
-     * @throws EE_Error
698
-     * @throws InvalidArgumentException
699
-     * @throws InvalidDataTypeException
700
-     * @throws InvalidInterfaceException
701
-     * @throws ReflectionException
702
-     */
703
-    public function get_admin_details_link()
704
-    {
705
-        return $this->get_admin_edit_link();
706
-    }
707
-
708
-
709
-    /**
710
-     * Returns the link to the editor for the object.  Sometimes this is the same as the details.
711
-     *
712
-     * @return string
713
-     * @throws EE_Error
714
-     * @throws InvalidArgumentException
715
-     * @throws ReflectionException
716
-     * @throws InvalidDataTypeException
717
-     * @throws InvalidInterfaceException
718
-     */
719
-    public function get_admin_edit_link()
720
-    {
721
-        EE_Registry::instance()->load_helper('URL');
722
-        return EEH_URL::add_query_args_and_nonce(
723
-            array(
724
-                'page'   => 'espresso_registrations',
725
-                'action' => 'edit_attendee',
726
-                'post'   => $this->ID(),
727
-            ),
728
-            admin_url('admin.php')
729
-        );
730
-    }
731
-
732
-
733
-    /**
734
-     * Returns the link to a settings page for the object.
735
-     *
736
-     * @return string
737
-     * @throws EE_Error
738
-     * @throws InvalidArgumentException
739
-     * @throws InvalidDataTypeException
740
-     * @throws InvalidInterfaceException
741
-     * @throws ReflectionException
742
-     */
743
-    public function get_admin_settings_link()
744
-    {
745
-        return $this->get_admin_edit_link();
746
-    }
747
-
748
-
749
-    /**
750
-     * Returns the link to the "overview" for the object (typically the "list table" view).
751
-     *
752
-     * @return string
753
-     * @throws EE_Error
754
-     * @throws InvalidArgumentException
755
-     * @throws ReflectionException
756
-     * @throws InvalidDataTypeException
757
-     * @throws InvalidInterfaceException
758
-     */
759
-    public function get_admin_overview_link()
760
-    {
761
-        EE_Registry::instance()->load_helper('URL');
762
-        return EEH_URL::add_query_args_and_nonce(
763
-            array(
764
-                'page'   => 'espresso_registrations',
765
-                'action' => 'contact_list',
766
-            ),
767
-            admin_url('admin.php')
768
-        );
769
-    }
28
+	/**
29
+	 * Sets some dynamic defaults
30
+	 *
31
+	 * @param array  $fieldValues
32
+	 * @param bool   $bydb
33
+	 * @param string $timezone
34
+	 * @param array  $date_formats
35
+	 * @throws EE_Error
36
+	 */
37
+	protected function __construct($fieldValues = null, $bydb = false, $timezone = null, $date_formats = array())
38
+	{
39
+		if (! isset($fieldValues['ATT_full_name'])) {
40
+			$fname = isset($fieldValues['ATT_fname']) ? $fieldValues['ATT_fname'] . ' ' : '';
41
+			$lname = isset($fieldValues['ATT_lname']) ? $fieldValues['ATT_lname'] : '';
42
+			$fieldValues['ATT_full_name'] = $fname . $lname;
43
+		}
44
+		if (! isset($fieldValues['ATT_slug'])) {
45
+			// $fieldValues['ATT_slug'] = sanitize_key(wp_generate_password(20));
46
+			$fieldValues['ATT_slug'] = sanitize_title($fieldValues['ATT_full_name']);
47
+		}
48
+		if (! isset($fieldValues['ATT_short_bio']) && isset($fieldValues['ATT_bio'])) {
49
+			$fieldValues['ATT_short_bio'] = substr($fieldValues['ATT_bio'], 0, 50);
50
+		}
51
+		parent::__construct($fieldValues, $bydb, $timezone, $date_formats);
52
+	}
53
+
54
+
55
+	/**
56
+	 * @param array  $props_n_values          incoming values
57
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
58
+	 *                                        used.)
59
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
60
+	 *                                        date_format and the second value is the time format
61
+	 * @return EE_Attendee
62
+	 * @throws EE_Error
63
+	 */
64
+	public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
65
+	{
66
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
67
+		return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
68
+	}
69
+
70
+
71
+	/**
72
+	 * @param array  $props_n_values  incoming values from the database
73
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
74
+	 *                                the website will be used.
75
+	 * @return EE_Attendee
76
+	 */
77
+	public static function new_instance_from_db($props_n_values = array(), $timezone = null)
78
+	{
79
+		return new self($props_n_values, true, $timezone);
80
+	}
81
+
82
+
83
+	/**
84
+	 *        Set Attendee First Name
85
+	 *
86
+	 * @access        public
87
+	 * @param string $fname
88
+	 * @throws EE_Error
89
+	 */
90
+	public function set_fname($fname = '')
91
+	{
92
+		$this->set('ATT_fname', $fname);
93
+	}
94
+
95
+
96
+	/**
97
+	 *        Set Attendee Last Name
98
+	 *
99
+	 * @access        public
100
+	 * @param string $lname
101
+	 * @throws EE_Error
102
+	 */
103
+	public function set_lname($lname = '')
104
+	{
105
+		$this->set('ATT_lname', $lname);
106
+	}
107
+
108
+
109
+	/**
110
+	 *        Set Attendee Address
111
+	 *
112
+	 * @access        public
113
+	 * @param string $address
114
+	 * @throws EE_Error
115
+	 */
116
+	public function set_address($address = '')
117
+	{
118
+		$this->set('ATT_address', $address);
119
+	}
120
+
121
+
122
+	/**
123
+	 *        Set Attendee Address2
124
+	 *
125
+	 * @access        public
126
+	 * @param        string $address2
127
+	 * @throws EE_Error
128
+	 */
129
+	public function set_address2($address2 = '')
130
+	{
131
+		$this->set('ATT_address2', $address2);
132
+	}
133
+
134
+
135
+	/**
136
+	 *        Set Attendee City
137
+	 *
138
+	 * @access        public
139
+	 * @param        string $city
140
+	 * @throws EE_Error
141
+	 */
142
+	public function set_city($city = '')
143
+	{
144
+		$this->set('ATT_city', $city);
145
+	}
146
+
147
+
148
+	/**
149
+	 *        Set Attendee State ID
150
+	 *
151
+	 * @access        public
152
+	 * @param        int $STA_ID
153
+	 * @throws EE_Error
154
+	 */
155
+	public function set_state($STA_ID = 0)
156
+	{
157
+		$this->set('STA_ID', $STA_ID);
158
+	}
159
+
160
+
161
+	/**
162
+	 *        Set Attendee Country ISO Code
163
+	 *
164
+	 * @access        public
165
+	 * @param        string $CNT_ISO
166
+	 * @throws EE_Error
167
+	 */
168
+	public function set_country($CNT_ISO = '')
169
+	{
170
+		$this->set('CNT_ISO', $CNT_ISO);
171
+	}
172
+
173
+
174
+	/**
175
+	 *        Set Attendee Zip/Postal Code
176
+	 *
177
+	 * @access        public
178
+	 * @param        string $zip
179
+	 * @throws EE_Error
180
+	 */
181
+	public function set_zip($zip = '')
182
+	{
183
+		$this->set('ATT_zip', $zip);
184
+	}
185
+
186
+
187
+	/**
188
+	 *        Set Attendee Email Address
189
+	 *
190
+	 * @access        public
191
+	 * @param        string $email
192
+	 * @throws EE_Error
193
+	 */
194
+	public function set_email($email = '')
195
+	{
196
+		$this->set('ATT_email', $email);
197
+	}
198
+
199
+
200
+	/**
201
+	 *        Set Attendee Phone
202
+	 *
203
+	 * @access        public
204
+	 * @param        string $phone
205
+	 * @throws EE_Error
206
+	 */
207
+	public function set_phone($phone = '')
208
+	{
209
+		$this->set('ATT_phone', $phone);
210
+	}
211
+
212
+
213
+	/**
214
+	 *        set deleted
215
+	 *
216
+	 * @access        public
217
+	 * @param        bool $ATT_deleted
218
+	 * @throws EE_Error
219
+	 */
220
+	public function set_deleted($ATT_deleted = false)
221
+	{
222
+		$this->set('ATT_deleted', $ATT_deleted);
223
+	}
224
+
225
+
226
+	/**
227
+	 * Returns the value for the post_author id saved with the cpt
228
+	 *
229
+	 * @since 4.5.0
230
+	 * @return int
231
+	 * @throws EE_Error
232
+	 */
233
+	public function wp_user()
234
+	{
235
+		return $this->get('ATT_author');
236
+	}
237
+
238
+
239
+	/**
240
+	 *        get Attendee First Name
241
+	 *
242
+	 * @access        public
243
+	 * @return string
244
+	 * @throws EE_Error
245
+	 */
246
+	public function fname()
247
+	{
248
+		return $this->get('ATT_fname');
249
+	}
250
+
251
+
252
+	/**
253
+	 * echoes out the attendee's first name
254
+	 *
255
+	 * @return void
256
+	 * @throws EE_Error
257
+	 */
258
+	public function e_full_name()
259
+	{
260
+		echo esc_html($this->full_name());
261
+	}
262
+
263
+
264
+	/**
265
+	 * Returns the first and last name concatenated together with a space.
266
+	 *
267
+	 * @param bool $apply_html_entities
268
+	 * @return string
269
+	 * @throws EE_Error
270
+	 */
271
+	public function full_name($apply_html_entities = false)
272
+	{
273
+		$full_name = array(
274
+			$this->fname(),
275
+			$this->lname(),
276
+		);
277
+		$full_name = array_filter($full_name);
278
+		$full_name = implode(' ', $full_name);
279
+		return $apply_html_entities ? htmlentities($full_name, ENT_QUOTES, 'UTF-8') : $full_name;
280
+	}
281
+
282
+
283
+	/**
284
+	 * This returns the value of the `ATT_full_name` field which is usually equivalent to calling `full_name()` unless
285
+	 * the post_title field has been directly modified in the db for the post (espresso_attendees post type) for this
286
+	 * attendee.
287
+	 *
288
+	 * @param bool $apply_html_entities
289
+	 * @return string
290
+	 * @throws EE_Error
291
+	 */
292
+	public function ATT_full_name($apply_html_entities = false)
293
+	{
294
+		return $apply_html_entities
295
+			? htmlentities($this->get('ATT_full_name'), ENT_QUOTES, 'UTF-8')
296
+			: $this->get('ATT_full_name');
297
+	}
298
+
299
+
300
+	/**
301
+	 *        get Attendee Last Name
302
+	 *
303
+	 * @access        public
304
+	 * @return string
305
+	 * @throws EE_Error
306
+	 */
307
+	public function lname()
308
+	{
309
+		return $this->get('ATT_lname');
310
+	}
311
+
312
+
313
+	/**
314
+	 * Gets the attendee's full address as an array so client code can decide hwo to display it
315
+	 *
316
+	 * @return array numerically indexed, with each part of the address that is known.
317
+	 * Eg, if the user only responded to state and country,
318
+	 * it would be array(0=>'Alabama',1=>'USA')
319
+	 * @return array
320
+	 * @throws EE_Error
321
+	 */
322
+	public function full_address_as_array()
323
+	{
324
+		$full_address_array = array();
325
+		$initial_address_fields = array('ATT_address', 'ATT_address2', 'ATT_city',);
326
+		foreach ($initial_address_fields as $address_field_name) {
327
+			$address_fields_value = $this->get($address_field_name);
328
+			if (! empty($address_fields_value)) {
329
+				$full_address_array[] = $address_fields_value;
330
+			}
331
+		}
332
+		// now handle state and country
333
+		$state_obj = $this->state_obj();
334
+		if ($state_obj instanceof EE_State) {
335
+			$full_address_array[] = $state_obj->name();
336
+		}
337
+		$country_obj = $this->country_obj();
338
+		if ($country_obj instanceof EE_Country) {
339
+			$full_address_array[] = $country_obj->name();
340
+		}
341
+		// lastly get the xip
342
+		$zip_value = $this->zip();
343
+		if (! empty($zip_value)) {
344
+			$full_address_array[] = $zip_value;
345
+		}
346
+		return $full_address_array;
347
+	}
348
+
349
+
350
+	/**
351
+	 *        get Attendee Address
352
+	 *
353
+	 * @return string
354
+	 * @throws EE_Error
355
+	 */
356
+	public function address()
357
+	{
358
+		return $this->get('ATT_address');
359
+	}
360
+
361
+
362
+	/**
363
+	 *        get Attendee Address2
364
+	 *
365
+	 * @return string
366
+	 * @throws EE_Error
367
+	 */
368
+	public function address2()
369
+	{
370
+		return $this->get('ATT_address2');
371
+	}
372
+
373
+
374
+	/**
375
+	 *        get Attendee City
376
+	 *
377
+	 * @return string
378
+	 * @throws EE_Error
379
+	 */
380
+	public function city()
381
+	{
382
+		return $this->get('ATT_city');
383
+	}
384
+
385
+
386
+	/**
387
+	 *        get Attendee State ID
388
+	 *
389
+	 * @return string
390
+	 * @throws EE_Error
391
+	 */
392
+	public function state_ID()
393
+	{
394
+		return $this->get('STA_ID');
395
+	}
396
+
397
+
398
+	/**
399
+	 * @return string
400
+	 * @throws EE_Error
401
+	 */
402
+	public function state_abbrev()
403
+	{
404
+		return $this->state_obj() instanceof EE_State ? $this->state_obj()->abbrev() : '';
405
+	}
406
+
407
+
408
+	/**
409
+	 * Gets the state set to this attendee
410
+	 *
411
+	 * @return EE_State
412
+	 * @throws EE_Error
413
+	 */
414
+	public function state_obj()
415
+	{
416
+		return $this->get_first_related('State');
417
+	}
418
+
419
+
420
+	/**
421
+	 * Returns the state's name, otherwise 'Unknown'
422
+	 *
423
+	 * @return string
424
+	 * @throws EE_Error
425
+	 */
426
+	public function state_name()
427
+	{
428
+		if ($this->state_obj()) {
429
+			return $this->state_obj()->name();
430
+		} else {
431
+			return '';
432
+		}
433
+	}
434
+
435
+
436
+	/**
437
+	 * either displays the state abbreviation or the state name, as determined
438
+	 * by the "FHEE__EEI_Address__state__use_abbreviation" filter.
439
+	 * defaults to abbreviation
440
+	 *
441
+	 * @return string
442
+	 * @throws EE_Error
443
+	 */
444
+	public function state()
445
+	{
446
+		if (apply_filters('FHEE__EEI_Address__state__use_abbreviation', true, $this->state_obj())) {
447
+			return $this->state_abbrev();
448
+		}
449
+		return $this->state_name();
450
+	}
451
+
452
+
453
+	/**
454
+	 *    get Attendee Country ISO Code
455
+	 *
456
+	 * @return string
457
+	 * @throws EE_Error
458
+	 */
459
+	public function country_ID()
460
+	{
461
+		return $this->get('CNT_ISO');
462
+	}
463
+
464
+
465
+	/**
466
+	 * Gets country set for this attendee
467
+	 *
468
+	 * @return EE_Country
469
+	 * @throws EE_Error
470
+	 */
471
+	public function country_obj()
472
+	{
473
+		return $this->get_first_related('Country');
474
+	}
475
+
476
+
477
+	/**
478
+	 * Returns the country's name if known, otherwise 'Unknown'
479
+	 *
480
+	 * @return string
481
+	 * @throws EE_Error
482
+	 */
483
+	public function country_name()
484
+	{
485
+		if ($this->country_obj()) {
486
+			return $this->country_obj()->name();
487
+		}
488
+		return '';
489
+	}
490
+
491
+
492
+	/**
493
+	 * either displays the country ISO2 code or the country name, as determined
494
+	 * by the "FHEE__EEI_Address__country__use_abbreviation" filter.
495
+	 * defaults to abbreviation
496
+	 *
497
+	 * @return string
498
+	 * @throws EE_Error
499
+	 */
500
+	public function country()
501
+	{
502
+		if (apply_filters('FHEE__EEI_Address__country__use_abbreviation', true, $this->country_obj())) {
503
+			return $this->country_ID();
504
+		}
505
+		return $this->country_name();
506
+	}
507
+
508
+
509
+	/**
510
+	 *        get Attendee Zip/Postal Code
511
+	 *
512
+	 * @return string
513
+	 * @throws EE_Error
514
+	 */
515
+	public function zip()
516
+	{
517
+		return $this->get('ATT_zip');
518
+	}
519
+
520
+
521
+	/**
522
+	 *        get Attendee Email Address
523
+	 *
524
+	 * @return string
525
+	 * @throws EE_Error
526
+	 */
527
+	public function email()
528
+	{
529
+		return $this->get('ATT_email');
530
+	}
531
+
532
+
533
+	/**
534
+	 *        get Attendee Phone #
535
+	 *
536
+	 * @return string
537
+	 * @throws EE_Error
538
+	 */
539
+	public function phone()
540
+	{
541
+		return $this->get('ATT_phone');
542
+	}
543
+
544
+
545
+	/**
546
+	 *    get deleted
547
+	 *
548
+	 * @return        bool
549
+	 * @throws EE_Error
550
+	 */
551
+	public function deleted()
552
+	{
553
+		return $this->get('ATT_deleted');
554
+	}
555
+
556
+
557
+	/**
558
+	 * Gets registrations of this attendee
559
+	 *
560
+	 * @param array $query_params
561
+	 * @return EE_Registration[]
562
+	 * @throws EE_Error
563
+	 */
564
+	public function get_registrations($query_params = array())
565
+	{
566
+		return $this->get_many_related('Registration', $query_params);
567
+	}
568
+
569
+
570
+	/**
571
+	 * Gets the most recent registration of this attendee
572
+	 *
573
+	 * @return EE_Registration
574
+	 * @throws EE_Error
575
+	 */
576
+	public function get_most_recent_registration()
577
+	{
578
+		return $this->get_first_related(
579
+			'Registration',
580
+			array('order_by' => array('REG_date' => 'DESC'))
581
+		); // null, 'REG_date', 'DESC', '=', 'OBJECT_K');
582
+	}
583
+
584
+
585
+	/**
586
+	 * Gets the most recent registration for this attend at this event
587
+	 *
588
+	 * @param int $event_id
589
+	 * @return EE_Registration
590
+	 * @throws EE_Error
591
+	 */
592
+	public function get_most_recent_registration_for_event($event_id)
593
+	{
594
+		return $this->get_first_related(
595
+			'Registration',
596
+			array(array('EVT_ID' => $event_id), 'order_by' => array('REG_date' => 'DESC'))
597
+		);
598
+	}
599
+
600
+
601
+	/**
602
+	 * returns any events attached to this attendee ($_Event property);
603
+	 *
604
+	 * @return array
605
+	 * @throws EE_Error
606
+	 */
607
+	public function events()
608
+	{
609
+		return $this->get_many_related('Event');
610
+	}
611
+
612
+
613
+	/**
614
+	 * Gets the billing info array where keys match espresso_reg_page_billing_inputs(),
615
+	 * and keys are their cleaned values. @see EE_Attendee::save_and_clean_billing_info_for_payment_method() which was
616
+	 * used to save the billing info
617
+	 *
618
+	 * @param EE_Payment_Method $payment_method the _gateway_name property on the gateway class
619
+	 * @return EE_Form_Section_Proper|null
620
+	 * @throws EE_Error
621
+	 */
622
+	public function billing_info_for_payment_method($payment_method)
623
+	{
624
+		$pm_type = $payment_method->type_obj();
625
+		if (! $pm_type instanceof EE_PMT_Base) {
626
+			return null;
627
+		}
628
+		$billing_info = $this->get_post_meta($this->get_billing_info_postmeta_name($payment_method), true);
629
+		if (! $billing_info) {
630
+			return null;
631
+		}
632
+		$billing_form = $pm_type->billing_form();
633
+		// double-check the form isn't totally hidden, in which case pretend there is no form
634
+		$form_totally_hidden = true;
635
+		foreach ($billing_form->inputs_in_subsections() as $input) {
636
+			if (! $input->get_display_strategy() instanceof EE_Hidden_Display_Strategy) {
637
+				$form_totally_hidden = false;
638
+				break;
639
+			}
640
+		}
641
+		if ($form_totally_hidden) {
642
+			return null;
643
+		}
644
+		if ($billing_form instanceof EE_Form_Section_Proper) {
645
+			$billing_form->receive_form_submission(array($billing_form->name() => $billing_info), false);
646
+		}
647
+
648
+		return $billing_form;
649
+	}
650
+
651
+
652
+	/**
653
+	 * Gets the postmeta key that holds this attendee's billing info for the
654
+	 * specified payment method
655
+	 *
656
+	 * @param EE_Payment_Method $payment_method
657
+	 * @return string
658
+	 * @throws EE_Error
659
+	 */
660
+	public function get_billing_info_postmeta_name($payment_method)
661
+	{
662
+		if ($payment_method->type_obj() instanceof EE_PMT_Base) {
663
+			return 'billing_info_' . $payment_method->type_obj()->system_name();
664
+		}
665
+		return null;
666
+	}
667
+
668
+
669
+	/**
670
+	 * Saves the billing info to the attendee. @see EE_Attendee::billing_info_for_payment_method() which is used to
671
+	 * retrieve it
672
+	 *
673
+	 * @param EE_Billing_Attendee_Info_Form $billing_form
674
+	 * @param EE_Payment_Method             $payment_method
675
+	 * @return boolean
676
+	 * @throws EE_Error
677
+	 */
678
+	public function save_and_clean_billing_info_for_payment_method($billing_form, $payment_method)
679
+	{
680
+		if (! $billing_form instanceof EE_Billing_Attendee_Info_Form) {
681
+			EE_Error::add_error(esc_html__('Cannot save billing info because there is none.', 'event_espresso'));
682
+			return false;
683
+		}
684
+		$billing_form->clean_sensitive_data();
685
+		return update_post_meta(
686
+			$this->ID(),
687
+			$this->get_billing_info_postmeta_name($payment_method),
688
+			$billing_form->input_values(true)
689
+		);
690
+	}
691
+
692
+
693
+	/**
694
+	 * Return the link to the admin details for the object.
695
+	 *
696
+	 * @return string
697
+	 * @throws EE_Error
698
+	 * @throws InvalidArgumentException
699
+	 * @throws InvalidDataTypeException
700
+	 * @throws InvalidInterfaceException
701
+	 * @throws ReflectionException
702
+	 */
703
+	public function get_admin_details_link()
704
+	{
705
+		return $this->get_admin_edit_link();
706
+	}
707
+
708
+
709
+	/**
710
+	 * Returns the link to the editor for the object.  Sometimes this is the same as the details.
711
+	 *
712
+	 * @return string
713
+	 * @throws EE_Error
714
+	 * @throws InvalidArgumentException
715
+	 * @throws ReflectionException
716
+	 * @throws InvalidDataTypeException
717
+	 * @throws InvalidInterfaceException
718
+	 */
719
+	public function get_admin_edit_link()
720
+	{
721
+		EE_Registry::instance()->load_helper('URL');
722
+		return EEH_URL::add_query_args_and_nonce(
723
+			array(
724
+				'page'   => 'espresso_registrations',
725
+				'action' => 'edit_attendee',
726
+				'post'   => $this->ID(),
727
+			),
728
+			admin_url('admin.php')
729
+		);
730
+	}
731
+
732
+
733
+	/**
734
+	 * Returns the link to a settings page for the object.
735
+	 *
736
+	 * @return string
737
+	 * @throws EE_Error
738
+	 * @throws InvalidArgumentException
739
+	 * @throws InvalidDataTypeException
740
+	 * @throws InvalidInterfaceException
741
+	 * @throws ReflectionException
742
+	 */
743
+	public function get_admin_settings_link()
744
+	{
745
+		return $this->get_admin_edit_link();
746
+	}
747
+
748
+
749
+	/**
750
+	 * Returns the link to the "overview" for the object (typically the "list table" view).
751
+	 *
752
+	 * @return string
753
+	 * @throws EE_Error
754
+	 * @throws InvalidArgumentException
755
+	 * @throws ReflectionException
756
+	 * @throws InvalidDataTypeException
757
+	 * @throws InvalidInterfaceException
758
+	 */
759
+	public function get_admin_overview_link()
760
+	{
761
+		EE_Registry::instance()->load_helper('URL');
762
+		return EEH_URL::add_query_args_and_nonce(
763
+			array(
764
+				'page'   => 'espresso_registrations',
765
+				'action' => 'contact_list',
766
+			),
767
+			admin_url('admin.php')
768
+		);
769
+	}
770 770
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Payment.class.php 1 patch
Indentation   +870 added lines, -870 removed lines patch added patch discarded remove patch
@@ -12,874 +12,874 @@
 block discarded – undo
12 12
 class EE_Payment extends EE_Base_Class implements EEI_Payment
13 13
 {
14 14
 
15
-    /**
16
-     * @param array  $props_n_values          incoming values
17
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
18
-     *                                        used.)
19
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
20
-     *                                        date_format and the second value is the time format
21
-     * @return EE_Payment
22
-     * @throws \EE_Error
23
-     */
24
-    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
25
-    {
26
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
27
-        return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
28
-    }
29
-
30
-
31
-    /**
32
-     * @param array  $props_n_values  incoming values from the database
33
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
34
-     *                                the website will be used.
35
-     * @return EE_Payment
36
-     * @throws \EE_Error
37
-     */
38
-    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
39
-    {
40
-        return new self($props_n_values, true, $timezone);
41
-    }
42
-
43
-
44
-    /**
45
-     * Set Transaction ID
46
-     *
47
-     * @access public
48
-     * @param int $TXN_ID
49
-     * @throws \EE_Error
50
-     */
51
-    public function set_transaction_id($TXN_ID = 0)
52
-    {
53
-        $this->set('TXN_ID', $TXN_ID);
54
-    }
55
-
56
-
57
-    /**
58
-     * Gets the transaction related to this payment
59
-     *
60
-     * @return EE_Transaction
61
-     * @throws \EE_Error
62
-     */
63
-    public function transaction()
64
-    {
65
-        return $this->get_first_related('Transaction');
66
-    }
67
-
68
-
69
-    /**
70
-     * Set Status
71
-     *
72
-     * @access public
73
-     * @param string $STS_ID
74
-     * @throws \EE_Error
75
-     */
76
-    public function set_status($STS_ID = '')
77
-    {
78
-        $this->set('STS_ID', $STS_ID);
79
-    }
80
-
81
-
82
-    /**
83
-     * Set Payment Timestamp
84
-     *
85
-     * @access public
86
-     * @param int $timestamp
87
-     * @throws \EE_Error
88
-     */
89
-    public function set_timestamp($timestamp = 0)
90
-    {
91
-        $this->set('PAY_timestamp', $timestamp);
92
-    }
93
-
94
-
95
-    /**
96
-     * Set Payment Method
97
-     *
98
-     * @access public
99
-     * @param string $PAY_source
100
-     * @throws \EE_Error
101
-     */
102
-    public function set_source($PAY_source = '')
103
-    {
104
-        $this->set('PAY_source', $PAY_source);
105
-    }
106
-
107
-
108
-    /**
109
-     * Set Payment Amount
110
-     *
111
-     * @access public
112
-     * @param float $amount
113
-     * @throws \EE_Error
114
-     */
115
-    public function set_amount($amount = 0.00)
116
-    {
117
-        $this->set('PAY_amount', (float) $amount);
118
-    }
119
-
120
-
121
-    /**
122
-     * Set Payment Gateway Response
123
-     *
124
-     * @access public
125
-     * @param string $gateway_response
126
-     * @throws \EE_Error
127
-     */
128
-    public function set_gateway_response($gateway_response = '')
129
-    {
130
-        $this->set('PAY_gateway_response', $gateway_response);
131
-    }
132
-
133
-
134
-    /**
135
-     * Returns the name of the payment method used on this payment (previously known merely as 'gateway')
136
-     * but since 4.6.0, payment methods are models and the payment keeps a foreign key to the payment method
137
-     * used on it
138
-     *
139
-     * @deprecated
140
-     * @return string
141
-     * @throws \EE_Error
142
-     */
143
-    public function gateway()
144
-    {
145
-        EE_Error::doing_it_wrong(
146
-            'EE_Payment::gateway',
147
-            esc_html__(
148
-                'The method EE_Payment::gateway() has been deprecated. Consider instead using EE_Payment::payment_method()->name()',
149
-                'event_espresso'
150
-            ),
151
-            '4.6.0'
152
-        );
153
-        return $this->payment_method() ? $this->payment_method()->name() : esc_html__('Unknown', 'event_espresso');
154
-    }
155
-
156
-
157
-    /**
158
-     * Set Gateway Transaction ID
159
-     *
160
-     * @access public
161
-     * @param string $txn_id_chq_nmbr
162
-     * @throws \EE_Error
163
-     */
164
-    public function set_txn_id_chq_nmbr($txn_id_chq_nmbr = '')
165
-    {
166
-        $this->set('PAY_txn_id_chq_nmbr', $txn_id_chq_nmbr);
167
-    }
168
-
169
-
170
-    /**
171
-     * Set Purchase Order Number
172
-     *
173
-     * @access public
174
-     * @param string $po_number
175
-     * @throws \EE_Error
176
-     */
177
-    public function set_po_number($po_number = '')
178
-    {
179
-        $this->set('PAY_po_number', $po_number);
180
-    }
181
-
182
-
183
-    /**
184
-     * Set Extra Accounting Field
185
-     *
186
-     * @access public
187
-     * @param string $extra_accntng
188
-     * @throws \EE_Error
189
-     */
190
-    public function set_extra_accntng($extra_accntng = '')
191
-    {
192
-        $this->set('PAY_extra_accntng', $extra_accntng);
193
-    }
194
-
195
-
196
-    /**
197
-     * Set Payment made via admin flag
198
-     *
199
-     * @access public
200
-     * @param bool $via_admin
201
-     * @throws \EE_Error
202
-     */
203
-    public function set_payment_made_via_admin($via_admin = false)
204
-    {
205
-        if ($via_admin) {
206
-            $this->set('PAY_source', EEM_Payment_Method::scope_admin);
207
-        } else {
208
-            $this->set('PAY_source', EEM_Payment_Method::scope_cart);
209
-        }
210
-    }
211
-
212
-
213
-    /**
214
-     * Set Payment Details
215
-     *
216
-     * @access public
217
-     * @param string|array $details
218
-     * @throws \EE_Error
219
-     */
220
-    public function set_details($details = '')
221
-    {
222
-        if (is_array($details)) {
223
-            array_walk_recursive($details, array($this, '_strip_all_tags_within_array'));
224
-        } else {
225
-            $details = wp_strip_all_tags($details);
226
-        }
227
-        $this->set('PAY_details', $details);
228
-    }
229
-
230
-
231
-    /**
232
-     * Sets redirect_url
233
-     *
234
-     * @param string $redirect_url
235
-     * @throws \EE_Error
236
-     */
237
-    public function set_redirect_url($redirect_url)
238
-    {
239
-        $this->set('PAY_redirect_url', $redirect_url);
240
-    }
241
-
242
-
243
-    /**
244
-     * Sets redirect_args
245
-     *
246
-     * @param array $redirect_args
247
-     * @throws \EE_Error
248
-     */
249
-    public function set_redirect_args($redirect_args)
250
-    {
251
-        $this->set('PAY_redirect_args', $redirect_args);
252
-    }
253
-
254
-
255
-    /**
256
-     * get Payment Transaction ID
257
-     *
258
-     * @access public
259
-     * @throws \EE_Error
260
-     */
261
-    public function TXN_ID()
262
-    {
263
-        return $this->get('TXN_ID');
264
-    }
265
-
266
-
267
-    /**
268
-     * get Payment Status
269
-     *
270
-     * @access public
271
-     * @throws \EE_Error
272
-     */
273
-    public function status()
274
-    {
275
-        return $this->get('STS_ID');
276
-    }
277
-
278
-
279
-    /**
280
-     * get Payment Status
281
-     *
282
-     * @access public
283
-     * @throws \EE_Error
284
-     */
285
-    public function STS_ID()
286
-    {
287
-        return $this->get('STS_ID');
288
-    }
289
-
290
-
291
-    /**
292
-     * get Payment Timestamp
293
-     *
294
-     * @access public
295
-     * @param string $dt_frmt
296
-     * @param string $tm_frmt
297
-     * @return string
298
-     * @throws \EE_Error
299
-     */
300
-    public function timestamp($dt_frmt = '', $tm_frmt = '')
301
-    {
302
-        return $this->get_i18n_datetime('PAY_timestamp', trim($dt_frmt . ' ' . $tm_frmt));
303
-    }
304
-
305
-
306
-    /**
307
-     * get Payment Source
308
-     *
309
-     * @access public
310
-     * @throws \EE_Error
311
-     */
312
-    public function source()
313
-    {
314
-        return $this->get('PAY_source');
315
-    }
316
-
317
-
318
-    /**
319
-     * get Payment Amount
320
-     *
321
-     * @access public
322
-     * @return float
323
-     * @throws \EE_Error
324
-     */
325
-    public function amount()
326
-    {
327
-        return (float) $this->get('PAY_amount');
328
-    }
329
-
330
-
331
-    /**
332
-     * @return mixed
333
-     * @throws \EE_Error
334
-     */
335
-    public function amount_no_code()
336
-    {
337
-        return $this->get_pretty('PAY_amount', 'no_currency_code');
338
-    }
339
-
340
-
341
-    /**
342
-     * get Payment Gateway Response
343
-     *
344
-     * @access public
345
-     * @throws \EE_Error
346
-     */
347
-    public function gateway_response()
348
-    {
349
-        return $this->get('PAY_gateway_response');
350
-    }
351
-
352
-
353
-    /**
354
-     * get Payment Gateway Transaction ID
355
-     *
356
-     * @access public
357
-     * @throws \EE_Error
358
-     */
359
-    public function txn_id_chq_nmbr()
360
-    {
361
-        return $this->get('PAY_txn_id_chq_nmbr');
362
-    }
363
-
364
-
365
-    /**
366
-     * get Purchase Order Number
367
-     *
368
-     * @access public
369
-     * @throws \EE_Error
370
-     */
371
-    public function po_number()
372
-    {
373
-        return $this->get('PAY_po_number');
374
-    }
375
-
376
-
377
-    /**
378
-     * get Extra Accounting Field
379
-     *
380
-     * @access public
381
-     * @throws \EE_Error
382
-     */
383
-    public function extra_accntng()
384
-    {
385
-        return $this->get('PAY_extra_accntng');
386
-    }
387
-
388
-
389
-    /**
390
-     * get Payment made via admin source
391
-     *
392
-     * @access public
393
-     * @throws \EE_Error
394
-     */
395
-    public function payment_made_via_admin()
396
-    {
397
-        return ($this->get('PAY_source') === EEM_Payment_Method::scope_admin);
398
-    }
399
-
400
-
401
-    /**
402
-     * get Payment Details
403
-     *
404
-     * @access public
405
-     * @throws \EE_Error
406
-     */
407
-    public function details()
408
-    {
409
-        return $this->get('PAY_details');
410
-    }
411
-
412
-
413
-    /**
414
-     * Gets redirect_url
415
-     *
416
-     * @return string
417
-     * @throws \EE_Error
418
-     */
419
-    public function redirect_url()
420
-    {
421
-        return $this->get('PAY_redirect_url');
422
-    }
423
-
424
-
425
-    /**
426
-     * Gets redirect_args
427
-     *
428
-     * @return array
429
-     * @throws \EE_Error
430
-     */
431
-    public function redirect_args()
432
-    {
433
-        return $this->get('PAY_redirect_args');
434
-    }
435
-
436
-
437
-    /**
438
-     * echoes $this->pretty_status()
439
-     *
440
-     * @param bool $show_icons
441
-     * @return void
442
-     * @throws \EE_Error
443
-     */
444
-    public function e_pretty_status($show_icons = false)
445
-    {
446
-        echo wp_kses($this->pretty_status($show_icons), AllowedTags::getAllowedTags());
447
-    }
448
-
449
-
450
-    /**
451
-     * returns a pretty version of the status, good for displaying to users
452
-     *
453
-     * @param bool $show_icons
454
-     * @return string
455
-     * @throws \EE_Error
456
-     */
457
-    public function pretty_status($show_icons = false)
458
-    {
459
-        $status = EEM_Status::instance()->localized_status(
460
-            array($this->STS_ID() => esc_html__('unknown', 'event_espresso')),
461
-            false,
462
-            'sentence'
463
-        );
464
-        $icon = '';
465
-        switch ($this->STS_ID()) {
466
-            case EEM_Payment::status_id_approved:
467
-                $icon = $show_icons
468
-                    ? '<span class="dashicons dashicons-yes ee-icon-size-24 green-text"></span>'
469
-                    : '';
470
-                break;
471
-            case EEM_Payment::status_id_pending:
472
-                $icon = $show_icons
473
-                    ? '<span class="dashicons dashicons-clock ee-icon-size-16 orange-text"></span>'
474
-                    : '';
475
-                break;
476
-            case EEM_Payment::status_id_cancelled:
477
-                $icon = $show_icons
478
-                    ? '<span class="dashicons dashicons-no ee-icon-size-16 lt-grey-text"></span>'
479
-                    : '';
480
-                break;
481
-            case EEM_Payment::status_id_declined:
482
-                $icon = $show_icons
483
-                    ? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>'
484
-                    : '';
485
-                break;
486
-        }
487
-        return $icon . $status[ $this->STS_ID() ];
488
-    }
489
-
490
-
491
-    /**
492
-     * For determining the status of the payment
493
-     *
494
-     * @return boolean whether the payment is approved or not
495
-     * @throws \EE_Error
496
-     */
497
-    public function is_approved()
498
-    {
499
-        return $this->status_is(EEM_Payment::status_id_approved);
500
-    }
501
-
502
-
503
-    /**
504
-     * Generally determines if the status of this payment equals
505
-     * the $STS_ID string
506
-     *
507
-     * @param string $STS_ID an ID from the esp_status table/
508
-     *                       one of the status_id_* on the EEM_Payment model
509
-     * @return boolean whether the status of this payment equals the status id
510
-     * @throws \EE_Error
511
-     */
512
-    protected function status_is($STS_ID)
513
-    {
514
-        return $STS_ID === $this->STS_ID() ? true : false;
515
-    }
516
-
517
-
518
-    /**
519
-     * For determining the status of the payment
520
-     *
521
-     * @return boolean whether the payment is pending or not
522
-     * @throws \EE_Error
523
-     */
524
-    public function is_pending()
525
-    {
526
-        return $this->status_is(EEM_Payment::status_id_pending);
527
-    }
528
-
529
-
530
-    /**
531
-     * For determining the status of the payment
532
-     *
533
-     * @return boolean
534
-     * @throws \EE_Error
535
-     */
536
-    public function is_cancelled()
537
-    {
538
-        return $this->status_is(EEM_Payment::status_id_cancelled);
539
-    }
540
-
541
-
542
-    /**
543
-     * For determining the status of the payment
544
-     *
545
-     * @return boolean
546
-     * @throws \EE_Error
547
-     */
548
-    public function is_declined()
549
-    {
550
-        return $this->status_is(EEM_Payment::status_id_declined);
551
-    }
552
-
553
-
554
-    /**
555
-     * For determining the status of the payment
556
-     *
557
-     * @return boolean
558
-     * @throws \EE_Error
559
-     */
560
-    public function is_failed()
561
-    {
562
-        return $this->status_is(EEM_Payment::status_id_failed);
563
-    }
564
-
565
-
566
-    /**
567
-     * For determining if the payment is actually a refund ( ie: has a negative value )
568
-     *
569
-     * @return boolean
570
-     * @throws \EE_Error
571
-     */
572
-    public function is_a_refund()
573
-    {
574
-        return $this->amount() < 0 ? true : false;
575
-    }
576
-
577
-
578
-    /**
579
-     * Get the status object of this object
580
-     *
581
-     * @return EE_Status
582
-     * @throws \EE_Error
583
-     */
584
-    public function status_obj()
585
-    {
586
-        return $this->get_first_related('Status');
587
-    }
588
-
589
-
590
-    /**
591
-     * Gets all the extra meta info on this payment
592
-     *
593
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
594
-     * @return EE_Extra_Meta
595
-     * @throws \EE_Error
596
-     */
597
-    public function extra_meta($query_params = array())
598
-    {
599
-        return $this->get_many_related('Extra_Meta', $query_params);
600
-    }
601
-
602
-
603
-    /**
604
-     * Gets the last-used payment method on this transaction
605
-     * (we COULD just use the last-made payment, but some payment methods, namely
606
-     * offline ones, dont' create payments)
607
-     *
608
-     * @return EE_Payment_Method
609
-     * @throws \EE_Error
610
-     */
611
-    public function payment_method()
612
-    {
613
-        return $this->get_first_related('Payment_Method');
614
-    }
615
-
616
-
617
-    /**
618
-     * Gets the HTML for redirecting the user to an offsite gateway
619
-     * You can pass it special content to put inside the form, or use
620
-     * the default inner content (or possibly generate this all yourself using
621
-     * redirect_url() and redirect_args() or redirect_args_as_inputs()).
622
-     * Creates a POST request by default, but if no redirect args are specified, creates a GET request instead
623
-     * (and any querystring variables in the redirect_url are converted into html inputs
624
-     * so browsers submit them properly)
625
-     *
626
-     * @param string $inside_form_html
627
-     * @return string html
628
-     * @throws \EE_Error
629
-     */
630
-    public function redirect_form($inside_form_html = null)
631
-    {
632
-        $redirect_url = $this->redirect_url();
633
-        if (! empty($redirect_url)) {
634
-            // what ? no inner form content?
635
-            if ($inside_form_html === null) {
636
-                $inside_form_html = EEH_HTML::p(
637
-                    sprintf(
638
-                        esc_html__(
639
-                            'If you are not automatically redirected to the payment website within 10 seconds... %1$s %2$s Click Here %3$s',
640
-                            'event_espresso'
641
-                        ),
642
-                        EEH_HTML::br(2),
643
-                        '<input type="submit" value="',
644
-                        '">'
645
-                    ),
646
-                    '',
647
-                    '',
648
-                    'text-align:center;'
649
-                );
650
-            }
651
-            $method = apply_filters(
652
-                'FHEE__EE_Payment__redirect_form__method',
653
-                $this->redirect_args() ? 'POST' : 'GET',
654
-                $this
655
-            );
656
-            // if it's a GET request, we need to remove all the GET params in the querystring
657
-            // and put them into the form instead
658
-            if ($method === 'GET') {
659
-                $querystring = parse_url($redirect_url, PHP_URL_QUERY);
660
-                $get_params = null;
661
-                parse_str($querystring, $get_params);
662
-                $inside_form_html .= $this->_args_as_inputs($get_params);
663
-                $redirect_url = str_replace('?' . $querystring, '', $redirect_url);
664
-            }
665
-            $form = EEH_HTML::nl(1)
666
-                    . '<form method="'
667
-                    . $method
668
-                    . '" name="gateway_form" action="'
669
-                    . $redirect_url
670
-                    . '">';
671
-            $form .= EEH_HTML::nl(1) . $this->redirect_args_as_inputs();
672
-            $form .= $inside_form_html;
673
-            $form .= EEH_HTML::nl(-1) . '</form>' . EEH_HTML::nl(-1);
674
-            return $form;
675
-        } else {
676
-            return null;
677
-        }
678
-    }
679
-
680
-
681
-    /**
682
-     * Changes all the name-value pairs of the redirect args into html inputs
683
-     * and returns the html as a string
684
-     *
685
-     * @return string
686
-     * @throws \EE_Error
687
-     */
688
-    public function redirect_args_as_inputs()
689
-    {
690
-        return $this->_args_as_inputs($this->redirect_args());
691
-    }
692
-
693
-
694
-    /**
695
-     * Converts a 2d array of key-value pairs into html hidden inputs
696
-     * and returns the string of html
697
-     *
698
-     * @param array $args key-value pairs
699
-     * @return string
700
-     */
701
-    protected function _args_as_inputs($args)
702
-    {
703
-        $html = '';
704
-        if ($args !== null && is_array($args)) {
705
-            foreach ($args as $name => $value) {
706
-                $html .= $this->generateInput($name, $value);
707
-            }
708
-        }
709
-        return $html;
710
-    }
711
-
712
-    /**
713
-     * Converts either a single name and value or array of values into html hidden inputs
714
-     * and returns the string of html
715
-     *
716
-     * @param string $name
717
-     * @param string|array $value
718
-     * @return string
719
-     */
720
-    private function generateInput($name, $value)
721
-    {
722
-        if (is_array($value)) {
723
-            $html = '';
724
-            $name = "{$name}[]";
725
-            foreach ($value as $array_value) {
726
-                $html .= $this->generateInput($name, $array_value);
727
-            }
728
-            return $html;
729
-        }
730
-        return EEH_HTML::nl()
731
-            . '<input type="hidden" name="' . $name . '"'
732
-            . ' value="' . esc_attr($value) . '"/>';
733
-    }
734
-
735
-
736
-    /**
737
-     * Returns the currency of the payment.
738
-     * (At the time of writing, this will always be the currency in the configuration;
739
-     * however in the future it is anticipated that this will be stored on the payment
740
-     * object itself)
741
-     *
742
-     * @return string for the currency code
743
-     */
744
-    public function currency_code()
745
-    {
746
-        return EE_Config::instance()->currency->code;
747
-    }
748
-
749
-
750
-    /**
751
-     * apply wp_strip_all_tags to all elements within an array
752
-     *
753
-     * @access private
754
-     * @param mixed $item
755
-     */
756
-    private function _strip_all_tags_within_array(&$item)
757
-    {
758
-        if (is_object($item)) {
759
-            $item = (array) $item;
760
-        }
761
-        if (is_array($item)) {
762
-            array_walk_recursive($item, array($this, '_strip_all_tags_within_array'));
763
-        } else {
764
-            $item = wp_strip_all_tags($item);
765
-        }
766
-    }
767
-
768
-
769
-    /**
770
-     * Returns TRUE is this payment was set to approved during this request (or
771
-     * is approved and was created during this request). False otherwise.
772
-     *
773
-     * @return boolean
774
-     * @throws \EE_Error
775
-     */
776
-    public function just_approved()
777
-    {
778
-        $original_status = EEH_Array::is_set(
779
-            $this->_props_n_values_provided_in_constructor,
780
-            'STS_ID',
781
-            $this->get_model()->field_settings_for('STS_ID')->get_default_value()
782
-        );
783
-        $current_status = $this->status();
784
-        if (
785
-            $original_status !== EEM_Payment::status_id_approved
786
-            && $current_status === EEM_Payment::status_id_approved
787
-        ) {
788
-            return true;
789
-        } else {
790
-            return false;
791
-        }
792
-    }
793
-
794
-
795
-    /**
796
-     * Overrides parents' get_pretty() function just for legacy reasons
797
-     * (to allow ticket https://events.codebasehq.com/projects/event-espresso/tickets/7420)
798
-     *
799
-     * @param string $field_name
800
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
801
-     *                                (in cases where the same property may be used for different outputs
802
-     *                                - i.e. datetime, money etc.)
803
-     * @return mixed
804
-     * @throws \EE_Error
805
-     */
806
-    public function get_pretty($field_name, $extra_cache_ref = null)
807
-    {
808
-        if ($field_name === 'PAY_gateway') {
809
-            return $this->payment_method() ? $this->payment_method()->name() : esc_html__('Unknown', 'event_espresso');
810
-        }
811
-        return $this->_get_cached_property($field_name, true, $extra_cache_ref);
812
-    }
813
-
814
-
815
-    /**
816
-     * Gets details regarding which registrations this payment was applied to
817
-     *
818
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
819
-     * @return EE_Registration_Payment[]
820
-     * @throws \EE_Error
821
-     */
822
-    public function registration_payments($query_params = array())
823
-    {
824
-        return $this->get_many_related('Registration_Payment', $query_params);
825
-    }
826
-
827
-
828
-    /**
829
-     * Gets the first event for this payment (it's possible that it could be for multiple)
830
-     *
831
-     * @return EE_Event|null
832
-     */
833
-    public function get_first_event()
834
-    {
835
-        $transaction = $this->transaction();
836
-        if ($transaction instanceof EE_Transaction) {
837
-            $primary_registrant = $transaction->primary_registration();
838
-            if ($primary_registrant instanceof EE_Registration) {
839
-                return $primary_registrant->event_obj();
840
-            }
841
-        }
842
-        return null;
843
-    }
844
-
845
-
846
-    /**
847
-     * Gets the name of the first event for which is being paid
848
-     *
849
-     * @return string
850
-     */
851
-    public function get_first_event_name()
852
-    {
853
-        $event = $this->get_first_event();
854
-        return $event instanceof EE_Event ? $event->name() : esc_html__('Event', 'event_espresso');
855
-    }
856
-
857
-
858
-    /**
859
-     * Returns the payment's transaction's primary registration
860
-     *
861
-     * @return EE_Registration|null
862
-     */
863
-    public function get_primary_registration()
864
-    {
865
-        if ($this->transaction() instanceof EE_Transaction) {
866
-            return $this->transaction()->primary_registration();
867
-        }
868
-        return null;
869
-    }
870
-
871
-
872
-    /**
873
-     * Gets the payment's transaction's primary registration's attendee, or null
874
-     *
875
-     * @return EE_Attendee|null
876
-     */
877
-    public function get_primary_attendee()
878
-    {
879
-        $primary_reg = $this->get_primary_registration();
880
-        if ($primary_reg instanceof EE_Registration) {
881
-            return $primary_reg->attendee();
882
-        }
883
-        return null;
884
-    }
15
+	/**
16
+	 * @param array  $props_n_values          incoming values
17
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
18
+	 *                                        used.)
19
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
20
+	 *                                        date_format and the second value is the time format
21
+	 * @return EE_Payment
22
+	 * @throws \EE_Error
23
+	 */
24
+	public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
25
+	{
26
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
27
+		return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
28
+	}
29
+
30
+
31
+	/**
32
+	 * @param array  $props_n_values  incoming values from the database
33
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
34
+	 *                                the website will be used.
35
+	 * @return EE_Payment
36
+	 * @throws \EE_Error
37
+	 */
38
+	public static function new_instance_from_db($props_n_values = array(), $timezone = null)
39
+	{
40
+		return new self($props_n_values, true, $timezone);
41
+	}
42
+
43
+
44
+	/**
45
+	 * Set Transaction ID
46
+	 *
47
+	 * @access public
48
+	 * @param int $TXN_ID
49
+	 * @throws \EE_Error
50
+	 */
51
+	public function set_transaction_id($TXN_ID = 0)
52
+	{
53
+		$this->set('TXN_ID', $TXN_ID);
54
+	}
55
+
56
+
57
+	/**
58
+	 * Gets the transaction related to this payment
59
+	 *
60
+	 * @return EE_Transaction
61
+	 * @throws \EE_Error
62
+	 */
63
+	public function transaction()
64
+	{
65
+		return $this->get_first_related('Transaction');
66
+	}
67
+
68
+
69
+	/**
70
+	 * Set Status
71
+	 *
72
+	 * @access public
73
+	 * @param string $STS_ID
74
+	 * @throws \EE_Error
75
+	 */
76
+	public function set_status($STS_ID = '')
77
+	{
78
+		$this->set('STS_ID', $STS_ID);
79
+	}
80
+
81
+
82
+	/**
83
+	 * Set Payment Timestamp
84
+	 *
85
+	 * @access public
86
+	 * @param int $timestamp
87
+	 * @throws \EE_Error
88
+	 */
89
+	public function set_timestamp($timestamp = 0)
90
+	{
91
+		$this->set('PAY_timestamp', $timestamp);
92
+	}
93
+
94
+
95
+	/**
96
+	 * Set Payment Method
97
+	 *
98
+	 * @access public
99
+	 * @param string $PAY_source
100
+	 * @throws \EE_Error
101
+	 */
102
+	public function set_source($PAY_source = '')
103
+	{
104
+		$this->set('PAY_source', $PAY_source);
105
+	}
106
+
107
+
108
+	/**
109
+	 * Set Payment Amount
110
+	 *
111
+	 * @access public
112
+	 * @param float $amount
113
+	 * @throws \EE_Error
114
+	 */
115
+	public function set_amount($amount = 0.00)
116
+	{
117
+		$this->set('PAY_amount', (float) $amount);
118
+	}
119
+
120
+
121
+	/**
122
+	 * Set Payment Gateway Response
123
+	 *
124
+	 * @access public
125
+	 * @param string $gateway_response
126
+	 * @throws \EE_Error
127
+	 */
128
+	public function set_gateway_response($gateway_response = '')
129
+	{
130
+		$this->set('PAY_gateway_response', $gateway_response);
131
+	}
132
+
133
+
134
+	/**
135
+	 * Returns the name of the payment method used on this payment (previously known merely as 'gateway')
136
+	 * but since 4.6.0, payment methods are models and the payment keeps a foreign key to the payment method
137
+	 * used on it
138
+	 *
139
+	 * @deprecated
140
+	 * @return string
141
+	 * @throws \EE_Error
142
+	 */
143
+	public function gateway()
144
+	{
145
+		EE_Error::doing_it_wrong(
146
+			'EE_Payment::gateway',
147
+			esc_html__(
148
+				'The method EE_Payment::gateway() has been deprecated. Consider instead using EE_Payment::payment_method()->name()',
149
+				'event_espresso'
150
+			),
151
+			'4.6.0'
152
+		);
153
+		return $this->payment_method() ? $this->payment_method()->name() : esc_html__('Unknown', 'event_espresso');
154
+	}
155
+
156
+
157
+	/**
158
+	 * Set Gateway Transaction ID
159
+	 *
160
+	 * @access public
161
+	 * @param string $txn_id_chq_nmbr
162
+	 * @throws \EE_Error
163
+	 */
164
+	public function set_txn_id_chq_nmbr($txn_id_chq_nmbr = '')
165
+	{
166
+		$this->set('PAY_txn_id_chq_nmbr', $txn_id_chq_nmbr);
167
+	}
168
+
169
+
170
+	/**
171
+	 * Set Purchase Order Number
172
+	 *
173
+	 * @access public
174
+	 * @param string $po_number
175
+	 * @throws \EE_Error
176
+	 */
177
+	public function set_po_number($po_number = '')
178
+	{
179
+		$this->set('PAY_po_number', $po_number);
180
+	}
181
+
182
+
183
+	/**
184
+	 * Set Extra Accounting Field
185
+	 *
186
+	 * @access public
187
+	 * @param string $extra_accntng
188
+	 * @throws \EE_Error
189
+	 */
190
+	public function set_extra_accntng($extra_accntng = '')
191
+	{
192
+		$this->set('PAY_extra_accntng', $extra_accntng);
193
+	}
194
+
195
+
196
+	/**
197
+	 * Set Payment made via admin flag
198
+	 *
199
+	 * @access public
200
+	 * @param bool $via_admin
201
+	 * @throws \EE_Error
202
+	 */
203
+	public function set_payment_made_via_admin($via_admin = false)
204
+	{
205
+		if ($via_admin) {
206
+			$this->set('PAY_source', EEM_Payment_Method::scope_admin);
207
+		} else {
208
+			$this->set('PAY_source', EEM_Payment_Method::scope_cart);
209
+		}
210
+	}
211
+
212
+
213
+	/**
214
+	 * Set Payment Details
215
+	 *
216
+	 * @access public
217
+	 * @param string|array $details
218
+	 * @throws \EE_Error
219
+	 */
220
+	public function set_details($details = '')
221
+	{
222
+		if (is_array($details)) {
223
+			array_walk_recursive($details, array($this, '_strip_all_tags_within_array'));
224
+		} else {
225
+			$details = wp_strip_all_tags($details);
226
+		}
227
+		$this->set('PAY_details', $details);
228
+	}
229
+
230
+
231
+	/**
232
+	 * Sets redirect_url
233
+	 *
234
+	 * @param string $redirect_url
235
+	 * @throws \EE_Error
236
+	 */
237
+	public function set_redirect_url($redirect_url)
238
+	{
239
+		$this->set('PAY_redirect_url', $redirect_url);
240
+	}
241
+
242
+
243
+	/**
244
+	 * Sets redirect_args
245
+	 *
246
+	 * @param array $redirect_args
247
+	 * @throws \EE_Error
248
+	 */
249
+	public function set_redirect_args($redirect_args)
250
+	{
251
+		$this->set('PAY_redirect_args', $redirect_args);
252
+	}
253
+
254
+
255
+	/**
256
+	 * get Payment Transaction ID
257
+	 *
258
+	 * @access public
259
+	 * @throws \EE_Error
260
+	 */
261
+	public function TXN_ID()
262
+	{
263
+		return $this->get('TXN_ID');
264
+	}
265
+
266
+
267
+	/**
268
+	 * get Payment Status
269
+	 *
270
+	 * @access public
271
+	 * @throws \EE_Error
272
+	 */
273
+	public function status()
274
+	{
275
+		return $this->get('STS_ID');
276
+	}
277
+
278
+
279
+	/**
280
+	 * get Payment Status
281
+	 *
282
+	 * @access public
283
+	 * @throws \EE_Error
284
+	 */
285
+	public function STS_ID()
286
+	{
287
+		return $this->get('STS_ID');
288
+	}
289
+
290
+
291
+	/**
292
+	 * get Payment Timestamp
293
+	 *
294
+	 * @access public
295
+	 * @param string $dt_frmt
296
+	 * @param string $tm_frmt
297
+	 * @return string
298
+	 * @throws \EE_Error
299
+	 */
300
+	public function timestamp($dt_frmt = '', $tm_frmt = '')
301
+	{
302
+		return $this->get_i18n_datetime('PAY_timestamp', trim($dt_frmt . ' ' . $tm_frmt));
303
+	}
304
+
305
+
306
+	/**
307
+	 * get Payment Source
308
+	 *
309
+	 * @access public
310
+	 * @throws \EE_Error
311
+	 */
312
+	public function source()
313
+	{
314
+		return $this->get('PAY_source');
315
+	}
316
+
317
+
318
+	/**
319
+	 * get Payment Amount
320
+	 *
321
+	 * @access public
322
+	 * @return float
323
+	 * @throws \EE_Error
324
+	 */
325
+	public function amount()
326
+	{
327
+		return (float) $this->get('PAY_amount');
328
+	}
329
+
330
+
331
+	/**
332
+	 * @return mixed
333
+	 * @throws \EE_Error
334
+	 */
335
+	public function amount_no_code()
336
+	{
337
+		return $this->get_pretty('PAY_amount', 'no_currency_code');
338
+	}
339
+
340
+
341
+	/**
342
+	 * get Payment Gateway Response
343
+	 *
344
+	 * @access public
345
+	 * @throws \EE_Error
346
+	 */
347
+	public function gateway_response()
348
+	{
349
+		return $this->get('PAY_gateway_response');
350
+	}
351
+
352
+
353
+	/**
354
+	 * get Payment Gateway Transaction ID
355
+	 *
356
+	 * @access public
357
+	 * @throws \EE_Error
358
+	 */
359
+	public function txn_id_chq_nmbr()
360
+	{
361
+		return $this->get('PAY_txn_id_chq_nmbr');
362
+	}
363
+
364
+
365
+	/**
366
+	 * get Purchase Order Number
367
+	 *
368
+	 * @access public
369
+	 * @throws \EE_Error
370
+	 */
371
+	public function po_number()
372
+	{
373
+		return $this->get('PAY_po_number');
374
+	}
375
+
376
+
377
+	/**
378
+	 * get Extra Accounting Field
379
+	 *
380
+	 * @access public
381
+	 * @throws \EE_Error
382
+	 */
383
+	public function extra_accntng()
384
+	{
385
+		return $this->get('PAY_extra_accntng');
386
+	}
387
+
388
+
389
+	/**
390
+	 * get Payment made via admin source
391
+	 *
392
+	 * @access public
393
+	 * @throws \EE_Error
394
+	 */
395
+	public function payment_made_via_admin()
396
+	{
397
+		return ($this->get('PAY_source') === EEM_Payment_Method::scope_admin);
398
+	}
399
+
400
+
401
+	/**
402
+	 * get Payment Details
403
+	 *
404
+	 * @access public
405
+	 * @throws \EE_Error
406
+	 */
407
+	public function details()
408
+	{
409
+		return $this->get('PAY_details');
410
+	}
411
+
412
+
413
+	/**
414
+	 * Gets redirect_url
415
+	 *
416
+	 * @return string
417
+	 * @throws \EE_Error
418
+	 */
419
+	public function redirect_url()
420
+	{
421
+		return $this->get('PAY_redirect_url');
422
+	}
423
+
424
+
425
+	/**
426
+	 * Gets redirect_args
427
+	 *
428
+	 * @return array
429
+	 * @throws \EE_Error
430
+	 */
431
+	public function redirect_args()
432
+	{
433
+		return $this->get('PAY_redirect_args');
434
+	}
435
+
436
+
437
+	/**
438
+	 * echoes $this->pretty_status()
439
+	 *
440
+	 * @param bool $show_icons
441
+	 * @return void
442
+	 * @throws \EE_Error
443
+	 */
444
+	public function e_pretty_status($show_icons = false)
445
+	{
446
+		echo wp_kses($this->pretty_status($show_icons), AllowedTags::getAllowedTags());
447
+	}
448
+
449
+
450
+	/**
451
+	 * returns a pretty version of the status, good for displaying to users
452
+	 *
453
+	 * @param bool $show_icons
454
+	 * @return string
455
+	 * @throws \EE_Error
456
+	 */
457
+	public function pretty_status($show_icons = false)
458
+	{
459
+		$status = EEM_Status::instance()->localized_status(
460
+			array($this->STS_ID() => esc_html__('unknown', 'event_espresso')),
461
+			false,
462
+			'sentence'
463
+		);
464
+		$icon = '';
465
+		switch ($this->STS_ID()) {
466
+			case EEM_Payment::status_id_approved:
467
+				$icon = $show_icons
468
+					? '<span class="dashicons dashicons-yes ee-icon-size-24 green-text"></span>'
469
+					: '';
470
+				break;
471
+			case EEM_Payment::status_id_pending:
472
+				$icon = $show_icons
473
+					? '<span class="dashicons dashicons-clock ee-icon-size-16 orange-text"></span>'
474
+					: '';
475
+				break;
476
+			case EEM_Payment::status_id_cancelled:
477
+				$icon = $show_icons
478
+					? '<span class="dashicons dashicons-no ee-icon-size-16 lt-grey-text"></span>'
479
+					: '';
480
+				break;
481
+			case EEM_Payment::status_id_declined:
482
+				$icon = $show_icons
483
+					? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>'
484
+					: '';
485
+				break;
486
+		}
487
+		return $icon . $status[ $this->STS_ID() ];
488
+	}
489
+
490
+
491
+	/**
492
+	 * For determining the status of the payment
493
+	 *
494
+	 * @return boolean whether the payment is approved or not
495
+	 * @throws \EE_Error
496
+	 */
497
+	public function is_approved()
498
+	{
499
+		return $this->status_is(EEM_Payment::status_id_approved);
500
+	}
501
+
502
+
503
+	/**
504
+	 * Generally determines if the status of this payment equals
505
+	 * the $STS_ID string
506
+	 *
507
+	 * @param string $STS_ID an ID from the esp_status table/
508
+	 *                       one of the status_id_* on the EEM_Payment model
509
+	 * @return boolean whether the status of this payment equals the status id
510
+	 * @throws \EE_Error
511
+	 */
512
+	protected function status_is($STS_ID)
513
+	{
514
+		return $STS_ID === $this->STS_ID() ? true : false;
515
+	}
516
+
517
+
518
+	/**
519
+	 * For determining the status of the payment
520
+	 *
521
+	 * @return boolean whether the payment is pending or not
522
+	 * @throws \EE_Error
523
+	 */
524
+	public function is_pending()
525
+	{
526
+		return $this->status_is(EEM_Payment::status_id_pending);
527
+	}
528
+
529
+
530
+	/**
531
+	 * For determining the status of the payment
532
+	 *
533
+	 * @return boolean
534
+	 * @throws \EE_Error
535
+	 */
536
+	public function is_cancelled()
537
+	{
538
+		return $this->status_is(EEM_Payment::status_id_cancelled);
539
+	}
540
+
541
+
542
+	/**
543
+	 * For determining the status of the payment
544
+	 *
545
+	 * @return boolean
546
+	 * @throws \EE_Error
547
+	 */
548
+	public function is_declined()
549
+	{
550
+		return $this->status_is(EEM_Payment::status_id_declined);
551
+	}
552
+
553
+
554
+	/**
555
+	 * For determining the status of the payment
556
+	 *
557
+	 * @return boolean
558
+	 * @throws \EE_Error
559
+	 */
560
+	public function is_failed()
561
+	{
562
+		return $this->status_is(EEM_Payment::status_id_failed);
563
+	}
564
+
565
+
566
+	/**
567
+	 * For determining if the payment is actually a refund ( ie: has a negative value )
568
+	 *
569
+	 * @return boolean
570
+	 * @throws \EE_Error
571
+	 */
572
+	public function is_a_refund()
573
+	{
574
+		return $this->amount() < 0 ? true : false;
575
+	}
576
+
577
+
578
+	/**
579
+	 * Get the status object of this object
580
+	 *
581
+	 * @return EE_Status
582
+	 * @throws \EE_Error
583
+	 */
584
+	public function status_obj()
585
+	{
586
+		return $this->get_first_related('Status');
587
+	}
588
+
589
+
590
+	/**
591
+	 * Gets all the extra meta info on this payment
592
+	 *
593
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
594
+	 * @return EE_Extra_Meta
595
+	 * @throws \EE_Error
596
+	 */
597
+	public function extra_meta($query_params = array())
598
+	{
599
+		return $this->get_many_related('Extra_Meta', $query_params);
600
+	}
601
+
602
+
603
+	/**
604
+	 * Gets the last-used payment method on this transaction
605
+	 * (we COULD just use the last-made payment, but some payment methods, namely
606
+	 * offline ones, dont' create payments)
607
+	 *
608
+	 * @return EE_Payment_Method
609
+	 * @throws \EE_Error
610
+	 */
611
+	public function payment_method()
612
+	{
613
+		return $this->get_first_related('Payment_Method');
614
+	}
615
+
616
+
617
+	/**
618
+	 * Gets the HTML for redirecting the user to an offsite gateway
619
+	 * You can pass it special content to put inside the form, or use
620
+	 * the default inner content (or possibly generate this all yourself using
621
+	 * redirect_url() and redirect_args() or redirect_args_as_inputs()).
622
+	 * Creates a POST request by default, but if no redirect args are specified, creates a GET request instead
623
+	 * (and any querystring variables in the redirect_url are converted into html inputs
624
+	 * so browsers submit them properly)
625
+	 *
626
+	 * @param string $inside_form_html
627
+	 * @return string html
628
+	 * @throws \EE_Error
629
+	 */
630
+	public function redirect_form($inside_form_html = null)
631
+	{
632
+		$redirect_url = $this->redirect_url();
633
+		if (! empty($redirect_url)) {
634
+			// what ? no inner form content?
635
+			if ($inside_form_html === null) {
636
+				$inside_form_html = EEH_HTML::p(
637
+					sprintf(
638
+						esc_html__(
639
+							'If you are not automatically redirected to the payment website within 10 seconds... %1$s %2$s Click Here %3$s',
640
+							'event_espresso'
641
+						),
642
+						EEH_HTML::br(2),
643
+						'<input type="submit" value="',
644
+						'">'
645
+					),
646
+					'',
647
+					'',
648
+					'text-align:center;'
649
+				);
650
+			}
651
+			$method = apply_filters(
652
+				'FHEE__EE_Payment__redirect_form__method',
653
+				$this->redirect_args() ? 'POST' : 'GET',
654
+				$this
655
+			);
656
+			// if it's a GET request, we need to remove all the GET params in the querystring
657
+			// and put them into the form instead
658
+			if ($method === 'GET') {
659
+				$querystring = parse_url($redirect_url, PHP_URL_QUERY);
660
+				$get_params = null;
661
+				parse_str($querystring, $get_params);
662
+				$inside_form_html .= $this->_args_as_inputs($get_params);
663
+				$redirect_url = str_replace('?' . $querystring, '', $redirect_url);
664
+			}
665
+			$form = EEH_HTML::nl(1)
666
+					. '<form method="'
667
+					. $method
668
+					. '" name="gateway_form" action="'
669
+					. $redirect_url
670
+					. '">';
671
+			$form .= EEH_HTML::nl(1) . $this->redirect_args_as_inputs();
672
+			$form .= $inside_form_html;
673
+			$form .= EEH_HTML::nl(-1) . '</form>' . EEH_HTML::nl(-1);
674
+			return $form;
675
+		} else {
676
+			return null;
677
+		}
678
+	}
679
+
680
+
681
+	/**
682
+	 * Changes all the name-value pairs of the redirect args into html inputs
683
+	 * and returns the html as a string
684
+	 *
685
+	 * @return string
686
+	 * @throws \EE_Error
687
+	 */
688
+	public function redirect_args_as_inputs()
689
+	{
690
+		return $this->_args_as_inputs($this->redirect_args());
691
+	}
692
+
693
+
694
+	/**
695
+	 * Converts a 2d array of key-value pairs into html hidden inputs
696
+	 * and returns the string of html
697
+	 *
698
+	 * @param array $args key-value pairs
699
+	 * @return string
700
+	 */
701
+	protected function _args_as_inputs($args)
702
+	{
703
+		$html = '';
704
+		if ($args !== null && is_array($args)) {
705
+			foreach ($args as $name => $value) {
706
+				$html .= $this->generateInput($name, $value);
707
+			}
708
+		}
709
+		return $html;
710
+	}
711
+
712
+	/**
713
+	 * Converts either a single name and value or array of values into html hidden inputs
714
+	 * and returns the string of html
715
+	 *
716
+	 * @param string $name
717
+	 * @param string|array $value
718
+	 * @return string
719
+	 */
720
+	private function generateInput($name, $value)
721
+	{
722
+		if (is_array($value)) {
723
+			$html = '';
724
+			$name = "{$name}[]";
725
+			foreach ($value as $array_value) {
726
+				$html .= $this->generateInput($name, $array_value);
727
+			}
728
+			return $html;
729
+		}
730
+		return EEH_HTML::nl()
731
+			. '<input type="hidden" name="' . $name . '"'
732
+			. ' value="' . esc_attr($value) . '"/>';
733
+	}
734
+
735
+
736
+	/**
737
+	 * Returns the currency of the payment.
738
+	 * (At the time of writing, this will always be the currency in the configuration;
739
+	 * however in the future it is anticipated that this will be stored on the payment
740
+	 * object itself)
741
+	 *
742
+	 * @return string for the currency code
743
+	 */
744
+	public function currency_code()
745
+	{
746
+		return EE_Config::instance()->currency->code;
747
+	}
748
+
749
+
750
+	/**
751
+	 * apply wp_strip_all_tags to all elements within an array
752
+	 *
753
+	 * @access private
754
+	 * @param mixed $item
755
+	 */
756
+	private function _strip_all_tags_within_array(&$item)
757
+	{
758
+		if (is_object($item)) {
759
+			$item = (array) $item;
760
+		}
761
+		if (is_array($item)) {
762
+			array_walk_recursive($item, array($this, '_strip_all_tags_within_array'));
763
+		} else {
764
+			$item = wp_strip_all_tags($item);
765
+		}
766
+	}
767
+
768
+
769
+	/**
770
+	 * Returns TRUE is this payment was set to approved during this request (or
771
+	 * is approved and was created during this request). False otherwise.
772
+	 *
773
+	 * @return boolean
774
+	 * @throws \EE_Error
775
+	 */
776
+	public function just_approved()
777
+	{
778
+		$original_status = EEH_Array::is_set(
779
+			$this->_props_n_values_provided_in_constructor,
780
+			'STS_ID',
781
+			$this->get_model()->field_settings_for('STS_ID')->get_default_value()
782
+		);
783
+		$current_status = $this->status();
784
+		if (
785
+			$original_status !== EEM_Payment::status_id_approved
786
+			&& $current_status === EEM_Payment::status_id_approved
787
+		) {
788
+			return true;
789
+		} else {
790
+			return false;
791
+		}
792
+	}
793
+
794
+
795
+	/**
796
+	 * Overrides parents' get_pretty() function just for legacy reasons
797
+	 * (to allow ticket https://events.codebasehq.com/projects/event-espresso/tickets/7420)
798
+	 *
799
+	 * @param string $field_name
800
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
801
+	 *                                (in cases where the same property may be used for different outputs
802
+	 *                                - i.e. datetime, money etc.)
803
+	 * @return mixed
804
+	 * @throws \EE_Error
805
+	 */
806
+	public function get_pretty($field_name, $extra_cache_ref = null)
807
+	{
808
+		if ($field_name === 'PAY_gateway') {
809
+			return $this->payment_method() ? $this->payment_method()->name() : esc_html__('Unknown', 'event_espresso');
810
+		}
811
+		return $this->_get_cached_property($field_name, true, $extra_cache_ref);
812
+	}
813
+
814
+
815
+	/**
816
+	 * Gets details regarding which registrations this payment was applied to
817
+	 *
818
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
819
+	 * @return EE_Registration_Payment[]
820
+	 * @throws \EE_Error
821
+	 */
822
+	public function registration_payments($query_params = array())
823
+	{
824
+		return $this->get_many_related('Registration_Payment', $query_params);
825
+	}
826
+
827
+
828
+	/**
829
+	 * Gets the first event for this payment (it's possible that it could be for multiple)
830
+	 *
831
+	 * @return EE_Event|null
832
+	 */
833
+	public function get_first_event()
834
+	{
835
+		$transaction = $this->transaction();
836
+		if ($transaction instanceof EE_Transaction) {
837
+			$primary_registrant = $transaction->primary_registration();
838
+			if ($primary_registrant instanceof EE_Registration) {
839
+				return $primary_registrant->event_obj();
840
+			}
841
+		}
842
+		return null;
843
+	}
844
+
845
+
846
+	/**
847
+	 * Gets the name of the first event for which is being paid
848
+	 *
849
+	 * @return string
850
+	 */
851
+	public function get_first_event_name()
852
+	{
853
+		$event = $this->get_first_event();
854
+		return $event instanceof EE_Event ? $event->name() : esc_html__('Event', 'event_espresso');
855
+	}
856
+
857
+
858
+	/**
859
+	 * Returns the payment's transaction's primary registration
860
+	 *
861
+	 * @return EE_Registration|null
862
+	 */
863
+	public function get_primary_registration()
864
+	{
865
+		if ($this->transaction() instanceof EE_Transaction) {
866
+			return $this->transaction()->primary_registration();
867
+		}
868
+		return null;
869
+	}
870
+
871
+
872
+	/**
873
+	 * Gets the payment's transaction's primary registration's attendee, or null
874
+	 *
875
+	 * @return EE_Attendee|null
876
+	 */
877
+	public function get_primary_attendee()
878
+	{
879
+		$primary_reg = $this->get_primary_registration();
880
+		if ($primary_reg instanceof EE_Registration) {
881
+			return $primary_reg->attendee();
882
+		}
883
+		return null;
884
+	}
885 885
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Datetime.class.php 1 patch
Indentation   +1404 added lines, -1404 removed lines patch added patch discarded remove patch
@@ -13,1412 +13,1412 @@
 block discarded – undo
13 13
 class EE_Datetime extends EE_Soft_Delete_Base_Class
14 14
 {
15 15
 
16
-    /**
17
-     * constant used by get_active_status, indicates datetime has no more available spaces
18
-     */
19
-    const sold_out = 'DTS';
20
-
21
-    /**
22
-     * constant used by get_active_status, indicating datetime is still active (even is not over, can be registered-for)
23
-     */
24
-    const active = 'DTA';
25
-
26
-    /**
27
-     * constant used by get_active_status, indicating the datetime cannot be used for registrations yet, but has not
28
-     * expired
29
-     */
30
-    const upcoming = 'DTU';
31
-
32
-    /**
33
-     * Datetime is postponed
34
-     */
35
-    const postponed = 'DTP';
36
-
37
-    /**
38
-     * Datetime is cancelled
39
-     */
40
-    const cancelled = 'DTC';
41
-
42
-    /**
43
-     * constant used by get_active_status, indicates datetime has expired (event is over)
44
-     */
45
-    const expired = 'DTE';
46
-
47
-    /**
48
-     * constant used in various places indicating that an event is INACTIVE (not yet ready to be published)
49
-     */
50
-    const inactive = 'DTI';
51
-
52
-
53
-    /**
54
-     * @param array  $props_n_values    incoming values
55
-     * @param string $timezone          incoming timezone (if not set the timezone set for the website will be used.)
56
-     * @param array  $date_formats      incoming date_formats in an array where the first value is the date_format
57
-     *                                  and the second value is the time format
58
-     * @return EE_Datetime
59
-     * @throws ReflectionException
60
-     * @throws InvalidArgumentException
61
-     * @throws InvalidInterfaceException
62
-     * @throws InvalidDataTypeException
63
-     * @throws EE_Error
64
-     */
65
-    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
66
-    {
67
-        $has_object = parent::_check_for_object(
68
-            $props_n_values,
69
-            __CLASS__,
70
-            $timezone,
71
-            $date_formats
72
-        );
73
-        return $has_object
74
-            ? $has_object
75
-            : new self($props_n_values, false, $timezone, $date_formats);
76
-    }
77
-
78
-
79
-    /**
80
-     * @param array  $props_n_values  incoming values from the database
81
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
82
-     *                                the website will be used.
83
-     * @return EE_Datetime
84
-     * @throws ReflectionException
85
-     * @throws InvalidArgumentException
86
-     * @throws InvalidInterfaceException
87
-     * @throws InvalidDataTypeException
88
-     * @throws EE_Error
89
-     */
90
-    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
91
-    {
92
-        return new self($props_n_values, true, $timezone);
93
-    }
94
-
95
-
96
-    /**
97
-     * @param $name
98
-     * @throws ReflectionException
99
-     * @throws InvalidArgumentException
100
-     * @throws InvalidInterfaceException
101
-     * @throws InvalidDataTypeException
102
-     * @throws EE_Error
103
-     */
104
-    public function set_name($name)
105
-    {
106
-        $this->set('DTT_name', $name);
107
-    }
108
-
109
-
110
-    /**
111
-     * @param $description
112
-     * @throws ReflectionException
113
-     * @throws InvalidArgumentException
114
-     * @throws InvalidInterfaceException
115
-     * @throws InvalidDataTypeException
116
-     * @throws EE_Error
117
-     */
118
-    public function set_description($description)
119
-    {
120
-        $this->set('DTT_description', $description);
121
-    }
122
-
123
-
124
-    /**
125
-     * Set event start date
126
-     * set the start date for an event
127
-     *
128
-     * @param string $date a string representation of the event's date ex:  Dec. 25, 2025 or 12-25-2025
129
-     * @throws ReflectionException
130
-     * @throws InvalidArgumentException
131
-     * @throws InvalidInterfaceException
132
-     * @throws InvalidDataTypeException
133
-     * @throws EE_Error
134
-     */
135
-    public function set_start_date($date)
136
-    {
137
-        $this->_set_date_for($date, 'DTT_EVT_start');
138
-    }
139
-
140
-
141
-    /**
142
-     * Set event start time
143
-     * set the start time for an event
144
-     *
145
-     * @param string $time a string representation of the event time ex:  9am  or  7:30 PM
146
-     * @throws ReflectionException
147
-     * @throws InvalidArgumentException
148
-     * @throws InvalidInterfaceException
149
-     * @throws InvalidDataTypeException
150
-     * @throws EE_Error
151
-     */
152
-    public function set_start_time($time)
153
-    {
154
-        $this->_set_time_for($time, 'DTT_EVT_start');
155
-    }
156
-
157
-
158
-    /**
159
-     * Set event end date
160
-     * set the end date for an event
161
-     *
162
-     * @param string $date a string representation of the event's date ex:  Dec. 25, 2025 or 12-25-2025
163
-     * @throws ReflectionException
164
-     * @throws InvalidArgumentException
165
-     * @throws InvalidInterfaceException
166
-     * @throws InvalidDataTypeException
167
-     * @throws EE_Error
168
-     */
169
-    public function set_end_date($date)
170
-    {
171
-        $this->_set_date_for($date, 'DTT_EVT_end');
172
-    }
173
-
174
-
175
-    /**
176
-     * Set event end time
177
-     * set the end time for an event
178
-     *
179
-     * @param string $time a string representation of the event time ex:  9am  or  7:30 PM
180
-     * @throws ReflectionException
181
-     * @throws InvalidArgumentException
182
-     * @throws InvalidInterfaceException
183
-     * @throws InvalidDataTypeException
184
-     * @throws EE_Error
185
-     */
186
-    public function set_end_time($time)
187
-    {
188
-        $this->_set_time_for($time, 'DTT_EVT_end');
189
-    }
190
-
191
-
192
-    /**
193
-     * Set registration limit
194
-     * set the maximum number of attendees that can be registered for this datetime slot
195
-     *
196
-     * @param int $reg_limit
197
-     * @throws ReflectionException
198
-     * @throws InvalidArgumentException
199
-     * @throws InvalidInterfaceException
200
-     * @throws InvalidDataTypeException
201
-     * @throws EE_Error
202
-     */
203
-    public function set_reg_limit($reg_limit)
204
-    {
205
-        $this->set('DTT_reg_limit', $reg_limit);
206
-    }
207
-
208
-
209
-    /**
210
-     * get the number of tickets sold for this datetime slot
211
-     *
212
-     * @return mixed int on success, FALSE on fail
213
-     * @throws ReflectionException
214
-     * @throws InvalidArgumentException
215
-     * @throws InvalidInterfaceException
216
-     * @throws InvalidDataTypeException
217
-     * @throws EE_Error
218
-     */
219
-    public function sold()
220
-    {
221
-        return $this->get_raw('DTT_sold');
222
-    }
223
-
224
-
225
-    /**
226
-     * @param int $sold
227
-     * @throws ReflectionException
228
-     * @throws InvalidArgumentException
229
-     * @throws InvalidInterfaceException
230
-     * @throws InvalidDataTypeException
231
-     * @throws EE_Error
232
-     */
233
-    public function set_sold($sold)
234
-    {
235
-        // sold can not go below zero
236
-        $sold = max(0, $sold);
237
-        $this->set('DTT_sold', $sold);
238
-    }
239
-
240
-
241
-    /**
242
-     * Increments sold by amount passed by $qty, and persists it immediately to the database.
243
-     * Simultaneously decreases the reserved count, unless $also_decrease_reserved is false.
244
-     *
245
-     * @param int $qty
246
-     * @param boolean $also_decrease_reserved
247
-     * @return boolean indicating success
248
-     * @throws ReflectionException
249
-     * @throws InvalidArgumentException
250
-     * @throws InvalidInterfaceException
251
-     * @throws InvalidDataTypeException
252
-     * @throws EE_Error
253
-     */
254
-    public function increaseSold($qty = 1, $also_decrease_reserved = true)
255
-    {
256
-        $qty = absint($qty);
257
-        if ($also_decrease_reserved) {
258
-            $success = $this->adjustNumericFieldsInDb(
259
-                [
260
-                    'DTT_reserved' => $qty * -1,
261
-                    'DTT_sold' => $qty
262
-                ]
263
-            );
264
-        } else {
265
-            $success = $this->adjustNumericFieldsInDb(
266
-                [
267
-                    'DTT_sold' => $qty
268
-                ]
269
-            );
270
-        }
271
-
272
-        do_action(
273
-            'AHEE__EE_Datetime__increase_sold',
274
-            $this,
275
-            $qty,
276
-            $this->sold(),
277
-            $success
278
-        );
279
-        return $success;
280
-    }
281
-
282
-
283
-    /**
284
-     * Decrements (subtracts) sold amount passed by $qty directly in the DB and on the model object. (Ie, no need
285
-     * to save afterwards.)
286
-     *
287
-     * @param int $qty
288
-     * @return boolean indicating success
289
-     * @throws ReflectionException
290
-     * @throws InvalidArgumentException
291
-     * @throws InvalidInterfaceException
292
-     * @throws InvalidDataTypeException
293
-     * @throws EE_Error
294
-     */
295
-    public function decreaseSold($qty = 1)
296
-    {
297
-        $qty = absint($qty);
298
-        $success = $this->adjustNumericFieldsInDb(
299
-            [
300
-                'DTT_sold' => $qty * -1
301
-            ]
302
-        );
303
-        do_action(
304
-            'AHEE__EE_Datetime__decrease_sold',
305
-            $this,
306
-            $qty,
307
-            $this->sold(),
308
-            $success
309
-        );
310
-        return $success;
311
-    }
312
-
313
-
314
-    /**
315
-     * Gets qty of reserved tickets for this datetime
316
-     *
317
-     * @return int
318
-     * @throws ReflectionException
319
-     * @throws InvalidArgumentException
320
-     * @throws InvalidInterfaceException
321
-     * @throws InvalidDataTypeException
322
-     * @throws EE_Error
323
-     */
324
-    public function reserved()
325
-    {
326
-        return $this->get_raw('DTT_reserved');
327
-    }
328
-
329
-
330
-    /**
331
-     * Sets qty of reserved tickets for this datetime
332
-     *
333
-     * @param int $reserved
334
-     * @throws ReflectionException
335
-     * @throws InvalidArgumentException
336
-     * @throws InvalidInterfaceException
337
-     * @throws InvalidDataTypeException
338
-     * @throws EE_Error
339
-     */
340
-    public function set_reserved($reserved)
341
-    {
342
-        // reserved can not go below zero
343
-        $reserved = max(0, (int) $reserved);
344
-        $this->set('DTT_reserved', $reserved);
345
-    }
346
-
347
-
348
-    /**
349
-     * Increments reserved by amount passed by $qty, and persists it immediately to the database.
350
-     *
351
-     * @param int $qty
352
-     * @return boolean indicating success
353
-     * @throws ReflectionException
354
-     * @throws InvalidArgumentException
355
-     * @throws InvalidInterfaceException
356
-     * @throws InvalidDataTypeException
357
-     * @throws EE_Error
358
-     */
359
-    public function increaseReserved($qty = 1)
360
-    {
361
-        $qty = absint($qty);
362
-        $success = $this->incrementFieldConditionallyInDb(
363
-            'DTT_reserved',
364
-            'DTT_sold',
365
-            'DTT_reg_limit',
366
-            $qty
367
-        );
368
-        do_action(
369
-            'AHEE__EE_Datetime__increase_reserved',
370
-            $this,
371
-            $qty,
372
-            $this->reserved(),
373
-            $success
374
-        );
375
-        return $success;
376
-    }
377
-
378
-
379
-    /**
380
-     * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
381
-     *
382
-     * @param int $qty
383
-     * @return boolean indicating success
384
-     * @throws ReflectionException
385
-     * @throws InvalidArgumentException
386
-     * @throws InvalidInterfaceException
387
-     * @throws InvalidDataTypeException
388
-     * @throws EE_Error
389
-     */
390
-    public function decreaseReserved($qty = 1)
391
-    {
392
-        $qty = absint($qty);
393
-        $success = $this->adjustNumericFieldsInDb(
394
-            [
395
-                'DTT_reserved' => $qty * -1
396
-            ]
397
-        );
398
-        do_action(
399
-            'AHEE__EE_Datetime__decrease_reserved',
400
-            $this,
401
-            $qty,
402
-            $this->reserved(),
403
-            $success
404
-        );
405
-        return $success;
406
-    }
407
-
408
-
409
-    /**
410
-     * total sold and reserved tickets
411
-     *
412
-     * @return int
413
-     * @throws ReflectionException
414
-     * @throws InvalidArgumentException
415
-     * @throws InvalidInterfaceException
416
-     * @throws InvalidDataTypeException
417
-     * @throws EE_Error
418
-     */
419
-    public function sold_and_reserved()
420
-    {
421
-        return $this->sold() + $this->reserved();
422
-    }
423
-
424
-
425
-    /**
426
-     * returns the datetime name
427
-     *
428
-     * @return string
429
-     * @throws ReflectionException
430
-     * @throws InvalidArgumentException
431
-     * @throws InvalidInterfaceException
432
-     * @throws InvalidDataTypeException
433
-     * @throws EE_Error
434
-     */
435
-    public function name()
436
-    {
437
-        return $this->get('DTT_name');
438
-    }
439
-
440
-
441
-    /**
442
-     * returns the datetime description
443
-     *
444
-     * @return string
445
-     * @throws ReflectionException
446
-     * @throws InvalidArgumentException
447
-     * @throws InvalidInterfaceException
448
-     * @throws InvalidDataTypeException
449
-     * @throws EE_Error
450
-     */
451
-    public function description()
452
-    {
453
-        return $this->get('DTT_description');
454
-    }
455
-
456
-
457
-    /**
458
-     * This helper simply returns whether the event_datetime for the current datetime is a primary datetime
459
-     *
460
-     * @return boolean  TRUE if is primary, FALSE if not.
461
-     * @throws ReflectionException
462
-     * @throws InvalidArgumentException
463
-     * @throws InvalidInterfaceException
464
-     * @throws InvalidDataTypeException
465
-     * @throws EE_Error
466
-     */
467
-    public function is_primary()
468
-    {
469
-        return $this->get('DTT_is_primary');
470
-    }
471
-
472
-
473
-    /**
474
-     * This helper simply returns the order for the datetime
475
-     *
476
-     * @return int  The order of the datetime for this event.
477
-     * @throws ReflectionException
478
-     * @throws InvalidArgumentException
479
-     * @throws InvalidInterfaceException
480
-     * @throws InvalidDataTypeException
481
-     * @throws EE_Error
482
-     */
483
-    public function order()
484
-    {
485
-        return $this->get('DTT_order');
486
-    }
487
-
488
-
489
-    /**
490
-     * This helper simply returns the parent id for the datetime
491
-     *
492
-     * @return int
493
-     * @throws ReflectionException
494
-     * @throws InvalidArgumentException
495
-     * @throws InvalidInterfaceException
496
-     * @throws InvalidDataTypeException
497
-     * @throws EE_Error
498
-     */
499
-    public function parent()
500
-    {
501
-        return $this->get('DTT_parent');
502
-    }
503
-
504
-
505
-    /**
506
-     * show date and/or time
507
-     *
508
-     * @param string $date_or_time    whether to display a date or time or both
509
-     * @param string $start_or_end    whether to display start or end datetimes
510
-     * @param string $dt_frmt
511
-     * @param string $tm_frmt
512
-     * @param bool   $echo            whether we echo or return (note echoing uses "pretty" formats,
513
-     *                                otherwise we use the standard formats)
514
-     * @return string|bool  string on success, FALSE on fail
515
-     * @throws ReflectionException
516
-     * @throws InvalidArgumentException
517
-     * @throws InvalidInterfaceException
518
-     * @throws InvalidDataTypeException
519
-     * @throws EE_Error
520
-     */
521
-    private function _show_datetime(
522
-        $date_or_time = null,
523
-        $start_or_end = 'start',
524
-        $dt_frmt = '',
525
-        $tm_frmt = '',
526
-        $echo = false
527
-    ) {
528
-        $field_name = "DTT_EVT_{$start_or_end}";
529
-        $dtt = $this->_get_datetime(
530
-            $field_name,
531
-            $dt_frmt,
532
-            $tm_frmt,
533
-            $date_or_time,
534
-            $echo
535
-        );
536
-        if (! $echo) {
537
-            return $dtt;
538
-        }
539
-        return '';
540
-    }
541
-
542
-
543
-    /**
544
-     * get event start date.  Provide either the date format, or NULL to re-use the
545
-     * last-used format, or '' to use the default date format
546
-     *
547
-     * @param string $dt_frmt string representation of date format defaults to 'F j, Y'
548
-     * @return mixed            string on success, FALSE on fail
549
-     * @throws ReflectionException
550
-     * @throws InvalidArgumentException
551
-     * @throws InvalidInterfaceException
552
-     * @throws InvalidDataTypeException
553
-     * @throws EE_Error
554
-     */
555
-    public function start_date($dt_frmt = '')
556
-    {
557
-        return $this->_show_datetime('D', 'start', $dt_frmt);
558
-    }
559
-
560
-
561
-    /**
562
-     * Echoes start_date()
563
-     *
564
-     * @param string $dt_frmt
565
-     * @throws ReflectionException
566
-     * @throws InvalidArgumentException
567
-     * @throws InvalidInterfaceException
568
-     * @throws InvalidDataTypeException
569
-     * @throws EE_Error
570
-     */
571
-    public function e_start_date($dt_frmt = '')
572
-    {
573
-        $this->_show_datetime('D', 'start', $dt_frmt, null, true);
574
-    }
575
-
576
-
577
-    /**
578
-     * get end date. Provide either the date format, or NULL to re-use the
579
-     * last-used format, or '' to use the default date format
580
-     *
581
-     * @param string $dt_frmt string representation of date format defaults to 'F j, Y'
582
-     * @return mixed            string on success, FALSE on fail
583
-     * @throws ReflectionException
584
-     * @throws InvalidArgumentException
585
-     * @throws InvalidInterfaceException
586
-     * @throws InvalidDataTypeException
587
-     * @throws EE_Error
588
-     */
589
-    public function end_date($dt_frmt = '')
590
-    {
591
-        return $this->_show_datetime('D', 'end', $dt_frmt);
592
-    }
593
-
594
-
595
-    /**
596
-     * Echoes the end date. See end_date()
597
-     *
598
-     * @param string $dt_frmt
599
-     * @throws ReflectionException
600
-     * @throws InvalidArgumentException
601
-     * @throws InvalidInterfaceException
602
-     * @throws InvalidDataTypeException
603
-     * @throws EE_Error
604
-     */
605
-    public function e_end_date($dt_frmt = '')
606
-    {
607
-        $this->_show_datetime('D', 'end', $dt_frmt, null, true);
608
-    }
609
-
610
-
611
-    /**
612
-     * get date_range - meaning the start AND end date
613
-     *
614
-     * @access public
615
-     * @param string $dt_frmt     string representation of date format defaults to WP settings
616
-     * @param string $conjunction conjunction junction what's your function ?
617
-     *                            this string joins the start date with the end date ie: Jan 01 "to" Dec 31
618
-     * @return mixed              string on success, FALSE on fail
619
-     * @throws ReflectionException
620
-     * @throws InvalidArgumentException
621
-     * @throws InvalidInterfaceException
622
-     * @throws InvalidDataTypeException
623
-     * @throws EE_Error
624
-     */
625
-    public function date_range($dt_frmt = '', $conjunction = ' - ')
626
-    {
627
-        $dt_frmt = ! empty($dt_frmt) ? $dt_frmt : $this->_dt_frmt;
628
-        $start = str_replace(
629
-            ' ',
630
-            '&nbsp;',
631
-            $this->get_i18n_datetime('DTT_EVT_start', $dt_frmt)
632
-        );
633
-        $end = str_replace(
634
-            ' ',
635
-            '&nbsp;',
636
-            $this->get_i18n_datetime('DTT_EVT_end', $dt_frmt)
637
-        );
638
-        return $start !== $end ? $start . $conjunction . $end : $start;
639
-    }
640
-
641
-
642
-    /**
643
-     * @param string $dt_frmt
644
-     * @param string $conjunction
645
-     * @throws ReflectionException
646
-     * @throws InvalidArgumentException
647
-     * @throws InvalidInterfaceException
648
-     * @throws InvalidDataTypeException
649
-     * @throws EE_Error
650
-     */
651
-    public function e_date_range($dt_frmt = '', $conjunction = ' - ')
652
-    {
653
-        echo esc_html($this->date_range($dt_frmt, $conjunction));
654
-    }
655
-
656
-
657
-    /**
658
-     * get start time
659
-     *
660
-     * @param string $tm_format - string representation of time format defaults to 'g:i a'
661
-     * @return mixed        string on success, FALSE on fail
662
-     * @throws ReflectionException
663
-     * @throws InvalidArgumentException
664
-     * @throws InvalidInterfaceException
665
-     * @throws InvalidDataTypeException
666
-     * @throws EE_Error
667
-     */
668
-    public function start_time($tm_format = '')
669
-    {
670
-        return $this->_show_datetime('T', 'start', null, $tm_format);
671
-    }
672
-
673
-
674
-    /**
675
-     * @param string $tm_format
676
-     * @throws ReflectionException
677
-     * @throws InvalidArgumentException
678
-     * @throws InvalidInterfaceException
679
-     * @throws InvalidDataTypeException
680
-     * @throws EE_Error
681
-     */
682
-    public function e_start_time($tm_format = '')
683
-    {
684
-        $this->_show_datetime('T', 'start', null, $tm_format, true);
685
-    }
686
-
687
-
688
-    /**
689
-     * get end time
690
-     *
691
-     * @param string $tm_format string representation of time format defaults to 'g:i a'
692
-     * @return mixed                string on success, FALSE on fail
693
-     * @throws ReflectionException
694
-     * @throws InvalidArgumentException
695
-     * @throws InvalidInterfaceException
696
-     * @throws InvalidDataTypeException
697
-     * @throws EE_Error
698
-     */
699
-    public function end_time($tm_format = '')
700
-    {
701
-        return $this->_show_datetime('T', 'end', null, $tm_format);
702
-    }
703
-
704
-
705
-    /**
706
-     * @param string $tm_format
707
-     * @throws ReflectionException
708
-     * @throws InvalidArgumentException
709
-     * @throws InvalidInterfaceException
710
-     * @throws InvalidDataTypeException
711
-     * @throws EE_Error
712
-     */
713
-    public function e_end_time($tm_format = '')
714
-    {
715
-        $this->_show_datetime('T', 'end', null, $tm_format, true);
716
-    }
717
-
718
-
719
-    /**
720
-     * get time_range
721
-     *
722
-     * @access public
723
-     * @param string $tm_format   string representation of time format defaults to 'g:i a'
724
-     * @param string $conjunction conjunction junction what's your function ?
725
-     *                            this string joins the start date with the end date ie: Jan 01 "to" Dec 31
726
-     * @return mixed              string on success, FALSE on fail
727
-     * @throws ReflectionException
728
-     * @throws InvalidArgumentException
729
-     * @throws InvalidInterfaceException
730
-     * @throws InvalidDataTypeException
731
-     * @throws EE_Error
732
-     */
733
-    public function time_range($tm_format = '', $conjunction = ' - ')
734
-    {
735
-        $tm_format = ! empty($tm_format) ? $tm_format : $this->_tm_frmt;
736
-        $start = str_replace(
737
-            ' ',
738
-            '&nbsp;',
739
-            $this->get_i18n_datetime('DTT_EVT_start', $tm_format)
740
-        );
741
-        $end = str_replace(
742
-            ' ',
743
-            '&nbsp;',
744
-            $this->get_i18n_datetime('DTT_EVT_end', $tm_format)
745
-        );
746
-        return $start !== $end ? $start . $conjunction . $end : $start;
747
-    }
748
-
749
-
750
-    /**
751
-     * @param string $tm_format
752
-     * @param string $conjunction
753
-     * @throws ReflectionException
754
-     * @throws InvalidArgumentException
755
-     * @throws InvalidInterfaceException
756
-     * @throws InvalidDataTypeException
757
-     * @throws EE_Error
758
-     */
759
-    public function e_time_range($tm_format = '', $conjunction = ' - ')
760
-    {
761
-        echo esc_html($this->time_range($tm_format, $conjunction));
762
-    }
763
-
764
-
765
-    /**
766
-     * This returns a range representation of the date and times.
767
-     * Output is dependent on the difference (or similarity) between DTT_EVT_start and DTT_EVT_end.
768
-     * Also, the return value is localized.
769
-     *
770
-     * @param string $dt_format
771
-     * @param string $tm_format
772
-     * @param string $conjunction used between two different dates or times.
773
-     *                            ex: Dec 1{$conjunction}}Dec 6, or 2pm{$conjunction}3pm
774
-     * @param string $separator   used between the date and time formats.
775
-     *                            ex: Dec 1, 2016{$separator}2pm
776
-     * @return string
777
-     * @throws ReflectionException
778
-     * @throws InvalidArgumentException
779
-     * @throws InvalidInterfaceException
780
-     * @throws InvalidDataTypeException
781
-     * @throws EE_Error
782
-     */
783
-    public function date_and_time_range(
784
-        $dt_format = '',
785
-        $tm_format = '',
786
-        $conjunction = ' - ',
787
-        $separator = ' '
788
-    ) {
789
-        $dt_format = ! empty($dt_format) ? $dt_format : $this->_dt_frmt;
790
-        $tm_format = ! empty($tm_format) ? $tm_format : $this->_tm_frmt;
791
-        $full_format = $dt_format . $separator . $tm_format;
792
-        // the range output depends on various conditions
793
-        switch (true) {
794
-            // start date timestamp and end date timestamp are the same.
795
-            case ($this->get_raw('DTT_EVT_start') === $this->get_raw('DTT_EVT_end')):
796
-                $output = $this->get_i18n_datetime('DTT_EVT_start', $full_format);
797
-                break;
798
-            // start and end date are the same but times are different
799
-            case ($this->start_date() === $this->end_date()):
800
-                $output = $this->get_i18n_datetime('DTT_EVT_start', $full_format)
801
-                          . $conjunction
802
-                          . $this->get_i18n_datetime('DTT_EVT_end', $tm_format);
803
-                break;
804
-            // all other conditions
805
-            default:
806
-                $output = $this->get_i18n_datetime('DTT_EVT_start', $full_format)
807
-                          . $conjunction
808
-                          . $this->get_i18n_datetime('DTT_EVT_end', $full_format);
809
-                break;
810
-        }
811
-        return $output;
812
-    }
813
-
814
-
815
-    /**
816
-     * This echos the results of date and time range.
817
-     *
818
-     * @see date_and_time_range() for more details on purpose.
819
-     * @param string $dt_format
820
-     * @param string $tm_format
821
-     * @param string $conjunction
822
-     * @return void
823
-     * @throws ReflectionException
824
-     * @throws InvalidArgumentException
825
-     * @throws InvalidInterfaceException
826
-     * @throws InvalidDataTypeException
827
-     * @throws EE_Error
828
-     */
829
-    public function e_date_and_time_range($dt_format = '', $tm_format = '', $conjunction = ' - ')
830
-    {
831
-        echo esc_html($this->date_and_time_range($dt_format, $tm_format, $conjunction));
832
-    }
833
-
834
-
835
-    /**
836
-     * get start date and start time
837
-     *
838
-     * @param    string $dt_format - string representation of date format defaults to 'F j, Y'
839
-     * @param    string $tm_format - string representation of time format defaults to 'g:i a'
840
-     * @return    mixed    string on success, FALSE on fail
841
-     * @throws ReflectionException
842
-     * @throws InvalidArgumentException
843
-     * @throws InvalidInterfaceException
844
-     * @throws InvalidDataTypeException
845
-     * @throws EE_Error
846
-     */
847
-    public function start_date_and_time($dt_format = '', $tm_format = '')
848
-    {
849
-        return $this->_show_datetime('', 'start', $dt_format, $tm_format);
850
-    }
851
-
852
-
853
-    /**
854
-     * @param string $dt_frmt
855
-     * @param string $tm_format
856
-     * @throws ReflectionException
857
-     * @throws InvalidArgumentException
858
-     * @throws InvalidInterfaceException
859
-     * @throws InvalidDataTypeException
860
-     * @throws EE_Error
861
-     */
862
-    public function e_start_date_and_time($dt_frmt = '', $tm_format = '')
863
-    {
864
-        $this->_show_datetime('', 'start', $dt_frmt, $tm_format, true);
865
-    }
866
-
867
-
868
-    /**
869
-     * Shows the length of the event (start to end time).
870
-     * Can be shown in 'seconds','minutes','hours', or 'days'.
871
-     * By default, rounds up. (So if you use 'days', and then event
872
-     * only occurs for 1 hour, it will return 1 day).
873
-     *
874
-     * @param string $units 'seconds','minutes','hours','days'
875
-     * @param bool   $round_up
876
-     * @return float|int|mixed
877
-     * @throws ReflectionException
878
-     * @throws InvalidArgumentException
879
-     * @throws InvalidInterfaceException
880
-     * @throws InvalidDataTypeException
881
-     * @throws EE_Error
882
-     */
883
-    public function length($units = 'seconds', $round_up = false)
884
-    {
885
-        $start = $this->get_raw('DTT_EVT_start');
886
-        $end = $this->get_raw('DTT_EVT_end');
887
-        $length_in_units = $end - $start;
888
-        switch ($units) {
889
-            // NOTE: We purposefully don't use "break;" in order to chain the divisions
890
-            /** @noinspection PhpMissingBreakStatementInspection */
891
-            // phpcs:disable PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
892
-            case 'days':
893
-                $length_in_units /= 24;
894
-            /** @noinspection PhpMissingBreakStatementInspection */
895
-            case 'hours':
896
-                // fall through is intentional
897
-                $length_in_units /= 60;
898
-            /** @noinspection PhpMissingBreakStatementInspection */
899
-            case 'minutes':
900
-                // fall through is intentional
901
-                $length_in_units /= 60;
902
-            case 'seconds':
903
-            default:
904
-                $length_in_units = ceil($length_in_units);
905
-        }
906
-        // phpcs:enable
907
-        if ($round_up) {
908
-            $length_in_units = max($length_in_units, 1);
909
-        }
910
-        return $length_in_units;
911
-    }
912
-
913
-
914
-    /**
915
-     *        get end date and time
916
-     *
917
-     * @param string $dt_frmt   - string representation of date format defaults to 'F j, Y'
918
-     * @param string $tm_format - string representation of time format defaults to 'g:i a'
919
-     * @return    mixed                string on success, FALSE on fail
920
-     * @throws ReflectionException
921
-     * @throws InvalidArgumentException
922
-     * @throws InvalidInterfaceException
923
-     * @throws InvalidDataTypeException
924
-     * @throws EE_Error
925
-     */
926
-    public function end_date_and_time($dt_frmt = '', $tm_format = '')
927
-    {
928
-        return $this->_show_datetime('', 'end', $dt_frmt, $tm_format);
929
-    }
930
-
931
-
932
-    /**
933
-     * @param string $dt_frmt
934
-     * @param string $tm_format
935
-     * @throws ReflectionException
936
-     * @throws InvalidArgumentException
937
-     * @throws InvalidInterfaceException
938
-     * @throws InvalidDataTypeException
939
-     * @throws EE_Error
940
-     */
941
-    public function e_end_date_and_time($dt_frmt = '', $tm_format = '')
942
-    {
943
-        $this->_show_datetime('', 'end', $dt_frmt, $tm_format, true);
944
-    }
945
-
946
-
947
-    /**
948
-     *        get start timestamp
949
-     *
950
-     * @return        int
951
-     * @throws ReflectionException
952
-     * @throws InvalidArgumentException
953
-     * @throws InvalidInterfaceException
954
-     * @throws InvalidDataTypeException
955
-     * @throws EE_Error
956
-     */
957
-    public function start()
958
-    {
959
-        return $this->get_raw('DTT_EVT_start');
960
-    }
961
-
962
-
963
-    /**
964
-     *        get end timestamp
965
-     *
966
-     * @return        int
967
-     * @throws ReflectionException
968
-     * @throws InvalidArgumentException
969
-     * @throws InvalidInterfaceException
970
-     * @throws InvalidDataTypeException
971
-     * @throws EE_Error
972
-     */
973
-    public function end()
974
-    {
975
-        return $this->get_raw('DTT_EVT_end');
976
-    }
977
-
978
-
979
-    /**
980
-     *    get the registration limit for this datetime slot
981
-     *
982
-     * @return        mixed        int on success, FALSE on fail
983
-     * @throws ReflectionException
984
-     * @throws InvalidArgumentException
985
-     * @throws InvalidInterfaceException
986
-     * @throws InvalidDataTypeException
987
-     * @throws EE_Error
988
-     */
989
-    public function reg_limit()
990
-    {
991
-        return $this->get_raw('DTT_reg_limit');
992
-    }
993
-
994
-
995
-    /**
996
-     *    have the tickets sold for this datetime, met or exceed the registration limit ?
997
-     *
998
-     * @return        boolean
999
-     * @throws ReflectionException
1000
-     * @throws InvalidArgumentException
1001
-     * @throws InvalidInterfaceException
1002
-     * @throws InvalidDataTypeException
1003
-     * @throws EE_Error
1004
-     */
1005
-    public function sold_out()
1006
-    {
1007
-        return $this->reg_limit() > 0 && $this->sold() >= $this->reg_limit();
1008
-    }
1009
-
1010
-
1011
-    /**
1012
-     * return the total number of spaces remaining at this venue.
1013
-     * This only takes the venue's capacity into account, NOT the tickets available for sale
1014
-     *
1015
-     * @param bool $consider_tickets Whether to consider tickets remaining when determining if there are any spaces left
1016
-     *                               Because if all tickets attached to this datetime have no spaces left,
1017
-     *                               then this datetime IS effectively sold out.
1018
-     *                               However, there are cases where we just want to know the spaces
1019
-     *                               remaining for this particular datetime, hence the flag.
1020
-     * @return int
1021
-     * @throws ReflectionException
1022
-     * @throws InvalidArgumentException
1023
-     * @throws InvalidInterfaceException
1024
-     * @throws InvalidDataTypeException
1025
-     * @throws EE_Error
1026
-     */
1027
-    public function spaces_remaining($consider_tickets = false)
1028
-    {
1029
-        // tickets remaining available for purchase
1030
-        // no need for special checks for infinite, because if DTT_reg_limit == EE_INF, then EE_INF - x = EE_INF
1031
-        $dtt_remaining = $this->reg_limit() - $this->sold_and_reserved();
1032
-        if (! $consider_tickets) {
1033
-            return $dtt_remaining;
1034
-        }
1035
-        $tickets_remaining = $this->tickets_remaining();
1036
-        return min($dtt_remaining, $tickets_remaining);
1037
-    }
1038
-
1039
-
1040
-    /**
1041
-     * Counts the total tickets available
1042
-     * (from all the different types of tickets which are available for this datetime).
1043
-     *
1044
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1045
-     * @return int
1046
-     * @throws ReflectionException
1047
-     * @throws InvalidArgumentException
1048
-     * @throws InvalidInterfaceException
1049
-     * @throws InvalidDataTypeException
1050
-     * @throws EE_Error
1051
-     */
1052
-    public function tickets_remaining($query_params = array())
1053
-    {
1054
-        $sum = 0;
1055
-        $tickets = $this->tickets($query_params);
1056
-        if (! empty($tickets)) {
1057
-            foreach ($tickets as $ticket) {
1058
-                if ($ticket instanceof EE_Ticket) {
1059
-                    // get the actual amount of tickets that can be sold
1060
-                    $qty = $ticket->qty('saleable');
1061
-                    if ($qty === EE_INF) {
1062
-                        return EE_INF;
1063
-                    }
1064
-                    // no negative ticket quantities plz
1065
-                    if ($qty > 0) {
1066
-                        $sum += $qty;
1067
-                    }
1068
-                }
1069
-            }
1070
-        }
1071
-        return $sum;
1072
-    }
1073
-
1074
-
1075
-    /**
1076
-     * Gets the count of all the tickets available at this datetime (not ticket types)
1077
-     * before any were sold
1078
-     *
1079
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1080
-     * @return int
1081
-     * @throws ReflectionException
1082
-     * @throws InvalidArgumentException
1083
-     * @throws InvalidInterfaceException
1084
-     * @throws InvalidDataTypeException
1085
-     * @throws EE_Error
1086
-     */
1087
-    public function sum_tickets_initially_available($query_params = array())
1088
-    {
1089
-        return $this->sum_related('Ticket', $query_params, 'TKT_qty');
1090
-    }
1091
-
1092
-
1093
-    /**
1094
-     * Returns the lesser-of-the two: spaces remaining at this datetime, or
1095
-     * the total tickets remaining (a sum of the tickets remaining for each ticket type
1096
-     * that is available for this datetime).
1097
-     *
1098
-     * @return int
1099
-     * @throws ReflectionException
1100
-     * @throws InvalidArgumentException
1101
-     * @throws InvalidInterfaceException
1102
-     * @throws InvalidDataTypeException
1103
-     * @throws EE_Error
1104
-     */
1105
-    public function total_tickets_available_at_this_datetime()
1106
-    {
1107
-        return $this->spaces_remaining(true);
1108
-    }
1109
-
1110
-
1111
-    /**
1112
-     * This simply compares the internal dtt for the given string with NOW
1113
-     * and determines if the date is upcoming or not.
1114
-     *
1115
-     * @access public
1116
-     * @return boolean
1117
-     * @throws ReflectionException
1118
-     * @throws InvalidArgumentException
1119
-     * @throws InvalidInterfaceException
1120
-     * @throws InvalidDataTypeException
1121
-     * @throws EE_Error
1122
-     */
1123
-    public function is_upcoming()
1124
-    {
1125
-        return ($this->get_raw('DTT_EVT_start') > time());
1126
-    }
1127
-
1128
-
1129
-    /**
1130
-     * This simply compares the internal datetime for the given string with NOW
1131
-     * and returns if the date is active (i.e. start and end time)
1132
-     *
1133
-     * @return boolean
1134
-     * @throws ReflectionException
1135
-     * @throws InvalidArgumentException
1136
-     * @throws InvalidInterfaceException
1137
-     * @throws InvalidDataTypeException
1138
-     * @throws EE_Error
1139
-     */
1140
-    public function is_active()
1141
-    {
1142
-        return ($this->get_raw('DTT_EVT_start') < time() && $this->get_raw('DTT_EVT_end') > time());
1143
-    }
1144
-
1145
-
1146
-    /**
1147
-     * This simply compares the internal dtt for the given string with NOW
1148
-     * and determines if the date is expired or not.
1149
-     *
1150
-     * @return boolean
1151
-     * @throws ReflectionException
1152
-     * @throws InvalidArgumentException
1153
-     * @throws InvalidInterfaceException
1154
-     * @throws InvalidDataTypeException
1155
-     * @throws EE_Error
1156
-     */
1157
-    public function is_expired()
1158
-    {
1159
-        return ($this->get_raw('DTT_EVT_end') < time());
1160
-    }
1161
-
1162
-
1163
-    /**
1164
-     * This returns the active status for whether an event is active, upcoming, or expired
1165
-     *
1166
-     * @return int return value will be one of the EE_Datetime status constants.
1167
-     * @throws ReflectionException
1168
-     * @throws InvalidArgumentException
1169
-     * @throws InvalidInterfaceException
1170
-     * @throws InvalidDataTypeException
1171
-     * @throws EE_Error
1172
-     */
1173
-    public function get_active_status()
1174
-    {
1175
-        $total_tickets_for_this_dtt = $this->total_tickets_available_at_this_datetime();
1176
-        if ($total_tickets_for_this_dtt !== false && $total_tickets_for_this_dtt < 1) {
1177
-            return EE_Datetime::sold_out;
1178
-        }
1179
-        if ($this->is_expired()) {
1180
-            return EE_Datetime::expired;
1181
-        }
1182
-        if ($this->is_upcoming()) {
1183
-            return EE_Datetime::upcoming;
1184
-        }
1185
-        if ($this->is_active()) {
1186
-            return EE_Datetime::active;
1187
-        }
1188
-        return null;
1189
-    }
1190
-
1191
-
1192
-    /**
1193
-     * This returns a nice display name for the datetime that is contingent on the span between the dates and times.
1194
-     *
1195
-     * @param  boolean $use_dtt_name if TRUE then we'll use DTT->name() if its not empty.
1196
-     * @return string
1197
-     * @throws ReflectionException
1198
-     * @throws InvalidArgumentException
1199
-     * @throws InvalidInterfaceException
1200
-     * @throws InvalidDataTypeException
1201
-     * @throws EE_Error
1202
-     */
1203
-    public function get_dtt_display_name($use_dtt_name = false)
1204
-    {
1205
-        if ($use_dtt_name) {
1206
-            $dtt_name = $this->name();
1207
-            if (! empty($dtt_name)) {
1208
-                return $dtt_name;
1209
-            }
1210
-        }
1211
-        // first condition is to see if the months are different
1212
-        if (
1213
-            date('m', $this->get_raw('DTT_EVT_start')) !== date('m', $this->get_raw('DTT_EVT_end'))
1214
-        ) {
1215
-            $display_date = $this->start_date('M j\, Y g:i a') . ' - ' . $this->end_date('M j\, Y g:i a');
1216
-            // next condition is if its the same month but different day
1217
-        } else {
1218
-            if (
1219
-                date('m', $this->get_raw('DTT_EVT_start')) === date('m', $this->get_raw('DTT_EVT_end'))
1220
-                && date('d', $this->get_raw('DTT_EVT_start')) !== date('d', $this->get_raw('DTT_EVT_end'))
1221
-            ) {
1222
-                $display_date = $this->start_date('M j\, g:i a') . ' - ' . $this->end_date('M j\, g:i a Y');
1223
-            } else {
1224
-                $display_date = $this->start_date('F j\, Y')
1225
-                                . ' @ '
1226
-                                . $this->start_date('g:i a')
1227
-                                . ' - '
1228
-                                . $this->end_date('g:i a');
1229
-            }
1230
-        }
1231
-        return $display_date;
1232
-    }
1233
-
1234
-
1235
-    /**
1236
-     * Gets all the tickets for this datetime
1237
-     *
1238
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1239
-     * @return EE_Base_Class[]|EE_Ticket[]
1240
-     * @throws ReflectionException
1241
-     * @throws InvalidArgumentException
1242
-     * @throws InvalidInterfaceException
1243
-     * @throws InvalidDataTypeException
1244
-     * @throws EE_Error
1245
-     */
1246
-    public function tickets($query_params = array())
1247
-    {
1248
-        return $this->get_many_related('Ticket', $query_params);
1249
-    }
1250
-
1251
-
1252
-    /**
1253
-     * Gets all the ticket types currently available for purchase
1254
-     *
1255
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1256
-     * @return EE_Ticket[]
1257
-     * @throws ReflectionException
1258
-     * @throws InvalidArgumentException
1259
-     * @throws InvalidInterfaceException
1260
-     * @throws InvalidDataTypeException
1261
-     * @throws EE_Error
1262
-     */
1263
-    public function ticket_types_available_for_purchase($query_params = array())
1264
-    {
1265
-        // first check if datetime is valid
1266
-        if ($this->sold_out() || ! ($this->is_upcoming() || $this->is_active())) {
1267
-            return array();
1268
-        }
1269
-        if (empty($query_params)) {
1270
-            $query_params = array(
1271
-                array(
1272
-                    'TKT_start_date' => array('<=', EEM_Ticket::instance()->current_time_for_query('TKT_start_date')),
1273
-                    'TKT_end_date'   => array('>=', EEM_Ticket::instance()->current_time_for_query('TKT_end_date')),
1274
-                    'TKT_deleted'    => false,
1275
-                ),
1276
-            );
1277
-        }
1278
-        return $this->tickets($query_params);
1279
-    }
1280
-
1281
-
1282
-    /**
1283
-     * @return EE_Base_Class|EE_Event
1284
-     * @throws ReflectionException
1285
-     * @throws InvalidArgumentException
1286
-     * @throws InvalidInterfaceException
1287
-     * @throws InvalidDataTypeException
1288
-     * @throws EE_Error
1289
-     */
1290
-    public function event()
1291
-    {
1292
-        return $this->get_first_related('Event');
1293
-    }
1294
-
1295
-
1296
-    /**
1297
-     * Updates the DTT_sold attribute (and saves) based on the number of registrations for this datetime
1298
-     * (via the tickets).
1299
-     *
1300
-     * @return int
1301
-     * @throws ReflectionException
1302
-     * @throws InvalidArgumentException
1303
-     * @throws InvalidInterfaceException
1304
-     * @throws InvalidDataTypeException
1305
-     * @throws EE_Error
1306
-     */
1307
-    public function update_sold()
1308
-    {
1309
-        $count_regs_for_this_datetime = EEM_Registration::instance()->count(
1310
-            array(
1311
-                array(
1312
-                    'STS_ID'                 => EEM_Registration::status_id_approved,
1313
-                    'REG_deleted'            => 0,
1314
-                    'Ticket.Datetime.DTT_ID' => $this->ID(),
1315
-                ),
1316
-            )
1317
-        );
1318
-        $this->set_sold($count_regs_for_this_datetime);
1319
-        $this->save();
1320
-        return $count_regs_for_this_datetime;
1321
-    }
1322
-
1323
-
1324
-    /*******************************************************************
16
+	/**
17
+	 * constant used by get_active_status, indicates datetime has no more available spaces
18
+	 */
19
+	const sold_out = 'DTS';
20
+
21
+	/**
22
+	 * constant used by get_active_status, indicating datetime is still active (even is not over, can be registered-for)
23
+	 */
24
+	const active = 'DTA';
25
+
26
+	/**
27
+	 * constant used by get_active_status, indicating the datetime cannot be used for registrations yet, but has not
28
+	 * expired
29
+	 */
30
+	const upcoming = 'DTU';
31
+
32
+	/**
33
+	 * Datetime is postponed
34
+	 */
35
+	const postponed = 'DTP';
36
+
37
+	/**
38
+	 * Datetime is cancelled
39
+	 */
40
+	const cancelled = 'DTC';
41
+
42
+	/**
43
+	 * constant used by get_active_status, indicates datetime has expired (event is over)
44
+	 */
45
+	const expired = 'DTE';
46
+
47
+	/**
48
+	 * constant used in various places indicating that an event is INACTIVE (not yet ready to be published)
49
+	 */
50
+	const inactive = 'DTI';
51
+
52
+
53
+	/**
54
+	 * @param array  $props_n_values    incoming values
55
+	 * @param string $timezone          incoming timezone (if not set the timezone set for the website will be used.)
56
+	 * @param array  $date_formats      incoming date_formats in an array where the first value is the date_format
57
+	 *                                  and the second value is the time format
58
+	 * @return EE_Datetime
59
+	 * @throws ReflectionException
60
+	 * @throws InvalidArgumentException
61
+	 * @throws InvalidInterfaceException
62
+	 * @throws InvalidDataTypeException
63
+	 * @throws EE_Error
64
+	 */
65
+	public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
66
+	{
67
+		$has_object = parent::_check_for_object(
68
+			$props_n_values,
69
+			__CLASS__,
70
+			$timezone,
71
+			$date_formats
72
+		);
73
+		return $has_object
74
+			? $has_object
75
+			: new self($props_n_values, false, $timezone, $date_formats);
76
+	}
77
+
78
+
79
+	/**
80
+	 * @param array  $props_n_values  incoming values from the database
81
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
82
+	 *                                the website will be used.
83
+	 * @return EE_Datetime
84
+	 * @throws ReflectionException
85
+	 * @throws InvalidArgumentException
86
+	 * @throws InvalidInterfaceException
87
+	 * @throws InvalidDataTypeException
88
+	 * @throws EE_Error
89
+	 */
90
+	public static function new_instance_from_db($props_n_values = array(), $timezone = null)
91
+	{
92
+		return new self($props_n_values, true, $timezone);
93
+	}
94
+
95
+
96
+	/**
97
+	 * @param $name
98
+	 * @throws ReflectionException
99
+	 * @throws InvalidArgumentException
100
+	 * @throws InvalidInterfaceException
101
+	 * @throws InvalidDataTypeException
102
+	 * @throws EE_Error
103
+	 */
104
+	public function set_name($name)
105
+	{
106
+		$this->set('DTT_name', $name);
107
+	}
108
+
109
+
110
+	/**
111
+	 * @param $description
112
+	 * @throws ReflectionException
113
+	 * @throws InvalidArgumentException
114
+	 * @throws InvalidInterfaceException
115
+	 * @throws InvalidDataTypeException
116
+	 * @throws EE_Error
117
+	 */
118
+	public function set_description($description)
119
+	{
120
+		$this->set('DTT_description', $description);
121
+	}
122
+
123
+
124
+	/**
125
+	 * Set event start date
126
+	 * set the start date for an event
127
+	 *
128
+	 * @param string $date a string representation of the event's date ex:  Dec. 25, 2025 or 12-25-2025
129
+	 * @throws ReflectionException
130
+	 * @throws InvalidArgumentException
131
+	 * @throws InvalidInterfaceException
132
+	 * @throws InvalidDataTypeException
133
+	 * @throws EE_Error
134
+	 */
135
+	public function set_start_date($date)
136
+	{
137
+		$this->_set_date_for($date, 'DTT_EVT_start');
138
+	}
139
+
140
+
141
+	/**
142
+	 * Set event start time
143
+	 * set the start time for an event
144
+	 *
145
+	 * @param string $time a string representation of the event time ex:  9am  or  7:30 PM
146
+	 * @throws ReflectionException
147
+	 * @throws InvalidArgumentException
148
+	 * @throws InvalidInterfaceException
149
+	 * @throws InvalidDataTypeException
150
+	 * @throws EE_Error
151
+	 */
152
+	public function set_start_time($time)
153
+	{
154
+		$this->_set_time_for($time, 'DTT_EVT_start');
155
+	}
156
+
157
+
158
+	/**
159
+	 * Set event end date
160
+	 * set the end date for an event
161
+	 *
162
+	 * @param string $date a string representation of the event's date ex:  Dec. 25, 2025 or 12-25-2025
163
+	 * @throws ReflectionException
164
+	 * @throws InvalidArgumentException
165
+	 * @throws InvalidInterfaceException
166
+	 * @throws InvalidDataTypeException
167
+	 * @throws EE_Error
168
+	 */
169
+	public function set_end_date($date)
170
+	{
171
+		$this->_set_date_for($date, 'DTT_EVT_end');
172
+	}
173
+
174
+
175
+	/**
176
+	 * Set event end time
177
+	 * set the end time for an event
178
+	 *
179
+	 * @param string $time a string representation of the event time ex:  9am  or  7:30 PM
180
+	 * @throws ReflectionException
181
+	 * @throws InvalidArgumentException
182
+	 * @throws InvalidInterfaceException
183
+	 * @throws InvalidDataTypeException
184
+	 * @throws EE_Error
185
+	 */
186
+	public function set_end_time($time)
187
+	{
188
+		$this->_set_time_for($time, 'DTT_EVT_end');
189
+	}
190
+
191
+
192
+	/**
193
+	 * Set registration limit
194
+	 * set the maximum number of attendees that can be registered for this datetime slot
195
+	 *
196
+	 * @param int $reg_limit
197
+	 * @throws ReflectionException
198
+	 * @throws InvalidArgumentException
199
+	 * @throws InvalidInterfaceException
200
+	 * @throws InvalidDataTypeException
201
+	 * @throws EE_Error
202
+	 */
203
+	public function set_reg_limit($reg_limit)
204
+	{
205
+		$this->set('DTT_reg_limit', $reg_limit);
206
+	}
207
+
208
+
209
+	/**
210
+	 * get the number of tickets sold for this datetime slot
211
+	 *
212
+	 * @return mixed int on success, FALSE on fail
213
+	 * @throws ReflectionException
214
+	 * @throws InvalidArgumentException
215
+	 * @throws InvalidInterfaceException
216
+	 * @throws InvalidDataTypeException
217
+	 * @throws EE_Error
218
+	 */
219
+	public function sold()
220
+	{
221
+		return $this->get_raw('DTT_sold');
222
+	}
223
+
224
+
225
+	/**
226
+	 * @param int $sold
227
+	 * @throws ReflectionException
228
+	 * @throws InvalidArgumentException
229
+	 * @throws InvalidInterfaceException
230
+	 * @throws InvalidDataTypeException
231
+	 * @throws EE_Error
232
+	 */
233
+	public function set_sold($sold)
234
+	{
235
+		// sold can not go below zero
236
+		$sold = max(0, $sold);
237
+		$this->set('DTT_sold', $sold);
238
+	}
239
+
240
+
241
+	/**
242
+	 * Increments sold by amount passed by $qty, and persists it immediately to the database.
243
+	 * Simultaneously decreases the reserved count, unless $also_decrease_reserved is false.
244
+	 *
245
+	 * @param int $qty
246
+	 * @param boolean $also_decrease_reserved
247
+	 * @return boolean indicating success
248
+	 * @throws ReflectionException
249
+	 * @throws InvalidArgumentException
250
+	 * @throws InvalidInterfaceException
251
+	 * @throws InvalidDataTypeException
252
+	 * @throws EE_Error
253
+	 */
254
+	public function increaseSold($qty = 1, $also_decrease_reserved = true)
255
+	{
256
+		$qty = absint($qty);
257
+		if ($also_decrease_reserved) {
258
+			$success = $this->adjustNumericFieldsInDb(
259
+				[
260
+					'DTT_reserved' => $qty * -1,
261
+					'DTT_sold' => $qty
262
+				]
263
+			);
264
+		} else {
265
+			$success = $this->adjustNumericFieldsInDb(
266
+				[
267
+					'DTT_sold' => $qty
268
+				]
269
+			);
270
+		}
271
+
272
+		do_action(
273
+			'AHEE__EE_Datetime__increase_sold',
274
+			$this,
275
+			$qty,
276
+			$this->sold(),
277
+			$success
278
+		);
279
+		return $success;
280
+	}
281
+
282
+
283
+	/**
284
+	 * Decrements (subtracts) sold amount passed by $qty directly in the DB and on the model object. (Ie, no need
285
+	 * to save afterwards.)
286
+	 *
287
+	 * @param int $qty
288
+	 * @return boolean indicating success
289
+	 * @throws ReflectionException
290
+	 * @throws InvalidArgumentException
291
+	 * @throws InvalidInterfaceException
292
+	 * @throws InvalidDataTypeException
293
+	 * @throws EE_Error
294
+	 */
295
+	public function decreaseSold($qty = 1)
296
+	{
297
+		$qty = absint($qty);
298
+		$success = $this->adjustNumericFieldsInDb(
299
+			[
300
+				'DTT_sold' => $qty * -1
301
+			]
302
+		);
303
+		do_action(
304
+			'AHEE__EE_Datetime__decrease_sold',
305
+			$this,
306
+			$qty,
307
+			$this->sold(),
308
+			$success
309
+		);
310
+		return $success;
311
+	}
312
+
313
+
314
+	/**
315
+	 * Gets qty of reserved tickets for this datetime
316
+	 *
317
+	 * @return int
318
+	 * @throws ReflectionException
319
+	 * @throws InvalidArgumentException
320
+	 * @throws InvalidInterfaceException
321
+	 * @throws InvalidDataTypeException
322
+	 * @throws EE_Error
323
+	 */
324
+	public function reserved()
325
+	{
326
+		return $this->get_raw('DTT_reserved');
327
+	}
328
+
329
+
330
+	/**
331
+	 * Sets qty of reserved tickets for this datetime
332
+	 *
333
+	 * @param int $reserved
334
+	 * @throws ReflectionException
335
+	 * @throws InvalidArgumentException
336
+	 * @throws InvalidInterfaceException
337
+	 * @throws InvalidDataTypeException
338
+	 * @throws EE_Error
339
+	 */
340
+	public function set_reserved($reserved)
341
+	{
342
+		// reserved can not go below zero
343
+		$reserved = max(0, (int) $reserved);
344
+		$this->set('DTT_reserved', $reserved);
345
+	}
346
+
347
+
348
+	/**
349
+	 * Increments reserved by amount passed by $qty, and persists it immediately to the database.
350
+	 *
351
+	 * @param int $qty
352
+	 * @return boolean indicating success
353
+	 * @throws ReflectionException
354
+	 * @throws InvalidArgumentException
355
+	 * @throws InvalidInterfaceException
356
+	 * @throws InvalidDataTypeException
357
+	 * @throws EE_Error
358
+	 */
359
+	public function increaseReserved($qty = 1)
360
+	{
361
+		$qty = absint($qty);
362
+		$success = $this->incrementFieldConditionallyInDb(
363
+			'DTT_reserved',
364
+			'DTT_sold',
365
+			'DTT_reg_limit',
366
+			$qty
367
+		);
368
+		do_action(
369
+			'AHEE__EE_Datetime__increase_reserved',
370
+			$this,
371
+			$qty,
372
+			$this->reserved(),
373
+			$success
374
+		);
375
+		return $success;
376
+	}
377
+
378
+
379
+	/**
380
+	 * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
381
+	 *
382
+	 * @param int $qty
383
+	 * @return boolean indicating success
384
+	 * @throws ReflectionException
385
+	 * @throws InvalidArgumentException
386
+	 * @throws InvalidInterfaceException
387
+	 * @throws InvalidDataTypeException
388
+	 * @throws EE_Error
389
+	 */
390
+	public function decreaseReserved($qty = 1)
391
+	{
392
+		$qty = absint($qty);
393
+		$success = $this->adjustNumericFieldsInDb(
394
+			[
395
+				'DTT_reserved' => $qty * -1
396
+			]
397
+		);
398
+		do_action(
399
+			'AHEE__EE_Datetime__decrease_reserved',
400
+			$this,
401
+			$qty,
402
+			$this->reserved(),
403
+			$success
404
+		);
405
+		return $success;
406
+	}
407
+
408
+
409
+	/**
410
+	 * total sold and reserved tickets
411
+	 *
412
+	 * @return int
413
+	 * @throws ReflectionException
414
+	 * @throws InvalidArgumentException
415
+	 * @throws InvalidInterfaceException
416
+	 * @throws InvalidDataTypeException
417
+	 * @throws EE_Error
418
+	 */
419
+	public function sold_and_reserved()
420
+	{
421
+		return $this->sold() + $this->reserved();
422
+	}
423
+
424
+
425
+	/**
426
+	 * returns the datetime name
427
+	 *
428
+	 * @return string
429
+	 * @throws ReflectionException
430
+	 * @throws InvalidArgumentException
431
+	 * @throws InvalidInterfaceException
432
+	 * @throws InvalidDataTypeException
433
+	 * @throws EE_Error
434
+	 */
435
+	public function name()
436
+	{
437
+		return $this->get('DTT_name');
438
+	}
439
+
440
+
441
+	/**
442
+	 * returns the datetime description
443
+	 *
444
+	 * @return string
445
+	 * @throws ReflectionException
446
+	 * @throws InvalidArgumentException
447
+	 * @throws InvalidInterfaceException
448
+	 * @throws InvalidDataTypeException
449
+	 * @throws EE_Error
450
+	 */
451
+	public function description()
452
+	{
453
+		return $this->get('DTT_description');
454
+	}
455
+
456
+
457
+	/**
458
+	 * This helper simply returns whether the event_datetime for the current datetime is a primary datetime
459
+	 *
460
+	 * @return boolean  TRUE if is primary, FALSE if not.
461
+	 * @throws ReflectionException
462
+	 * @throws InvalidArgumentException
463
+	 * @throws InvalidInterfaceException
464
+	 * @throws InvalidDataTypeException
465
+	 * @throws EE_Error
466
+	 */
467
+	public function is_primary()
468
+	{
469
+		return $this->get('DTT_is_primary');
470
+	}
471
+
472
+
473
+	/**
474
+	 * This helper simply returns the order for the datetime
475
+	 *
476
+	 * @return int  The order of the datetime for this event.
477
+	 * @throws ReflectionException
478
+	 * @throws InvalidArgumentException
479
+	 * @throws InvalidInterfaceException
480
+	 * @throws InvalidDataTypeException
481
+	 * @throws EE_Error
482
+	 */
483
+	public function order()
484
+	{
485
+		return $this->get('DTT_order');
486
+	}
487
+
488
+
489
+	/**
490
+	 * This helper simply returns the parent id for the datetime
491
+	 *
492
+	 * @return int
493
+	 * @throws ReflectionException
494
+	 * @throws InvalidArgumentException
495
+	 * @throws InvalidInterfaceException
496
+	 * @throws InvalidDataTypeException
497
+	 * @throws EE_Error
498
+	 */
499
+	public function parent()
500
+	{
501
+		return $this->get('DTT_parent');
502
+	}
503
+
504
+
505
+	/**
506
+	 * show date and/or time
507
+	 *
508
+	 * @param string $date_or_time    whether to display a date or time or both
509
+	 * @param string $start_or_end    whether to display start or end datetimes
510
+	 * @param string $dt_frmt
511
+	 * @param string $tm_frmt
512
+	 * @param bool   $echo            whether we echo or return (note echoing uses "pretty" formats,
513
+	 *                                otherwise we use the standard formats)
514
+	 * @return string|bool  string on success, FALSE on fail
515
+	 * @throws ReflectionException
516
+	 * @throws InvalidArgumentException
517
+	 * @throws InvalidInterfaceException
518
+	 * @throws InvalidDataTypeException
519
+	 * @throws EE_Error
520
+	 */
521
+	private function _show_datetime(
522
+		$date_or_time = null,
523
+		$start_or_end = 'start',
524
+		$dt_frmt = '',
525
+		$tm_frmt = '',
526
+		$echo = false
527
+	) {
528
+		$field_name = "DTT_EVT_{$start_or_end}";
529
+		$dtt = $this->_get_datetime(
530
+			$field_name,
531
+			$dt_frmt,
532
+			$tm_frmt,
533
+			$date_or_time,
534
+			$echo
535
+		);
536
+		if (! $echo) {
537
+			return $dtt;
538
+		}
539
+		return '';
540
+	}
541
+
542
+
543
+	/**
544
+	 * get event start date.  Provide either the date format, or NULL to re-use the
545
+	 * last-used format, or '' to use the default date format
546
+	 *
547
+	 * @param string $dt_frmt string representation of date format defaults to 'F j, Y'
548
+	 * @return mixed            string on success, FALSE on fail
549
+	 * @throws ReflectionException
550
+	 * @throws InvalidArgumentException
551
+	 * @throws InvalidInterfaceException
552
+	 * @throws InvalidDataTypeException
553
+	 * @throws EE_Error
554
+	 */
555
+	public function start_date($dt_frmt = '')
556
+	{
557
+		return $this->_show_datetime('D', 'start', $dt_frmt);
558
+	}
559
+
560
+
561
+	/**
562
+	 * Echoes start_date()
563
+	 *
564
+	 * @param string $dt_frmt
565
+	 * @throws ReflectionException
566
+	 * @throws InvalidArgumentException
567
+	 * @throws InvalidInterfaceException
568
+	 * @throws InvalidDataTypeException
569
+	 * @throws EE_Error
570
+	 */
571
+	public function e_start_date($dt_frmt = '')
572
+	{
573
+		$this->_show_datetime('D', 'start', $dt_frmt, null, true);
574
+	}
575
+
576
+
577
+	/**
578
+	 * get end date. Provide either the date format, or NULL to re-use the
579
+	 * last-used format, or '' to use the default date format
580
+	 *
581
+	 * @param string $dt_frmt string representation of date format defaults to 'F j, Y'
582
+	 * @return mixed            string on success, FALSE on fail
583
+	 * @throws ReflectionException
584
+	 * @throws InvalidArgumentException
585
+	 * @throws InvalidInterfaceException
586
+	 * @throws InvalidDataTypeException
587
+	 * @throws EE_Error
588
+	 */
589
+	public function end_date($dt_frmt = '')
590
+	{
591
+		return $this->_show_datetime('D', 'end', $dt_frmt);
592
+	}
593
+
594
+
595
+	/**
596
+	 * Echoes the end date. See end_date()
597
+	 *
598
+	 * @param string $dt_frmt
599
+	 * @throws ReflectionException
600
+	 * @throws InvalidArgumentException
601
+	 * @throws InvalidInterfaceException
602
+	 * @throws InvalidDataTypeException
603
+	 * @throws EE_Error
604
+	 */
605
+	public function e_end_date($dt_frmt = '')
606
+	{
607
+		$this->_show_datetime('D', 'end', $dt_frmt, null, true);
608
+	}
609
+
610
+
611
+	/**
612
+	 * get date_range - meaning the start AND end date
613
+	 *
614
+	 * @access public
615
+	 * @param string $dt_frmt     string representation of date format defaults to WP settings
616
+	 * @param string $conjunction conjunction junction what's your function ?
617
+	 *                            this string joins the start date with the end date ie: Jan 01 "to" Dec 31
618
+	 * @return mixed              string on success, FALSE on fail
619
+	 * @throws ReflectionException
620
+	 * @throws InvalidArgumentException
621
+	 * @throws InvalidInterfaceException
622
+	 * @throws InvalidDataTypeException
623
+	 * @throws EE_Error
624
+	 */
625
+	public function date_range($dt_frmt = '', $conjunction = ' - ')
626
+	{
627
+		$dt_frmt = ! empty($dt_frmt) ? $dt_frmt : $this->_dt_frmt;
628
+		$start = str_replace(
629
+			' ',
630
+			'&nbsp;',
631
+			$this->get_i18n_datetime('DTT_EVT_start', $dt_frmt)
632
+		);
633
+		$end = str_replace(
634
+			' ',
635
+			'&nbsp;',
636
+			$this->get_i18n_datetime('DTT_EVT_end', $dt_frmt)
637
+		);
638
+		return $start !== $end ? $start . $conjunction . $end : $start;
639
+	}
640
+
641
+
642
+	/**
643
+	 * @param string $dt_frmt
644
+	 * @param string $conjunction
645
+	 * @throws ReflectionException
646
+	 * @throws InvalidArgumentException
647
+	 * @throws InvalidInterfaceException
648
+	 * @throws InvalidDataTypeException
649
+	 * @throws EE_Error
650
+	 */
651
+	public function e_date_range($dt_frmt = '', $conjunction = ' - ')
652
+	{
653
+		echo esc_html($this->date_range($dt_frmt, $conjunction));
654
+	}
655
+
656
+
657
+	/**
658
+	 * get start time
659
+	 *
660
+	 * @param string $tm_format - string representation of time format defaults to 'g:i a'
661
+	 * @return mixed        string on success, FALSE on fail
662
+	 * @throws ReflectionException
663
+	 * @throws InvalidArgumentException
664
+	 * @throws InvalidInterfaceException
665
+	 * @throws InvalidDataTypeException
666
+	 * @throws EE_Error
667
+	 */
668
+	public function start_time($tm_format = '')
669
+	{
670
+		return $this->_show_datetime('T', 'start', null, $tm_format);
671
+	}
672
+
673
+
674
+	/**
675
+	 * @param string $tm_format
676
+	 * @throws ReflectionException
677
+	 * @throws InvalidArgumentException
678
+	 * @throws InvalidInterfaceException
679
+	 * @throws InvalidDataTypeException
680
+	 * @throws EE_Error
681
+	 */
682
+	public function e_start_time($tm_format = '')
683
+	{
684
+		$this->_show_datetime('T', 'start', null, $tm_format, true);
685
+	}
686
+
687
+
688
+	/**
689
+	 * get end time
690
+	 *
691
+	 * @param string $tm_format string representation of time format defaults to 'g:i a'
692
+	 * @return mixed                string on success, FALSE on fail
693
+	 * @throws ReflectionException
694
+	 * @throws InvalidArgumentException
695
+	 * @throws InvalidInterfaceException
696
+	 * @throws InvalidDataTypeException
697
+	 * @throws EE_Error
698
+	 */
699
+	public function end_time($tm_format = '')
700
+	{
701
+		return $this->_show_datetime('T', 'end', null, $tm_format);
702
+	}
703
+
704
+
705
+	/**
706
+	 * @param string $tm_format
707
+	 * @throws ReflectionException
708
+	 * @throws InvalidArgumentException
709
+	 * @throws InvalidInterfaceException
710
+	 * @throws InvalidDataTypeException
711
+	 * @throws EE_Error
712
+	 */
713
+	public function e_end_time($tm_format = '')
714
+	{
715
+		$this->_show_datetime('T', 'end', null, $tm_format, true);
716
+	}
717
+
718
+
719
+	/**
720
+	 * get time_range
721
+	 *
722
+	 * @access public
723
+	 * @param string $tm_format   string representation of time format defaults to 'g:i a'
724
+	 * @param string $conjunction conjunction junction what's your function ?
725
+	 *                            this string joins the start date with the end date ie: Jan 01 "to" Dec 31
726
+	 * @return mixed              string on success, FALSE on fail
727
+	 * @throws ReflectionException
728
+	 * @throws InvalidArgumentException
729
+	 * @throws InvalidInterfaceException
730
+	 * @throws InvalidDataTypeException
731
+	 * @throws EE_Error
732
+	 */
733
+	public function time_range($tm_format = '', $conjunction = ' - ')
734
+	{
735
+		$tm_format = ! empty($tm_format) ? $tm_format : $this->_tm_frmt;
736
+		$start = str_replace(
737
+			' ',
738
+			'&nbsp;',
739
+			$this->get_i18n_datetime('DTT_EVT_start', $tm_format)
740
+		);
741
+		$end = str_replace(
742
+			' ',
743
+			'&nbsp;',
744
+			$this->get_i18n_datetime('DTT_EVT_end', $tm_format)
745
+		);
746
+		return $start !== $end ? $start . $conjunction . $end : $start;
747
+	}
748
+
749
+
750
+	/**
751
+	 * @param string $tm_format
752
+	 * @param string $conjunction
753
+	 * @throws ReflectionException
754
+	 * @throws InvalidArgumentException
755
+	 * @throws InvalidInterfaceException
756
+	 * @throws InvalidDataTypeException
757
+	 * @throws EE_Error
758
+	 */
759
+	public function e_time_range($tm_format = '', $conjunction = ' - ')
760
+	{
761
+		echo esc_html($this->time_range($tm_format, $conjunction));
762
+	}
763
+
764
+
765
+	/**
766
+	 * This returns a range representation of the date and times.
767
+	 * Output is dependent on the difference (or similarity) between DTT_EVT_start and DTT_EVT_end.
768
+	 * Also, the return value is localized.
769
+	 *
770
+	 * @param string $dt_format
771
+	 * @param string $tm_format
772
+	 * @param string $conjunction used between two different dates or times.
773
+	 *                            ex: Dec 1{$conjunction}}Dec 6, or 2pm{$conjunction}3pm
774
+	 * @param string $separator   used between the date and time formats.
775
+	 *                            ex: Dec 1, 2016{$separator}2pm
776
+	 * @return string
777
+	 * @throws ReflectionException
778
+	 * @throws InvalidArgumentException
779
+	 * @throws InvalidInterfaceException
780
+	 * @throws InvalidDataTypeException
781
+	 * @throws EE_Error
782
+	 */
783
+	public function date_and_time_range(
784
+		$dt_format = '',
785
+		$tm_format = '',
786
+		$conjunction = ' - ',
787
+		$separator = ' '
788
+	) {
789
+		$dt_format = ! empty($dt_format) ? $dt_format : $this->_dt_frmt;
790
+		$tm_format = ! empty($tm_format) ? $tm_format : $this->_tm_frmt;
791
+		$full_format = $dt_format . $separator . $tm_format;
792
+		// the range output depends on various conditions
793
+		switch (true) {
794
+			// start date timestamp and end date timestamp are the same.
795
+			case ($this->get_raw('DTT_EVT_start') === $this->get_raw('DTT_EVT_end')):
796
+				$output = $this->get_i18n_datetime('DTT_EVT_start', $full_format);
797
+				break;
798
+			// start and end date are the same but times are different
799
+			case ($this->start_date() === $this->end_date()):
800
+				$output = $this->get_i18n_datetime('DTT_EVT_start', $full_format)
801
+						  . $conjunction
802
+						  . $this->get_i18n_datetime('DTT_EVT_end', $tm_format);
803
+				break;
804
+			// all other conditions
805
+			default:
806
+				$output = $this->get_i18n_datetime('DTT_EVT_start', $full_format)
807
+						  . $conjunction
808
+						  . $this->get_i18n_datetime('DTT_EVT_end', $full_format);
809
+				break;
810
+		}
811
+		return $output;
812
+	}
813
+
814
+
815
+	/**
816
+	 * This echos the results of date and time range.
817
+	 *
818
+	 * @see date_and_time_range() for more details on purpose.
819
+	 * @param string $dt_format
820
+	 * @param string $tm_format
821
+	 * @param string $conjunction
822
+	 * @return void
823
+	 * @throws ReflectionException
824
+	 * @throws InvalidArgumentException
825
+	 * @throws InvalidInterfaceException
826
+	 * @throws InvalidDataTypeException
827
+	 * @throws EE_Error
828
+	 */
829
+	public function e_date_and_time_range($dt_format = '', $tm_format = '', $conjunction = ' - ')
830
+	{
831
+		echo esc_html($this->date_and_time_range($dt_format, $tm_format, $conjunction));
832
+	}
833
+
834
+
835
+	/**
836
+	 * get start date and start time
837
+	 *
838
+	 * @param    string $dt_format - string representation of date format defaults to 'F j, Y'
839
+	 * @param    string $tm_format - string representation of time format defaults to 'g:i a'
840
+	 * @return    mixed    string on success, FALSE on fail
841
+	 * @throws ReflectionException
842
+	 * @throws InvalidArgumentException
843
+	 * @throws InvalidInterfaceException
844
+	 * @throws InvalidDataTypeException
845
+	 * @throws EE_Error
846
+	 */
847
+	public function start_date_and_time($dt_format = '', $tm_format = '')
848
+	{
849
+		return $this->_show_datetime('', 'start', $dt_format, $tm_format);
850
+	}
851
+
852
+
853
+	/**
854
+	 * @param string $dt_frmt
855
+	 * @param string $tm_format
856
+	 * @throws ReflectionException
857
+	 * @throws InvalidArgumentException
858
+	 * @throws InvalidInterfaceException
859
+	 * @throws InvalidDataTypeException
860
+	 * @throws EE_Error
861
+	 */
862
+	public function e_start_date_and_time($dt_frmt = '', $tm_format = '')
863
+	{
864
+		$this->_show_datetime('', 'start', $dt_frmt, $tm_format, true);
865
+	}
866
+
867
+
868
+	/**
869
+	 * Shows the length of the event (start to end time).
870
+	 * Can be shown in 'seconds','minutes','hours', or 'days'.
871
+	 * By default, rounds up. (So if you use 'days', and then event
872
+	 * only occurs for 1 hour, it will return 1 day).
873
+	 *
874
+	 * @param string $units 'seconds','minutes','hours','days'
875
+	 * @param bool   $round_up
876
+	 * @return float|int|mixed
877
+	 * @throws ReflectionException
878
+	 * @throws InvalidArgumentException
879
+	 * @throws InvalidInterfaceException
880
+	 * @throws InvalidDataTypeException
881
+	 * @throws EE_Error
882
+	 */
883
+	public function length($units = 'seconds', $round_up = false)
884
+	{
885
+		$start = $this->get_raw('DTT_EVT_start');
886
+		$end = $this->get_raw('DTT_EVT_end');
887
+		$length_in_units = $end - $start;
888
+		switch ($units) {
889
+			// NOTE: We purposefully don't use "break;" in order to chain the divisions
890
+			/** @noinspection PhpMissingBreakStatementInspection */
891
+			// phpcs:disable PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
892
+			case 'days':
893
+				$length_in_units /= 24;
894
+			/** @noinspection PhpMissingBreakStatementInspection */
895
+			case 'hours':
896
+				// fall through is intentional
897
+				$length_in_units /= 60;
898
+			/** @noinspection PhpMissingBreakStatementInspection */
899
+			case 'minutes':
900
+				// fall through is intentional
901
+				$length_in_units /= 60;
902
+			case 'seconds':
903
+			default:
904
+				$length_in_units = ceil($length_in_units);
905
+		}
906
+		// phpcs:enable
907
+		if ($round_up) {
908
+			$length_in_units = max($length_in_units, 1);
909
+		}
910
+		return $length_in_units;
911
+	}
912
+
913
+
914
+	/**
915
+	 *        get end date and time
916
+	 *
917
+	 * @param string $dt_frmt   - string representation of date format defaults to 'F j, Y'
918
+	 * @param string $tm_format - string representation of time format defaults to 'g:i a'
919
+	 * @return    mixed                string on success, FALSE on fail
920
+	 * @throws ReflectionException
921
+	 * @throws InvalidArgumentException
922
+	 * @throws InvalidInterfaceException
923
+	 * @throws InvalidDataTypeException
924
+	 * @throws EE_Error
925
+	 */
926
+	public function end_date_and_time($dt_frmt = '', $tm_format = '')
927
+	{
928
+		return $this->_show_datetime('', 'end', $dt_frmt, $tm_format);
929
+	}
930
+
931
+
932
+	/**
933
+	 * @param string $dt_frmt
934
+	 * @param string $tm_format
935
+	 * @throws ReflectionException
936
+	 * @throws InvalidArgumentException
937
+	 * @throws InvalidInterfaceException
938
+	 * @throws InvalidDataTypeException
939
+	 * @throws EE_Error
940
+	 */
941
+	public function e_end_date_and_time($dt_frmt = '', $tm_format = '')
942
+	{
943
+		$this->_show_datetime('', 'end', $dt_frmt, $tm_format, true);
944
+	}
945
+
946
+
947
+	/**
948
+	 *        get start timestamp
949
+	 *
950
+	 * @return        int
951
+	 * @throws ReflectionException
952
+	 * @throws InvalidArgumentException
953
+	 * @throws InvalidInterfaceException
954
+	 * @throws InvalidDataTypeException
955
+	 * @throws EE_Error
956
+	 */
957
+	public function start()
958
+	{
959
+		return $this->get_raw('DTT_EVT_start');
960
+	}
961
+
962
+
963
+	/**
964
+	 *        get end timestamp
965
+	 *
966
+	 * @return        int
967
+	 * @throws ReflectionException
968
+	 * @throws InvalidArgumentException
969
+	 * @throws InvalidInterfaceException
970
+	 * @throws InvalidDataTypeException
971
+	 * @throws EE_Error
972
+	 */
973
+	public function end()
974
+	{
975
+		return $this->get_raw('DTT_EVT_end');
976
+	}
977
+
978
+
979
+	/**
980
+	 *    get the registration limit for this datetime slot
981
+	 *
982
+	 * @return        mixed        int on success, FALSE on fail
983
+	 * @throws ReflectionException
984
+	 * @throws InvalidArgumentException
985
+	 * @throws InvalidInterfaceException
986
+	 * @throws InvalidDataTypeException
987
+	 * @throws EE_Error
988
+	 */
989
+	public function reg_limit()
990
+	{
991
+		return $this->get_raw('DTT_reg_limit');
992
+	}
993
+
994
+
995
+	/**
996
+	 *    have the tickets sold for this datetime, met or exceed the registration limit ?
997
+	 *
998
+	 * @return        boolean
999
+	 * @throws ReflectionException
1000
+	 * @throws InvalidArgumentException
1001
+	 * @throws InvalidInterfaceException
1002
+	 * @throws InvalidDataTypeException
1003
+	 * @throws EE_Error
1004
+	 */
1005
+	public function sold_out()
1006
+	{
1007
+		return $this->reg_limit() > 0 && $this->sold() >= $this->reg_limit();
1008
+	}
1009
+
1010
+
1011
+	/**
1012
+	 * return the total number of spaces remaining at this venue.
1013
+	 * This only takes the venue's capacity into account, NOT the tickets available for sale
1014
+	 *
1015
+	 * @param bool $consider_tickets Whether to consider tickets remaining when determining if there are any spaces left
1016
+	 *                               Because if all tickets attached to this datetime have no spaces left,
1017
+	 *                               then this datetime IS effectively sold out.
1018
+	 *                               However, there are cases where we just want to know the spaces
1019
+	 *                               remaining for this particular datetime, hence the flag.
1020
+	 * @return int
1021
+	 * @throws ReflectionException
1022
+	 * @throws InvalidArgumentException
1023
+	 * @throws InvalidInterfaceException
1024
+	 * @throws InvalidDataTypeException
1025
+	 * @throws EE_Error
1026
+	 */
1027
+	public function spaces_remaining($consider_tickets = false)
1028
+	{
1029
+		// tickets remaining available for purchase
1030
+		// no need for special checks for infinite, because if DTT_reg_limit == EE_INF, then EE_INF - x = EE_INF
1031
+		$dtt_remaining = $this->reg_limit() - $this->sold_and_reserved();
1032
+		if (! $consider_tickets) {
1033
+			return $dtt_remaining;
1034
+		}
1035
+		$tickets_remaining = $this->tickets_remaining();
1036
+		return min($dtt_remaining, $tickets_remaining);
1037
+	}
1038
+
1039
+
1040
+	/**
1041
+	 * Counts the total tickets available
1042
+	 * (from all the different types of tickets which are available for this datetime).
1043
+	 *
1044
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1045
+	 * @return int
1046
+	 * @throws ReflectionException
1047
+	 * @throws InvalidArgumentException
1048
+	 * @throws InvalidInterfaceException
1049
+	 * @throws InvalidDataTypeException
1050
+	 * @throws EE_Error
1051
+	 */
1052
+	public function tickets_remaining($query_params = array())
1053
+	{
1054
+		$sum = 0;
1055
+		$tickets = $this->tickets($query_params);
1056
+		if (! empty($tickets)) {
1057
+			foreach ($tickets as $ticket) {
1058
+				if ($ticket instanceof EE_Ticket) {
1059
+					// get the actual amount of tickets that can be sold
1060
+					$qty = $ticket->qty('saleable');
1061
+					if ($qty === EE_INF) {
1062
+						return EE_INF;
1063
+					}
1064
+					// no negative ticket quantities plz
1065
+					if ($qty > 0) {
1066
+						$sum += $qty;
1067
+					}
1068
+				}
1069
+			}
1070
+		}
1071
+		return $sum;
1072
+	}
1073
+
1074
+
1075
+	/**
1076
+	 * Gets the count of all the tickets available at this datetime (not ticket types)
1077
+	 * before any were sold
1078
+	 *
1079
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1080
+	 * @return int
1081
+	 * @throws ReflectionException
1082
+	 * @throws InvalidArgumentException
1083
+	 * @throws InvalidInterfaceException
1084
+	 * @throws InvalidDataTypeException
1085
+	 * @throws EE_Error
1086
+	 */
1087
+	public function sum_tickets_initially_available($query_params = array())
1088
+	{
1089
+		return $this->sum_related('Ticket', $query_params, 'TKT_qty');
1090
+	}
1091
+
1092
+
1093
+	/**
1094
+	 * Returns the lesser-of-the two: spaces remaining at this datetime, or
1095
+	 * the total tickets remaining (a sum of the tickets remaining for each ticket type
1096
+	 * that is available for this datetime).
1097
+	 *
1098
+	 * @return int
1099
+	 * @throws ReflectionException
1100
+	 * @throws InvalidArgumentException
1101
+	 * @throws InvalidInterfaceException
1102
+	 * @throws InvalidDataTypeException
1103
+	 * @throws EE_Error
1104
+	 */
1105
+	public function total_tickets_available_at_this_datetime()
1106
+	{
1107
+		return $this->spaces_remaining(true);
1108
+	}
1109
+
1110
+
1111
+	/**
1112
+	 * This simply compares the internal dtt for the given string with NOW
1113
+	 * and determines if the date is upcoming or not.
1114
+	 *
1115
+	 * @access public
1116
+	 * @return boolean
1117
+	 * @throws ReflectionException
1118
+	 * @throws InvalidArgumentException
1119
+	 * @throws InvalidInterfaceException
1120
+	 * @throws InvalidDataTypeException
1121
+	 * @throws EE_Error
1122
+	 */
1123
+	public function is_upcoming()
1124
+	{
1125
+		return ($this->get_raw('DTT_EVT_start') > time());
1126
+	}
1127
+
1128
+
1129
+	/**
1130
+	 * This simply compares the internal datetime for the given string with NOW
1131
+	 * and returns if the date is active (i.e. start and end time)
1132
+	 *
1133
+	 * @return boolean
1134
+	 * @throws ReflectionException
1135
+	 * @throws InvalidArgumentException
1136
+	 * @throws InvalidInterfaceException
1137
+	 * @throws InvalidDataTypeException
1138
+	 * @throws EE_Error
1139
+	 */
1140
+	public function is_active()
1141
+	{
1142
+		return ($this->get_raw('DTT_EVT_start') < time() && $this->get_raw('DTT_EVT_end') > time());
1143
+	}
1144
+
1145
+
1146
+	/**
1147
+	 * This simply compares the internal dtt for the given string with NOW
1148
+	 * and determines if the date is expired or not.
1149
+	 *
1150
+	 * @return boolean
1151
+	 * @throws ReflectionException
1152
+	 * @throws InvalidArgumentException
1153
+	 * @throws InvalidInterfaceException
1154
+	 * @throws InvalidDataTypeException
1155
+	 * @throws EE_Error
1156
+	 */
1157
+	public function is_expired()
1158
+	{
1159
+		return ($this->get_raw('DTT_EVT_end') < time());
1160
+	}
1161
+
1162
+
1163
+	/**
1164
+	 * This returns the active status for whether an event is active, upcoming, or expired
1165
+	 *
1166
+	 * @return int return value will be one of the EE_Datetime status constants.
1167
+	 * @throws ReflectionException
1168
+	 * @throws InvalidArgumentException
1169
+	 * @throws InvalidInterfaceException
1170
+	 * @throws InvalidDataTypeException
1171
+	 * @throws EE_Error
1172
+	 */
1173
+	public function get_active_status()
1174
+	{
1175
+		$total_tickets_for_this_dtt = $this->total_tickets_available_at_this_datetime();
1176
+		if ($total_tickets_for_this_dtt !== false && $total_tickets_for_this_dtt < 1) {
1177
+			return EE_Datetime::sold_out;
1178
+		}
1179
+		if ($this->is_expired()) {
1180
+			return EE_Datetime::expired;
1181
+		}
1182
+		if ($this->is_upcoming()) {
1183
+			return EE_Datetime::upcoming;
1184
+		}
1185
+		if ($this->is_active()) {
1186
+			return EE_Datetime::active;
1187
+		}
1188
+		return null;
1189
+	}
1190
+
1191
+
1192
+	/**
1193
+	 * This returns a nice display name for the datetime that is contingent on the span between the dates and times.
1194
+	 *
1195
+	 * @param  boolean $use_dtt_name if TRUE then we'll use DTT->name() if its not empty.
1196
+	 * @return string
1197
+	 * @throws ReflectionException
1198
+	 * @throws InvalidArgumentException
1199
+	 * @throws InvalidInterfaceException
1200
+	 * @throws InvalidDataTypeException
1201
+	 * @throws EE_Error
1202
+	 */
1203
+	public function get_dtt_display_name($use_dtt_name = false)
1204
+	{
1205
+		if ($use_dtt_name) {
1206
+			$dtt_name = $this->name();
1207
+			if (! empty($dtt_name)) {
1208
+				return $dtt_name;
1209
+			}
1210
+		}
1211
+		// first condition is to see if the months are different
1212
+		if (
1213
+			date('m', $this->get_raw('DTT_EVT_start')) !== date('m', $this->get_raw('DTT_EVT_end'))
1214
+		) {
1215
+			$display_date = $this->start_date('M j\, Y g:i a') . ' - ' . $this->end_date('M j\, Y g:i a');
1216
+			// next condition is if its the same month but different day
1217
+		} else {
1218
+			if (
1219
+				date('m', $this->get_raw('DTT_EVT_start')) === date('m', $this->get_raw('DTT_EVT_end'))
1220
+				&& date('d', $this->get_raw('DTT_EVT_start')) !== date('d', $this->get_raw('DTT_EVT_end'))
1221
+			) {
1222
+				$display_date = $this->start_date('M j\, g:i a') . ' - ' . $this->end_date('M j\, g:i a Y');
1223
+			} else {
1224
+				$display_date = $this->start_date('F j\, Y')
1225
+								. ' @ '
1226
+								. $this->start_date('g:i a')
1227
+								. ' - '
1228
+								. $this->end_date('g:i a');
1229
+			}
1230
+		}
1231
+		return $display_date;
1232
+	}
1233
+
1234
+
1235
+	/**
1236
+	 * Gets all the tickets for this datetime
1237
+	 *
1238
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1239
+	 * @return EE_Base_Class[]|EE_Ticket[]
1240
+	 * @throws ReflectionException
1241
+	 * @throws InvalidArgumentException
1242
+	 * @throws InvalidInterfaceException
1243
+	 * @throws InvalidDataTypeException
1244
+	 * @throws EE_Error
1245
+	 */
1246
+	public function tickets($query_params = array())
1247
+	{
1248
+		return $this->get_many_related('Ticket', $query_params);
1249
+	}
1250
+
1251
+
1252
+	/**
1253
+	 * Gets all the ticket types currently available for purchase
1254
+	 *
1255
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1256
+	 * @return EE_Ticket[]
1257
+	 * @throws ReflectionException
1258
+	 * @throws InvalidArgumentException
1259
+	 * @throws InvalidInterfaceException
1260
+	 * @throws InvalidDataTypeException
1261
+	 * @throws EE_Error
1262
+	 */
1263
+	public function ticket_types_available_for_purchase($query_params = array())
1264
+	{
1265
+		// first check if datetime is valid
1266
+		if ($this->sold_out() || ! ($this->is_upcoming() || $this->is_active())) {
1267
+			return array();
1268
+		}
1269
+		if (empty($query_params)) {
1270
+			$query_params = array(
1271
+				array(
1272
+					'TKT_start_date' => array('<=', EEM_Ticket::instance()->current_time_for_query('TKT_start_date')),
1273
+					'TKT_end_date'   => array('>=', EEM_Ticket::instance()->current_time_for_query('TKT_end_date')),
1274
+					'TKT_deleted'    => false,
1275
+				),
1276
+			);
1277
+		}
1278
+		return $this->tickets($query_params);
1279
+	}
1280
+
1281
+
1282
+	/**
1283
+	 * @return EE_Base_Class|EE_Event
1284
+	 * @throws ReflectionException
1285
+	 * @throws InvalidArgumentException
1286
+	 * @throws InvalidInterfaceException
1287
+	 * @throws InvalidDataTypeException
1288
+	 * @throws EE_Error
1289
+	 */
1290
+	public function event()
1291
+	{
1292
+		return $this->get_first_related('Event');
1293
+	}
1294
+
1295
+
1296
+	/**
1297
+	 * Updates the DTT_sold attribute (and saves) based on the number of registrations for this datetime
1298
+	 * (via the tickets).
1299
+	 *
1300
+	 * @return int
1301
+	 * @throws ReflectionException
1302
+	 * @throws InvalidArgumentException
1303
+	 * @throws InvalidInterfaceException
1304
+	 * @throws InvalidDataTypeException
1305
+	 * @throws EE_Error
1306
+	 */
1307
+	public function update_sold()
1308
+	{
1309
+		$count_regs_for_this_datetime = EEM_Registration::instance()->count(
1310
+			array(
1311
+				array(
1312
+					'STS_ID'                 => EEM_Registration::status_id_approved,
1313
+					'REG_deleted'            => 0,
1314
+					'Ticket.Datetime.DTT_ID' => $this->ID(),
1315
+				),
1316
+			)
1317
+		);
1318
+		$this->set_sold($count_regs_for_this_datetime);
1319
+		$this->save();
1320
+		return $count_regs_for_this_datetime;
1321
+	}
1322
+
1323
+
1324
+	/*******************************************************************
1325 1325
      ***********************  DEPRECATED METHODS  **********************
1326 1326
      *******************************************************************/
1327 1327
 
1328 1328
 
1329
-    /**
1330
-     * Increments sold by amount passed by $qty, and persists it immediately to the database.
1331
-     *
1332
-     * @deprecated 4.9.80.p
1333
-     * @param int $qty
1334
-     * @return boolean
1335
-     * @throws ReflectionException
1336
-     * @throws InvalidArgumentException
1337
-     * @throws InvalidInterfaceException
1338
-     * @throws InvalidDataTypeException
1339
-     * @throws EE_Error
1340
-     */
1341
-    public function increase_sold($qty = 1)
1342
-    {
1343
-        EE_Error::doing_it_wrong(
1344
-            __FUNCTION__,
1345
-            esc_html__('Please use EE_Datetime::increaseSold() instead', 'event_espresso'),
1346
-            '4.9.80.p',
1347
-            '5.0.0.p'
1348
-        );
1349
-        return $this->increaseSold($qty);
1350
-    }
1351
-
1352
-
1353
-    /**
1354
-     * Decrements (subtracts) sold amount passed by $qty directly in the DB and on the model object. (Ie, no need
1355
-     * to save afterwards.)
1356
-     *
1357
-     * @deprecated 4.9.80.p
1358
-     * @param int $qty
1359
-     * @return boolean
1360
-     * @throws ReflectionException
1361
-     * @throws InvalidArgumentException
1362
-     * @throws InvalidInterfaceException
1363
-     * @throws InvalidDataTypeException
1364
-     * @throws EE_Error
1365
-     */
1366
-    public function decrease_sold($qty = 1)
1367
-    {
1368
-        EE_Error::doing_it_wrong(
1369
-            __FUNCTION__,
1370
-            esc_html__('Please use EE_Datetime::decreaseSold() instead', 'event_espresso'),
1371
-            '4.9.80.p',
1372
-            '5.0.0.p'
1373
-        );
1374
-        return $this->decreaseSold($qty);
1375
-    }
1376
-
1377
-
1378
-    /**
1379
-     * Increments reserved by amount passed by $qty, and persists it immediately to the database.
1380
-     *
1381
-     * @deprecated 4.9.80.p
1382
-     * @param int $qty
1383
-     * @return boolean indicating success
1384
-     * @throws ReflectionException
1385
-     * @throws InvalidArgumentException
1386
-     * @throws InvalidInterfaceException
1387
-     * @throws InvalidDataTypeException
1388
-     * @throws EE_Error
1389
-     */
1390
-    public function increase_reserved($qty = 1)
1391
-    {
1392
-        EE_Error::doing_it_wrong(
1393
-            __FUNCTION__,
1394
-            esc_html__('Please use EE_Datetime::increaseReserved() instead', 'event_espresso'),
1395
-            '4.9.80.p',
1396
-            '5.0.0.p'
1397
-        );
1398
-        return $this->increaseReserved($qty);
1399
-    }
1400
-
1401
-
1402
-    /**
1403
-     * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
1404
-     *
1405
-     * @deprecated 4.9.80.p
1406
-     * @param int $qty
1407
-     * @return boolean
1408
-     * @throws ReflectionException
1409
-     * @throws InvalidArgumentException
1410
-     * @throws InvalidInterfaceException
1411
-     * @throws InvalidDataTypeException
1412
-     * @throws EE_Error
1413
-     */
1414
-    public function decrease_reserved($qty = 1)
1415
-    {
1416
-        EE_Error::doing_it_wrong(
1417
-            __FUNCTION__,
1418
-            esc_html__('Please use EE_Datetime::decreaseReserved() instead', 'event_espresso'),
1419
-            '4.9.80.p',
1420
-            '5.0.0.p'
1421
-        );
1422
-        return $this->decreaseReserved($qty);
1423
-    }
1329
+	/**
1330
+	 * Increments sold by amount passed by $qty, and persists it immediately to the database.
1331
+	 *
1332
+	 * @deprecated 4.9.80.p
1333
+	 * @param int $qty
1334
+	 * @return boolean
1335
+	 * @throws ReflectionException
1336
+	 * @throws InvalidArgumentException
1337
+	 * @throws InvalidInterfaceException
1338
+	 * @throws InvalidDataTypeException
1339
+	 * @throws EE_Error
1340
+	 */
1341
+	public function increase_sold($qty = 1)
1342
+	{
1343
+		EE_Error::doing_it_wrong(
1344
+			__FUNCTION__,
1345
+			esc_html__('Please use EE_Datetime::increaseSold() instead', 'event_espresso'),
1346
+			'4.9.80.p',
1347
+			'5.0.0.p'
1348
+		);
1349
+		return $this->increaseSold($qty);
1350
+	}
1351
+
1352
+
1353
+	/**
1354
+	 * Decrements (subtracts) sold amount passed by $qty directly in the DB and on the model object. (Ie, no need
1355
+	 * to save afterwards.)
1356
+	 *
1357
+	 * @deprecated 4.9.80.p
1358
+	 * @param int $qty
1359
+	 * @return boolean
1360
+	 * @throws ReflectionException
1361
+	 * @throws InvalidArgumentException
1362
+	 * @throws InvalidInterfaceException
1363
+	 * @throws InvalidDataTypeException
1364
+	 * @throws EE_Error
1365
+	 */
1366
+	public function decrease_sold($qty = 1)
1367
+	{
1368
+		EE_Error::doing_it_wrong(
1369
+			__FUNCTION__,
1370
+			esc_html__('Please use EE_Datetime::decreaseSold() instead', 'event_espresso'),
1371
+			'4.9.80.p',
1372
+			'5.0.0.p'
1373
+		);
1374
+		return $this->decreaseSold($qty);
1375
+	}
1376
+
1377
+
1378
+	/**
1379
+	 * Increments reserved by amount passed by $qty, and persists it immediately to the database.
1380
+	 *
1381
+	 * @deprecated 4.9.80.p
1382
+	 * @param int $qty
1383
+	 * @return boolean indicating success
1384
+	 * @throws ReflectionException
1385
+	 * @throws InvalidArgumentException
1386
+	 * @throws InvalidInterfaceException
1387
+	 * @throws InvalidDataTypeException
1388
+	 * @throws EE_Error
1389
+	 */
1390
+	public function increase_reserved($qty = 1)
1391
+	{
1392
+		EE_Error::doing_it_wrong(
1393
+			__FUNCTION__,
1394
+			esc_html__('Please use EE_Datetime::increaseReserved() instead', 'event_espresso'),
1395
+			'4.9.80.p',
1396
+			'5.0.0.p'
1397
+		);
1398
+		return $this->increaseReserved($qty);
1399
+	}
1400
+
1401
+
1402
+	/**
1403
+	 * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
1404
+	 *
1405
+	 * @deprecated 4.9.80.p
1406
+	 * @param int $qty
1407
+	 * @return boolean
1408
+	 * @throws ReflectionException
1409
+	 * @throws InvalidArgumentException
1410
+	 * @throws InvalidInterfaceException
1411
+	 * @throws InvalidDataTypeException
1412
+	 * @throws EE_Error
1413
+	 */
1414
+	public function decrease_reserved($qty = 1)
1415
+	{
1416
+		EE_Error::doing_it_wrong(
1417
+			__FUNCTION__,
1418
+			esc_html__('Please use EE_Datetime::decreaseReserved() instead', 'event_espresso'),
1419
+			'4.9.80.p',
1420
+			'5.0.0.p'
1421
+		);
1422
+		return $this->decreaseReserved($qty);
1423
+	}
1424 1424
 }
Please login to merge, or discard this patch.