Completed
Branch master (724475)
by
unknown
05:49
created
core/db_models/fields/EE_Foreign_Key_Int_Field.php 1 patch
Indentation   +30 added lines, -30 removed lines patch added patch discarded remove patch
@@ -5,37 +5,37 @@
 block discarded – undo
5 5
 
6 6
 class EE_Foreign_Key_Int_Field extends EE_Foreign_Key_Field_Base
7 7
 {
8
-    /**
9
-     * @param string  $table_column  name fo column for field
10
-     * @param string  $nicename      should eb internationalized with esc_html__('blah','event_espresso')
11
-     * @param boolean $nullable
12
-     * @param mixed $default_value   if this is an integer field, it should be an int.
13
-     *                               if it's a string field, it should be a string
14
-     * @param string|string[]  $model_name    eg 'Event','Answer','Term', etc. Basically its the model class's name without the
15
-     *                               "EEM_"
16
-     */
17
-    public function __construct($table_column, $nicename, $nullable, $default_value, $model_name)
18
-    {
19
-        parent::__construct($table_column, $nicename, $nullable, $default_value, $model_name);
20
-        $this->setSchemaType(SchemaType::INTEGER);
21
-        $this->setDataType(DataType::INTEGER);
22
-    }
8
+	/**
9
+	 * @param string  $table_column  name fo column for field
10
+	 * @param string  $nicename      should eb internationalized with esc_html__('blah','event_espresso')
11
+	 * @param boolean $nullable
12
+	 * @param mixed $default_value   if this is an integer field, it should be an int.
13
+	 *                               if it's a string field, it should be a string
14
+	 * @param string|string[]  $model_name    eg 'Event','Answer','Term', etc. Basically its the model class's name without the
15
+	 *                               "EEM_"
16
+	 */
17
+	public function __construct($table_column, $nicename, $nullable, $default_value, $model_name)
18
+	{
19
+		parent::__construct($table_column, $nicename, $nullable, $default_value, $model_name);
20
+		$this->setSchemaType(SchemaType::INTEGER);
21
+		$this->setDataType(DataType::INTEGER);
22
+	}
23 23
 
24 24
 
25
-    /**
26
-     * @param int|EE_Base_Class $value_inputted_for_field_on_model_object
27
-     * @return int
28
-     */
29
-    public function prepare_for_set($value_inputted_for_field_on_model_object)
30
-    {
31
-        if ($this->is_model_obj_of_type_pointed_to($value_inputted_for_field_on_model_object)) {
32
-            $value_inputted_for_field_on_model_object = $value_inputted_for_field_on_model_object->ID();
33
-        }
34
-        return absint($value_inputted_for_field_on_model_object);
35
-    }
25
+	/**
26
+	 * @param int|EE_Base_Class $value_inputted_for_field_on_model_object
27
+	 * @return int
28
+	 */
29
+	public function prepare_for_set($value_inputted_for_field_on_model_object)
30
+	{
31
+		if ($this->is_model_obj_of_type_pointed_to($value_inputted_for_field_on_model_object)) {
32
+			$value_inputted_for_field_on_model_object = $value_inputted_for_field_on_model_object->ID();
33
+		}
34
+		return absint($value_inputted_for_field_on_model_object);
35
+	}
36 36
 
37
-    public function prepare_for_set_from_db($value_found_in_db_for_model_object)
38
-    {
39
-        return intval($value_found_in_db_for_model_object);
40
-    }
37
+	public function prepare_for_set_from_db($value_found_in_db_for_model_object)
38
+	{
39
+		return intval($value_found_in_db_for_model_object);
40
+	}
41 41
 }
Please login to merge, or discard this patch.
core/db_models/fields/EE_Enum_Text_Field.php 1 patch
Indentation   +106 added lines, -106 removed lines patch added patch discarded remove patch
@@ -13,122 +13,122 @@
 block discarded – undo
13 13
  */
14 14
 class EE_Enum_Text_Field extends EE_Text_Field_Base
15 15
 {
16
-    public array $_allowed_enum_values;
16
+	public array $_allowed_enum_values;
17 17
 
18 18
 
19
-    /**
20
-     * @param string  $table_column
21
-     * @param string  $nice_name
22
-     * @param boolean $nullable
23
-     * @param mixed   $default_value
24
-     * @param array   $allowed_enum_values keys are values to be used in the DB, values are how they should be displayed
25
-     */
26
-    public function __construct($table_column, $nice_name, $nullable, $default_value, array $allowed_enum_values)
27
-    {
28
-        $this->_allowed_enum_values = $allowed_enum_values;
29
-        parent::__construct($table_column, $nice_name, $nullable, $default_value);
30
-        $this->setSchemaType(SchemaType::OBJECT);
31
-        $this->setDataType(DataType::STRING);
32
-    }
19
+	/**
20
+	 * @param string  $table_column
21
+	 * @param string  $nice_name
22
+	 * @param boolean $nullable
23
+	 * @param mixed   $default_value
24
+	 * @param array   $allowed_enum_values keys are values to be used in the DB, values are how they should be displayed
25
+	 */
26
+	public function __construct($table_column, $nice_name, $nullable, $default_value, array $allowed_enum_values)
27
+	{
28
+		$this->_allowed_enum_values = $allowed_enum_values;
29
+		parent::__construct($table_column, $nice_name, $nullable, $default_value);
30
+		$this->setSchemaType(SchemaType::OBJECT);
31
+		$this->setDataType(DataType::STRING);
32
+	}
33 33
 
34 34
 
35
-    /**
36
-     * Returns the list of allowed enum options, but filterable.
37
-     * This is used internally
38
-     *
39
-     * @return array
40
-     */
41
-    protected function _allowed_enum_values(): array
42
-    {
43
-        return (array) apply_filters(
44
-            'FHEE__EE_Enum_Text_Field___allowed_enum_options',
45
-            $this->_allowed_enum_values,
46
-            $this
47
-        );
48
-    }
35
+	/**
36
+	 * Returns the list of allowed enum options, but filterable.
37
+	 * This is used internally
38
+	 *
39
+	 * @return array
40
+	 */
41
+	protected function _allowed_enum_values(): array
42
+	{
43
+		return (array) apply_filters(
44
+			'FHEE__EE_Enum_Text_Field___allowed_enum_options',
45
+			$this->_allowed_enum_values,
46
+			$this
47
+		);
48
+	}
49 49
 
50 50
 
51
-    /**
52
-     * When setting, just verify that the value being used matches what we've defined as allowable enum values.
53
-     * If not, throw an error (but if WP_DEBUG is false, just set the value to default).
54
-     *
55
-     * @param string $value_inputted_for_field_on_model_object
56
-     * @return string
57
-     */
58
-    public function prepare_for_set($value_inputted_for_field_on_model_object)
59
-    {
60
-        if (
61
-            $value_inputted_for_field_on_model_object !== null
62
-            && ! array_key_exists($value_inputted_for_field_on_model_object, $this->_allowed_enum_values())
63
-        ) {
64
-            if (defined('WP_DEBUG') && WP_DEBUG) {
65
-                $msg  = sprintf(
66
-                    esc_html__('System is assigning incompatible value "%1$s" to field "%2$s"', 'event_espresso'),
67
-                    $value_inputted_for_field_on_model_object,
68
-                    $this->_name
69
-                );
70
-                $msg2 = sprintf(
71
-                    esc_html__('Allowed values for "%1$s" are "%2$s". You provided: "%3$s"', 'event_espresso'),
72
-                    $this->_name,
73
-                    implode(', ', array_keys($this->_allowed_enum_values())),
74
-                    $value_inputted_for_field_on_model_object
75
-                );
76
-                EE_Error::add_error("$msg||$msg2", __FILE__, __FUNCTION__, __LINE__);
77
-            }
78
-            return $this->get_default_value();
79
-        }
80
-        return $value_inputted_for_field_on_model_object;
81
-    }
51
+	/**
52
+	 * When setting, just verify that the value being used matches what we've defined as allowable enum values.
53
+	 * If not, throw an error (but if WP_DEBUG is false, just set the value to default).
54
+	 *
55
+	 * @param string $value_inputted_for_field_on_model_object
56
+	 * @return string
57
+	 */
58
+	public function prepare_for_set($value_inputted_for_field_on_model_object)
59
+	{
60
+		if (
61
+			$value_inputted_for_field_on_model_object !== null
62
+			&& ! array_key_exists($value_inputted_for_field_on_model_object, $this->_allowed_enum_values())
63
+		) {
64
+			if (defined('WP_DEBUG') && WP_DEBUG) {
65
+				$msg  = sprintf(
66
+					esc_html__('System is assigning incompatible value "%1$s" to field "%2$s"', 'event_espresso'),
67
+					$value_inputted_for_field_on_model_object,
68
+					$this->_name
69
+				);
70
+				$msg2 = sprintf(
71
+					esc_html__('Allowed values for "%1$s" are "%2$s". You provided: "%3$s"', 'event_espresso'),
72
+					$this->_name,
73
+					implode(', ', array_keys($this->_allowed_enum_values())),
74
+					$value_inputted_for_field_on_model_object
75
+				);
76
+				EE_Error::add_error("$msg||$msg2", __FILE__, __FUNCTION__, __LINE__);
77
+			}
78
+			return $this->get_default_value();
79
+		}
80
+		return $value_inputted_for_field_on_model_object;
81
+	}
82 82
 
83 83
 
84
-    /**
85
-     * Gets the pretty version of the enum's value.
86
-     *
87
-     * @param int|string  $value_on_field_to_be_outputted
88
-     * @param string|null $schema
89
-     * @return    string
90
-     */
91
-    public function prepare_for_pretty_echoing($value_on_field_to_be_outputted, ?string $schema = null)
92
-    {
93
-        $options = $this->_allowed_enum_values();
94
-        return $options[ $value_on_field_to_be_outputted ] ?? $value_on_field_to_be_outputted;
95
-    }
84
+	/**
85
+	 * Gets the pretty version of the enum's value.
86
+	 *
87
+	 * @param int|string  $value_on_field_to_be_outputted
88
+	 * @param string|null $schema
89
+	 * @return    string
90
+	 */
91
+	public function prepare_for_pretty_echoing($value_on_field_to_be_outputted, ?string $schema = null)
92
+	{
93
+		$options = $this->_allowed_enum_values();
94
+		return $options[ $value_on_field_to_be_outputted ] ?? $value_on_field_to_be_outputted;
95
+	}
96 96
 
97 97
 
98
-    /**
99
-     * When retrieving something from the DB, don't enforce the enum's options. If it's in the DB, we just have to live
100
-     * with that. Note also: when we're saving to the DB again, we also don't enforce the enum options. It's ONLY
101
-     * when we're receiving USER input from prepare_for_set() that we enforce the enum options.
102
-     *
103
-     * @param mixed $value_in_db
104
-     * @return mixed
105
-     */
106
-    public function prepare_for_set_from_db($value_in_db)
107
-    {
108
-        return $value_in_db;
109
-    }
98
+	/**
99
+	 * When retrieving something from the DB, don't enforce the enum's options. If it's in the DB, we just have to live
100
+	 * with that. Note also: when we're saving to the DB again, we also don't enforce the enum options. It's ONLY
101
+	 * when we're receiving USER input from prepare_for_set() that we enforce the enum options.
102
+	 *
103
+	 * @param mixed $value_in_db
104
+	 * @return mixed
105
+	 */
106
+	public function prepare_for_set_from_db($value_in_db)
107
+	{
108
+		return $value_in_db;
109
+	}
110 110
 
111 111
 
112
-    public function getSchemaProperties(): array
113
-    {
114
-        return [
115
-            'raw'    => [
116
-                'description' => sprintf(
117
-                    esc_html__('%s - the value in the database.', 'event_espresso'),
118
-                    $this->get_nicename()
119
-                ),
120
-                'type'        => SchemaType::STRING,
121
-                'enum'        => array_keys($this->_allowed_enum_values),
122
-            ],
123
-            'pretty' => [
124
-                'description' => sprintf(
125
-                    esc_html__('%s - the value for display.', 'event_espresso'),
126
-                    $this->get_nicename()
127
-                ),
128
-                'type'        => SchemaType::STRING,
129
-                'enum'        => array_values($this->_allowed_enum_values),
130
-                'read_only'   => true,
131
-            ],
132
-        ];
133
-    }
112
+	public function getSchemaProperties(): array
113
+	{
114
+		return [
115
+			'raw'    => [
116
+				'description' => sprintf(
117
+					esc_html__('%s - the value in the database.', 'event_espresso'),
118
+					$this->get_nicename()
119
+				),
120
+				'type'        => SchemaType::STRING,
121
+				'enum'        => array_keys($this->_allowed_enum_values),
122
+			],
123
+			'pretty' => [
124
+				'description' => sprintf(
125
+					esc_html__('%s - the value for display.', 'event_espresso'),
126
+					$this->get_nicename()
127
+				),
128
+				'type'        => SchemaType::STRING,
129
+				'enum'        => array_values($this->_allowed_enum_values),
130
+				'read_only'   => true,
131
+			],
132
+		];
133
+	}
134 134
 }
Please login to merge, or discard this patch.
core/db_models/fields/EE_JSON_Field.php 1 patch
Indentation   +63 added lines, -63 removed lines patch added patch discarded remove patch
@@ -6,75 +6,75 @@
 block discarded – undo
6 6
 
7 7
 class EE_JSON_Field extends EE_Model_Field_Base
8 8
 {
9
-    private JsonDataHandler $json_data_handler;
9
+	private JsonDataHandler $json_data_handler;
10 10
 
11 11
 
12
-    /**
13
-     * @param string $table_column
14
-     * @param string $nicename
15
-     * @param bool   $nullable
16
-     * @param null   $default_value
17
-     */
18
-    public function __construct(
19
-        $table_column,
20
-        $nicename,
21
-        $nullable,
22
-        $default_value = null
23
-    ) {
24
-        $this->json_data_handler = new JsonDataHandler();
25
-        $this->json_data_handler->configure(
26
-            JsonDataHandler::DATA_TYPE_OBJECT
27
-        );
28
-        parent::__construct($table_column, $nicename, $nullable, $default_value);
29
-        $this->setSchemaType(SchemaType::STRING);
30
-        $this->setDataType(DataType::STRING);
31
-    }
12
+	/**
13
+	 * @param string $table_column
14
+	 * @param string $nicename
15
+	 * @param bool   $nullable
16
+	 * @param null   $default_value
17
+	 */
18
+	public function __construct(
19
+		$table_column,
20
+		$nicename,
21
+		$nullable,
22
+		$default_value = null
23
+	) {
24
+		$this->json_data_handler = new JsonDataHandler();
25
+		$this->json_data_handler->configure(
26
+			JsonDataHandler::DATA_TYPE_OBJECT
27
+		);
28
+		parent::__construct($table_column, $nicename, $nullable, $default_value);
29
+		$this->setSchemaType(SchemaType::STRING);
30
+		$this->setDataType(DataType::STRING);
31
+	}
32 32
 
33 33
 
34
-    // /**
35
-    //  * When get() is called on a model object (eg EE_Event), before returning its value,
36
-    //  * call this function on it, allowing us to customize the returned value based on
37
-    //  * the field's type. Eg, we may want to unserialize it, strip tags, etc. By default,
38
-    //  * we simply return it.
39
-    //  *
40
-    //  * @param mixed $value_of_field_on_model_object
41
-    //  * @return mixed
42
-    //  */
43
-    // public function prepare_for_get($value_of_field_on_model_object)
44
-    // {
45
-    //     // return $this->json_data_handler->decodeJson($value_of_field_on_model_object);
46
-    //     return $value_of_field_on_model_object;
47
-    // }
34
+	// /**
35
+	//  * When get() is called on a model object (eg EE_Event), before returning its value,
36
+	//  * call this function on it, allowing us to customize the returned value based on
37
+	//  * the field's type. Eg, we may want to unserialize it, strip tags, etc. By default,
38
+	//  * we simply return it.
39
+	//  *
40
+	//  * @param mixed $value_of_field_on_model_object
41
+	//  * @return mixed
42
+	//  */
43
+	// public function prepare_for_get($value_of_field_on_model_object)
44
+	// {
45
+	//     // return $this->json_data_handler->decodeJson($value_of_field_on_model_object);
46
+	//     return $value_of_field_on_model_object;
47
+	// }
48 48
 
49 49
 
50
-    /**
51
-     * When creating a brand-new model object, or setting a particular value for one of its fields, this function
52
-     * is called before setting it on the model object. We may want to strip slashes, unserialize the value, etc.
53
-     * By default, we do nothing.
54
-     *
55
-     * If the model field is going to perform any validation on the input, this is where it should be done
56
-     * (once the value is on the model object, it may be used in other ways besides putting it into the DB
57
-     * so it's best to validate it right away).
58
-     *
59
-     * @param mixed $value_inputted_for_field_on_model_object
60
-     * @return string
61
-     */
62
-    public function prepare_for_set($value_inputted_for_field_on_model_object)
63
-    {
64
-        return $this->json_data_handler->encodeData($value_inputted_for_field_on_model_object);
65
-    }
50
+	/**
51
+	 * When creating a brand-new model object, or setting a particular value for one of its fields, this function
52
+	 * is called before setting it on the model object. We may want to strip slashes, unserialize the value, etc.
53
+	 * By default, we do nothing.
54
+	 *
55
+	 * If the model field is going to perform any validation on the input, this is where it should be done
56
+	 * (once the value is on the model object, it may be used in other ways besides putting it into the DB
57
+	 * so it's best to validate it right away).
58
+	 *
59
+	 * @param mixed $value_inputted_for_field_on_model_object
60
+	 * @return string
61
+	 */
62
+	public function prepare_for_set($value_inputted_for_field_on_model_object)
63
+	{
64
+		return $this->json_data_handler->encodeData($value_inputted_for_field_on_model_object);
65
+	}
66 66
 
67 67
 
68
-    /**
69
-     * When inserting or updating a field on a model object, run this function on each
70
-     * value to prepare it for insertion into the db. Generally this converts
71
-     * the validated input on the model object into the format used in the DB.
72
-     *
73
-     * @param mixed $value_of_field_on_model_object
74
-     * @return string
75
-     */
76
-    public function prepare_for_use_in_db($value_of_field_on_model_object)
77
-    {
78
-        return $this->json_data_handler->encodeData($value_of_field_on_model_object);
79
-    }
68
+	/**
69
+	 * When inserting or updating a field on a model object, run this function on each
70
+	 * value to prepare it for insertion into the db. Generally this converts
71
+	 * the validated input on the model object into the format used in the DB.
72
+	 *
73
+	 * @param mixed $value_of_field_on_model_object
74
+	 * @return string
75
+	 */
76
+	public function prepare_for_use_in_db($value_of_field_on_model_object)
77
+	{
78
+		return $this->json_data_handler->encodeData($value_of_field_on_model_object);
79
+	}
80 80
 }
Please login to merge, or discard this patch.
core/db_models/EEM_Payment_Method.model.php 2 patches
Indentation   +470 added lines, -470 removed lines patch added patch discarded remove patch
@@ -19,474 +19,474 @@
 block discarded – undo
19 19
  */
20 20
 class EEM_Payment_Method extends EEM_Base
21 21
 {
22
-    const scope_cart  = 'CART';
23
-
24
-    const scope_admin = 'ADMIN';
25
-
26
-    const scope_api   = 'API';
27
-
28
-
29
-    protected static ?EEM_Payment_Method $_instance = null;
30
-
31
-
32
-    /**
33
-     * private constructor to prevent direct creation
34
-     *
35
-     * @param string|null $timezone
36
-     * @throws EE_Error
37
-     */
38
-    protected function __construct(?string $timezone = '')
39
-    {
40
-        $this->singular_item    = esc_html__('Payment Method', 'event_espresso');
41
-        $this->plural_item      = esc_html__('Payment Methods', 'event_espresso');
42
-        $this->_tables          = [
43
-            'Payment_Method' => new EE_Primary_Table('esp_payment_method', 'PMD_ID'),
44
-        ];
45
-        $this->_fields          = [
46
-            'Payment_Method' => [
47
-                'PMD_ID'              => new EE_Primary_Key_Int_Field(
48
-                    'PMD_ID',
49
-                    esc_html__('ID', 'event_espresso')
50
-                ),
51
-                'PMD_type'            => new EE_Plain_Text_Field(
52
-                    'PMD_type',
53
-                    esc_html__('Payment Method Type', 'event_espresso'),
54
-                    false,
55
-                    'Admin_Only'
56
-                ),
57
-                'PMD_name'            => new EE_Plain_Text_Field(
58
-                    'PMD_name',
59
-                    esc_html__('Name', 'event_espresso'),
60
-                    false
61
-                ),
62
-                'PMD_desc'            => new EE_Post_Content_Field(
63
-                    'PMD_desc',
64
-                    esc_html__('Description', 'event_espresso'),
65
-                    false,
66
-                    ''
67
-                ),
68
-                'PMD_admin_name'      => new EE_Plain_Text_Field(
69
-                    'PMD_admin_name',
70
-                    esc_html__('Admin-Only Name', 'event_espresso'),
71
-                    true
72
-                ),
73
-                'PMD_admin_desc'      => new EE_Post_Content_Field(
74
-                    'PMD_admin_desc',
75
-                    esc_html__('Admin-Only Description', 'event_espresso'),
76
-                    true,
77
-                    ''
78
-                ),
79
-                'PMD_slug'            => new EE_Slug_Field(
80
-                    'PMD_slug',
81
-                    esc_html__('Slug', 'event_espresso'),
82
-                    false
83
-                ),
84
-                'PMD_order'           => new EE_Integer_Field(
85
-                    'PMD_order',
86
-                    esc_html__('Order', 'event_espresso'),
87
-                    false,
88
-                    0
89
-                ),
90
-                'PMD_debug_mode'      => new EE_Boolean_Field(
91
-                    'PMD_debug_mode',
92
-                    esc_html__('Sandbox Mode On? (AKA: debug mode)', 'event_espresso'),
93
-                    false,
94
-                    false
95
-                ),
96
-                'PMD_wp_user'         => new EE_WP_User_Field(
97
-                    'PMD_wp_user',
98
-                    esc_html__('Payment Method Creator ID', 'event_espresso'),
99
-                    false
100
-                ),
101
-                'PMD_open_by_default' => new EE_Boolean_Field(
102
-                    'PMD_open_by_default',
103
-                    esc_html__('Open by Default?', 'event_espresso'),
104
-                    false,
105
-                    false
106
-                ),
107
-                'PMD_button_url'      => new EE_Plain_Text_Field(
108
-                    'PMD_button_url',
109
-                    esc_html__('Button URL', 'event_espresso'),
110
-                    true,
111
-                    ''
112
-                ),
113
-                'PMD_scope'           => new EE_Serialized_Text_Field(
114
-                    'PMD_scope',
115
-                    esc_html__('Usable From?', 'event_espresso'),
116
-                    false,
117
-                    []// possible values currently are 'CART','ADMIN','API'
118
-                ),
119
-            ],
120
-        ];
121
-        $this->_model_relations = [
122
-            'Currency'    => new EE_HABTM_Relation('Currency_Payment_Method'),
123
-            'Payment'     => new EE_Has_Many_Relation(),
124
-            'Transaction' => new EE_Has_Many_Relation(),
125
-            'WP_User'     => new EE_Belongs_To_Relation(),
126
-        ];
127
-        parent::__construct($timezone);
128
-    }
129
-
130
-
131
-    /**
132
-     * Gets one by the slug provided
133
-     *
134
-     * @param string $slug
135
-     * @return EE_Payment_Method|null
136
-     * @throws EE_Error
137
-     * @throws ReflectionException
138
-     */
139
-    public function get_one_by_slug($slug): ?EE_Payment_Method
140
-    {
141
-        return $slug ? $this->get_one([['PMD_slug' => (string) $slug]]) : null;
142
-    }
143
-
144
-
145
-    /**
146
-     * Gets all the acceptable scopes for payment methods.
147
-     * Keys are their names as store din the DB, and values are nice names for displaying them
148
-     *
149
-     * @return array
150
-     */
151
-    public function scopes(): array
152
-    {
153
-        return apply_filters(
154
-            'FHEE__EEM_Payment_Method__scopes',
155
-            [
156
-                EEM_Payment_Method::scope_cart  => esc_html__('Front-end Registration Page', 'event_espresso'),
157
-                EEM_Payment_Method::scope_admin => esc_html__(
158
-                    'Admin Registration Page (no online processing)',
159
-                    'event_espresso'
160
-                ),
161
-            ]
162
-        );
163
-    }
164
-
165
-
166
-    /**
167
-     * Determines if this is a valid scope
168
-     *
169
-     * @param string $scope like one of EEM_Payment_Method::instance()->scopes()
170
-     * @return boolean
171
-     */
172
-    public function is_valid_scope(string $scope): bool
173
-    {
174
-        $scopes = $this->scopes();
175
-        return isset($scopes[ $scope ]);
176
-    }
177
-
178
-
179
-    /**
180
-     * Gets all active payment methods
181
-     *
182
-     * @param string $scope one of
183
-     * @param array  $query_params
184
-     * @return EE_Payment_Method[]
185
-     * @throws EE_Error
186
-     * @throws ReflectionException
187
-     */
188
-    public function get_all_active(string $scope = '', array $query_params = []): array
189
-    {
190
-        if (! isset($query_params['order_by']) && ! isset($query_params['order'])) {
191
-            $query_params['order_by'] = ['PMD_order' => 'ASC', 'PMD_ID' => 'ASC'];
192
-        }
193
-        return $this->get_all($this->_get_query_params_for_all_active($scope, $query_params));
194
-    }
195
-
196
-
197
-    /**
198
-     * Counts all active gateways in the specified scope
199
-     *
200
-     * @param string $scope one of EEM_Payment_Method::scope_*
201
-     * @param array  $query_params
202
-     * @return int
203
-     * @throws EE_Error
204
-     * @throws ReflectionException
205
-     */
206
-    public function count_active(string $scope = '', array $query_params = []): int
207
-    {
208
-        return $this->count($this->_get_query_params_for_all_active($scope, $query_params));
209
-    }
210
-
211
-
212
-    /**
213
-     * Creates the $query_params that can be passed into any EEM_Payment_Method as their $query_params
214
-     * argument to get all active for a given scope
215
-     *
216
-     * @param string $scope one of the constants EEM_Payment_Method::scope_*
217
-     * @param array  $query_params
218
-     * @return array
219
-     * @throws EE_Error
220
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
221
-     */
222
-    protected function _get_query_params_for_all_active(string $scope = '', array $query_params = []): array
223
-    {
224
-        if ($scope) {
225
-            if ($this->is_valid_scope($scope)) {
226
-                return array_replace_recursive([['PMD_scope' => ['LIKE', "%$scope%"]]], $query_params);
227
-            }
228
-            throw new EE_Error(
229
-                sprintf(
230
-                    esc_html__("'%s' is not a valid scope for a payment method", 'event_espresso'),
231
-                    $scope
232
-                )
233
-            );
234
-        }
235
-        $acceptable_scopes = [];
236
-        $count             = 0;
237
-        foreach ($this->scopes() as $scope_name => $desc) {
238
-            $count++;
239
-            $acceptable_scopes[ 'PMD_scope*' . $count ] = ['LIKE', '%' . $scope_name . '%'];
240
-        }
241
-        return array_replace_recursive([['OR*active_scope' => $acceptable_scopes]], $query_params);
242
-    }
243
-
244
-
245
-    /**
246
-     * Creates the $query_params that can be passed into any EEM_Payment_Method as their $query_params
247
-     * argument to get all active for a given scope
248
-     *
249
-     * @param string $scope one of the constants EEM_Payment_Method::scope_*
250
-     * @param array  $query_params
251
-     * @return array
252
-     * @throws EE_Error
253
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
254
-     */
255
-    public function get_query_params_for_all_active(string $scope = '', array $query_params = []): array
256
-    {
257
-        return $this->_get_query_params_for_all_active($scope, $query_params);
258
-    }
259
-
260
-
261
-    /**
262
-     * Gets one active payment method. see @get_all_active for documentation
263
-     *
264
-     * @param string $scope
265
-     * @param array  $query_params
266
-     * @return EE_Payment_Method|null
267
-     * @throws EE_Error
268
-     * @throws ReflectionException
269
-     */
270
-    public function get_one_active(string $scope = '', array $query_params = []): ?EE_Payment_Method
271
-    {
272
-        return $this->get_one($this->_get_query_params_for_all_active($scope, $query_params));
273
-    }
274
-
275
-
276
-    /**
277
-     * Gets one payment method of that type, regardless of whether it's active or not
278
-     *
279
-     * @param string $type
280
-     * @return EE_Payment_Method|null
281
-     * @throws EE_Error
282
-     * @throws ReflectionException
283
-     */
284
-    public function get_one_of_type(string $type): ?EE_Payment_Method
285
-    {
286
-        return $this->get_one([['PMD_type' => $type]]);
287
-    }
288
-
289
-
290
-    /**
291
-     * Overrides parent ot also check by the slug
292
-     *
293
-     * @param string|int|EE_Payment_Method $base_class_obj_or_id
294
-     * @param boolean                      $ensure_is_in_db
295
-     * @return EE_Payment_Method
296
-     * @throws EE_Error
297
-     * @throws ReflectionException
298
-     * @see EEM_Base::ensure_is_obj()
299
-     */
300
-    public function ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db = false)
301
-    {
302
-        // first: check if it's a slug
303
-        if (is_string($base_class_obj_or_id)) {
304
-            $obj = $this->get_one_by_slug($base_class_obj_or_id);
305
-            if ($obj instanceof EE_Payment_Method) {
306
-                return $obj;
307
-            }
308
-        }
309
-        // ok so it wasn't a slug we were passed. try the usual then (ie, it's an object or an ID)
310
-        try {
311
-            return parent::ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db);
312
-        } catch (EE_Error $e) {
313
-            // handle it outside the catch
314
-        }
315
-        throw new EE_Error(
316
-            sprintf(
317
-                esc_html__("'%s' is neither a Payment Method ID, slug, nor object.", 'event_espresso'),
318
-                $base_class_obj_or_id
319
-            )
320
-        );
321
-    }
322
-
323
-
324
-    /**
325
-     * Gets the ID of this object, or if it's a string finds the object's id
326
-     * associated with that slug
327
-     *
328
-     * @param mixed $base_obj_or_id_or_slug
329
-     * @return int
330
-     * @throws EE_Error
331
-     * @throws ReflectionException
332
-     */
333
-    public function ensure_is_ID($base_obj_or_id_or_slug)
334
-    {
335
-        if (is_string($base_obj_or_id_or_slug)) {
336
-            // assume it's a slug
337
-            $base_obj_or_id_or_slug = $this->get_one_by_slug($base_obj_or_id_or_slug);
338
-        }
339
-        return parent::ensure_is_ID($base_obj_or_id_or_slug);
340
-    }
341
-
342
-
343
-    /**
344
-     * Verifies the button urls on all the passed payment methods have a valid button url.
345
-     * If not, resets them to their default.
346
-     *
347
-     * @param EE_Payment_Method[] $payment_methods if empty, defaults to all payment methods active in the cart
348
-     * @throws EE_Error
349
-     * @throws ReflectionException
350
-     */
351
-    public function verify_button_urls(array $payment_methods = [])
352
-    {
353
-        $payment_methods = $payment_methods ?: $this->get_all_active(EEM_Payment_Method::scope_cart);
354
-        foreach ($payment_methods as $payment_method) {
355
-            try {
356
-                // If there is really no button URL at all, or if the button URLs still point to decaf folder even
357
-                // though this is a caffeinated install, reset it to the default.
358
-                $current_button_url = $payment_method->button_url();
359
-                if (
360
-                    empty($current_button_url)
361
-                    || (
362
-                        strpos($current_button_url, 'decaf') !== false
363
-                        && strpos($payment_method->type_obj()->default_button_url(), 'decaf') === false
364
-                    )
365
-                ) {
366
-                    $payment_method->save(
367
-                        [
368
-                            'PMD_button_url' => $payment_method->type_obj()->default_button_url(),
369
-                        ]
370
-                    );
371
-                }
372
-            } catch (EE_Error $e) {
373
-                $payment_method->deactivate();
374
-            }
375
-        }
376
-    }
377
-
378
-
379
-    /**
380
-     * Overrides parent to not only turn wpdb results into EE_Payment_Method objects,
381
-     * but also verifies the payment method type of each is a usable object. If not,
382
-     * deactivate it, sets a notification, and deactivates it
383
-     *
384
-     * @param array $rows
385
-     * @return EE_Payment_Method[]
386
-     * @throws EE_Error
387
-     * @throws InvalidDataTypeException
388
-     * @throws ReflectionException
389
-     */
390
-    protected function _create_objects($rows = [])
391
-    {
392
-        $PMM             = LoaderFactory::getLoader()->getShared(EE_Payment_Method_Manager::class);
393
-        $payment_methods = parent::_create_objects($rows);
394
-        /* @var $payment_methods EE_Payment_Method[] */
395
-        $usable_payment_methods = [];
396
-        foreach ($payment_methods as $key => $payment_method) {
397
-            // check if the payment method type exists and force recheck
398
-            $pm_type_exists = $PMM->payment_method_type_exists($payment_method->type(), true);
399
-            $nag_notice_name = 'auto-deactivated-' . $payment_method->slug();
400
-            if ($pm_type_exists) {
401
-                $usable_payment_methods[ $key ] = $payment_method;
402
-                // some payment methods enqueue their scripts in EE_PMT_*::__construct
403
-                // which is kinda a no-no (just because it's being constructed doesn't mean we need to enqueue
404
-                // its scripts). but for backwards-compat we should continue to do that
405
-                $payment_method->type_obj();
406
-                if (
407
-                    ! $payment_method->active()
408
-                    && PersistentAdminNoticeManager::hasPersistentAdminNotice($nag_notice_name)
409
-                ) {
410
-                    $payment_method->set_active();
411
-                    $payment_method->save();
412
-                    PersistentAdminNoticeManager::dismissPersistentAdminNotice($nag_notice_name);
413
-                }
414
-            } elseif ($payment_method->active()) {
415
-                // only deactivate and notify the admin if the payment is active somewhere
416
-                $payment_method->deactivate();
417
-                $payment_method->save();
418
-                do_action(
419
-                    'AHEE__EEM_Payment_Method___create_objects_auto_deactivated_payment_method',
420
-                    $payment_method
421
-                );
422
-                new PersistentAdminNotice(
423
-                    $nag_notice_name,
424
-                    sprintf(
425
-                        esc_html__(
426
-                            'The payment method %1$s was automatically deactivated because it appears its associated Event Espresso Addon was recently deactivated.%2$sIt can be reactivated on the %3$sPlugins admin page%4$s, then you can reactivate the payment method.',
427
-                            'event_espresso'
428
-                        ),
429
-                        $payment_method->admin_name(),
430
-                        '<br />',
431
-                        '<a href="' . admin_url('plugins.php') . '">',
432
-                        '</a>'
433
-                    ),
434
-                    false,
435
-                    'manage_options',
436
-                    'view persistent admin notice',
437
-                    false,
438
-                    'attention'
439
-                );
440
-            }
441
-        }
442
-        return $usable_payment_methods;
443
-    }
444
-
445
-
446
-    /**
447
-     * Gets all the payment methods which can be used for transaction
448
-     * (according to the relations between payment methods and events, and
449
-     * the currencies used for the transaction and their relation to payment methods)
450
-     *
451
-     * @param EE_Transaction $transaction
452
-     * @param string         $scope @see EEM_Payment_Method::get_all_for_events
453
-     * @return EE_Payment_Method[]
454
-     * @throws EE_Error
455
-     * @throws ReflectionException
456
-     */
457
-    public function get_all_for_transaction(EE_Transaction $transaction, string $scope): array
458
-    {
459
-        // give addons a chance to override what payment methods are chosen based on the transaction
460
-        return apply_filters(
461
-            'FHEE__EEM_Payment_Method__get_all_for_transaction__payment_methods',
462
-            $this->get_all_active($scope, ['group_by' => 'PMD_type']),
463
-            $transaction,
464
-            $scope
465
-        );
466
-    }
467
-
468
-
469
-    /**
470
-     * Returns the payment method used for the last payment made for a registration.
471
-     * Note: if an offline payment method was selected on the related transaction then this will have no payment
472
-     * methods returned. It will ONLY return a payment method for a PAYMENT recorded against the registration.
473
-     *
474
-     * @param EE_Registration|int $registration_or_reg_id Either the EE_Registration object or the id for the
475
-     *                                                    registration.
476
-     * @return EE_Payment_Method|null
477
-     * @throws EE_Error
478
-     * @throws ReflectionException
479
-     */
480
-    public function get_last_used_for_registration($registration_or_reg_id): ?EE_Payment_Method
481
-    {
482
-        $registration_id = EEM_Registration::instance()->ensure_is_ID($registration_or_reg_id);
483
-
484
-        $query_params = [
485
-            0          => [
486
-                'Payment.Registration.REG_ID' => $registration_id,
487
-            ],
488
-            'order_by' => ['Payment.PAY_ID' => 'DESC'],
489
-        ];
490
-        return $this->get_one($query_params);
491
-    }
22
+	const scope_cart  = 'CART';
23
+
24
+	const scope_admin = 'ADMIN';
25
+
26
+	const scope_api   = 'API';
27
+
28
+
29
+	protected static ?EEM_Payment_Method $_instance = null;
30
+
31
+
32
+	/**
33
+	 * private constructor to prevent direct creation
34
+	 *
35
+	 * @param string|null $timezone
36
+	 * @throws EE_Error
37
+	 */
38
+	protected function __construct(?string $timezone = '')
39
+	{
40
+		$this->singular_item    = esc_html__('Payment Method', 'event_espresso');
41
+		$this->plural_item      = esc_html__('Payment Methods', 'event_espresso');
42
+		$this->_tables          = [
43
+			'Payment_Method' => new EE_Primary_Table('esp_payment_method', 'PMD_ID'),
44
+		];
45
+		$this->_fields          = [
46
+			'Payment_Method' => [
47
+				'PMD_ID'              => new EE_Primary_Key_Int_Field(
48
+					'PMD_ID',
49
+					esc_html__('ID', 'event_espresso')
50
+				),
51
+				'PMD_type'            => new EE_Plain_Text_Field(
52
+					'PMD_type',
53
+					esc_html__('Payment Method Type', 'event_espresso'),
54
+					false,
55
+					'Admin_Only'
56
+				),
57
+				'PMD_name'            => new EE_Plain_Text_Field(
58
+					'PMD_name',
59
+					esc_html__('Name', 'event_espresso'),
60
+					false
61
+				),
62
+				'PMD_desc'            => new EE_Post_Content_Field(
63
+					'PMD_desc',
64
+					esc_html__('Description', 'event_espresso'),
65
+					false,
66
+					''
67
+				),
68
+				'PMD_admin_name'      => new EE_Plain_Text_Field(
69
+					'PMD_admin_name',
70
+					esc_html__('Admin-Only Name', 'event_espresso'),
71
+					true
72
+				),
73
+				'PMD_admin_desc'      => new EE_Post_Content_Field(
74
+					'PMD_admin_desc',
75
+					esc_html__('Admin-Only Description', 'event_espresso'),
76
+					true,
77
+					''
78
+				),
79
+				'PMD_slug'            => new EE_Slug_Field(
80
+					'PMD_slug',
81
+					esc_html__('Slug', 'event_espresso'),
82
+					false
83
+				),
84
+				'PMD_order'           => new EE_Integer_Field(
85
+					'PMD_order',
86
+					esc_html__('Order', 'event_espresso'),
87
+					false,
88
+					0
89
+				),
90
+				'PMD_debug_mode'      => new EE_Boolean_Field(
91
+					'PMD_debug_mode',
92
+					esc_html__('Sandbox Mode On? (AKA: debug mode)', 'event_espresso'),
93
+					false,
94
+					false
95
+				),
96
+				'PMD_wp_user'         => new EE_WP_User_Field(
97
+					'PMD_wp_user',
98
+					esc_html__('Payment Method Creator ID', 'event_espresso'),
99
+					false
100
+				),
101
+				'PMD_open_by_default' => new EE_Boolean_Field(
102
+					'PMD_open_by_default',
103
+					esc_html__('Open by Default?', 'event_espresso'),
104
+					false,
105
+					false
106
+				),
107
+				'PMD_button_url'      => new EE_Plain_Text_Field(
108
+					'PMD_button_url',
109
+					esc_html__('Button URL', 'event_espresso'),
110
+					true,
111
+					''
112
+				),
113
+				'PMD_scope'           => new EE_Serialized_Text_Field(
114
+					'PMD_scope',
115
+					esc_html__('Usable From?', 'event_espresso'),
116
+					false,
117
+					[]// possible values currently are 'CART','ADMIN','API'
118
+				),
119
+			],
120
+		];
121
+		$this->_model_relations = [
122
+			'Currency'    => new EE_HABTM_Relation('Currency_Payment_Method'),
123
+			'Payment'     => new EE_Has_Many_Relation(),
124
+			'Transaction' => new EE_Has_Many_Relation(),
125
+			'WP_User'     => new EE_Belongs_To_Relation(),
126
+		];
127
+		parent::__construct($timezone);
128
+	}
129
+
130
+
131
+	/**
132
+	 * Gets one by the slug provided
133
+	 *
134
+	 * @param string $slug
135
+	 * @return EE_Payment_Method|null
136
+	 * @throws EE_Error
137
+	 * @throws ReflectionException
138
+	 */
139
+	public function get_one_by_slug($slug): ?EE_Payment_Method
140
+	{
141
+		return $slug ? $this->get_one([['PMD_slug' => (string) $slug]]) : null;
142
+	}
143
+
144
+
145
+	/**
146
+	 * Gets all the acceptable scopes for payment methods.
147
+	 * Keys are their names as store din the DB, and values are nice names for displaying them
148
+	 *
149
+	 * @return array
150
+	 */
151
+	public function scopes(): array
152
+	{
153
+		return apply_filters(
154
+			'FHEE__EEM_Payment_Method__scopes',
155
+			[
156
+				EEM_Payment_Method::scope_cart  => esc_html__('Front-end Registration Page', 'event_espresso'),
157
+				EEM_Payment_Method::scope_admin => esc_html__(
158
+					'Admin Registration Page (no online processing)',
159
+					'event_espresso'
160
+				),
161
+			]
162
+		);
163
+	}
164
+
165
+
166
+	/**
167
+	 * Determines if this is a valid scope
168
+	 *
169
+	 * @param string $scope like one of EEM_Payment_Method::instance()->scopes()
170
+	 * @return boolean
171
+	 */
172
+	public function is_valid_scope(string $scope): bool
173
+	{
174
+		$scopes = $this->scopes();
175
+		return isset($scopes[ $scope ]);
176
+	}
177
+
178
+
179
+	/**
180
+	 * Gets all active payment methods
181
+	 *
182
+	 * @param string $scope one of
183
+	 * @param array  $query_params
184
+	 * @return EE_Payment_Method[]
185
+	 * @throws EE_Error
186
+	 * @throws ReflectionException
187
+	 */
188
+	public function get_all_active(string $scope = '', array $query_params = []): array
189
+	{
190
+		if (! isset($query_params['order_by']) && ! isset($query_params['order'])) {
191
+			$query_params['order_by'] = ['PMD_order' => 'ASC', 'PMD_ID' => 'ASC'];
192
+		}
193
+		return $this->get_all($this->_get_query_params_for_all_active($scope, $query_params));
194
+	}
195
+
196
+
197
+	/**
198
+	 * Counts all active gateways in the specified scope
199
+	 *
200
+	 * @param string $scope one of EEM_Payment_Method::scope_*
201
+	 * @param array  $query_params
202
+	 * @return int
203
+	 * @throws EE_Error
204
+	 * @throws ReflectionException
205
+	 */
206
+	public function count_active(string $scope = '', array $query_params = []): int
207
+	{
208
+		return $this->count($this->_get_query_params_for_all_active($scope, $query_params));
209
+	}
210
+
211
+
212
+	/**
213
+	 * Creates the $query_params that can be passed into any EEM_Payment_Method as their $query_params
214
+	 * argument to get all active for a given scope
215
+	 *
216
+	 * @param string $scope one of the constants EEM_Payment_Method::scope_*
217
+	 * @param array  $query_params
218
+	 * @return array
219
+	 * @throws EE_Error
220
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
221
+	 */
222
+	protected function _get_query_params_for_all_active(string $scope = '', array $query_params = []): array
223
+	{
224
+		if ($scope) {
225
+			if ($this->is_valid_scope($scope)) {
226
+				return array_replace_recursive([['PMD_scope' => ['LIKE', "%$scope%"]]], $query_params);
227
+			}
228
+			throw new EE_Error(
229
+				sprintf(
230
+					esc_html__("'%s' is not a valid scope for a payment method", 'event_espresso'),
231
+					$scope
232
+				)
233
+			);
234
+		}
235
+		$acceptable_scopes = [];
236
+		$count             = 0;
237
+		foreach ($this->scopes() as $scope_name => $desc) {
238
+			$count++;
239
+			$acceptable_scopes[ 'PMD_scope*' . $count ] = ['LIKE', '%' . $scope_name . '%'];
240
+		}
241
+		return array_replace_recursive([['OR*active_scope' => $acceptable_scopes]], $query_params);
242
+	}
243
+
244
+
245
+	/**
246
+	 * Creates the $query_params that can be passed into any EEM_Payment_Method as their $query_params
247
+	 * argument to get all active for a given scope
248
+	 *
249
+	 * @param string $scope one of the constants EEM_Payment_Method::scope_*
250
+	 * @param array  $query_params
251
+	 * @return array
252
+	 * @throws EE_Error
253
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
254
+	 */
255
+	public function get_query_params_for_all_active(string $scope = '', array $query_params = []): array
256
+	{
257
+		return $this->_get_query_params_for_all_active($scope, $query_params);
258
+	}
259
+
260
+
261
+	/**
262
+	 * Gets one active payment method. see @get_all_active for documentation
263
+	 *
264
+	 * @param string $scope
265
+	 * @param array  $query_params
266
+	 * @return EE_Payment_Method|null
267
+	 * @throws EE_Error
268
+	 * @throws ReflectionException
269
+	 */
270
+	public function get_one_active(string $scope = '', array $query_params = []): ?EE_Payment_Method
271
+	{
272
+		return $this->get_one($this->_get_query_params_for_all_active($scope, $query_params));
273
+	}
274
+
275
+
276
+	/**
277
+	 * Gets one payment method of that type, regardless of whether it's active or not
278
+	 *
279
+	 * @param string $type
280
+	 * @return EE_Payment_Method|null
281
+	 * @throws EE_Error
282
+	 * @throws ReflectionException
283
+	 */
284
+	public function get_one_of_type(string $type): ?EE_Payment_Method
285
+	{
286
+		return $this->get_one([['PMD_type' => $type]]);
287
+	}
288
+
289
+
290
+	/**
291
+	 * Overrides parent ot also check by the slug
292
+	 *
293
+	 * @param string|int|EE_Payment_Method $base_class_obj_or_id
294
+	 * @param boolean                      $ensure_is_in_db
295
+	 * @return EE_Payment_Method
296
+	 * @throws EE_Error
297
+	 * @throws ReflectionException
298
+	 * @see EEM_Base::ensure_is_obj()
299
+	 */
300
+	public function ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db = false)
301
+	{
302
+		// first: check if it's a slug
303
+		if (is_string($base_class_obj_or_id)) {
304
+			$obj = $this->get_one_by_slug($base_class_obj_or_id);
305
+			if ($obj instanceof EE_Payment_Method) {
306
+				return $obj;
307
+			}
308
+		}
309
+		// ok so it wasn't a slug we were passed. try the usual then (ie, it's an object or an ID)
310
+		try {
311
+			return parent::ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db);
312
+		} catch (EE_Error $e) {
313
+			// handle it outside the catch
314
+		}
315
+		throw new EE_Error(
316
+			sprintf(
317
+				esc_html__("'%s' is neither a Payment Method ID, slug, nor object.", 'event_espresso'),
318
+				$base_class_obj_or_id
319
+			)
320
+		);
321
+	}
322
+
323
+
324
+	/**
325
+	 * Gets the ID of this object, or if it's a string finds the object's id
326
+	 * associated with that slug
327
+	 *
328
+	 * @param mixed $base_obj_or_id_or_slug
329
+	 * @return int
330
+	 * @throws EE_Error
331
+	 * @throws ReflectionException
332
+	 */
333
+	public function ensure_is_ID($base_obj_or_id_or_slug)
334
+	{
335
+		if (is_string($base_obj_or_id_or_slug)) {
336
+			// assume it's a slug
337
+			$base_obj_or_id_or_slug = $this->get_one_by_slug($base_obj_or_id_or_slug);
338
+		}
339
+		return parent::ensure_is_ID($base_obj_or_id_or_slug);
340
+	}
341
+
342
+
343
+	/**
344
+	 * Verifies the button urls on all the passed payment methods have a valid button url.
345
+	 * If not, resets them to their default.
346
+	 *
347
+	 * @param EE_Payment_Method[] $payment_methods if empty, defaults to all payment methods active in the cart
348
+	 * @throws EE_Error
349
+	 * @throws ReflectionException
350
+	 */
351
+	public function verify_button_urls(array $payment_methods = [])
352
+	{
353
+		$payment_methods = $payment_methods ?: $this->get_all_active(EEM_Payment_Method::scope_cart);
354
+		foreach ($payment_methods as $payment_method) {
355
+			try {
356
+				// If there is really no button URL at all, or if the button URLs still point to decaf folder even
357
+				// though this is a caffeinated install, reset it to the default.
358
+				$current_button_url = $payment_method->button_url();
359
+				if (
360
+					empty($current_button_url)
361
+					|| (
362
+						strpos($current_button_url, 'decaf') !== false
363
+						&& strpos($payment_method->type_obj()->default_button_url(), 'decaf') === false
364
+					)
365
+				) {
366
+					$payment_method->save(
367
+						[
368
+							'PMD_button_url' => $payment_method->type_obj()->default_button_url(),
369
+						]
370
+					);
371
+				}
372
+			} catch (EE_Error $e) {
373
+				$payment_method->deactivate();
374
+			}
375
+		}
376
+	}
377
+
378
+
379
+	/**
380
+	 * Overrides parent to not only turn wpdb results into EE_Payment_Method objects,
381
+	 * but also verifies the payment method type of each is a usable object. If not,
382
+	 * deactivate it, sets a notification, and deactivates it
383
+	 *
384
+	 * @param array $rows
385
+	 * @return EE_Payment_Method[]
386
+	 * @throws EE_Error
387
+	 * @throws InvalidDataTypeException
388
+	 * @throws ReflectionException
389
+	 */
390
+	protected function _create_objects($rows = [])
391
+	{
392
+		$PMM             = LoaderFactory::getLoader()->getShared(EE_Payment_Method_Manager::class);
393
+		$payment_methods = parent::_create_objects($rows);
394
+		/* @var $payment_methods EE_Payment_Method[] */
395
+		$usable_payment_methods = [];
396
+		foreach ($payment_methods as $key => $payment_method) {
397
+			// check if the payment method type exists and force recheck
398
+			$pm_type_exists = $PMM->payment_method_type_exists($payment_method->type(), true);
399
+			$nag_notice_name = 'auto-deactivated-' . $payment_method->slug();
400
+			if ($pm_type_exists) {
401
+				$usable_payment_methods[ $key ] = $payment_method;
402
+				// some payment methods enqueue their scripts in EE_PMT_*::__construct
403
+				// which is kinda a no-no (just because it's being constructed doesn't mean we need to enqueue
404
+				// its scripts). but for backwards-compat we should continue to do that
405
+				$payment_method->type_obj();
406
+				if (
407
+					! $payment_method->active()
408
+					&& PersistentAdminNoticeManager::hasPersistentAdminNotice($nag_notice_name)
409
+				) {
410
+					$payment_method->set_active();
411
+					$payment_method->save();
412
+					PersistentAdminNoticeManager::dismissPersistentAdminNotice($nag_notice_name);
413
+				}
414
+			} elseif ($payment_method->active()) {
415
+				// only deactivate and notify the admin if the payment is active somewhere
416
+				$payment_method->deactivate();
417
+				$payment_method->save();
418
+				do_action(
419
+					'AHEE__EEM_Payment_Method___create_objects_auto_deactivated_payment_method',
420
+					$payment_method
421
+				);
422
+				new PersistentAdminNotice(
423
+					$nag_notice_name,
424
+					sprintf(
425
+						esc_html__(
426
+							'The payment method %1$s was automatically deactivated because it appears its associated Event Espresso Addon was recently deactivated.%2$sIt can be reactivated on the %3$sPlugins admin page%4$s, then you can reactivate the payment method.',
427
+							'event_espresso'
428
+						),
429
+						$payment_method->admin_name(),
430
+						'<br />',
431
+						'<a href="' . admin_url('plugins.php') . '">',
432
+						'</a>'
433
+					),
434
+					false,
435
+					'manage_options',
436
+					'view persistent admin notice',
437
+					false,
438
+					'attention'
439
+				);
440
+			}
441
+		}
442
+		return $usable_payment_methods;
443
+	}
444
+
445
+
446
+	/**
447
+	 * Gets all the payment methods which can be used for transaction
448
+	 * (according to the relations between payment methods and events, and
449
+	 * the currencies used for the transaction and their relation to payment methods)
450
+	 *
451
+	 * @param EE_Transaction $transaction
452
+	 * @param string         $scope @see EEM_Payment_Method::get_all_for_events
453
+	 * @return EE_Payment_Method[]
454
+	 * @throws EE_Error
455
+	 * @throws ReflectionException
456
+	 */
457
+	public function get_all_for_transaction(EE_Transaction $transaction, string $scope): array
458
+	{
459
+		// give addons a chance to override what payment methods are chosen based on the transaction
460
+		return apply_filters(
461
+			'FHEE__EEM_Payment_Method__get_all_for_transaction__payment_methods',
462
+			$this->get_all_active($scope, ['group_by' => 'PMD_type']),
463
+			$transaction,
464
+			$scope
465
+		);
466
+	}
467
+
468
+
469
+	/**
470
+	 * Returns the payment method used for the last payment made for a registration.
471
+	 * Note: if an offline payment method was selected on the related transaction then this will have no payment
472
+	 * methods returned. It will ONLY return a payment method for a PAYMENT recorded against the registration.
473
+	 *
474
+	 * @param EE_Registration|int $registration_or_reg_id Either the EE_Registration object or the id for the
475
+	 *                                                    registration.
476
+	 * @return EE_Payment_Method|null
477
+	 * @throws EE_Error
478
+	 * @throws ReflectionException
479
+	 */
480
+	public function get_last_used_for_registration($registration_or_reg_id): ?EE_Payment_Method
481
+	{
482
+		$registration_id = EEM_Registration::instance()->ensure_is_ID($registration_or_reg_id);
483
+
484
+		$query_params = [
485
+			0          => [
486
+				'Payment.Registration.REG_ID' => $registration_id,
487
+			],
488
+			'order_by' => ['Payment.PAY_ID' => 'DESC'],
489
+		];
490
+		return $this->get_one($query_params);
491
+	}
492 492
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -172,7 +172,7 @@  discard block
 block discarded – undo
172 172
     public function is_valid_scope(string $scope): bool
173 173
     {
174 174
         $scopes = $this->scopes();
175
-        return isset($scopes[ $scope ]);
175
+        return isset($scopes[$scope]);
176 176
     }
177 177
 
178 178
 
@@ -187,7 +187,7 @@  discard block
 block discarded – undo
187 187
      */
188 188
     public function get_all_active(string $scope = '', array $query_params = []): array
189 189
     {
190
-        if (! isset($query_params['order_by']) && ! isset($query_params['order'])) {
190
+        if ( ! isset($query_params['order_by']) && ! isset($query_params['order'])) {
191 191
             $query_params['order_by'] = ['PMD_order' => 'ASC', 'PMD_ID' => 'ASC'];
192 192
         }
193 193
         return $this->get_all($this->_get_query_params_for_all_active($scope, $query_params));
@@ -236,7 +236,7 @@  discard block
 block discarded – undo
236 236
         $count             = 0;
237 237
         foreach ($this->scopes() as $scope_name => $desc) {
238 238
             $count++;
239
-            $acceptable_scopes[ 'PMD_scope*' . $count ] = ['LIKE', '%' . $scope_name . '%'];
239
+            $acceptable_scopes['PMD_scope*'.$count] = ['LIKE', '%'.$scope_name.'%'];
240 240
         }
241 241
         return array_replace_recursive([['OR*active_scope' => $acceptable_scopes]], $query_params);
242 242
     }
@@ -396,9 +396,9 @@  discard block
 block discarded – undo
396 396
         foreach ($payment_methods as $key => $payment_method) {
397 397
             // check if the payment method type exists and force recheck
398 398
             $pm_type_exists = $PMM->payment_method_type_exists($payment_method->type(), true);
399
-            $nag_notice_name = 'auto-deactivated-' . $payment_method->slug();
399
+            $nag_notice_name = 'auto-deactivated-'.$payment_method->slug();
400 400
             if ($pm_type_exists) {
401
-                $usable_payment_methods[ $key ] = $payment_method;
401
+                $usable_payment_methods[$key] = $payment_method;
402 402
                 // some payment methods enqueue their scripts in EE_PMT_*::__construct
403 403
                 // which is kinda a no-no (just because it's being constructed doesn't mean we need to enqueue
404 404
                 // its scripts). but for backwards-compat we should continue to do that
@@ -428,7 +428,7 @@  discard block
 block discarded – undo
428 428
                         ),
429 429
                         $payment_method->admin_name(),
430 430
                         '<br />',
431
-                        '<a href="' . admin_url('plugins.php') . '">',
431
+                        '<a href="'.admin_url('plugins.php').'">',
432 432
                         '</a>'
433 433
                     ),
434 434
                     false,
Please login to merge, or discard this patch.
core/db_models/EEM_Event.model.php 2 patches
Indentation   +946 added lines, -946 removed lines patch added patch discarded remove patch
@@ -18,950 +18,950 @@
 block discarded – undo
18 18
  */
19 19
 class EEM_Event extends EEM_CPT_Base
20 20
 {
21
-    /**
22
-     * constant used by status(), indicating that no more tickets can be purchased for any of the datetimes for the
23
-     * event
24
-     */
25
-    const sold_out = 'sold_out';
26
-
27
-    /**
28
-     * constant used by status(), indicating that upcoming event dates have been postponed (may be pushed to a later
29
-     * date)
30
-     */
31
-    const postponed = 'postponed';
32
-
33
-    /**
34
-     * constant used by status(), indicating that the event will no longer occur
35
-     */
36
-    const cancelled = 'cancelled';
37
-
38
-
39
-    protected static string $_default_reg_status;
40
-
41
-    protected static int $_default_additional_limit = 10;
42
-
43
-    protected static ?EEM_Event $_instance = null;
44
-
45
-
46
-    /**
47
-     * Adds a relationship to Term_Taxonomy for each CPT_Base
48
-     *
49
-     * @param string|null $timezone
50
-     * @throws EE_Error
51
-     * @throws ReflectionException
52
-     * @throws Exception
53
-     */
54
-    protected function __construct(?string $timezone = '')
55
-    {
56
-        EE_Registry::instance()->load_model('Registration');
57
-        $this->singular_item = esc_html__('Event', 'event_espresso');
58
-        $this->plural_item   = esc_html__('Events', 'event_espresso');
59
-        // to remove Cancelled events from the frontend, copy the following filter to your functions.php file
60
-        // add_filter( 'AFEE__EEM_Event__construct___custom_stati__cancelled__Public', '__return_false' );
61
-        // to remove Postponed events from the frontend, copy the following filter to your functions.php file
62
-        // add_filter( 'AFEE__EEM_Event__construct___custom_stati__postponed__Public', '__return_false' );
63
-        // to remove Sold Out events from the frontend, copy the following filter to your functions.php file
64
-        //  add_filter( 'AFEE__EEM_Event__construct___custom_stati__sold_out__Public', '__return_false' );
65
-        $this->_custom_stati       = apply_filters(
66
-            'AFEE__EEM_Event__construct___custom_stati',
67
-            [
68
-                EEM_Event::cancelled => [
69
-                    'label'  => esc_html__('Cancelled', 'event_espresso'),
70
-                    'public' => apply_filters('AFEE__EEM_Event__construct___custom_stati__cancelled__Public', true),
71
-                ],
72
-                EEM_Event::postponed => [
73
-                    'label'  => esc_html__('Postponed', 'event_espresso'),
74
-                    'public' => apply_filters('AFEE__EEM_Event__construct___custom_stati__postponed__Public', true),
75
-                ],
76
-                EEM_Event::sold_out  => [
77
-                    'label'  => esc_html__('Sold Out', 'event_espresso'),
78
-                    'public' => apply_filters('AFEE__EEM_Event__construct___custom_stati__sold_out__Public', true),
79
-                ],
80
-            ]
81
-        );
82
-        self::$_default_reg_status = empty(self::$_default_reg_status)
83
-            ? RegStatus::PENDING_PAYMENT
84
-            : self::$_default_reg_status;
85
-        $this->_tables             = [
86
-            'Event_CPT'  => new EE_Primary_Table('posts', 'ID'),
87
-            'Event_Meta' => new EE_Secondary_Table('esp_event_meta', 'EVTM_ID', 'EVT_ID'),
88
-        ];
89
-        $this->_fields             = [
90
-            'Event_CPT'  => [
91
-                'EVT_ID'         => new EE_Primary_Key_Int_Field(
92
-                    'ID',
93
-                    esc_html__('Post ID for Event', 'event_espresso')
94
-                ),
95
-                'EVT_name'       => new EE_Plain_Text_Field(
96
-                    'post_title',
97
-                    esc_html__('Event Name', 'event_espresso'),
98
-                    false,
99
-                    ''
100
-                ),
101
-                'EVT_desc'       => new EE_Post_Content_Field(
102
-                    'post_content',
103
-                    esc_html__('Event Description', 'event_espresso'),
104
-                    false,
105
-                    ''
106
-                ),
107
-                'EVT_slug'       => new EE_Slug_Field(
108
-                    'post_name',
109
-                    esc_html__('Event Slug', 'event_espresso'),
110
-                    false,
111
-                    ''
112
-                ),
113
-                'EVT_created'    => new EE_Datetime_Field(
114
-                    'post_date',
115
-                    esc_html__('Date/Time Event Created', 'event_espresso'),
116
-                    false,
117
-                    EE_Datetime_Field::now
118
-                ),
119
-                'EVT_short_desc' => new EE_Simple_HTML_Field(
120
-                    'post_excerpt',
121
-                    esc_html__('Event Short Description', 'event_espresso'),
122
-                    false,
123
-                    ''
124
-                ),
125
-                'EVT_modified'   => new EE_Datetime_Field(
126
-                    'post_modified',
127
-                    esc_html__('Date/Time Event Modified', 'event_espresso'),
128
-                    false,
129
-                    EE_Datetime_Field::now
130
-                ),
131
-                'EVT_wp_user'    => new EE_WP_User_Field(
132
-                    'post_author',
133
-                    esc_html__('Event Creator ID', 'event_espresso'),
134
-                    false
135
-                ),
136
-                'parent'         => new EE_Integer_Field(
137
-                    'post_parent',
138
-                    esc_html__('Event Parent ID', 'event_espresso'),
139
-                    false,
140
-                    0
141
-                ),
142
-                'EVT_order'      => new EE_Integer_Field(
143
-                    'menu_order',
144
-                    esc_html__('Event Menu Order', 'event_espresso'),
145
-                    false,
146
-                    1
147
-                ),
148
-                'post_type'      => new EE_WP_Post_Type_Field(EspressoPostType::EVENTS),
149
-                // EE_Plain_Text_Field( 'post_type', esc_html__( 'Event Post Type', 'event_espresso' ), FALSE, 'espresso_events' ),
150
-                'status'         => new EE_WP_Post_Status_Field(
151
-                    'post_status',
152
-                    esc_html__('Event Status', 'event_espresso'),
153
-                    false,
154
-                    'draft',
155
-                    $this->_custom_stati
156
-                ),
157
-                'password'       => new EE_Password_Field(
158
-                    'post_password',
159
-                    esc_html__('Password', 'event_espresso'),
160
-                    false,
161
-                    '',
162
-                    [
163
-                        'EVT_desc',
164
-                        'EVT_short_desc',
165
-                        'EVT_display_desc',
166
-                        'EVT_display_ticket_selector',
167
-                        'EVT_visible_on',
168
-                        'EVT_additional_limit',
169
-                        'EVT_default_registration_status',
170
-                        'EVT_member_only',
171
-                        'EVT_phone',
172
-                        'EVT_allow_overflow',
173
-                        'EVT_timezone_string',
174
-                        'EVT_external_URL',
175
-                        'EVT_donations',
176
-                    ]
177
-                ),
178
-            ],
179
-            'Event_Meta' => [
180
-                'EVTM_ID'                         => new EE_DB_Only_Float_Field(
181
-                    'EVTM_ID',
182
-                    esc_html__('Event Meta Row ID', 'event_espresso'),
183
-                    false
184
-                ),
185
-                'EVT_ID_fk'                       => new EE_DB_Only_Int_Field(
186
-                    'EVT_ID',
187
-                    esc_html__('Foreign key to Event ID from Event Meta table', 'event_espresso'),
188
-                    false
189
-                ),
190
-                'VNU_ID'                          => new EE_Foreign_Key_Int_Field(
191
-                    'VNU_ID',
192
-                    __('Venue ID', 'event_espresso'),
193
-                    false,
194
-                    0,
195
-                    'Venue'
196
-                ),
197
-                'EVT_display_desc'                => new EE_Boolean_Field(
198
-                    'EVT_display_desc',
199
-                    esc_html__('Display Description Flag', 'event_espresso'),
200
-                    false,
201
-                    true
202
-                ),
203
-                'EVT_display_ticket_selector'     => new EE_Boolean_Field(
204
-                    'EVT_display_ticket_selector',
205
-                    esc_html__('Display Ticket Selector Flag', 'event_espresso'),
206
-                    false,
207
-                    true
208
-                ),
209
-                'EVT_visible_on'                  => new EE_Datetime_Field(
210
-                    'EVT_visible_on',
211
-                    esc_html__('Event Visible Date', 'event_espresso'),
212
-                    true,
213
-                    EE_Datetime_Field::now
214
-                ),
215
-                'EVT_additional_limit'            => new EE_Integer_Field(
216
-                    'EVT_additional_limit',
217
-                    esc_html__('Limit of Additional Registrations on Same Transaction', 'event_espresso'),
218
-                    false,
219
-                    self::$_default_additional_limit
220
-                ),
221
-                'EVT_default_registration_status' => new EE_Enum_Text_Field(
222
-                    'EVT_default_registration_status',
223
-                    esc_html__('Default Registration Status on this Event', 'event_espresso'),
224
-                    false,
225
-                    EEM_Event::$_default_reg_status,
226
-                    EEM_Registration::reg_status_array()
227
-                ),
228
-                'EVT_member_only'                 => new EE_Boolean_Field(
229
-                    'EVT_member_only',
230
-                    esc_html__('Member-Only Event Flag', 'event_espresso'),
231
-                    false,
232
-                    false
233
-                ),
234
-                'EVT_phone'                       => new EE_Plain_Text_Field(
235
-                    'EVT_phone',
236
-                    esc_html__('Event Phone Number', 'event_espresso'),
237
-                    false,
238
-                    ''
239
-                ),
240
-                'EVT_allow_overflow'              => new EE_Boolean_Field(
241
-                    'EVT_allow_overflow',
242
-                    esc_html__('Allow Overflow on Event', 'event_espresso'),
243
-                    false,
244
-                    false
245
-                ),
246
-                'EVT_timezone_string'             => new EE_Plain_Text_Field(
247
-                    'EVT_timezone_string',
248
-                    esc_html__('Timezone (name) for Event times', 'event_espresso'),
249
-                    false,
250
-                    ''
251
-                ),
252
-                'EVT_external_URL'                => new EE_Plain_Text_Field(
253
-                    'EVT_external_URL',
254
-                    esc_html__('URL of Event Page if hosted elsewhere', 'event_espresso'),
255
-                    true
256
-                ),
257
-                'EVT_donations'                   => new EE_Boolean_Field(
258
-                    'EVT_donations',
259
-                    esc_html__('Accept Donations?', 'event_espresso'),
260
-                    false,
261
-                    false
262
-                ),
263
-                'FSC_UUID'                        => new EE_Foreign_Key_String_Field(
264
-                    'FSC_UUID',
265
-                    esc_html__('Registration Form UUID (universally unique identifier)', 'event_espresso'),
266
-                    true,
267
-                    null,
268
-                    'Form_Section',
269
-                    false
270
-                ),
271
-            ],
272
-        ];
273
-        $this->_model_relations    = [
274
-            'Attendee'               => new EE_HABTM_Relation('Registration'),
275
-            'Datetime'               => new EE_Has_Many_Relation(),
276
-            'Event_Question_Group'   => new EE_Has_Many_Relation(),
277
-            'Form_Section'           => new EE_Belongs_To_Relation(),
278
-            'Message_Template_Group' => new EE_HABTM_Relation('Event_Message_Template'),
279
-            'Question_Group'         => new EE_HABTM_Relation('Event_Question_Group'),
280
-            'Registration'           => new EE_Has_Many_Relation(),
281
-            'Term_Relationship'      => new EE_Has_Many_Relation(),
282
-            'Term_Taxonomy'          => new EE_HABTM_Relation('Term_Relationship'),
283
-            'Venue'                  => new EE_Belongs_To_Relation(),
284
-            'WP_User'                => new EE_Belongs_To_Relation(),
285
-        ];
286
-        // this model is generally available for reading
287
-        $this->_cap_restriction_generators[ EEM_Base::caps_read ] = new EE_Restriction_Generator_Public();
288
-        $this->model_chain_to_password                            = '';
289
-        parent::__construct($timezone);
290
-    }
291
-
292
-
293
-    /**
294
-     * @param string $default_reg_status
295
-     * @throws EE_Error
296
-     * @throws ReflectionException
297
-     */
298
-    public static function set_default_reg_status(string $default_reg_status)
299
-    {
300
-        self::$_default_reg_status = $default_reg_status;
301
-        // if EEM_Event has already been instantiated,
302
-        // then we need to reset the `EVT_default_reg_status` field to use the new default.
303
-        if (self::$_instance instanceof EEM_Event) {
304
-            $default_reg_status = new EE_Enum_Text_Field(
305
-                'EVT_default_registration_status',
306
-                esc_html__('Default Registration Status on this Event', 'event_espresso'),
307
-                false,
308
-                $default_reg_status,
309
-                EEM_Registration::reg_status_array()
310
-            );
311
-            $default_reg_status->_construct_finalize(
312
-                'Event_Meta',
313
-                'EVT_default_registration_status',
314
-                'EEM_Event'
315
-            );
316
-            self::$_instance->_fields['Event_Meta']['EVT_default_registration_status'] = $default_reg_status;
317
-        }
318
-    }
319
-
320
-
321
-    /**
322
-     * Used to override the default for the additional limit field.
323
-     *
324
-     * @param int $additional_limit
325
-     */
326
-    public static function set_default_additional_limit(int $additional_limit)
327
-    {
328
-        self::$_default_additional_limit = $additional_limit;
329
-        if (self::$_instance instanceof EEM_Event) {
330
-            self::$_instance->_fields['Event_Meta']['EVT_additional_limit'] = new EE_Integer_Field(
331
-                'EVT_additional_limit',
332
-                esc_html__('Limit of Additional Registrations on Same Transaction', 'event_espresso'),
333
-                false,
334
-                self::$_default_additional_limit
335
-            );
336
-            self::$_instance->_fields['Event_Meta']['EVT_additional_limit']->_construct_finalize(
337
-                'Event_Meta',
338
-                'EVT_additional_limit',
339
-                'EEM_Event'
340
-            );
341
-        }
342
-    }
343
-
344
-
345
-    /**
346
-     * Return what is currently set as the default additional limit for the event.
347
-     *
348
-     * @return int
349
-     */
350
-    public static function get_default_additional_limit(): int
351
-    {
352
-        return apply_filters('FHEE__EEM_Event__get_default_additional_limit', self::$_default_additional_limit);
353
-    }
354
-
355
-
356
-    /**
357
-     * get_question_groups
358
-     *
359
-     * @return array
360
-     * @throws EE_Error
361
-     * @throws ReflectionException
362
-     */
363
-    public function get_all_question_groups()
364
-    {
365
-        return EE_Registry::instance()->load_model('Question_Group')->get_all(
366
-            [
367
-                ['QSG_deleted' => false],
368
-                'order_by' => ['QSG_order' => 'ASC'],
369
-            ]
370
-        );
371
-    }
372
-
373
-
374
-    /**
375
-     * get_question_groups
376
-     *
377
-     * @param int $EVT_ID
378
-     * @return array|bool
379
-     * @throws EE_Error
380
-     * @throws ReflectionException
381
-     */
382
-    public function get_all_event_question_groups(int $EVT_ID = 0)
383
-    {
384
-        if (! isset($EVT_ID) || ! absint($EVT_ID)) {
385
-            EE_Error::add_error(
386
-                esc_html__(
387
-                    'An error occurred. No Event Question Groups could be retrieved because an Event ID was not received.',
388
-                    'event_espresso'
389
-                ),
390
-                __FILE__,
391
-                __FUNCTION__,
392
-                __LINE__
393
-            );
394
-            return false;
395
-        }
396
-        return EE_Registry::instance()->load_model('Event_Question_Group')->get_all(
397
-            [
398
-                ['EVT_ID' => $EVT_ID],
399
-            ]
400
-        );
401
-    }
402
-
403
-
404
-    /**
405
-     * get_question_groups
406
-     *
407
-     * @param int     $EVT_ID
408
-     * @param boolean $for_primary_attendee
409
-     * @return array|bool
410
-     * @throws EE_Error
411
-     * @throws InvalidArgumentException
412
-     * @throws ReflectionException
413
-     * @throws InvalidDataTypeException
414
-     * @throws InvalidInterfaceException
415
-     */
416
-    public function get_event_question_groups(int $EVT_ID = 0, bool $for_primary_attendee = true)
417
-    {
418
-        if (! isset($EVT_ID) || ! absint($EVT_ID)) {
419
-            EE_Error::add_error(
420
-                esc_html__(
421
-                // @codingStandardsIgnoreStart
422
-                    'An error occurred. No Event Question Groups could be retrieved because an Event ID was not received.',
423
-                    // @codingStandardsIgnoreEnd
424
-                    'event_espresso'
425
-                ),
426
-                __FILE__,
427
-                __FUNCTION__,
428
-                __LINE__
429
-            );
430
-            return false;
431
-        }
432
-        $query_params = [
433
-            [
434
-                'EVT_ID'                                                                         => $EVT_ID,
435
-                EEM_Event_Question_Group::instance()->fieldNameForContext($for_primary_attendee) => true,
436
-            ],
437
-        ];
438
-        if ($for_primary_attendee) {
439
-            $query_params[0]['EQG_primary'] = true;
440
-        } else {
441
-            $query_params[0]['EQG_additional'] = true;
442
-        }
443
-        return EE_Registry::instance()->load_model('Event_Question_Group')->get_all($query_params);
444
-    }
445
-
446
-
447
-    /**
448
-     * get_question_groups
449
-     *
450
-     * @param int             $EVT_ID
451
-     * @param EE_Registration $registration
452
-     * @return array|bool
453
-     * @throws EE_Error
454
-     * @throws InvalidArgumentException
455
-     * @throws InvalidDataTypeException
456
-     * @throws InvalidInterfaceException
457
-     * @throws ReflectionException
458
-     */
459
-    public function get_question_groups_for_event(int $EVT_ID, EE_Registration $registration)
460
-    {
461
-        if (! absint($EVT_ID)) {
462
-            EE_Error::add_error(
463
-                esc_html__(
464
-                    'An error occurred. No Question Groups could be retrieved because an Event ID was not received.',
465
-                    'event_espresso'
466
-                ),
467
-                __FILE__,
468
-                __FUNCTION__,
469
-                __LINE__
470
-            );
471
-            return false;
472
-        }
473
-        return EE_Registry::instance()->load_model('Question_Group')->get_all(
474
-            [
475
-                [
476
-                    'Event_Question_Group.EVT_ID' => $EVT_ID,
477
-                    'Event_Question_Group.'
478
-                    . EEM_Event_Question_Group::instance()->fieldNameForContext(
479
-                        $registration->is_primary_registrant()
480
-                    )                             => true,
481
-                ],
482
-                'order_by' => ['QSG_order' => 'ASC'],
483
-            ]
484
-        );
485
-    }
486
-
487
-
488
-    /**
489
-     * get_question_target_db_column
490
-     *
491
-     * @param string $QSG_IDs csv list of $QSG IDs
492
-     * @return array|bool
493
-     * @throws EE_Error
494
-     * @throws ReflectionException
495
-     */
496
-    public function get_questions_in_groups(string $QSG_IDs = '')
497
-    {
498
-        if (empty($QSG_IDs)) {
499
-            EE_Error::add_error(
500
-                esc_html__('An error occurred. No Question Group IDs were received.', 'event_espresso'),
501
-                __FILE__,
502
-                __FUNCTION__,
503
-                __LINE__
504
-            );
505
-            return false;
506
-        }
507
-        return EE_Registry::instance()->load_model('Question')->get_all(
508
-            [
509
-                [
510
-                    'Question_Group.QSG_ID' => ['IN', $QSG_IDs],
511
-                    'QST_deleted'           => false,
512
-                    'QST_admin_only'        => is_admin(),
513
-                ],
514
-                'order_by' => 'QST_order',
515
-            ]
516
-        );
517
-    }
518
-
519
-
520
-    /**
521
-     * get_options_for_question
522
-     *
523
-     * @param string $QST_IDs csv list of $QST IDs
524
-     * @return array|bool
525
-     * @throws EE_Error
526
-     * @throws ReflectionException
527
-     */
528
-    public function get_options_for_question(string $QST_IDs)
529
-    {
530
-        if (empty($QST_IDs)) {
531
-            EE_Error::add_error(
532
-                esc_html__('An error occurred. No Question IDs were received.', 'event_espresso'),
533
-                __FILE__,
534
-                __FUNCTION__,
535
-                __LINE__
536
-            );
537
-            return false;
538
-        }
539
-        return EE_Registry::instance()->load_model('Question_Option')->get_all(
540
-            [
541
-                [
542
-                    'Question.QST_ID' => ['IN', $QST_IDs],
543
-                    'QSO_deleted'     => false,
544
-                ],
545
-                'order_by' => 'QSO_ID',
546
-            ]
547
-        );
548
-    }
549
-
550
-
551
-    /**
552
-     * Gets all events that are published
553
-     * and have event start time earlier than now and an event end time later than now
554
-     *
555
-     * @param array $query_params  An array of query params to further filter on
556
-     *                             (note that status and DTT_EVT_start and DTT_EVT_end will be overridden)
557
-     * @param bool  $count         whether to return the count or not (default FALSE)
558
-     * @return EE_Event[]|int
559
-     * @throws EE_Error
560
-     * @throws ReflectionException
561
-     */
562
-    public function get_active_events(array $query_params, bool $count = false)
563
-    {
564
-        if (array_key_exists(0, $query_params)) {
565
-            $where_params = $query_params[0];
566
-            unset($query_params[0]);
567
-        } else {
568
-            $where_params = [];
569
-        }
570
-        // if we have count make sure we don't include group by
571
-        if ($count && isset($query_params['group_by'])) {
572
-            unset($query_params['group_by']);
573
-        }
574
-        // add status query
575
-        $where_params = $this->set_where_conditions_for_status($where_params);
576
-        // if already have where params for DTT_EVT_start or DTT_EVT_end then append these conditions
577
-        if (isset($where_params['Datetime.DTT_EVT_start'])) {
578
-            $where_params['Datetime.DTT_EVT_start******'] = [
579
-                '<',
580
-                EEM_Datetime::instance()->current_time_for_query('DTT_EVT_start'),
581
-            ];
582
-        } else {
583
-            $where_params['Datetime.DTT_EVT_start'] = [
584
-                '<',
585
-                EEM_Datetime::instance()->current_time_for_query('DTT_EVT_start'),
586
-            ];
587
-        }
588
-        $where_params = $this->set_where_conditions_for_end_datetime($where_params);
589
-        return $this->_get_count_or_all($query_params, $where_params, $count);
590
-    }
591
-
592
-
593
-    /**
594
-     * get all events that are published and have an event start time later than now
595
-     *
596
-     * @param array $query_params  An array of query params to further filter on
597
-     *                             (Note that status and DTT_EVT_start will be overridden)
598
-     * @param bool  $count         whether to return the count or not (default FALSE)
599
-     * @return EE_Event[]|int
600
-     * @throws EE_Error
601
-     * @throws ReflectionException
602
-     */
603
-    public function get_upcoming_events(array $query_params, bool $count = false)
604
-    {
605
-        if (array_key_exists(0, $query_params)) {
606
-            $where_params = $query_params[0];
607
-            unset($query_params[0]);
608
-        } else {
609
-            $where_params = [];
610
-        }
611
-        // if we have count make sure we don't include group by
612
-        if ($count && isset($query_params['group_by'])) {
613
-            unset($query_params['group_by']);
614
-        }
615
-        // add status query
616
-        $where_params = $this->set_where_conditions_for_status($where_params);
617
-        // if there are already query_params matching DTT_EVT_start then we need to modify that to add them.
618
-        if (isset($where_params['Datetime.DTT_EVT_start'])) {
619
-            $where_params['Datetime.DTT_EVT_start*****'] = [
620
-                '>',
621
-                EEM_Datetime::instance()->current_time_for_query('DTT_EVT_start'),
622
-            ];
623
-        } else {
624
-            $where_params['Datetime.DTT_EVT_start'] = [
625
-                '>',
626
-                EEM_Datetime::instance()->current_time_for_query('DTT_EVT_start'),
627
-            ];
628
-        }
629
-        return $this->_get_count_or_all($query_params, $where_params, $count);
630
-    }
631
-
632
-
633
-    /**
634
-     * Gets all events that are published
635
-     * and have an event end time later than now
636
-     *
637
-     * @param array $query_params  An array of query params to further filter on
638
-     *                             (note that status and DTT_EVT_end will be overridden)
639
-     * @param bool  $count         whether to return the count or not (default FALSE)
640
-     * @return EE_Event[]|int
641
-     * @throws EE_Error
642
-     * @throws ReflectionException
643
-     */
644
-    public function get_active_and_upcoming_events(array $query_params, bool $count = false)
645
-    {
646
-        if (array_key_exists(0, $query_params)) {
647
-            $where_params = $query_params[0];
648
-            unset($query_params[0]);
649
-        } else {
650
-            $where_params = [];
651
-        }
652
-        // if we have count make sure we don't include group by
653
-        if ($count && isset($query_params['group_by'])) {
654
-            unset($query_params['group_by']);
655
-        }
656
-        // add status query
657
-        $where_params = $this->set_where_conditions_for_status($where_params);
658
-        // add where params for DTT_EVT_end
659
-        $where_params = $this->set_where_conditions_for_end_datetime($where_params);
660
-        return $this->_get_count_or_all($query_params, $where_params, $count);
661
-    }
662
-
663
-
664
-    /**
665
-     * This only returns events that are expired.
666
-     * They may still be published but all their datetimes have expired.
667
-     *
668
-     * @param array $query_params  An array of query params to further filter on
669
-     *                             (note that status and DTT_EVT_end will be overridden)
670
-     * @param bool  $count         whether to return the count or not (default FALSE)
671
-     * @return EE_Event[]|int
672
-     * @throws EE_Error
673
-     * @throws ReflectionException
674
-     */
675
-    public function get_expired_events(array $query_params, bool $count = false)
676
-    {
677
-        $where_params = $query_params[0] ?? [];
678
-        // if we have count make sure we don't include group by
679
-        if ($count && isset($query_params['group_by'])) {
680
-            unset($query_params['group_by']);
681
-        }
682
-        // let's add specific query_params for active_events
683
-        // keep in mind this will override any sent status in the query AND any date queries.
684
-        if (isset($where_params['status'])) {
685
-            unset($where_params['status']);
686
-        }
687
-        // first get all events that have datetimes where it's not expired.
688
-        $event_ids = $this->get_all_not_expired_event_ids($query_params);
689
-        // if we have any additional query_params, let's add them to the 'AND' condition
690
-        $and_condition = [
691
-            'Datetime.DTT_EVT_end' => ['<', EEM_Datetime::instance()->current_time_for_query('DTT_EVT_end')],
692
-            'EVT_ID'               => ['NOT IN', $event_ids],
693
-        ];
694
-        if (isset($where_params['OR'])) {
695
-            $and_condition['OR'] = $where_params['OR'];
696
-            unset($where_params['OR']);
697
-        }
698
-        if (isset($where_params['Datetime.DTT_EVT_end'])) {
699
-            $and_condition['Datetime.DTT_EVT_end****'] = $where_params['Datetime.DTT_EVT_end'];
700
-            unset($where_params['Datetime.DTT_EVT_end']);
701
-        }
702
-        if (isset($where_params['Datetime.DTT_EVT_start'])) {
703
-            $and_condition['Datetime.DTT_EVT_start'] = $where_params['Datetime.DTT_EVT_start'];
704
-            unset($where_params['Datetime.DTT_EVT_start']);
705
-        }
706
-        // merge remaining $where params with the and conditions.
707
-        $where_params['AND'] = array_merge($and_condition, $where_params);
708
-        return $this->_get_count_or_all($query_params, $where_params, $count);
709
-    }
710
-
711
-
712
-    /**
713
-     * @param array $query_params
714
-     * @return int[]
715
-     * @throws EE_Error
716
-     * @throws ReflectionException
717
-     */
718
-    public function get_all_not_expired_event_ids(array $query_params = []): array
719
-    {
720
-        $query_params[0] = [
721
-            'Datetime.DTT_EVT_end' => [
722
-                '>',
723
-                EEM_Datetime::instance()->current_time_for_query('DTT_EVT_end'),
724
-            ],
725
-        ];
726
-        $event_ids       = $this->_get_all_wpdb_results($query_params, OBJECT_K, 'Event_CPT.ID');
727
-        return array_keys($event_ids);
728
-    }
729
-
730
-
731
-    /**
732
-     * This basically just returns the events that do not have the publish status.
733
-     *
734
-     * @param array   $query_params  An array of query params to further filter on
735
-     *                               (note that status will be overwritten)
736
-     * @param boolean $count         whether to return the count or not (default FALSE)
737
-     * @return EE_Event[]|int
738
-     * @throws EE_Error
739
-     * @throws ReflectionException
740
-     */
741
-    public function get_inactive_events(array $query_params, bool $count = false)
742
-    {
743
-        $where_params = $query_params[0] ?? [];
744
-        // let's add in specific query_params for inactive events.
745
-        if (isset($where_params['status'])) {
746
-            unset($where_params['status']);
747
-        }
748
-        // if we have count make sure we don't include group by
749
-        if ($count && isset($query_params['group_by'])) {
750
-            unset($query_params['group_by']);
751
-        }
752
-        // if we have any additional query_params, let's add them to the 'AND' condition
753
-        $where_params['AND']['status'] = ['!=', 'publish'];
754
-        if (isset($where_params['OR'])) {
755
-            $where_params['AND']['OR'] = $where_params['OR'];
756
-            unset($where_params['OR']);
757
-        }
758
-        if (isset($where_params['Datetime.DTT_EVT_end'])) {
759
-            $where_params['AND']['Datetime.DTT_EVT_end****'] = $where_params['Datetime.DTT_EVT_end'];
760
-            unset($where_params['Datetime.DTT_EVT_end']);
761
-        }
762
-        if (isset($where_params['Datetime.DTT_EVT_start'])) {
763
-            $where_params['AND']['Datetime.DTT_EVT_start'] = $where_params['Datetime.DTT_EVT_start'];
764
-            unset($where_params['Datetime.DTT_EVT_start']);
765
-        }
766
-        return $this->_get_count_or_all($query_params, $where_params, $count);
767
-    }
768
-
769
-
770
-    /**
771
-     * This is just injecting into the parent add_relationship_to so we do special handling on price relationships
772
-     * because we don't want to override any existing global default prices but instead insert NEW prices that get
773
-     * attached to the event. See parent for param descriptions
774
-     *
775
-     * @param int|string|EE_Base_Class $id_or_obj
776
-     * @param int|string|EE_Base_Class $other_model_id_or_obj
777
-     * @param string $relationName
778
-     * @param array  $where_query
779
-     * @return EE_Base_Class
780
-     * @throws EE_Error
781
-     * @throws ReflectionException
782
-     */
783
-    public function add_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query = []): EE_Base_Class
784
-    {
785
-        if ($relationName === 'Price') {
786
-            // let's get the PRC object for the given ID to make sure that we aren't dealing with a default
787
-            $prc_chk = $this->get_related_model_obj($relationName)->ensure_is_obj($other_model_id_or_obj);
788
-            // if EVT_ID = 0, then this is a default
789
-            if ((int) $prc_chk->get('EVT_ID') === 0) {
790
-                // let's set the prc_id as 0 so we force an insert on the add_relation_to carried out by relation
791
-                $prc_chk->set('PRC_ID', 0);
792
-            }
793
-            // run parent
794
-            return parent::add_relationship_to($id_or_obj, $prc_chk, $relationName, $where_query);
795
-        }
796
-        // otherwise carry on as normal
797
-        return parent::add_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query);
798
-    }
799
-
800
-
801
-    /**
802
-     * @param array $where_params
803
-     * @return array
804
-     */
805
-    public function set_where_conditions_for_status(array $where_params): array
806
-    {
807
-        // let's add specific query_params for active_events
808
-        // keep in mind this will override any sent status in the query AND any date queries.
809
-        // we need to pull events with a status of 'publish' and 'sold_out'
810
-        $event_status = ['publish', EEM_Event::sold_out];
811
-        // check if the user can read private events and if so add the 'private status to the where params'
812
-        if (EE_Registry::instance()->CAP->current_user_can('ee_read_private_events', 'get_upcoming_events')) {
813
-            $event_status[] = 'private';
814
-        }
815
-        $where_params['status'] = ['IN', $event_status];
816
-        return $where_params;
817
-    }
818
-
819
-
820
-    /**
821
-     * @param array $where_params
822
-     * @return array
823
-     * @throws EE_Error
824
-     * @throws ReflectionException
825
-     */
826
-    public function set_where_conditions_for_end_datetime(array $where_params): array
827
-    {
828
-        $end_date_field_name = isset($where_params['Datetime.DTT_EVT_end'])
829
-            ? 'Datetime.DTT_EVT_end*****'
830
-            // prevents overwrite of existing where condition
831
-            : 'Datetime.DTT_EVT_end';
832
-
833
-        $where_params[ $end_date_field_name ] = [
834
-            '>',
835
-            EEM_Datetime::instance()->current_time_for_query('DTT_EVT_end'),
836
-        ];
837
-
838
-        return $where_params;
839
-    }
840
-
841
-
842
-    /**
843
-     * @param array $query_params
844
-     * @param array $where_params
845
-     * @param bool  $count
846
-     * @return EE_Event[]|int
847
-     * @throws EE_Error
848
-     * @throws ReflectionException
849
-     */
850
-    protected function _get_count_or_all(array $query_params, array $where_params, bool $count = false)
851
-    {
852
-        $query_params[0] = $where_params;
853
-        // don't use $query_params with count()
854
-        // because we don't want to include additional query clauses like "GROUP BY"
855
-        return $count
856
-            ? $this->count([$where_params], 'EVT_ID', true)
857
-            : $this->get_all($query_params);
858
-    }
859
-
860
-
861
-    /******************** DEPRECATED METHODS ********************/
862
-
863
-
864
-    /**
865
-     * _get_question_target_db_column
866
-     *
867
-     * @param EE_Registration $registration    (so existing answers for registration are included)
868
-     * @param int             $EVT_ID          so all question groups are included for event (not just answers from
869
-     *                                         registration).
870
-     * @return    array
871
-     * @throws ReflectionException
872
-     * @throws EE_Error
873
-     * @deprecated as of 4.8.32.rc.001. Instead consider using
874
-     *                                         EE_Registration_Custom_Questions_Form located in
875
-     *                                         admin_pages/registrations/form_sections/EE_Registration_Custom_Questions_Form.form.php
876
-     * @access     public
877
-     */
878
-    public function assemble_array_of_groups_questions_and_options(EE_Registration $registration, $EVT_ID = 0)
879
-    {
880
-        if (empty($EVT_ID)) {
881
-            throw new EE_Error(
882
-                esc_html__(
883
-                    'An error occurred. No EVT_ID is included.  Needed to know which question groups to retrieve.',
884
-                    'event_espresso'
885
-                )
886
-            );
887
-        }
888
-        $questions = [];
889
-        // get all question groups for event
890
-        $qgs = $this->get_question_groups_for_event($EVT_ID, $registration);
891
-        if (! empty($qgs)) {
892
-            foreach ($qgs as $qg) {
893
-                $qsts                                    = $qg->questions();
894
-                $questions[ $qg->ID() ]                  = $qg->model_field_array();
895
-                $questions[ $qg->ID() ]['QSG_questions'] = [];
896
-                foreach ($qsts as $qst) {
897
-                    if ($qst->is_system_question()) {
898
-                        continue;
899
-                    }
900
-                    $answer                                                                   =
901
-                        EEM_Answer::instance()->get_one(
902
-                            [
903
-                                [
904
-                                    'QST_ID' => $qst->ID(),
905
-                                    'REG_ID' => $registration->ID(),
906
-                                ],
907
-                            ]
908
-                        );
909
-                    $answer                                                                   =
910
-                        $answer instanceof EE_Answer
911
-                            ? $answer
912
-                            : EEM_Answer::instance()->create_default_object();
913
-                    $qst_name                                                                 = $qstn_id = $qst->ID();
914
-                    $ans_id                                                                   = $answer->ID();
915
-                    $qst_name                                                                 = ! empty($ans_id)
916
-                        ? '[' . $qst_name . '][' . $ans_id . ']'
917
-                        : '[' . $qst_name . ']';
918
-                    $input_name                                                               = '';
919
-                    $input_id                                                                 =
920
-                        sanitize_key($qst->display_text());
921
-                    $input_class                                                              = '';
922
-                    $questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]                    =
923
-                        $qst->model_field_array();
924
-                    $questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['QST_input_name']  = 'qstn'
925
-                                                                                                . $input_name
926
-                                                                                                . $qst_name;
927
-                    $questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['QST_input_id']    =
928
-                        $input_id . '-' . $qstn_id;
929
-                    $questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['QST_input_class'] = $input_class;
930
-                    $questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['QST_options']     = [];
931
-                    $questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['qst_obj']         = $qst;
932
-                    $questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['ans_obj']         = $answer;
933
-                    // leave responses as-is, don't convert stuff into html entities please!
934
-                    $questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['htmlentities'] = false;
935
-                    if ($qst->type() == 'RADIO_BTN' || $qst->type() == 'CHECKBOX' || $qst->type() == 'DROPDOWN') {
936
-                        $QSOs = $qst->options(true, $answer->value());
937
-                        if (is_array($QSOs)) {
938
-                            foreach ($QSOs as $QSO_ID => $QSO) {
939
-                                $questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['QST_options'][ $QSO_ID ] =
940
-                                    $QSO->model_field_array();
941
-                            }
942
-                        }
943
-                    }
944
-                }
945
-            }
946
-        }
947
-        return $questions;
948
-    }
949
-
950
-
951
-    /**
952
-     * @param mixed $cols_n_values either an array of where each key is the name of a field, and the value is its value
953
-     *                             or an stdClass where each property is the name of a column,
954
-     * @return EE_Base_Class
955
-     * @throws EE_Error
956
-     * @throws ReflectionException
957
-     */
958
-    public function instantiate_class_from_array_or_object($cols_n_values)
959
-    {
960
-        $classInstance = parent::instantiate_class_from_array_or_object($cols_n_values);
961
-        if ($classInstance instanceof EE_Event) {
962
-            // events have their timezone defined in the DB, so use it immediately
963
-            $this->set_timezone($classInstance->get_timezone());
964
-        }
965
-        return $classInstance;
966
-    }
21
+	/**
22
+	 * constant used by status(), indicating that no more tickets can be purchased for any of the datetimes for the
23
+	 * event
24
+	 */
25
+	const sold_out = 'sold_out';
26
+
27
+	/**
28
+	 * constant used by status(), indicating that upcoming event dates have been postponed (may be pushed to a later
29
+	 * date)
30
+	 */
31
+	const postponed = 'postponed';
32
+
33
+	/**
34
+	 * constant used by status(), indicating that the event will no longer occur
35
+	 */
36
+	const cancelled = 'cancelled';
37
+
38
+
39
+	protected static string $_default_reg_status;
40
+
41
+	protected static int $_default_additional_limit = 10;
42
+
43
+	protected static ?EEM_Event $_instance = null;
44
+
45
+
46
+	/**
47
+	 * Adds a relationship to Term_Taxonomy for each CPT_Base
48
+	 *
49
+	 * @param string|null $timezone
50
+	 * @throws EE_Error
51
+	 * @throws ReflectionException
52
+	 * @throws Exception
53
+	 */
54
+	protected function __construct(?string $timezone = '')
55
+	{
56
+		EE_Registry::instance()->load_model('Registration');
57
+		$this->singular_item = esc_html__('Event', 'event_espresso');
58
+		$this->plural_item   = esc_html__('Events', 'event_espresso');
59
+		// to remove Cancelled events from the frontend, copy the following filter to your functions.php file
60
+		// add_filter( 'AFEE__EEM_Event__construct___custom_stati__cancelled__Public', '__return_false' );
61
+		// to remove Postponed events from the frontend, copy the following filter to your functions.php file
62
+		// add_filter( 'AFEE__EEM_Event__construct___custom_stati__postponed__Public', '__return_false' );
63
+		// to remove Sold Out events from the frontend, copy the following filter to your functions.php file
64
+		//  add_filter( 'AFEE__EEM_Event__construct___custom_stati__sold_out__Public', '__return_false' );
65
+		$this->_custom_stati       = apply_filters(
66
+			'AFEE__EEM_Event__construct___custom_stati',
67
+			[
68
+				EEM_Event::cancelled => [
69
+					'label'  => esc_html__('Cancelled', 'event_espresso'),
70
+					'public' => apply_filters('AFEE__EEM_Event__construct___custom_stati__cancelled__Public', true),
71
+				],
72
+				EEM_Event::postponed => [
73
+					'label'  => esc_html__('Postponed', 'event_espresso'),
74
+					'public' => apply_filters('AFEE__EEM_Event__construct___custom_stati__postponed__Public', true),
75
+				],
76
+				EEM_Event::sold_out  => [
77
+					'label'  => esc_html__('Sold Out', 'event_espresso'),
78
+					'public' => apply_filters('AFEE__EEM_Event__construct___custom_stati__sold_out__Public', true),
79
+				],
80
+			]
81
+		);
82
+		self::$_default_reg_status = empty(self::$_default_reg_status)
83
+			? RegStatus::PENDING_PAYMENT
84
+			: self::$_default_reg_status;
85
+		$this->_tables             = [
86
+			'Event_CPT'  => new EE_Primary_Table('posts', 'ID'),
87
+			'Event_Meta' => new EE_Secondary_Table('esp_event_meta', 'EVTM_ID', 'EVT_ID'),
88
+		];
89
+		$this->_fields             = [
90
+			'Event_CPT'  => [
91
+				'EVT_ID'         => new EE_Primary_Key_Int_Field(
92
+					'ID',
93
+					esc_html__('Post ID for Event', 'event_espresso')
94
+				),
95
+				'EVT_name'       => new EE_Plain_Text_Field(
96
+					'post_title',
97
+					esc_html__('Event Name', 'event_espresso'),
98
+					false,
99
+					''
100
+				),
101
+				'EVT_desc'       => new EE_Post_Content_Field(
102
+					'post_content',
103
+					esc_html__('Event Description', 'event_espresso'),
104
+					false,
105
+					''
106
+				),
107
+				'EVT_slug'       => new EE_Slug_Field(
108
+					'post_name',
109
+					esc_html__('Event Slug', 'event_espresso'),
110
+					false,
111
+					''
112
+				),
113
+				'EVT_created'    => new EE_Datetime_Field(
114
+					'post_date',
115
+					esc_html__('Date/Time Event Created', 'event_espresso'),
116
+					false,
117
+					EE_Datetime_Field::now
118
+				),
119
+				'EVT_short_desc' => new EE_Simple_HTML_Field(
120
+					'post_excerpt',
121
+					esc_html__('Event Short Description', 'event_espresso'),
122
+					false,
123
+					''
124
+				),
125
+				'EVT_modified'   => new EE_Datetime_Field(
126
+					'post_modified',
127
+					esc_html__('Date/Time Event Modified', 'event_espresso'),
128
+					false,
129
+					EE_Datetime_Field::now
130
+				),
131
+				'EVT_wp_user'    => new EE_WP_User_Field(
132
+					'post_author',
133
+					esc_html__('Event Creator ID', 'event_espresso'),
134
+					false
135
+				),
136
+				'parent'         => new EE_Integer_Field(
137
+					'post_parent',
138
+					esc_html__('Event Parent ID', 'event_espresso'),
139
+					false,
140
+					0
141
+				),
142
+				'EVT_order'      => new EE_Integer_Field(
143
+					'menu_order',
144
+					esc_html__('Event Menu Order', 'event_espresso'),
145
+					false,
146
+					1
147
+				),
148
+				'post_type'      => new EE_WP_Post_Type_Field(EspressoPostType::EVENTS),
149
+				// EE_Plain_Text_Field( 'post_type', esc_html__( 'Event Post Type', 'event_espresso' ), FALSE, 'espresso_events' ),
150
+				'status'         => new EE_WP_Post_Status_Field(
151
+					'post_status',
152
+					esc_html__('Event Status', 'event_espresso'),
153
+					false,
154
+					'draft',
155
+					$this->_custom_stati
156
+				),
157
+				'password'       => new EE_Password_Field(
158
+					'post_password',
159
+					esc_html__('Password', 'event_espresso'),
160
+					false,
161
+					'',
162
+					[
163
+						'EVT_desc',
164
+						'EVT_short_desc',
165
+						'EVT_display_desc',
166
+						'EVT_display_ticket_selector',
167
+						'EVT_visible_on',
168
+						'EVT_additional_limit',
169
+						'EVT_default_registration_status',
170
+						'EVT_member_only',
171
+						'EVT_phone',
172
+						'EVT_allow_overflow',
173
+						'EVT_timezone_string',
174
+						'EVT_external_URL',
175
+						'EVT_donations',
176
+					]
177
+				),
178
+			],
179
+			'Event_Meta' => [
180
+				'EVTM_ID'                         => new EE_DB_Only_Float_Field(
181
+					'EVTM_ID',
182
+					esc_html__('Event Meta Row ID', 'event_espresso'),
183
+					false
184
+				),
185
+				'EVT_ID_fk'                       => new EE_DB_Only_Int_Field(
186
+					'EVT_ID',
187
+					esc_html__('Foreign key to Event ID from Event Meta table', 'event_espresso'),
188
+					false
189
+				),
190
+				'VNU_ID'                          => new EE_Foreign_Key_Int_Field(
191
+					'VNU_ID',
192
+					__('Venue ID', 'event_espresso'),
193
+					false,
194
+					0,
195
+					'Venue'
196
+				),
197
+				'EVT_display_desc'                => new EE_Boolean_Field(
198
+					'EVT_display_desc',
199
+					esc_html__('Display Description Flag', 'event_espresso'),
200
+					false,
201
+					true
202
+				),
203
+				'EVT_display_ticket_selector'     => new EE_Boolean_Field(
204
+					'EVT_display_ticket_selector',
205
+					esc_html__('Display Ticket Selector Flag', 'event_espresso'),
206
+					false,
207
+					true
208
+				),
209
+				'EVT_visible_on'                  => new EE_Datetime_Field(
210
+					'EVT_visible_on',
211
+					esc_html__('Event Visible Date', 'event_espresso'),
212
+					true,
213
+					EE_Datetime_Field::now
214
+				),
215
+				'EVT_additional_limit'            => new EE_Integer_Field(
216
+					'EVT_additional_limit',
217
+					esc_html__('Limit of Additional Registrations on Same Transaction', 'event_espresso'),
218
+					false,
219
+					self::$_default_additional_limit
220
+				),
221
+				'EVT_default_registration_status' => new EE_Enum_Text_Field(
222
+					'EVT_default_registration_status',
223
+					esc_html__('Default Registration Status on this Event', 'event_espresso'),
224
+					false,
225
+					EEM_Event::$_default_reg_status,
226
+					EEM_Registration::reg_status_array()
227
+				),
228
+				'EVT_member_only'                 => new EE_Boolean_Field(
229
+					'EVT_member_only',
230
+					esc_html__('Member-Only Event Flag', 'event_espresso'),
231
+					false,
232
+					false
233
+				),
234
+				'EVT_phone'                       => new EE_Plain_Text_Field(
235
+					'EVT_phone',
236
+					esc_html__('Event Phone Number', 'event_espresso'),
237
+					false,
238
+					''
239
+				),
240
+				'EVT_allow_overflow'              => new EE_Boolean_Field(
241
+					'EVT_allow_overflow',
242
+					esc_html__('Allow Overflow on Event', 'event_espresso'),
243
+					false,
244
+					false
245
+				),
246
+				'EVT_timezone_string'             => new EE_Plain_Text_Field(
247
+					'EVT_timezone_string',
248
+					esc_html__('Timezone (name) for Event times', 'event_espresso'),
249
+					false,
250
+					''
251
+				),
252
+				'EVT_external_URL'                => new EE_Plain_Text_Field(
253
+					'EVT_external_URL',
254
+					esc_html__('URL of Event Page if hosted elsewhere', 'event_espresso'),
255
+					true
256
+				),
257
+				'EVT_donations'                   => new EE_Boolean_Field(
258
+					'EVT_donations',
259
+					esc_html__('Accept Donations?', 'event_espresso'),
260
+					false,
261
+					false
262
+				),
263
+				'FSC_UUID'                        => new EE_Foreign_Key_String_Field(
264
+					'FSC_UUID',
265
+					esc_html__('Registration Form UUID (universally unique identifier)', 'event_espresso'),
266
+					true,
267
+					null,
268
+					'Form_Section',
269
+					false
270
+				),
271
+			],
272
+		];
273
+		$this->_model_relations    = [
274
+			'Attendee'               => new EE_HABTM_Relation('Registration'),
275
+			'Datetime'               => new EE_Has_Many_Relation(),
276
+			'Event_Question_Group'   => new EE_Has_Many_Relation(),
277
+			'Form_Section'           => new EE_Belongs_To_Relation(),
278
+			'Message_Template_Group' => new EE_HABTM_Relation('Event_Message_Template'),
279
+			'Question_Group'         => new EE_HABTM_Relation('Event_Question_Group'),
280
+			'Registration'           => new EE_Has_Many_Relation(),
281
+			'Term_Relationship'      => new EE_Has_Many_Relation(),
282
+			'Term_Taxonomy'          => new EE_HABTM_Relation('Term_Relationship'),
283
+			'Venue'                  => new EE_Belongs_To_Relation(),
284
+			'WP_User'                => new EE_Belongs_To_Relation(),
285
+		];
286
+		// this model is generally available for reading
287
+		$this->_cap_restriction_generators[ EEM_Base::caps_read ] = new EE_Restriction_Generator_Public();
288
+		$this->model_chain_to_password                            = '';
289
+		parent::__construct($timezone);
290
+	}
291
+
292
+
293
+	/**
294
+	 * @param string $default_reg_status
295
+	 * @throws EE_Error
296
+	 * @throws ReflectionException
297
+	 */
298
+	public static function set_default_reg_status(string $default_reg_status)
299
+	{
300
+		self::$_default_reg_status = $default_reg_status;
301
+		// if EEM_Event has already been instantiated,
302
+		// then we need to reset the `EVT_default_reg_status` field to use the new default.
303
+		if (self::$_instance instanceof EEM_Event) {
304
+			$default_reg_status = new EE_Enum_Text_Field(
305
+				'EVT_default_registration_status',
306
+				esc_html__('Default Registration Status on this Event', 'event_espresso'),
307
+				false,
308
+				$default_reg_status,
309
+				EEM_Registration::reg_status_array()
310
+			);
311
+			$default_reg_status->_construct_finalize(
312
+				'Event_Meta',
313
+				'EVT_default_registration_status',
314
+				'EEM_Event'
315
+			);
316
+			self::$_instance->_fields['Event_Meta']['EVT_default_registration_status'] = $default_reg_status;
317
+		}
318
+	}
319
+
320
+
321
+	/**
322
+	 * Used to override the default for the additional limit field.
323
+	 *
324
+	 * @param int $additional_limit
325
+	 */
326
+	public static function set_default_additional_limit(int $additional_limit)
327
+	{
328
+		self::$_default_additional_limit = $additional_limit;
329
+		if (self::$_instance instanceof EEM_Event) {
330
+			self::$_instance->_fields['Event_Meta']['EVT_additional_limit'] = new EE_Integer_Field(
331
+				'EVT_additional_limit',
332
+				esc_html__('Limit of Additional Registrations on Same Transaction', 'event_espresso'),
333
+				false,
334
+				self::$_default_additional_limit
335
+			);
336
+			self::$_instance->_fields['Event_Meta']['EVT_additional_limit']->_construct_finalize(
337
+				'Event_Meta',
338
+				'EVT_additional_limit',
339
+				'EEM_Event'
340
+			);
341
+		}
342
+	}
343
+
344
+
345
+	/**
346
+	 * Return what is currently set as the default additional limit for the event.
347
+	 *
348
+	 * @return int
349
+	 */
350
+	public static function get_default_additional_limit(): int
351
+	{
352
+		return apply_filters('FHEE__EEM_Event__get_default_additional_limit', self::$_default_additional_limit);
353
+	}
354
+
355
+
356
+	/**
357
+	 * get_question_groups
358
+	 *
359
+	 * @return array
360
+	 * @throws EE_Error
361
+	 * @throws ReflectionException
362
+	 */
363
+	public function get_all_question_groups()
364
+	{
365
+		return EE_Registry::instance()->load_model('Question_Group')->get_all(
366
+			[
367
+				['QSG_deleted' => false],
368
+				'order_by' => ['QSG_order' => 'ASC'],
369
+			]
370
+		);
371
+	}
372
+
373
+
374
+	/**
375
+	 * get_question_groups
376
+	 *
377
+	 * @param int $EVT_ID
378
+	 * @return array|bool
379
+	 * @throws EE_Error
380
+	 * @throws ReflectionException
381
+	 */
382
+	public function get_all_event_question_groups(int $EVT_ID = 0)
383
+	{
384
+		if (! isset($EVT_ID) || ! absint($EVT_ID)) {
385
+			EE_Error::add_error(
386
+				esc_html__(
387
+					'An error occurred. No Event Question Groups could be retrieved because an Event ID was not received.',
388
+					'event_espresso'
389
+				),
390
+				__FILE__,
391
+				__FUNCTION__,
392
+				__LINE__
393
+			);
394
+			return false;
395
+		}
396
+		return EE_Registry::instance()->load_model('Event_Question_Group')->get_all(
397
+			[
398
+				['EVT_ID' => $EVT_ID],
399
+			]
400
+		);
401
+	}
402
+
403
+
404
+	/**
405
+	 * get_question_groups
406
+	 *
407
+	 * @param int     $EVT_ID
408
+	 * @param boolean $for_primary_attendee
409
+	 * @return array|bool
410
+	 * @throws EE_Error
411
+	 * @throws InvalidArgumentException
412
+	 * @throws ReflectionException
413
+	 * @throws InvalidDataTypeException
414
+	 * @throws InvalidInterfaceException
415
+	 */
416
+	public function get_event_question_groups(int $EVT_ID = 0, bool $for_primary_attendee = true)
417
+	{
418
+		if (! isset($EVT_ID) || ! absint($EVT_ID)) {
419
+			EE_Error::add_error(
420
+				esc_html__(
421
+				// @codingStandardsIgnoreStart
422
+					'An error occurred. No Event Question Groups could be retrieved because an Event ID was not received.',
423
+					// @codingStandardsIgnoreEnd
424
+					'event_espresso'
425
+				),
426
+				__FILE__,
427
+				__FUNCTION__,
428
+				__LINE__
429
+			);
430
+			return false;
431
+		}
432
+		$query_params = [
433
+			[
434
+				'EVT_ID'                                                                         => $EVT_ID,
435
+				EEM_Event_Question_Group::instance()->fieldNameForContext($for_primary_attendee) => true,
436
+			],
437
+		];
438
+		if ($for_primary_attendee) {
439
+			$query_params[0]['EQG_primary'] = true;
440
+		} else {
441
+			$query_params[0]['EQG_additional'] = true;
442
+		}
443
+		return EE_Registry::instance()->load_model('Event_Question_Group')->get_all($query_params);
444
+	}
445
+
446
+
447
+	/**
448
+	 * get_question_groups
449
+	 *
450
+	 * @param int             $EVT_ID
451
+	 * @param EE_Registration $registration
452
+	 * @return array|bool
453
+	 * @throws EE_Error
454
+	 * @throws InvalidArgumentException
455
+	 * @throws InvalidDataTypeException
456
+	 * @throws InvalidInterfaceException
457
+	 * @throws ReflectionException
458
+	 */
459
+	public function get_question_groups_for_event(int $EVT_ID, EE_Registration $registration)
460
+	{
461
+		if (! absint($EVT_ID)) {
462
+			EE_Error::add_error(
463
+				esc_html__(
464
+					'An error occurred. No Question Groups could be retrieved because an Event ID was not received.',
465
+					'event_espresso'
466
+				),
467
+				__FILE__,
468
+				__FUNCTION__,
469
+				__LINE__
470
+			);
471
+			return false;
472
+		}
473
+		return EE_Registry::instance()->load_model('Question_Group')->get_all(
474
+			[
475
+				[
476
+					'Event_Question_Group.EVT_ID' => $EVT_ID,
477
+					'Event_Question_Group.'
478
+					. EEM_Event_Question_Group::instance()->fieldNameForContext(
479
+						$registration->is_primary_registrant()
480
+					)                             => true,
481
+				],
482
+				'order_by' => ['QSG_order' => 'ASC'],
483
+			]
484
+		);
485
+	}
486
+
487
+
488
+	/**
489
+	 * get_question_target_db_column
490
+	 *
491
+	 * @param string $QSG_IDs csv list of $QSG IDs
492
+	 * @return array|bool
493
+	 * @throws EE_Error
494
+	 * @throws ReflectionException
495
+	 */
496
+	public function get_questions_in_groups(string $QSG_IDs = '')
497
+	{
498
+		if (empty($QSG_IDs)) {
499
+			EE_Error::add_error(
500
+				esc_html__('An error occurred. No Question Group IDs were received.', 'event_espresso'),
501
+				__FILE__,
502
+				__FUNCTION__,
503
+				__LINE__
504
+			);
505
+			return false;
506
+		}
507
+		return EE_Registry::instance()->load_model('Question')->get_all(
508
+			[
509
+				[
510
+					'Question_Group.QSG_ID' => ['IN', $QSG_IDs],
511
+					'QST_deleted'           => false,
512
+					'QST_admin_only'        => is_admin(),
513
+				],
514
+				'order_by' => 'QST_order',
515
+			]
516
+		);
517
+	}
518
+
519
+
520
+	/**
521
+	 * get_options_for_question
522
+	 *
523
+	 * @param string $QST_IDs csv list of $QST IDs
524
+	 * @return array|bool
525
+	 * @throws EE_Error
526
+	 * @throws ReflectionException
527
+	 */
528
+	public function get_options_for_question(string $QST_IDs)
529
+	{
530
+		if (empty($QST_IDs)) {
531
+			EE_Error::add_error(
532
+				esc_html__('An error occurred. No Question IDs were received.', 'event_espresso'),
533
+				__FILE__,
534
+				__FUNCTION__,
535
+				__LINE__
536
+			);
537
+			return false;
538
+		}
539
+		return EE_Registry::instance()->load_model('Question_Option')->get_all(
540
+			[
541
+				[
542
+					'Question.QST_ID' => ['IN', $QST_IDs],
543
+					'QSO_deleted'     => false,
544
+				],
545
+				'order_by' => 'QSO_ID',
546
+			]
547
+		);
548
+	}
549
+
550
+
551
+	/**
552
+	 * Gets all events that are published
553
+	 * and have event start time earlier than now and an event end time later than now
554
+	 *
555
+	 * @param array $query_params  An array of query params to further filter on
556
+	 *                             (note that status and DTT_EVT_start and DTT_EVT_end will be overridden)
557
+	 * @param bool  $count         whether to return the count or not (default FALSE)
558
+	 * @return EE_Event[]|int
559
+	 * @throws EE_Error
560
+	 * @throws ReflectionException
561
+	 */
562
+	public function get_active_events(array $query_params, bool $count = false)
563
+	{
564
+		if (array_key_exists(0, $query_params)) {
565
+			$where_params = $query_params[0];
566
+			unset($query_params[0]);
567
+		} else {
568
+			$where_params = [];
569
+		}
570
+		// if we have count make sure we don't include group by
571
+		if ($count && isset($query_params['group_by'])) {
572
+			unset($query_params['group_by']);
573
+		}
574
+		// add status query
575
+		$where_params = $this->set_where_conditions_for_status($where_params);
576
+		// if already have where params for DTT_EVT_start or DTT_EVT_end then append these conditions
577
+		if (isset($where_params['Datetime.DTT_EVT_start'])) {
578
+			$where_params['Datetime.DTT_EVT_start******'] = [
579
+				'<',
580
+				EEM_Datetime::instance()->current_time_for_query('DTT_EVT_start'),
581
+			];
582
+		} else {
583
+			$where_params['Datetime.DTT_EVT_start'] = [
584
+				'<',
585
+				EEM_Datetime::instance()->current_time_for_query('DTT_EVT_start'),
586
+			];
587
+		}
588
+		$where_params = $this->set_where_conditions_for_end_datetime($where_params);
589
+		return $this->_get_count_or_all($query_params, $where_params, $count);
590
+	}
591
+
592
+
593
+	/**
594
+	 * get all events that are published and have an event start time later than now
595
+	 *
596
+	 * @param array $query_params  An array of query params to further filter on
597
+	 *                             (Note that status and DTT_EVT_start will be overridden)
598
+	 * @param bool  $count         whether to return the count or not (default FALSE)
599
+	 * @return EE_Event[]|int
600
+	 * @throws EE_Error
601
+	 * @throws ReflectionException
602
+	 */
603
+	public function get_upcoming_events(array $query_params, bool $count = false)
604
+	{
605
+		if (array_key_exists(0, $query_params)) {
606
+			$where_params = $query_params[0];
607
+			unset($query_params[0]);
608
+		} else {
609
+			$where_params = [];
610
+		}
611
+		// if we have count make sure we don't include group by
612
+		if ($count && isset($query_params['group_by'])) {
613
+			unset($query_params['group_by']);
614
+		}
615
+		// add status query
616
+		$where_params = $this->set_where_conditions_for_status($where_params);
617
+		// if there are already query_params matching DTT_EVT_start then we need to modify that to add them.
618
+		if (isset($where_params['Datetime.DTT_EVT_start'])) {
619
+			$where_params['Datetime.DTT_EVT_start*****'] = [
620
+				'>',
621
+				EEM_Datetime::instance()->current_time_for_query('DTT_EVT_start'),
622
+			];
623
+		} else {
624
+			$where_params['Datetime.DTT_EVT_start'] = [
625
+				'>',
626
+				EEM_Datetime::instance()->current_time_for_query('DTT_EVT_start'),
627
+			];
628
+		}
629
+		return $this->_get_count_or_all($query_params, $where_params, $count);
630
+	}
631
+
632
+
633
+	/**
634
+	 * Gets all events that are published
635
+	 * and have an event end time later than now
636
+	 *
637
+	 * @param array $query_params  An array of query params to further filter on
638
+	 *                             (note that status and DTT_EVT_end will be overridden)
639
+	 * @param bool  $count         whether to return the count or not (default FALSE)
640
+	 * @return EE_Event[]|int
641
+	 * @throws EE_Error
642
+	 * @throws ReflectionException
643
+	 */
644
+	public function get_active_and_upcoming_events(array $query_params, bool $count = false)
645
+	{
646
+		if (array_key_exists(0, $query_params)) {
647
+			$where_params = $query_params[0];
648
+			unset($query_params[0]);
649
+		} else {
650
+			$where_params = [];
651
+		}
652
+		// if we have count make sure we don't include group by
653
+		if ($count && isset($query_params['group_by'])) {
654
+			unset($query_params['group_by']);
655
+		}
656
+		// add status query
657
+		$where_params = $this->set_where_conditions_for_status($where_params);
658
+		// add where params for DTT_EVT_end
659
+		$where_params = $this->set_where_conditions_for_end_datetime($where_params);
660
+		return $this->_get_count_or_all($query_params, $where_params, $count);
661
+	}
662
+
663
+
664
+	/**
665
+	 * This only returns events that are expired.
666
+	 * They may still be published but all their datetimes have expired.
667
+	 *
668
+	 * @param array $query_params  An array of query params to further filter on
669
+	 *                             (note that status and DTT_EVT_end will be overridden)
670
+	 * @param bool  $count         whether to return the count or not (default FALSE)
671
+	 * @return EE_Event[]|int
672
+	 * @throws EE_Error
673
+	 * @throws ReflectionException
674
+	 */
675
+	public function get_expired_events(array $query_params, bool $count = false)
676
+	{
677
+		$where_params = $query_params[0] ?? [];
678
+		// if we have count make sure we don't include group by
679
+		if ($count && isset($query_params['group_by'])) {
680
+			unset($query_params['group_by']);
681
+		}
682
+		// let's add specific query_params for active_events
683
+		// keep in mind this will override any sent status in the query AND any date queries.
684
+		if (isset($where_params['status'])) {
685
+			unset($where_params['status']);
686
+		}
687
+		// first get all events that have datetimes where it's not expired.
688
+		$event_ids = $this->get_all_not_expired_event_ids($query_params);
689
+		// if we have any additional query_params, let's add them to the 'AND' condition
690
+		$and_condition = [
691
+			'Datetime.DTT_EVT_end' => ['<', EEM_Datetime::instance()->current_time_for_query('DTT_EVT_end')],
692
+			'EVT_ID'               => ['NOT IN', $event_ids],
693
+		];
694
+		if (isset($where_params['OR'])) {
695
+			$and_condition['OR'] = $where_params['OR'];
696
+			unset($where_params['OR']);
697
+		}
698
+		if (isset($where_params['Datetime.DTT_EVT_end'])) {
699
+			$and_condition['Datetime.DTT_EVT_end****'] = $where_params['Datetime.DTT_EVT_end'];
700
+			unset($where_params['Datetime.DTT_EVT_end']);
701
+		}
702
+		if (isset($where_params['Datetime.DTT_EVT_start'])) {
703
+			$and_condition['Datetime.DTT_EVT_start'] = $where_params['Datetime.DTT_EVT_start'];
704
+			unset($where_params['Datetime.DTT_EVT_start']);
705
+		}
706
+		// merge remaining $where params with the and conditions.
707
+		$where_params['AND'] = array_merge($and_condition, $where_params);
708
+		return $this->_get_count_or_all($query_params, $where_params, $count);
709
+	}
710
+
711
+
712
+	/**
713
+	 * @param array $query_params
714
+	 * @return int[]
715
+	 * @throws EE_Error
716
+	 * @throws ReflectionException
717
+	 */
718
+	public function get_all_not_expired_event_ids(array $query_params = []): array
719
+	{
720
+		$query_params[0] = [
721
+			'Datetime.DTT_EVT_end' => [
722
+				'>',
723
+				EEM_Datetime::instance()->current_time_for_query('DTT_EVT_end'),
724
+			],
725
+		];
726
+		$event_ids       = $this->_get_all_wpdb_results($query_params, OBJECT_K, 'Event_CPT.ID');
727
+		return array_keys($event_ids);
728
+	}
729
+
730
+
731
+	/**
732
+	 * This basically just returns the events that do not have the publish status.
733
+	 *
734
+	 * @param array   $query_params  An array of query params to further filter on
735
+	 *                               (note that status will be overwritten)
736
+	 * @param boolean $count         whether to return the count or not (default FALSE)
737
+	 * @return EE_Event[]|int
738
+	 * @throws EE_Error
739
+	 * @throws ReflectionException
740
+	 */
741
+	public function get_inactive_events(array $query_params, bool $count = false)
742
+	{
743
+		$where_params = $query_params[0] ?? [];
744
+		// let's add in specific query_params for inactive events.
745
+		if (isset($where_params['status'])) {
746
+			unset($where_params['status']);
747
+		}
748
+		// if we have count make sure we don't include group by
749
+		if ($count && isset($query_params['group_by'])) {
750
+			unset($query_params['group_by']);
751
+		}
752
+		// if we have any additional query_params, let's add them to the 'AND' condition
753
+		$where_params['AND']['status'] = ['!=', 'publish'];
754
+		if (isset($where_params['OR'])) {
755
+			$where_params['AND']['OR'] = $where_params['OR'];
756
+			unset($where_params['OR']);
757
+		}
758
+		if (isset($where_params['Datetime.DTT_EVT_end'])) {
759
+			$where_params['AND']['Datetime.DTT_EVT_end****'] = $where_params['Datetime.DTT_EVT_end'];
760
+			unset($where_params['Datetime.DTT_EVT_end']);
761
+		}
762
+		if (isset($where_params['Datetime.DTT_EVT_start'])) {
763
+			$where_params['AND']['Datetime.DTT_EVT_start'] = $where_params['Datetime.DTT_EVT_start'];
764
+			unset($where_params['Datetime.DTT_EVT_start']);
765
+		}
766
+		return $this->_get_count_or_all($query_params, $where_params, $count);
767
+	}
768
+
769
+
770
+	/**
771
+	 * This is just injecting into the parent add_relationship_to so we do special handling on price relationships
772
+	 * because we don't want to override any existing global default prices but instead insert NEW prices that get
773
+	 * attached to the event. See parent for param descriptions
774
+	 *
775
+	 * @param int|string|EE_Base_Class $id_or_obj
776
+	 * @param int|string|EE_Base_Class $other_model_id_or_obj
777
+	 * @param string $relationName
778
+	 * @param array  $where_query
779
+	 * @return EE_Base_Class
780
+	 * @throws EE_Error
781
+	 * @throws ReflectionException
782
+	 */
783
+	public function add_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query = []): EE_Base_Class
784
+	{
785
+		if ($relationName === 'Price') {
786
+			// let's get the PRC object for the given ID to make sure that we aren't dealing with a default
787
+			$prc_chk = $this->get_related_model_obj($relationName)->ensure_is_obj($other_model_id_or_obj);
788
+			// if EVT_ID = 0, then this is a default
789
+			if ((int) $prc_chk->get('EVT_ID') === 0) {
790
+				// let's set the prc_id as 0 so we force an insert on the add_relation_to carried out by relation
791
+				$prc_chk->set('PRC_ID', 0);
792
+			}
793
+			// run parent
794
+			return parent::add_relationship_to($id_or_obj, $prc_chk, $relationName, $where_query);
795
+		}
796
+		// otherwise carry on as normal
797
+		return parent::add_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query);
798
+	}
799
+
800
+
801
+	/**
802
+	 * @param array $where_params
803
+	 * @return array
804
+	 */
805
+	public function set_where_conditions_for_status(array $where_params): array
806
+	{
807
+		// let's add specific query_params for active_events
808
+		// keep in mind this will override any sent status in the query AND any date queries.
809
+		// we need to pull events with a status of 'publish' and 'sold_out'
810
+		$event_status = ['publish', EEM_Event::sold_out];
811
+		// check if the user can read private events and if so add the 'private status to the where params'
812
+		if (EE_Registry::instance()->CAP->current_user_can('ee_read_private_events', 'get_upcoming_events')) {
813
+			$event_status[] = 'private';
814
+		}
815
+		$where_params['status'] = ['IN', $event_status];
816
+		return $where_params;
817
+	}
818
+
819
+
820
+	/**
821
+	 * @param array $where_params
822
+	 * @return array
823
+	 * @throws EE_Error
824
+	 * @throws ReflectionException
825
+	 */
826
+	public function set_where_conditions_for_end_datetime(array $where_params): array
827
+	{
828
+		$end_date_field_name = isset($where_params['Datetime.DTT_EVT_end'])
829
+			? 'Datetime.DTT_EVT_end*****'
830
+			// prevents overwrite of existing where condition
831
+			: 'Datetime.DTT_EVT_end';
832
+
833
+		$where_params[ $end_date_field_name ] = [
834
+			'>',
835
+			EEM_Datetime::instance()->current_time_for_query('DTT_EVT_end'),
836
+		];
837
+
838
+		return $where_params;
839
+	}
840
+
841
+
842
+	/**
843
+	 * @param array $query_params
844
+	 * @param array $where_params
845
+	 * @param bool  $count
846
+	 * @return EE_Event[]|int
847
+	 * @throws EE_Error
848
+	 * @throws ReflectionException
849
+	 */
850
+	protected function _get_count_or_all(array $query_params, array $where_params, bool $count = false)
851
+	{
852
+		$query_params[0] = $where_params;
853
+		// don't use $query_params with count()
854
+		// because we don't want to include additional query clauses like "GROUP BY"
855
+		return $count
856
+			? $this->count([$where_params], 'EVT_ID', true)
857
+			: $this->get_all($query_params);
858
+	}
859
+
860
+
861
+	/******************** DEPRECATED METHODS ********************/
862
+
863
+
864
+	/**
865
+	 * _get_question_target_db_column
866
+	 *
867
+	 * @param EE_Registration $registration    (so existing answers for registration are included)
868
+	 * @param int             $EVT_ID          so all question groups are included for event (not just answers from
869
+	 *                                         registration).
870
+	 * @return    array
871
+	 * @throws ReflectionException
872
+	 * @throws EE_Error
873
+	 * @deprecated as of 4.8.32.rc.001. Instead consider using
874
+	 *                                         EE_Registration_Custom_Questions_Form located in
875
+	 *                                         admin_pages/registrations/form_sections/EE_Registration_Custom_Questions_Form.form.php
876
+	 * @access     public
877
+	 */
878
+	public function assemble_array_of_groups_questions_and_options(EE_Registration $registration, $EVT_ID = 0)
879
+	{
880
+		if (empty($EVT_ID)) {
881
+			throw new EE_Error(
882
+				esc_html__(
883
+					'An error occurred. No EVT_ID is included.  Needed to know which question groups to retrieve.',
884
+					'event_espresso'
885
+				)
886
+			);
887
+		}
888
+		$questions = [];
889
+		// get all question groups for event
890
+		$qgs = $this->get_question_groups_for_event($EVT_ID, $registration);
891
+		if (! empty($qgs)) {
892
+			foreach ($qgs as $qg) {
893
+				$qsts                                    = $qg->questions();
894
+				$questions[ $qg->ID() ]                  = $qg->model_field_array();
895
+				$questions[ $qg->ID() ]['QSG_questions'] = [];
896
+				foreach ($qsts as $qst) {
897
+					if ($qst->is_system_question()) {
898
+						continue;
899
+					}
900
+					$answer                                                                   =
901
+						EEM_Answer::instance()->get_one(
902
+							[
903
+								[
904
+									'QST_ID' => $qst->ID(),
905
+									'REG_ID' => $registration->ID(),
906
+								],
907
+							]
908
+						);
909
+					$answer                                                                   =
910
+						$answer instanceof EE_Answer
911
+							? $answer
912
+							: EEM_Answer::instance()->create_default_object();
913
+					$qst_name                                                                 = $qstn_id = $qst->ID();
914
+					$ans_id                                                                   = $answer->ID();
915
+					$qst_name                                                                 = ! empty($ans_id)
916
+						? '[' . $qst_name . '][' . $ans_id . ']'
917
+						: '[' . $qst_name . ']';
918
+					$input_name                                                               = '';
919
+					$input_id                                                                 =
920
+						sanitize_key($qst->display_text());
921
+					$input_class                                                              = '';
922
+					$questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]                    =
923
+						$qst->model_field_array();
924
+					$questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['QST_input_name']  = 'qstn'
925
+																								. $input_name
926
+																								. $qst_name;
927
+					$questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['QST_input_id']    =
928
+						$input_id . '-' . $qstn_id;
929
+					$questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['QST_input_class'] = $input_class;
930
+					$questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['QST_options']     = [];
931
+					$questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['qst_obj']         = $qst;
932
+					$questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['ans_obj']         = $answer;
933
+					// leave responses as-is, don't convert stuff into html entities please!
934
+					$questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['htmlentities'] = false;
935
+					if ($qst->type() == 'RADIO_BTN' || $qst->type() == 'CHECKBOX' || $qst->type() == 'DROPDOWN') {
936
+						$QSOs = $qst->options(true, $answer->value());
937
+						if (is_array($QSOs)) {
938
+							foreach ($QSOs as $QSO_ID => $QSO) {
939
+								$questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['QST_options'][ $QSO_ID ] =
940
+									$QSO->model_field_array();
941
+							}
942
+						}
943
+					}
944
+				}
945
+			}
946
+		}
947
+		return $questions;
948
+	}
949
+
950
+
951
+	/**
952
+	 * @param mixed $cols_n_values either an array of where each key is the name of a field, and the value is its value
953
+	 *                             or an stdClass where each property is the name of a column,
954
+	 * @return EE_Base_Class
955
+	 * @throws EE_Error
956
+	 * @throws ReflectionException
957
+	 */
958
+	public function instantiate_class_from_array_or_object($cols_n_values)
959
+	{
960
+		$classInstance = parent::instantiate_class_from_array_or_object($cols_n_values);
961
+		if ($classInstance instanceof EE_Event) {
962
+			// events have their timezone defined in the DB, so use it immediately
963
+			$this->set_timezone($classInstance->get_timezone());
964
+		}
965
+		return $classInstance;
966
+	}
967 967
 }
Please login to merge, or discard this patch.
Spacing   +26 added lines, -26 removed lines patch added patch discarded remove patch
@@ -62,7 +62,7 @@  discard block
 block discarded – undo
62 62
         // add_filter( 'AFEE__EEM_Event__construct___custom_stati__postponed__Public', '__return_false' );
63 63
         // to remove Sold Out events from the frontend, copy the following filter to your functions.php file
64 64
         //  add_filter( 'AFEE__EEM_Event__construct___custom_stati__sold_out__Public', '__return_false' );
65
-        $this->_custom_stati       = apply_filters(
65
+        $this->_custom_stati = apply_filters(
66 66
             'AFEE__EEM_Event__construct___custom_stati',
67 67
             [
68 68
                 EEM_Event::cancelled => [
@@ -86,7 +86,7 @@  discard block
 block discarded – undo
86 86
             'Event_CPT'  => new EE_Primary_Table('posts', 'ID'),
87 87
             'Event_Meta' => new EE_Secondary_Table('esp_event_meta', 'EVTM_ID', 'EVT_ID'),
88 88
         ];
89
-        $this->_fields             = [
89
+        $this->_fields = [
90 90
             'Event_CPT'  => [
91 91
                 'EVT_ID'         => new EE_Primary_Key_Int_Field(
92 92
                     'ID',
@@ -270,7 +270,7 @@  discard block
 block discarded – undo
270 270
                 ),
271 271
             ],
272 272
         ];
273
-        $this->_model_relations    = [
273
+        $this->_model_relations = [
274 274
             'Attendee'               => new EE_HABTM_Relation('Registration'),
275 275
             'Datetime'               => new EE_Has_Many_Relation(),
276 276
             'Event_Question_Group'   => new EE_Has_Many_Relation(),
@@ -284,7 +284,7 @@  discard block
 block discarded – undo
284 284
             'WP_User'                => new EE_Belongs_To_Relation(),
285 285
         ];
286 286
         // this model is generally available for reading
287
-        $this->_cap_restriction_generators[ EEM_Base::caps_read ] = new EE_Restriction_Generator_Public();
287
+        $this->_cap_restriction_generators[EEM_Base::caps_read] = new EE_Restriction_Generator_Public();
288 288
         $this->model_chain_to_password                            = '';
289 289
         parent::__construct($timezone);
290 290
     }
@@ -381,7 +381,7 @@  discard block
 block discarded – undo
381 381
      */
382 382
     public function get_all_event_question_groups(int $EVT_ID = 0)
383 383
     {
384
-        if (! isset($EVT_ID) || ! absint($EVT_ID)) {
384
+        if ( ! isset($EVT_ID) || ! absint($EVT_ID)) {
385 385
             EE_Error::add_error(
386 386
                 esc_html__(
387 387
                     'An error occurred. No Event Question Groups could be retrieved because an Event ID was not received.',
@@ -415,7 +415,7 @@  discard block
 block discarded – undo
415 415
      */
416 416
     public function get_event_question_groups(int $EVT_ID = 0, bool $for_primary_attendee = true)
417 417
     {
418
-        if (! isset($EVT_ID) || ! absint($EVT_ID)) {
418
+        if ( ! isset($EVT_ID) || ! absint($EVT_ID)) {
419 419
             EE_Error::add_error(
420 420
                 esc_html__(
421 421
                 // @codingStandardsIgnoreStart
@@ -458,7 +458,7 @@  discard block
 block discarded – undo
458 458
      */
459 459
     public function get_question_groups_for_event(int $EVT_ID, EE_Registration $registration)
460 460
     {
461
-        if (! absint($EVT_ID)) {
461
+        if ( ! absint($EVT_ID)) {
462 462
             EE_Error::add_error(
463 463
                 esc_html__(
464 464
                     'An error occurred. No Question Groups could be retrieved because an Event ID was not received.',
@@ -723,7 +723,7 @@  discard block
 block discarded – undo
723 723
                 EEM_Datetime::instance()->current_time_for_query('DTT_EVT_end'),
724 724
             ],
725 725
         ];
726
-        $event_ids       = $this->_get_all_wpdb_results($query_params, OBJECT_K, 'Event_CPT.ID');
726
+        $event_ids = $this->_get_all_wpdb_results($query_params, OBJECT_K, 'Event_CPT.ID');
727 727
         return array_keys($event_ids);
728 728
     }
729 729
 
@@ -830,7 +830,7 @@  discard block
 block discarded – undo
830 830
             // prevents overwrite of existing where condition
831 831
             : 'Datetime.DTT_EVT_end';
832 832
 
833
-        $where_params[ $end_date_field_name ] = [
833
+        $where_params[$end_date_field_name] = [
834 834
             '>',
835 835
             EEM_Datetime::instance()->current_time_for_query('DTT_EVT_end'),
836 836
         ];
@@ -888,16 +888,16 @@  discard block
 block discarded – undo
888 888
         $questions = [];
889 889
         // get all question groups for event
890 890
         $qgs = $this->get_question_groups_for_event($EVT_ID, $registration);
891
-        if (! empty($qgs)) {
891
+        if ( ! empty($qgs)) {
892 892
             foreach ($qgs as $qg) {
893 893
                 $qsts                                    = $qg->questions();
894
-                $questions[ $qg->ID() ]                  = $qg->model_field_array();
895
-                $questions[ $qg->ID() ]['QSG_questions'] = [];
894
+                $questions[$qg->ID()]                  = $qg->model_field_array();
895
+                $questions[$qg->ID()]['QSG_questions'] = [];
896 896
                 foreach ($qsts as $qst) {
897 897
                     if ($qst->is_system_question()) {
898 898
                         continue;
899 899
                     }
900
-                    $answer                                                                   =
900
+                    $answer =
901 901
                         EEM_Answer::instance()->get_one(
902 902
                             [
903 903
                                 [
@@ -906,37 +906,37 @@  discard block
 block discarded – undo
906 906
                                 ],
907 907
                             ]
908 908
                         );
909
-                    $answer                                                                   =
909
+                    $answer =
910 910
                         $answer instanceof EE_Answer
911 911
                             ? $answer
912 912
                             : EEM_Answer::instance()->create_default_object();
913 913
                     $qst_name                                                                 = $qstn_id = $qst->ID();
914 914
                     $ans_id                                                                   = $answer->ID();
915 915
                     $qst_name                                                                 = ! empty($ans_id)
916
-                        ? '[' . $qst_name . '][' . $ans_id . ']'
917
-                        : '[' . $qst_name . ']';
916
+                        ? '['.$qst_name.']['.$ans_id.']'
917
+                        : '['.$qst_name.']';
918 918
                     $input_name                                                               = '';
919 919
                     $input_id                                                                 =
920 920
                         sanitize_key($qst->display_text());
921 921
                     $input_class                                                              = '';
922
-                    $questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]                    =
922
+                    $questions[$qg->ID()]['QSG_questions'][$qst->ID()]                    =
923 923
                         $qst->model_field_array();
924
-                    $questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['QST_input_name']  = 'qstn'
924
+                    $questions[$qg->ID()]['QSG_questions'][$qst->ID()]['QST_input_name']  = 'qstn'
925 925
                                                                                                 . $input_name
926 926
                                                                                                 . $qst_name;
927
-                    $questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['QST_input_id']    =
928
-                        $input_id . '-' . $qstn_id;
929
-                    $questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['QST_input_class'] = $input_class;
930
-                    $questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['QST_options']     = [];
931
-                    $questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['qst_obj']         = $qst;
932
-                    $questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['ans_obj']         = $answer;
927
+                    $questions[$qg->ID()]['QSG_questions'][$qst->ID()]['QST_input_id']    =
928
+                        $input_id.'-'.$qstn_id;
929
+                    $questions[$qg->ID()]['QSG_questions'][$qst->ID()]['QST_input_class'] = $input_class;
930
+                    $questions[$qg->ID()]['QSG_questions'][$qst->ID()]['QST_options']     = [];
931
+                    $questions[$qg->ID()]['QSG_questions'][$qst->ID()]['qst_obj']         = $qst;
932
+                    $questions[$qg->ID()]['QSG_questions'][$qst->ID()]['ans_obj']         = $answer;
933 933
                     // leave responses as-is, don't convert stuff into html entities please!
934
-                    $questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['htmlentities'] = false;
934
+                    $questions[$qg->ID()]['QSG_questions'][$qst->ID()]['htmlentities'] = false;
935 935
                     if ($qst->type() == 'RADIO_BTN' || $qst->type() == 'CHECKBOX' || $qst->type() == 'DROPDOWN') {
936 936
                         $QSOs = $qst->options(true, $answer->value());
937 937
                         if (is_array($QSOs)) {
938 938
                             foreach ($QSOs as $QSO_ID => $QSO) {
939
-                                $questions[ $qg->ID() ]['QSG_questions'][ $qst->ID() ]['QST_options'][ $QSO_ID ] =
939
+                                $questions[$qg->ID()]['QSG_questions'][$qst->ID()]['QST_options'][$QSO_ID] =
940 940
                                     $QSO->model_field_array();
941 941
                             }
942 942
                         }
Please login to merge, or discard this patch.
core/db_models/EEM_Base.model.php 2 patches
Indentation   +6678 added lines, -6678 removed lines patch added patch discarded remove patch
@@ -39,6688 +39,6688 @@
 block discarded – undo
39 39
  */
40 40
 abstract class EEM_Base extends EE_Base implements ResettableInterface
41 41
 {
42
-    private string $class_name;
43
-
44
-
45
-    /**
46
-     * Flag to indicate whether the values provided to EEM_Base have already been prepared
47
-     * by the model object or not (ie, the model object has used the field's _prepare_for_set function on the values).
48
-     * They almost always WILL NOT, but it's not necessarily a requirement.
49
-     * For example, if you want to run EEM_Event::instance()->get_all(array(array('EVT_ID'=>$_GET['event_id'])));
50
-     *
51
-     * @var boolean
52
-     */
53
-    private $_values_already_prepared_by_model_object = 0;
54
-
55
-    /**
56
-     * when $_values_already_prepared_by_model_object equals this, we assume
57
-     * the data is just like form input that needs to have the model fields'
58
-     * prepare_for_set and prepare_for_use_in_db called on it
59
-     */
60
-    const not_prepared_by_model_object = 0;
61
-
62
-    /**
63
-     * when $_values_already_prepared_by_model_object equals this, we
64
-     * assume this value is coming from a model object and doesn't need to have
65
-     * prepare_for_set called on it, just prepare_for_use_in_db is used
66
-     */
67
-    const prepared_by_model_object = 1;
68
-
69
-    /**
70
-     * when $_values_already_prepared_by_model_object equals this, we assume
71
-     * the values are already to be used in the database (ie no processing is done
72
-     * on them by the model's fields)
73
-     */
74
-    const prepared_for_use_in_db = 2;
75
-
76
-
77
-    protected string $singular_item = 'Item';
78
-
79
-    protected string $plural_item   = 'Items';
80
-
81
-    /**
82
-     * @type EE_Table_Base[] $_tables array of EE_Table objects for defining which tables comprise this model.
83
-     */
84
-    protected $_tables;
85
-
86
-    /**
87
-     * With two levels: top-level has array keys which are database table aliases (ie, keys in _tables)
88
-     * and the value is an array. Each of those sub-arrays have keys of field names (eg 'ATT_ID', which should also be
89
-     * variable names on the model objects (eg, EE_Attendee), and the keys should be children of EE_Model_Field
90
-     *
91
-     * @var EE_Model_Field_Base[][] $_fields
92
-     */
93
-    protected $_fields;
94
-
95
-    /**
96
-     * array of different relations
97
-     *
98
-     * @var EE_Model_Relation_Base[] $_model_relations
99
-     */
100
-    protected array $_model_relations = [];
101
-
102
-    /**
103
-     * @var EE_Index[] $_indexes
104
-     */
105
-    protected array $_indexes = [];
106
-
107
-    /**
108
-     * Default strategy for getting where conditions on this model. This strategy is used to get default
109
-     * where conditions which are added to get_all, update, and delete queries. They can be overridden
110
-     * by setting the same columns as used in these queries in the query yourself.
111
-     *
112
-     * @var EE_Default_Where_Conditions|null
113
-     */
114
-    protected ?EE_Default_Where_Conditions $_default_where_conditions_strategy = null;
115
-
116
-    /**
117
-     * Strategy for getting conditions on this model when 'default_where_conditions' equals 'minimum'.
118
-     * This is particularly useful when you want something between 'none' and 'default'
119
-     *
120
-     * @var EE_Default_Where_Conditions|null
121
-     */
122
-    protected ?EE_Default_Where_Conditions $_minimum_where_conditions_strategy = null;
123
-
124
-    /**
125
-     * String describing how to find the "owner" of this model's objects.
126
-     * When there is a foreign key on this model to the wp_users table, this isn't needed.
127
-     * But when there isn't, this indicates which related model, or transiently-related model,
128
-     * has the foreign key to the wp_users table.
129
-     * Eg, for EEM_Registration this would be 'Event' because registrations are directly
130
-     * related to events, and events have a foreign key to wp_users.
131
-     * On EEM_Transaction, this would be 'Transaction.Event'
132
-     *
133
-     * @var string
134
-     */
135
-    protected string $_model_chain_to_wp_user = '';
136
-
137
-    /**
138
-     * String describing how to find the model with a password controlling access to this model. This property has the
139
-     * same format as $_model_chain_to_wp_user. This is primarily used by the query param "exclude_protected".
140
-     * This value is the path of models to follow to arrive at the model with the password field.
141
-     * If it is an empty string, it means this model has the password field. If it is null, it means there is no
142
-     * model with a password that should affect reading this on the front-end.
143
-     * Eg this is an empty string for the Event model because it has a password.
144
-     * This is null for the Registration model, because its event's password has no bearing on whether
145
-     * you can read the registration or not on the front-end (it just depends on your capabilities.)
146
-     * This is 'Datetime.Event' on the Ticket model, because model queries for tickets that set "exclude_protected"
147
-     * should hide tickets for datetimes for events that have a password set.
148
-     *
149
-     * @var string|null
150
-     */
151
-    protected ?string $model_chain_to_password = null;
152
-
153
-    /**
154
-     * This is a flag typically set by updates so that we don't load the where strategy on updates because updates
155
-     * don't need it (particularly CPT models)
156
-     *
157
-     * @var bool
158
-     */
159
-    protected bool $_ignore_where_strategy = false;
160
-
161
-    /**
162
-     * String used in caps relating to this model. Eg, if the caps relating to this
163
-     * model are 'ee_edit_events', 'ee_read_events', etc, it would be 'events'.
164
-     *
165
-     * @var string|bool|null.    If null it hasn't been initialized yet.
166
-     *                      If false then we have indicated capabilities don't apply to this
167
-     */
168
-    protected $_caps_slug = null;
169
-
170
-    /**
171
-     * 2d array where top-level keys are one of EEM_Base::valid_cap_contexts(),
172
-     * and next-level keys are capability names, and each's value is an
173
-     * EE_Default_Where_Condition. If the requester requests to apply caps to the query,
174
-     * they specify which context to use (ie, frontend, backend, edit or delete)
175
-     * and then each capability in the corresponding sub-array that they're missing
176
-     * adds the where conditions onto the query.
177
-     *
178
-     * @var array
179
-     */
180
-    protected array $_cap_restrictions = [
181
-        self::caps_read       => [],
182
-        self::caps_read_admin => [],
183
-        self::caps_edit       => [],
184
-        self::caps_delete     => [],
185
-    ];
186
-
187
-    /**
188
-     * Array defining which cap restriction generators to use to create default
189
-     * cap restrictions to put in EEM_Base::_cap_restrictions.
190
-     * Array-keys are one of EEM_Base::valid_cap_contexts(), and values are a child of
191
-     * EE_Restriction_Generator_Base. If you don't want any cap restrictions generated
192
-     * automatically set this to false (not just null).
193
-     *
194
-     * @var EE_Restriction_Generator_Base[]
195
-     */
196
-    protected array $_cap_restriction_generators = [];
197
-
198
-    /**
199
-     * constants used to categorize capability restrictions on EEM_Base::_caps_restrictions
200
-     */
201
-    const caps_read       = 'read';
202
-
203
-    const caps_read_admin = 'read_admin';
204
-
205
-    const caps_edit       = 'edit';
206
-
207
-    const caps_delete     = 'delete';
208
-
209
-    /**
210
-     * Keys are all the cap contexts (ie constants EEM_Base::_caps_*) and values are their 'action'
211
-     * as how they'd be used in capability names. Eg EEM_Base::caps_read ('read_frontend')
212
-     * maps to 'read' because when looking for relevant permissions we're going to use
213
-     * 'read' in the capabilities names like 'ee_read_events' etc.
214
-     *
215
-     * @var array
216
-     */
217
-    protected array $_cap_contexts_to_cap_action_map = [
218
-        self::caps_read       => 'read',
219
-        self::caps_read_admin => 'read',
220
-        self::caps_edit       => 'edit',
221
-        self::caps_delete     => 'delete',
222
-    ];
223
-
224
-    /**
225
-     * Timezone
226
-     * This gets set via the constructor so that we know what timezone incoming strings|timestamps are in when there
227
-     * are EE_Datetime_Fields in use.  This can also be used before a get to set what timezone you want strings coming
228
-     * out of the created objects.  NOT all EEM_Base child classes use this property but any that use an
229
-     * EE_Datetime_Field data type will have access to it.
230
-     *
231
-     * @var string
232
-     */
233
-    protected $_timezone = '';
234
-
235
-
236
-    /**
237
-     * This holds the id of the blog currently making the query.  Has no bearing on single site but is used for
238
-     * multisite.
239
-     *
240
-     * @var int
241
-     */
242
-    protected static int $_model_query_blog_id = 0;
243
-
244
-    /**
245
-     * A copy of _fields, except the array keys are the model names pointed to by
246
-     * the field
247
-     *
248
-     * @var EE_Model_Field_Base[]
249
-     */
250
-    private array $_cache_foreign_key_to_fields = [];
251
-
252
-    /**
253
-     * Cached list of all the fields on the model, indexed by their name
254
-     *
255
-     * @var EE_Model_Field_Base[]
256
-     */
257
-    private ?array $_cached_fields = null;
258
-
259
-    /**
260
-     * Cached list of all the fields on the model, except those that are
261
-     * marked as only pertinent to the database
262
-     *
263
-     * @var EE_Model_Field_Base[]
264
-     */
265
-    private ?array $_cached_fields_non_db_only = null;
266
-
267
-    /**
268
-     * A cached reference to the primary key for quick lookup
269
-     *
270
-     * @var EE_Model_Field_Base|null
271
-     */
272
-    private ?EE_Model_Field_Base $_primary_key_field = null;
273
-
274
-    /**
275
-     * Flag indicating whether this model has a primary key or not
276
-     *
277
-     * @var bool|null
278
-     */
279
-    protected ?bool $_has_primary_key_field = null;
280
-
281
-    /**
282
-     * array in the format:  [ FK alias => full PK ]
283
-     * where keys are local column name aliases for foreign keys
284
-     * and values are the fully qualified column name for the primary key they represent
285
-     *  ex:
286
-     *      [ 'Event.EVT_wp_user' => 'WP_User.ID' ]
287
-     *
288
-     * @var array $foreign_key_aliases
289
-     */
290
-    protected array $foreign_key_aliases = [];
291
-
292
-    /**
293
-     * Whether this model is based off a table in WP core only (CPTs should set
294
-     * this to FALSE, but if we were to make an EE_WP_Post model, it should set this to true).
295
-     * This should be true for models that deal with data that should exist independent of EE.
296
-     * For example, if the model can read and insert data that isn't used by EE, this should be true.
297
-     * It would be false, however, if you could guarantee the model would only interact with EE data,
298
-     * even if it uses a WP core table (eg event and venue models set this to false for that reason:
299
-     * they can only read and insert events and venues custom post types, not arbitrary post types)
300
-     *
301
-     * @var bool
302
-     */
303
-    protected bool $_wp_core_model = false;
304
-
305
-    /**
306
-     * @var bool|null stores whether this model has a password field or not.
307
-     * null until initialized by hasPasswordField()
308
-     */
309
-    protected ?bool $has_password_field = null;
310
-
311
-    /**
312
-     * @var EE_Password_Field|null Automatically set when calling getPasswordField()
313
-     */
314
-    protected ?EE_Password_Field $password_field = null;
315
-
316
-    /**
317
-     *    List of valid operators that can be used for querying.
318
-     * The keys are all operators we'll accept, the values are the real SQL
319
-     * operators used
320
-     *
321
-     * @var array
322
-     */
323
-    protected array $_valid_operators = [
324
-        '='           => '=',
325
-        '<='          => '<=',
326
-        '<'           => '<',
327
-        '>='          => '>=',
328
-        '>'           => '>',
329
-        '!='          => '!=',
330
-        'LIKE'        => 'LIKE',
331
-        'like'        => 'LIKE',
332
-        'NOT_LIKE'    => 'NOT LIKE',
333
-        'not_like'    => 'NOT LIKE',
334
-        'NOT LIKE'    => 'NOT LIKE',
335
-        'not like'    => 'NOT LIKE',
336
-        'IN'          => 'IN',
337
-        'in'          => 'IN',
338
-        'NOT_IN'      => 'NOT IN',
339
-        'not_in'      => 'NOT IN',
340
-        'NOT IN'      => 'NOT IN',
341
-        'not in'      => 'NOT IN',
342
-        'between'     => 'BETWEEN',
343
-        'BETWEEN'     => 'BETWEEN',
344
-        'IS_NOT_NULL' => 'IS NOT NULL',
345
-        'is_not_null' => 'IS NOT NULL',
346
-        'IS NOT NULL' => 'IS NOT NULL',
347
-        'is not null' => 'IS NOT NULL',
348
-        'IS_NULL'     => 'IS NULL',
349
-        'is_null'     => 'IS NULL',
350
-        'IS NULL'     => 'IS NULL',
351
-        'is null'     => 'IS NULL',
352
-        'REGEXP'      => 'REGEXP',
353
-        'regexp'      => 'REGEXP',
354
-        'NOT_REGEXP'  => 'NOT REGEXP',
355
-        'not_regexp'  => 'NOT REGEXP',
356
-        'NOT REGEXP'  => 'NOT REGEXP',
357
-        'not regexp'  => 'NOT REGEXP',
358
-    ];
359
-
360
-    /**
361
-     * Operators that work like 'IN', accepting a comma-separated list of values inside brackets. Eg '(1,2,3)'
362
-     *
363
-     * @var array
364
-     */
365
-    protected array $_in_style_operators = ['IN', 'NOT IN'];
366
-
367
-    /**
368
-     * Operators that work like 'BETWEEN'.  Typically used for datetime calculations, i.e. "BETWEEN '12-1-2011' AND
369
-     * '12-31-2012'"
370
-     *
371
-     * @var array
372
-     */
373
-    protected array $_between_style_operators = ['BETWEEN'];
374
-
375
-    /**
376
-     * Operators that work like SQL's like: input should be assumed to be a string, already prepared for a LIKE query.
377
-     *
378
-     * @var array
379
-     */
380
-    protected array $_like_style_operators = ['LIKE', 'NOT LIKE'];
381
-
382
-    /**
383
-     * Operators that are used for handling NUll and !NULL queries.  Typically used for when checking if a row exists
384
-     * on a join table.
385
-     *
386
-     * @var array
387
-     */
388
-    protected array $_null_style_operators = ['IS NOT NULL', 'IS NULL'];
389
-
390
-    /**
391
-     * Allowed values for $query_params['order'] for ordering in queries
392
-     *
393
-     * @var array
394
-     */
395
-    protected array $_allowed_order_values = ['asc', 'desc', 'ASC', 'DESC'];
396
-
397
-    /**
398
-     * When these are keys in a WHERE or HAVING clause, they are handled much differently
399
-     * than regular field names. It is assumed that their values are an array of WHERE conditions
400
-     *
401
-     * @var array
402
-     */
403
-    private array $_logic_query_param_keys = ['not', 'and', 'or', 'NOT', 'AND', 'OR'];
404
-
405
-    /**
406
-     * Allowed keys in $query_params arrays passed into queries. Note that 0 is meant to always be a
407
-     * 'where', but 'where' clauses are so common that we thought we'd omit it
408
-     *
409
-     * @var array
410
-     */
411
-    private array $_allowed_query_params = [
412
-        0,
413
-        'limit',
414
-        'order_by',
415
-        'group_by',
416
-        'having',
417
-        'force_join',
418
-        'order',
419
-        'on_join_limit',
420
-        'default_where_conditions',
421
-        'caps',
422
-        'extra_selects',
423
-        'exclude_protected',
424
-    ];
425
-
426
-    /**
427
-     * All the data types that can be used in $wpdb->prepare statements.
428
-     *
429
-     * @var array
430
-     */
431
-    private array $_valid_wpdb_data_types = ['%d', '%s', '%f'];
432
-
433
-    /**
434
-     * @var EE_Registry|null $EE
435
-     */
436
-    protected ?EE_Registry $EE = null;
437
-
438
-
439
-    /**
440
-     * Property which, when set, will have this model echo out the next X queries to the page for debugging.
441
-     *
442
-     * @var int
443
-     */
444
-    protected int $_show_next_x_db_queries = 0;
445
-
446
-    /**
447
-     * When using _get_all_wpdb_results, you can specify a custom selection. If you do so,
448
-     * it gets saved on this property as an instance of CustomSelects so those selections can be used in
449
-     * WHERE, GROUP_BY, etc.
450
-     *
451
-     * @var CustomSelects|null
452
-     */
453
-    protected $_custom_selections = null;
454
-
455
-    /**
456
-     * key => value Entity Map using  array( EEM_Base::$_model_query_blog_id => array( ID => model object ) )
457
-     * caches every model object we've fetched from the DB on this request
458
-     *
459
-     * @var array
460
-     */
461
-    protected $_entity_map = [];
462
-
463
-    /**
464
-     * @var LoaderInterface|null
465
-     */
466
-    protected static ?LoaderInterface $loader = null;
467
-
468
-    /**
469
-     * @var Mirror|null
470
-     */
471
-    private static ?Mirror $mirror = null;
472
-
473
-
474
-    /**
475
-     * constant used to show EEM_Base has not yet verified the db on this http request
476
-     */
477
-    const db_verified_none = 0;
478
-
479
-    /**
480
-     * constant used to show EEM_Base has verified the EE core db on this http request,
481
-     * but not the addons' dbs
482
-     */
483
-    const db_verified_core = 1;
484
-
485
-    /**
486
-     * constant used to show EEM_Base has verified the addons' dbs (and implicitly
487
-     * the EE core db too)
488
-     */
489
-    const db_verified_addons = 2;
490
-
491
-    /**
492
-     * Indicates whether an EEM_Base child has already re-verified the DB
493
-     * is ok (we don't want to do it repetitively). Should be set to one the constants
494
-     * looking like EEM_Base::db_verified_*
495
-     *
496
-     * @var int - 0 = none, 1 = core, 2 = addons
497
-     */
498
-    protected static int $_db_verification_level = EEM_Base::db_verified_none;
499
-
500
-    /**
501
-     * @deprecatd 5.0.40.p
502
-     */
503
-    const default_where_conditions_all = EE_Default_Where_Conditions::ALL;
504
-
505
-    /**
506
-     * @deprecatd 5.0.40.p
507
-     */
508
-    const default_where_conditions_this_only = EE_Default_Where_Conditions::THIS_MODEL_ONLY;
509
-
510
-    /**
511
-     * @deprecatd 5.0.40.p
512
-     */
513
-    const default_where_conditions_others_only = EE_Default_Where_Conditions::OTHER_MODELS_ONLY;
514
-
515
-    /**
516
-     * @deprecatd 5.0.40.p
517
-     */
518
-    const default_where_conditions_minimum_all = EE_Default_Where_Conditions::MINIMUM_ALL;
519
-
520
-    /**
521
-     * @deprecatd 5.0.40.p
522
-     */
523
-    const default_where_conditions_minimum_others = EE_Default_Where_Conditions::MINIMUM_OTHERS;
524
-
525
-    /**
526
-     * @deprecatd 5.0.40.p
527
-     */
528
-    const default_where_conditions_none = EE_Default_Where_Conditions::NONE;
529
-
530
-
531
-    /**
532
-     * About all child constructors:
533
-     * they should define the _tables, _fields and _model_relations arrays.
534
-     * Should ALWAYS be called after child constructor.
535
-     * To make the child constructors as simple as possible, this parent constructor
536
-     * finalizes constructing all the object's attributes.
537
-     * Generally, rather than requiring a child to code
538
-     * $this->_tables = array(
539
-     *        'Event_Post_Table' => new EE_Table('Event_Post_Table','wp_posts')
540
-     *        ...);
541
-     *  (thus repeating itself in the array key and in the constructor of the new EE_Table,)
542
-     * each EE_Table has a function to set the table's alias after the constructor, using
543
-     * the array key ('Event_Post_Table'), instead of repeating it. The model fields and model relations
544
-     * do something similar.
545
-     *
546
-     * @param string|null $timezone
547
-     * @throws EE_Error
548
-     * @throws Exception
549
-     */
550
-    protected function __construct(?string $timezone = '')
551
-    {
552
-        $this->class_name = get_class($this);
553
-        // check that the model has not been loaded too soon
554
-        if (! did_action('AHEE__EE_System__load_espresso_addons')) {
555
-            throw new EE_Error(
556
-                sprintf(
557
-                    esc_html__(
558
-                        'The %1$s model can not be loaded before the "AHEE__EE_System__load_espresso_addons" hook has been called. This gives other addons a chance to extend this model.',
559
-                        'event_espresso'
560
-                    ),
561
-                    $this->class_name
562
-                )
563
-            );
564
-        }
565
-        do_action("AHEE__{$this->class_name}__construct__start");
566
-        /**
567
-         * Filters the list of tables on a model. It is best to NOT use this directly and instead
568
-         * just use EE_Register_Model_Extension
569
-         *
570
-         * @var EE_Table_Base[] $_tables
571
-         */
572
-        $this->_tables = (array) apply_filters("FHEE__{$this->class_name}__construct__tables", $this->_tables);
573
-        foreach ($this->_tables as $table_alias => $table_obj) {
574
-            /** @var $table_obj EE_Table_Base */
575
-            $table_obj->_construct_finalize_with_alias($table_alias);
576
-            if ($table_obj instanceof EE_Secondary_Table) {
577
-                $table_obj->_construct_finalize_set_table_to_join_with($this->_get_main_table());
578
-            }
579
-        }
580
-        /**
581
-         * Filters the list of fields on a model. It is best to NOT use this directly and instead just use
582
-         * EE_Register_Model_Extension
583
-         *
584
-         * @param EE_Model_Field_Base[] $_fields
585
-         */
586
-        $this->_fields = (array) apply_filters("FHEE__{$this->class_name}__construct__fields", $this->_fields);
587
-        $this->_invalidate_field_caches();
588
-        foreach ($this->_fields as $table_alias => $fields_for_table) {
589
-            if (! array_key_exists($table_alias, $this->_tables)) {
590
-                throw new EE_Error(
591
-                    sprintf(
592
-                        esc_html__(
593
-                            "Table alias %s does not exist in EEM_Base child's _tables array. Only tables defined are %s",
594
-                            'event_espresso'
595
-                        ),
596
-                        $table_alias,
597
-                        implode(",", $this->_fields)
598
-                    )
599
-                );
600
-            }
601
-            foreach ($fields_for_table as $field_name => $field_obj) {
602
-                /** @var $field_obj EE_Model_Field_Base | EE_Primary_Key_Field_Base */
603
-                // primary key field base has a slightly different _construct_finalize
604
-                /** @var $field_obj EE_Model_Field_Base */
605
-                $field_obj->_construct_finalize($table_alias, $field_name, $this->get_this_model_name());
606
-            }
607
-        }
608
-        // everything is related to Extra_Meta
609
-        if ($this->class_name !== 'EEM_Extra_Meta') {
610
-            // make extra meta related to everything, but don't block deleting things just
611
-            // because they have related extra meta info. For now just orphan those extra meta
612
-            // in the future we should automatically delete them
613
-            $this->_model_relations['Extra_Meta'] = new EE_Has_Many_Any_Relation(false);
614
-        }
615
-        // and change logs
616
-        if ($this->class_name !== 'EEM_Change_Log') {
617
-            $this->_model_relations['Change_Log'] = new EE_Has_Many_Any_Relation(false);
618
-        }
619
-        /**
620
-         * Filters the list of relations on a model. It is best to NOT use this directly and instead just use
621
-         * EE_Register_Model_Extension
622
-         *
623
-         * @param EE_Model_Relation_Base[] $_model_relations
624
-         */
625
-        $this->_model_relations = (array) apply_filters(
626
-            "FHEE__{$this->class_name}__construct__model_relations",
627
-            $this->_model_relations
628
-        );
629
-        foreach ($this->_model_relations as $model_name => $relation_obj) {
630
-            /** @var $relation_obj EE_Model_Relation_Base */
631
-            $relation_obj->_construct_finalize_set_models($this->get_this_model_name(), $model_name);
632
-        }
633
-        foreach ($this->_indexes as $index_name => $index_obj) {
634
-            $index_obj->_construct_finalize($index_name, $this->get_this_model_name());
635
-        }
636
-        $this->set_timezone($timezone);
637
-        // finalize default where condition strategy, or set default
638
-        if (! $this->_default_where_conditions_strategy) {
639
-            // nothing was set during child constructor, so set default
640
-            $this->_default_where_conditions_strategy = new EE_Default_Where_Conditions();
641
-        }
642
-        $this->_default_where_conditions_strategy->_finalize_construct($this);
643
-        if (! $this->_minimum_where_conditions_strategy) {
644
-            // nothing was set during child constructor, so set default
645
-            $this->_minimum_where_conditions_strategy = new EE_Default_Where_Conditions();
646
-        }
647
-        $this->_minimum_where_conditions_strategy->_finalize_construct($this);
648
-        // if the cap slug hasn't been set, and we haven't set it to false on purpose
649
-        // to indicate to NOT set it, set it to the logical default
650
-        if ($this->_caps_slug === null) {
651
-            $this->_caps_slug = EEH_Inflector::pluralize_and_lower($this->get_this_model_name());
652
-        }
653
-        // initialize the standard cap restriction generators if none were specified by the child constructor
654
-        if (! empty($this->_cap_restriction_generators)) {
655
-            foreach ($this->cap_contexts_to_cap_action_map() as $cap_context => $action) {
656
-                if (! isset($this->_cap_restriction_generators[ $cap_context ])) {
657
-                    $this->_cap_restriction_generators[ $cap_context ] = apply_filters(
658
-                        'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
659
-                        new EE_Restriction_Generator_Protected(),
660
-                        $cap_context,
661
-                        $this
662
-                    );
663
-                }
664
-            }
665
-        }
666
-        // if there are cap restriction generators, use them to make the default cap restrictions
667
-        if (! empty($this->_cap_restriction_generators)) {
668
-            foreach ($this->_cap_restriction_generators as $context => $generator_object) {
669
-                if (! $generator_object) {
670
-                    continue;
671
-                }
672
-                if (! $generator_object instanceof EE_Restriction_Generator_Base) {
673
-                    throw new EE_Error(
674
-                        sprintf(
675
-                            esc_html__(
676
-                                'Index "%1$s" in the model %2$s\'s _cap_restriction_generators is not a child of EE_Restriction_Generator_Base. It should be that or NULL.',
677
-                                'event_espresso'
678
-                            ),
679
-                            $context,
680
-                            $this->get_this_model_name()
681
-                        )
682
-                    );
683
-                }
684
-                $action = $this->cap_action_for_context($context);
685
-                if (! $generator_object->construction_finalized()) {
686
-                    $generator_object->_construct_finalize($this, $action);
687
-                }
688
-            }
689
-        }
690
-        do_action("AHEE__{$this->class_name}__construct__end");
691
-    }
692
-
693
-
694
-    /**
695
-     * @return LoaderInterface
696
-     * @throws InvalidArgumentException
697
-     * @throws InvalidDataTypeException
698
-     * @throws InvalidInterfaceException
699
-     */
700
-    protected static function getLoader(): LoaderInterface
701
-    {
702
-        if (! EEM_Base::$loader instanceof LoaderInterface) {
703
-            EEM_Base::$loader = LoaderFactory::getLoader();
704
-        }
705
-        return EEM_Base::$loader;
706
-    }
707
-
708
-
709
-    /**
710
-     * @return Mirror
711
-     * @since   5.0.0.p
712
-     */
713
-    private static function getMirror(): Mirror
714
-    {
715
-        if (! EEM_Base::$mirror instanceof Mirror) {
716
-            EEM_Base::$mirror = EEM_Base::getLoader()->getShared(Mirror::class);
717
-        }
718
-        return EEM_Base::$mirror;
719
-    }
720
-
721
-
722
-    /**
723
-     * @param string $model_class_Name
724
-     * @param string $timezone
725
-     * @return array
726
-     * @throws ReflectionException
727
-     * @since   5.0.0.p
728
-     */
729
-    private static function getModelArguments(string $model_class_Name, string $timezone): array
730
-    {
731
-        $arguments = [$timezone];
732
-        $params    = EEM_Base::getMirror()->getParameters($model_class_Name);
733
-        if (count($params) > 1) {
734
-            if ($params[1]->getName() === 'model_field_factory') {
735
-                $arguments = [
736
-                    $timezone,
737
-                    EEM_Base::getLoader()->getShared(ModelFieldFactory::class),
738
-                ];
739
-            } elseif ($model_class_Name === 'EEM_Form_Section') {
740
-                $arguments = [
741
-                    EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
742
-                    $timezone,
743
-                ];
744
-            } elseif ($model_class_Name === 'EEM_Form_Element') {
745
-                $arguments = [
746
-                    EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
747
-                    EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\InputTypes'),
748
-                    $timezone,
749
-                ];
750
-            }
751
-        }
752
-        return $arguments;
753
-    }
754
-
755
-
756
-    /**
757
-     * This function is a singleton method used to instantiate the Espresso_model object
758
-     *
759
-     * @param string|null $timezone   string representing the timezone we want to set for returned Date Time Strings
760
-     *                                (and any incoming timezone data that gets saved).
761
-     *                                Note this just sends the timezone info to the date time model field objects.
762
-     *                                Default is NULL
763
-     *                                (and will be assumed using the set timezone in the 'timezone_string' wp option)
764
-     * @return EEM_Base (as in the concrete child class)
765
-     * @throws EE_Error
766
-     * @throws ReflectionException
767
-     */
768
-    public static function instance($timezone = '')
769
-    {
770
-        // check if instance of Espresso_model already exists
771
-        if (! static::$_instance instanceof static) {
772
-            /**
773
-             * Set blog id for models to current blog. However we ONLY do this if $_model_query_blog_id is not already set.
774
-             */
775
-            if (empty(EEM_Base::$_model_query_blog_id)) {
776
-                EEM_Base::set_model_query_blog_id();
777
-            }
778
-            $model_class = static::class;
779
-            $arguments = EEM_Base::getModelArguments($model_class, (string) $timezone);
780
-            do_action("AHEE__{$model_class}__instance__before_construct", $model_class, $arguments);
781
-            $model = new static(...$arguments);
782
-            EEM_Base::getLoader()->share($model_class, $model, $arguments);
783
-            static::$_instance = $model;
784
-        }
785
-        // we might have a timezone set, let set_timezone decide what to do with it
786
-        if ($timezone) {
787
-            static::$_instance->set_timezone($timezone);
788
-        }
789
-        // Espresso_model object
790
-        return static::$_instance;
791
-    }
792
-
793
-
794
-    /**
795
-     * resets the model and returns it
796
-     *
797
-     * @param string|null $timezone
798
-     * @return EEM_Base|null (if the model was already instantiated, returns it, with
799
-     * all its properties reset; if it wasn't instantiated, returns null)
800
-     * @throws EE_Error
801
-     * @throws ReflectionException
802
-     * @throws InvalidArgumentException
803
-     * @throws InvalidDataTypeException
804
-     * @throws InvalidInterfaceException
805
-     */
806
-    public static function reset($timezone = '')
807
-    {
808
-        if (! static::$_instance instanceof EEM_Base) {
809
-            return null;
810
-        }
811
-        // Let's NOT swap out the current instance for a new one
812
-        // because if someone has a reference to it, we can't remove their reference.
813
-        // It's best to keep using the same reference but change the original object instead,
814
-        // so reset all its properties to their original values as defined in the class.
815
-        $static_properties = EEM_Base::getMirror()->getStaticProperties(static::class);
816
-        foreach (EEM_Base::getMirror()->getDefaultProperties(static::class) as $property => $value) {
817
-            // don't set instance to null like it was originally,
818
-            // but it's static anyways, and we're ignoring static properties (for now at least)
819
-            if (! isset($static_properties[ $property ])) {
820
-                static::$_instance->{$property} = $value;
821
-            }
822
-        }
823
-        // and then directly call its constructor again, like we would if we were creating a new one
824
-        $arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
825
-        static::$_instance->__construct(...$arguments);
826
-        return self::instance();
827
-    }
828
-
829
-
830
-    /**
831
-     * Used to set the $_model_query_blog_id static property.
832
-     *
833
-     * @param int $blog_id  If provided then will set the blog_id for the models to this id.  If not provided then the
834
-     *                      value for get_current_blog_id() will be used.
835
-     */
836
-    public static function set_model_query_blog_id(int $blog_id = 0)
837
-    {
838
-        EEM_Base::$_model_query_blog_id = $blog_id > 0 ? $blog_id : get_current_blog_id();
839
-    }
840
-
841
-
842
-    /**
843
-     * Returns whatever is set as the internal $model_query_blog_id.
844
-     *
845
-     * @return int
846
-     */
847
-    public static function get_model_query_blog_id()
848
-    {
849
-        return EEM_Base::$_model_query_blog_id;
850
-    }
851
-
852
-
853
-    /**
854
-     * retrieve the status details from esp_status table as an array IF this model has the status table as a relation.
855
-     *
856
-     * @param boolean $translated return localized strings or JUST the array.
857
-     * @return array
858
-     * @throws EE_Error
859
-     * @throws InvalidArgumentException
860
-     * @throws InvalidDataTypeException
861
-     * @throws InvalidInterfaceException
862
-     * @throws ReflectionException
863
-     */
864
-    public function status_array($translated = false)
865
-    {
866
-        if (! array_key_exists('Status', $this->_model_relations)) {
867
-            return [];
868
-        }
869
-        $model_name   = $this->get_this_model_name();
870
-        $status_type  = str_replace(' ', '_', strtolower(str_replace('_', ' ', $model_name)));
871
-        $stati        = EEM_Status::instance()->get_all([['STS_type' => $status_type]]);
872
-        $status_array = [];
873
-        foreach ($stati as $status) {
874
-            $status_array[ $status->ID() ] = $status->get('STS_code');
875
-        }
876
-        return $translated
877
-            ? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
878
-            : $status_array;
879
-    }
880
-
881
-
882
-    /**
883
-     * Gets all the EE_Base_Class objects which match the $query_params, by querying the DB.
884
-     *
885
-     * @param array $query_params             @see
886
-     *                                        https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
887
-     *                                        or if you have the development copy of EE you can view this at the path:
888
-     *                                        /docs/G--Model-System/model-query-params.md
889
-     * @return EE_Base_Class[]  *note that there is NO option to pass the output type. If you want results different
890
-     *                                        from EE_Base_Class[], use get_all_wpdb_results(). Array keys are object
891
-     *                                        IDs (if there is a primary key on the model. if not, numerically indexed)
892
-     *                                        Some full examples: get 10 transactions which have Scottish attendees:
893
-     *                                          EEM_Transaction::instance()->get_all(
894
-     *                                              [
42
+	private string $class_name;
43
+
44
+
45
+	/**
46
+	 * Flag to indicate whether the values provided to EEM_Base have already been prepared
47
+	 * by the model object or not (ie, the model object has used the field's _prepare_for_set function on the values).
48
+	 * They almost always WILL NOT, but it's not necessarily a requirement.
49
+	 * For example, if you want to run EEM_Event::instance()->get_all(array(array('EVT_ID'=>$_GET['event_id'])));
50
+	 *
51
+	 * @var boolean
52
+	 */
53
+	private $_values_already_prepared_by_model_object = 0;
54
+
55
+	/**
56
+	 * when $_values_already_prepared_by_model_object equals this, we assume
57
+	 * the data is just like form input that needs to have the model fields'
58
+	 * prepare_for_set and prepare_for_use_in_db called on it
59
+	 */
60
+	const not_prepared_by_model_object = 0;
61
+
62
+	/**
63
+	 * when $_values_already_prepared_by_model_object equals this, we
64
+	 * assume this value is coming from a model object and doesn't need to have
65
+	 * prepare_for_set called on it, just prepare_for_use_in_db is used
66
+	 */
67
+	const prepared_by_model_object = 1;
68
+
69
+	/**
70
+	 * when $_values_already_prepared_by_model_object equals this, we assume
71
+	 * the values are already to be used in the database (ie no processing is done
72
+	 * on them by the model's fields)
73
+	 */
74
+	const prepared_for_use_in_db = 2;
75
+
76
+
77
+	protected string $singular_item = 'Item';
78
+
79
+	protected string $plural_item   = 'Items';
80
+
81
+	/**
82
+	 * @type EE_Table_Base[] $_tables array of EE_Table objects for defining which tables comprise this model.
83
+	 */
84
+	protected $_tables;
85
+
86
+	/**
87
+	 * With two levels: top-level has array keys which are database table aliases (ie, keys in _tables)
88
+	 * and the value is an array. Each of those sub-arrays have keys of field names (eg 'ATT_ID', which should also be
89
+	 * variable names on the model objects (eg, EE_Attendee), and the keys should be children of EE_Model_Field
90
+	 *
91
+	 * @var EE_Model_Field_Base[][] $_fields
92
+	 */
93
+	protected $_fields;
94
+
95
+	/**
96
+	 * array of different relations
97
+	 *
98
+	 * @var EE_Model_Relation_Base[] $_model_relations
99
+	 */
100
+	protected array $_model_relations = [];
101
+
102
+	/**
103
+	 * @var EE_Index[] $_indexes
104
+	 */
105
+	protected array $_indexes = [];
106
+
107
+	/**
108
+	 * Default strategy for getting where conditions on this model. This strategy is used to get default
109
+	 * where conditions which are added to get_all, update, and delete queries. They can be overridden
110
+	 * by setting the same columns as used in these queries in the query yourself.
111
+	 *
112
+	 * @var EE_Default_Where_Conditions|null
113
+	 */
114
+	protected ?EE_Default_Where_Conditions $_default_where_conditions_strategy = null;
115
+
116
+	/**
117
+	 * Strategy for getting conditions on this model when 'default_where_conditions' equals 'minimum'.
118
+	 * This is particularly useful when you want something between 'none' and 'default'
119
+	 *
120
+	 * @var EE_Default_Where_Conditions|null
121
+	 */
122
+	protected ?EE_Default_Where_Conditions $_minimum_where_conditions_strategy = null;
123
+
124
+	/**
125
+	 * String describing how to find the "owner" of this model's objects.
126
+	 * When there is a foreign key on this model to the wp_users table, this isn't needed.
127
+	 * But when there isn't, this indicates which related model, or transiently-related model,
128
+	 * has the foreign key to the wp_users table.
129
+	 * Eg, for EEM_Registration this would be 'Event' because registrations are directly
130
+	 * related to events, and events have a foreign key to wp_users.
131
+	 * On EEM_Transaction, this would be 'Transaction.Event'
132
+	 *
133
+	 * @var string
134
+	 */
135
+	protected string $_model_chain_to_wp_user = '';
136
+
137
+	/**
138
+	 * String describing how to find the model with a password controlling access to this model. This property has the
139
+	 * same format as $_model_chain_to_wp_user. This is primarily used by the query param "exclude_protected".
140
+	 * This value is the path of models to follow to arrive at the model with the password field.
141
+	 * If it is an empty string, it means this model has the password field. If it is null, it means there is no
142
+	 * model with a password that should affect reading this on the front-end.
143
+	 * Eg this is an empty string for the Event model because it has a password.
144
+	 * This is null for the Registration model, because its event's password has no bearing on whether
145
+	 * you can read the registration or not on the front-end (it just depends on your capabilities.)
146
+	 * This is 'Datetime.Event' on the Ticket model, because model queries for tickets that set "exclude_protected"
147
+	 * should hide tickets for datetimes for events that have a password set.
148
+	 *
149
+	 * @var string|null
150
+	 */
151
+	protected ?string $model_chain_to_password = null;
152
+
153
+	/**
154
+	 * This is a flag typically set by updates so that we don't load the where strategy on updates because updates
155
+	 * don't need it (particularly CPT models)
156
+	 *
157
+	 * @var bool
158
+	 */
159
+	protected bool $_ignore_where_strategy = false;
160
+
161
+	/**
162
+	 * String used in caps relating to this model. Eg, if the caps relating to this
163
+	 * model are 'ee_edit_events', 'ee_read_events', etc, it would be 'events'.
164
+	 *
165
+	 * @var string|bool|null.    If null it hasn't been initialized yet.
166
+	 *                      If false then we have indicated capabilities don't apply to this
167
+	 */
168
+	protected $_caps_slug = null;
169
+
170
+	/**
171
+	 * 2d array where top-level keys are one of EEM_Base::valid_cap_contexts(),
172
+	 * and next-level keys are capability names, and each's value is an
173
+	 * EE_Default_Where_Condition. If the requester requests to apply caps to the query,
174
+	 * they specify which context to use (ie, frontend, backend, edit or delete)
175
+	 * and then each capability in the corresponding sub-array that they're missing
176
+	 * adds the where conditions onto the query.
177
+	 *
178
+	 * @var array
179
+	 */
180
+	protected array $_cap_restrictions = [
181
+		self::caps_read       => [],
182
+		self::caps_read_admin => [],
183
+		self::caps_edit       => [],
184
+		self::caps_delete     => [],
185
+	];
186
+
187
+	/**
188
+	 * Array defining which cap restriction generators to use to create default
189
+	 * cap restrictions to put in EEM_Base::_cap_restrictions.
190
+	 * Array-keys are one of EEM_Base::valid_cap_contexts(), and values are a child of
191
+	 * EE_Restriction_Generator_Base. If you don't want any cap restrictions generated
192
+	 * automatically set this to false (not just null).
193
+	 *
194
+	 * @var EE_Restriction_Generator_Base[]
195
+	 */
196
+	protected array $_cap_restriction_generators = [];
197
+
198
+	/**
199
+	 * constants used to categorize capability restrictions on EEM_Base::_caps_restrictions
200
+	 */
201
+	const caps_read       = 'read';
202
+
203
+	const caps_read_admin = 'read_admin';
204
+
205
+	const caps_edit       = 'edit';
206
+
207
+	const caps_delete     = 'delete';
208
+
209
+	/**
210
+	 * Keys are all the cap contexts (ie constants EEM_Base::_caps_*) and values are their 'action'
211
+	 * as how they'd be used in capability names. Eg EEM_Base::caps_read ('read_frontend')
212
+	 * maps to 'read' because when looking for relevant permissions we're going to use
213
+	 * 'read' in the capabilities names like 'ee_read_events' etc.
214
+	 *
215
+	 * @var array
216
+	 */
217
+	protected array $_cap_contexts_to_cap_action_map = [
218
+		self::caps_read       => 'read',
219
+		self::caps_read_admin => 'read',
220
+		self::caps_edit       => 'edit',
221
+		self::caps_delete     => 'delete',
222
+	];
223
+
224
+	/**
225
+	 * Timezone
226
+	 * This gets set via the constructor so that we know what timezone incoming strings|timestamps are in when there
227
+	 * are EE_Datetime_Fields in use.  This can also be used before a get to set what timezone you want strings coming
228
+	 * out of the created objects.  NOT all EEM_Base child classes use this property but any that use an
229
+	 * EE_Datetime_Field data type will have access to it.
230
+	 *
231
+	 * @var string
232
+	 */
233
+	protected $_timezone = '';
234
+
235
+
236
+	/**
237
+	 * This holds the id of the blog currently making the query.  Has no bearing on single site but is used for
238
+	 * multisite.
239
+	 *
240
+	 * @var int
241
+	 */
242
+	protected static int $_model_query_blog_id = 0;
243
+
244
+	/**
245
+	 * A copy of _fields, except the array keys are the model names pointed to by
246
+	 * the field
247
+	 *
248
+	 * @var EE_Model_Field_Base[]
249
+	 */
250
+	private array $_cache_foreign_key_to_fields = [];
251
+
252
+	/**
253
+	 * Cached list of all the fields on the model, indexed by their name
254
+	 *
255
+	 * @var EE_Model_Field_Base[]
256
+	 */
257
+	private ?array $_cached_fields = null;
258
+
259
+	/**
260
+	 * Cached list of all the fields on the model, except those that are
261
+	 * marked as only pertinent to the database
262
+	 *
263
+	 * @var EE_Model_Field_Base[]
264
+	 */
265
+	private ?array $_cached_fields_non_db_only = null;
266
+
267
+	/**
268
+	 * A cached reference to the primary key for quick lookup
269
+	 *
270
+	 * @var EE_Model_Field_Base|null
271
+	 */
272
+	private ?EE_Model_Field_Base $_primary_key_field = null;
273
+
274
+	/**
275
+	 * Flag indicating whether this model has a primary key or not
276
+	 *
277
+	 * @var bool|null
278
+	 */
279
+	protected ?bool $_has_primary_key_field = null;
280
+
281
+	/**
282
+	 * array in the format:  [ FK alias => full PK ]
283
+	 * where keys are local column name aliases for foreign keys
284
+	 * and values are the fully qualified column name for the primary key they represent
285
+	 *  ex:
286
+	 *      [ 'Event.EVT_wp_user' => 'WP_User.ID' ]
287
+	 *
288
+	 * @var array $foreign_key_aliases
289
+	 */
290
+	protected array $foreign_key_aliases = [];
291
+
292
+	/**
293
+	 * Whether this model is based off a table in WP core only (CPTs should set
294
+	 * this to FALSE, but if we were to make an EE_WP_Post model, it should set this to true).
295
+	 * This should be true for models that deal with data that should exist independent of EE.
296
+	 * For example, if the model can read and insert data that isn't used by EE, this should be true.
297
+	 * It would be false, however, if you could guarantee the model would only interact with EE data,
298
+	 * even if it uses a WP core table (eg event and venue models set this to false for that reason:
299
+	 * they can only read and insert events and venues custom post types, not arbitrary post types)
300
+	 *
301
+	 * @var bool
302
+	 */
303
+	protected bool $_wp_core_model = false;
304
+
305
+	/**
306
+	 * @var bool|null stores whether this model has a password field or not.
307
+	 * null until initialized by hasPasswordField()
308
+	 */
309
+	protected ?bool $has_password_field = null;
310
+
311
+	/**
312
+	 * @var EE_Password_Field|null Automatically set when calling getPasswordField()
313
+	 */
314
+	protected ?EE_Password_Field $password_field = null;
315
+
316
+	/**
317
+	 *    List of valid operators that can be used for querying.
318
+	 * The keys are all operators we'll accept, the values are the real SQL
319
+	 * operators used
320
+	 *
321
+	 * @var array
322
+	 */
323
+	protected array $_valid_operators = [
324
+		'='           => '=',
325
+		'<='          => '<=',
326
+		'<'           => '<',
327
+		'>='          => '>=',
328
+		'>'           => '>',
329
+		'!='          => '!=',
330
+		'LIKE'        => 'LIKE',
331
+		'like'        => 'LIKE',
332
+		'NOT_LIKE'    => 'NOT LIKE',
333
+		'not_like'    => 'NOT LIKE',
334
+		'NOT LIKE'    => 'NOT LIKE',
335
+		'not like'    => 'NOT LIKE',
336
+		'IN'          => 'IN',
337
+		'in'          => 'IN',
338
+		'NOT_IN'      => 'NOT IN',
339
+		'not_in'      => 'NOT IN',
340
+		'NOT IN'      => 'NOT IN',
341
+		'not in'      => 'NOT IN',
342
+		'between'     => 'BETWEEN',
343
+		'BETWEEN'     => 'BETWEEN',
344
+		'IS_NOT_NULL' => 'IS NOT NULL',
345
+		'is_not_null' => 'IS NOT NULL',
346
+		'IS NOT NULL' => 'IS NOT NULL',
347
+		'is not null' => 'IS NOT NULL',
348
+		'IS_NULL'     => 'IS NULL',
349
+		'is_null'     => 'IS NULL',
350
+		'IS NULL'     => 'IS NULL',
351
+		'is null'     => 'IS NULL',
352
+		'REGEXP'      => 'REGEXP',
353
+		'regexp'      => 'REGEXP',
354
+		'NOT_REGEXP'  => 'NOT REGEXP',
355
+		'not_regexp'  => 'NOT REGEXP',
356
+		'NOT REGEXP'  => 'NOT REGEXP',
357
+		'not regexp'  => 'NOT REGEXP',
358
+	];
359
+
360
+	/**
361
+	 * Operators that work like 'IN', accepting a comma-separated list of values inside brackets. Eg '(1,2,3)'
362
+	 *
363
+	 * @var array
364
+	 */
365
+	protected array $_in_style_operators = ['IN', 'NOT IN'];
366
+
367
+	/**
368
+	 * Operators that work like 'BETWEEN'.  Typically used for datetime calculations, i.e. "BETWEEN '12-1-2011' AND
369
+	 * '12-31-2012'"
370
+	 *
371
+	 * @var array
372
+	 */
373
+	protected array $_between_style_operators = ['BETWEEN'];
374
+
375
+	/**
376
+	 * Operators that work like SQL's like: input should be assumed to be a string, already prepared for a LIKE query.
377
+	 *
378
+	 * @var array
379
+	 */
380
+	protected array $_like_style_operators = ['LIKE', 'NOT LIKE'];
381
+
382
+	/**
383
+	 * Operators that are used for handling NUll and !NULL queries.  Typically used for when checking if a row exists
384
+	 * on a join table.
385
+	 *
386
+	 * @var array
387
+	 */
388
+	protected array $_null_style_operators = ['IS NOT NULL', 'IS NULL'];
389
+
390
+	/**
391
+	 * Allowed values for $query_params['order'] for ordering in queries
392
+	 *
393
+	 * @var array
394
+	 */
395
+	protected array $_allowed_order_values = ['asc', 'desc', 'ASC', 'DESC'];
396
+
397
+	/**
398
+	 * When these are keys in a WHERE or HAVING clause, they are handled much differently
399
+	 * than regular field names. It is assumed that their values are an array of WHERE conditions
400
+	 *
401
+	 * @var array
402
+	 */
403
+	private array $_logic_query_param_keys = ['not', 'and', 'or', 'NOT', 'AND', 'OR'];
404
+
405
+	/**
406
+	 * Allowed keys in $query_params arrays passed into queries. Note that 0 is meant to always be a
407
+	 * 'where', but 'where' clauses are so common that we thought we'd omit it
408
+	 *
409
+	 * @var array
410
+	 */
411
+	private array $_allowed_query_params = [
412
+		0,
413
+		'limit',
414
+		'order_by',
415
+		'group_by',
416
+		'having',
417
+		'force_join',
418
+		'order',
419
+		'on_join_limit',
420
+		'default_where_conditions',
421
+		'caps',
422
+		'extra_selects',
423
+		'exclude_protected',
424
+	];
425
+
426
+	/**
427
+	 * All the data types that can be used in $wpdb->prepare statements.
428
+	 *
429
+	 * @var array
430
+	 */
431
+	private array $_valid_wpdb_data_types = ['%d', '%s', '%f'];
432
+
433
+	/**
434
+	 * @var EE_Registry|null $EE
435
+	 */
436
+	protected ?EE_Registry $EE = null;
437
+
438
+
439
+	/**
440
+	 * Property which, when set, will have this model echo out the next X queries to the page for debugging.
441
+	 *
442
+	 * @var int
443
+	 */
444
+	protected int $_show_next_x_db_queries = 0;
445
+
446
+	/**
447
+	 * When using _get_all_wpdb_results, you can specify a custom selection. If you do so,
448
+	 * it gets saved on this property as an instance of CustomSelects so those selections can be used in
449
+	 * WHERE, GROUP_BY, etc.
450
+	 *
451
+	 * @var CustomSelects|null
452
+	 */
453
+	protected $_custom_selections = null;
454
+
455
+	/**
456
+	 * key => value Entity Map using  array( EEM_Base::$_model_query_blog_id => array( ID => model object ) )
457
+	 * caches every model object we've fetched from the DB on this request
458
+	 *
459
+	 * @var array
460
+	 */
461
+	protected $_entity_map = [];
462
+
463
+	/**
464
+	 * @var LoaderInterface|null
465
+	 */
466
+	protected static ?LoaderInterface $loader = null;
467
+
468
+	/**
469
+	 * @var Mirror|null
470
+	 */
471
+	private static ?Mirror $mirror = null;
472
+
473
+
474
+	/**
475
+	 * constant used to show EEM_Base has not yet verified the db on this http request
476
+	 */
477
+	const db_verified_none = 0;
478
+
479
+	/**
480
+	 * constant used to show EEM_Base has verified the EE core db on this http request,
481
+	 * but not the addons' dbs
482
+	 */
483
+	const db_verified_core = 1;
484
+
485
+	/**
486
+	 * constant used to show EEM_Base has verified the addons' dbs (and implicitly
487
+	 * the EE core db too)
488
+	 */
489
+	const db_verified_addons = 2;
490
+
491
+	/**
492
+	 * Indicates whether an EEM_Base child has already re-verified the DB
493
+	 * is ok (we don't want to do it repetitively). Should be set to one the constants
494
+	 * looking like EEM_Base::db_verified_*
495
+	 *
496
+	 * @var int - 0 = none, 1 = core, 2 = addons
497
+	 */
498
+	protected static int $_db_verification_level = EEM_Base::db_verified_none;
499
+
500
+	/**
501
+	 * @deprecatd 5.0.40.p
502
+	 */
503
+	const default_where_conditions_all = EE_Default_Where_Conditions::ALL;
504
+
505
+	/**
506
+	 * @deprecatd 5.0.40.p
507
+	 */
508
+	const default_where_conditions_this_only = EE_Default_Where_Conditions::THIS_MODEL_ONLY;
509
+
510
+	/**
511
+	 * @deprecatd 5.0.40.p
512
+	 */
513
+	const default_where_conditions_others_only = EE_Default_Where_Conditions::OTHER_MODELS_ONLY;
514
+
515
+	/**
516
+	 * @deprecatd 5.0.40.p
517
+	 */
518
+	const default_where_conditions_minimum_all = EE_Default_Where_Conditions::MINIMUM_ALL;
519
+
520
+	/**
521
+	 * @deprecatd 5.0.40.p
522
+	 */
523
+	const default_where_conditions_minimum_others = EE_Default_Where_Conditions::MINIMUM_OTHERS;
524
+
525
+	/**
526
+	 * @deprecatd 5.0.40.p
527
+	 */
528
+	const default_where_conditions_none = EE_Default_Where_Conditions::NONE;
529
+
530
+
531
+	/**
532
+	 * About all child constructors:
533
+	 * they should define the _tables, _fields and _model_relations arrays.
534
+	 * Should ALWAYS be called after child constructor.
535
+	 * To make the child constructors as simple as possible, this parent constructor
536
+	 * finalizes constructing all the object's attributes.
537
+	 * Generally, rather than requiring a child to code
538
+	 * $this->_tables = array(
539
+	 *        'Event_Post_Table' => new EE_Table('Event_Post_Table','wp_posts')
540
+	 *        ...);
541
+	 *  (thus repeating itself in the array key and in the constructor of the new EE_Table,)
542
+	 * each EE_Table has a function to set the table's alias after the constructor, using
543
+	 * the array key ('Event_Post_Table'), instead of repeating it. The model fields and model relations
544
+	 * do something similar.
545
+	 *
546
+	 * @param string|null $timezone
547
+	 * @throws EE_Error
548
+	 * @throws Exception
549
+	 */
550
+	protected function __construct(?string $timezone = '')
551
+	{
552
+		$this->class_name = get_class($this);
553
+		// check that the model has not been loaded too soon
554
+		if (! did_action('AHEE__EE_System__load_espresso_addons')) {
555
+			throw new EE_Error(
556
+				sprintf(
557
+					esc_html__(
558
+						'The %1$s model can not be loaded before the "AHEE__EE_System__load_espresso_addons" hook has been called. This gives other addons a chance to extend this model.',
559
+						'event_espresso'
560
+					),
561
+					$this->class_name
562
+				)
563
+			);
564
+		}
565
+		do_action("AHEE__{$this->class_name}__construct__start");
566
+		/**
567
+		 * Filters the list of tables on a model. It is best to NOT use this directly and instead
568
+		 * just use EE_Register_Model_Extension
569
+		 *
570
+		 * @var EE_Table_Base[] $_tables
571
+		 */
572
+		$this->_tables = (array) apply_filters("FHEE__{$this->class_name}__construct__tables", $this->_tables);
573
+		foreach ($this->_tables as $table_alias => $table_obj) {
574
+			/** @var $table_obj EE_Table_Base */
575
+			$table_obj->_construct_finalize_with_alias($table_alias);
576
+			if ($table_obj instanceof EE_Secondary_Table) {
577
+				$table_obj->_construct_finalize_set_table_to_join_with($this->_get_main_table());
578
+			}
579
+		}
580
+		/**
581
+		 * Filters the list of fields on a model. It is best to NOT use this directly and instead just use
582
+		 * EE_Register_Model_Extension
583
+		 *
584
+		 * @param EE_Model_Field_Base[] $_fields
585
+		 */
586
+		$this->_fields = (array) apply_filters("FHEE__{$this->class_name}__construct__fields", $this->_fields);
587
+		$this->_invalidate_field_caches();
588
+		foreach ($this->_fields as $table_alias => $fields_for_table) {
589
+			if (! array_key_exists($table_alias, $this->_tables)) {
590
+				throw new EE_Error(
591
+					sprintf(
592
+						esc_html__(
593
+							"Table alias %s does not exist in EEM_Base child's _tables array. Only tables defined are %s",
594
+							'event_espresso'
595
+						),
596
+						$table_alias,
597
+						implode(",", $this->_fields)
598
+					)
599
+				);
600
+			}
601
+			foreach ($fields_for_table as $field_name => $field_obj) {
602
+				/** @var $field_obj EE_Model_Field_Base | EE_Primary_Key_Field_Base */
603
+				// primary key field base has a slightly different _construct_finalize
604
+				/** @var $field_obj EE_Model_Field_Base */
605
+				$field_obj->_construct_finalize($table_alias, $field_name, $this->get_this_model_name());
606
+			}
607
+		}
608
+		// everything is related to Extra_Meta
609
+		if ($this->class_name !== 'EEM_Extra_Meta') {
610
+			// make extra meta related to everything, but don't block deleting things just
611
+			// because they have related extra meta info. For now just orphan those extra meta
612
+			// in the future we should automatically delete them
613
+			$this->_model_relations['Extra_Meta'] = new EE_Has_Many_Any_Relation(false);
614
+		}
615
+		// and change logs
616
+		if ($this->class_name !== 'EEM_Change_Log') {
617
+			$this->_model_relations['Change_Log'] = new EE_Has_Many_Any_Relation(false);
618
+		}
619
+		/**
620
+		 * Filters the list of relations on a model. It is best to NOT use this directly and instead just use
621
+		 * EE_Register_Model_Extension
622
+		 *
623
+		 * @param EE_Model_Relation_Base[] $_model_relations
624
+		 */
625
+		$this->_model_relations = (array) apply_filters(
626
+			"FHEE__{$this->class_name}__construct__model_relations",
627
+			$this->_model_relations
628
+		);
629
+		foreach ($this->_model_relations as $model_name => $relation_obj) {
630
+			/** @var $relation_obj EE_Model_Relation_Base */
631
+			$relation_obj->_construct_finalize_set_models($this->get_this_model_name(), $model_name);
632
+		}
633
+		foreach ($this->_indexes as $index_name => $index_obj) {
634
+			$index_obj->_construct_finalize($index_name, $this->get_this_model_name());
635
+		}
636
+		$this->set_timezone($timezone);
637
+		// finalize default where condition strategy, or set default
638
+		if (! $this->_default_where_conditions_strategy) {
639
+			// nothing was set during child constructor, so set default
640
+			$this->_default_where_conditions_strategy = new EE_Default_Where_Conditions();
641
+		}
642
+		$this->_default_where_conditions_strategy->_finalize_construct($this);
643
+		if (! $this->_minimum_where_conditions_strategy) {
644
+			// nothing was set during child constructor, so set default
645
+			$this->_minimum_where_conditions_strategy = new EE_Default_Where_Conditions();
646
+		}
647
+		$this->_minimum_where_conditions_strategy->_finalize_construct($this);
648
+		// if the cap slug hasn't been set, and we haven't set it to false on purpose
649
+		// to indicate to NOT set it, set it to the logical default
650
+		if ($this->_caps_slug === null) {
651
+			$this->_caps_slug = EEH_Inflector::pluralize_and_lower($this->get_this_model_name());
652
+		}
653
+		// initialize the standard cap restriction generators if none were specified by the child constructor
654
+		if (! empty($this->_cap_restriction_generators)) {
655
+			foreach ($this->cap_contexts_to_cap_action_map() as $cap_context => $action) {
656
+				if (! isset($this->_cap_restriction_generators[ $cap_context ])) {
657
+					$this->_cap_restriction_generators[ $cap_context ] = apply_filters(
658
+						'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
659
+						new EE_Restriction_Generator_Protected(),
660
+						$cap_context,
661
+						$this
662
+					);
663
+				}
664
+			}
665
+		}
666
+		// if there are cap restriction generators, use them to make the default cap restrictions
667
+		if (! empty($this->_cap_restriction_generators)) {
668
+			foreach ($this->_cap_restriction_generators as $context => $generator_object) {
669
+				if (! $generator_object) {
670
+					continue;
671
+				}
672
+				if (! $generator_object instanceof EE_Restriction_Generator_Base) {
673
+					throw new EE_Error(
674
+						sprintf(
675
+							esc_html__(
676
+								'Index "%1$s" in the model %2$s\'s _cap_restriction_generators is not a child of EE_Restriction_Generator_Base. It should be that or NULL.',
677
+								'event_espresso'
678
+							),
679
+							$context,
680
+							$this->get_this_model_name()
681
+						)
682
+					);
683
+				}
684
+				$action = $this->cap_action_for_context($context);
685
+				if (! $generator_object->construction_finalized()) {
686
+					$generator_object->_construct_finalize($this, $action);
687
+				}
688
+			}
689
+		}
690
+		do_action("AHEE__{$this->class_name}__construct__end");
691
+	}
692
+
693
+
694
+	/**
695
+	 * @return LoaderInterface
696
+	 * @throws InvalidArgumentException
697
+	 * @throws InvalidDataTypeException
698
+	 * @throws InvalidInterfaceException
699
+	 */
700
+	protected static function getLoader(): LoaderInterface
701
+	{
702
+		if (! EEM_Base::$loader instanceof LoaderInterface) {
703
+			EEM_Base::$loader = LoaderFactory::getLoader();
704
+		}
705
+		return EEM_Base::$loader;
706
+	}
707
+
708
+
709
+	/**
710
+	 * @return Mirror
711
+	 * @since   5.0.0.p
712
+	 */
713
+	private static function getMirror(): Mirror
714
+	{
715
+		if (! EEM_Base::$mirror instanceof Mirror) {
716
+			EEM_Base::$mirror = EEM_Base::getLoader()->getShared(Mirror::class);
717
+		}
718
+		return EEM_Base::$mirror;
719
+	}
720
+
721
+
722
+	/**
723
+	 * @param string $model_class_Name
724
+	 * @param string $timezone
725
+	 * @return array
726
+	 * @throws ReflectionException
727
+	 * @since   5.0.0.p
728
+	 */
729
+	private static function getModelArguments(string $model_class_Name, string $timezone): array
730
+	{
731
+		$arguments = [$timezone];
732
+		$params    = EEM_Base::getMirror()->getParameters($model_class_Name);
733
+		if (count($params) > 1) {
734
+			if ($params[1]->getName() === 'model_field_factory') {
735
+				$arguments = [
736
+					$timezone,
737
+					EEM_Base::getLoader()->getShared(ModelFieldFactory::class),
738
+				];
739
+			} elseif ($model_class_Name === 'EEM_Form_Section') {
740
+				$arguments = [
741
+					EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
742
+					$timezone,
743
+				];
744
+			} elseif ($model_class_Name === 'EEM_Form_Element') {
745
+				$arguments = [
746
+					EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
747
+					EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\InputTypes'),
748
+					$timezone,
749
+				];
750
+			}
751
+		}
752
+		return $arguments;
753
+	}
754
+
755
+
756
+	/**
757
+	 * This function is a singleton method used to instantiate the Espresso_model object
758
+	 *
759
+	 * @param string|null $timezone   string representing the timezone we want to set for returned Date Time Strings
760
+	 *                                (and any incoming timezone data that gets saved).
761
+	 *                                Note this just sends the timezone info to the date time model field objects.
762
+	 *                                Default is NULL
763
+	 *                                (and will be assumed using the set timezone in the 'timezone_string' wp option)
764
+	 * @return EEM_Base (as in the concrete child class)
765
+	 * @throws EE_Error
766
+	 * @throws ReflectionException
767
+	 */
768
+	public static function instance($timezone = '')
769
+	{
770
+		// check if instance of Espresso_model already exists
771
+		if (! static::$_instance instanceof static) {
772
+			/**
773
+			 * Set blog id for models to current blog. However we ONLY do this if $_model_query_blog_id is not already set.
774
+			 */
775
+			if (empty(EEM_Base::$_model_query_blog_id)) {
776
+				EEM_Base::set_model_query_blog_id();
777
+			}
778
+			$model_class = static::class;
779
+			$arguments = EEM_Base::getModelArguments($model_class, (string) $timezone);
780
+			do_action("AHEE__{$model_class}__instance__before_construct", $model_class, $arguments);
781
+			$model = new static(...$arguments);
782
+			EEM_Base::getLoader()->share($model_class, $model, $arguments);
783
+			static::$_instance = $model;
784
+		}
785
+		// we might have a timezone set, let set_timezone decide what to do with it
786
+		if ($timezone) {
787
+			static::$_instance->set_timezone($timezone);
788
+		}
789
+		// Espresso_model object
790
+		return static::$_instance;
791
+	}
792
+
793
+
794
+	/**
795
+	 * resets the model and returns it
796
+	 *
797
+	 * @param string|null $timezone
798
+	 * @return EEM_Base|null (if the model was already instantiated, returns it, with
799
+	 * all its properties reset; if it wasn't instantiated, returns null)
800
+	 * @throws EE_Error
801
+	 * @throws ReflectionException
802
+	 * @throws InvalidArgumentException
803
+	 * @throws InvalidDataTypeException
804
+	 * @throws InvalidInterfaceException
805
+	 */
806
+	public static function reset($timezone = '')
807
+	{
808
+		if (! static::$_instance instanceof EEM_Base) {
809
+			return null;
810
+		}
811
+		// Let's NOT swap out the current instance for a new one
812
+		// because if someone has a reference to it, we can't remove their reference.
813
+		// It's best to keep using the same reference but change the original object instead,
814
+		// so reset all its properties to their original values as defined in the class.
815
+		$static_properties = EEM_Base::getMirror()->getStaticProperties(static::class);
816
+		foreach (EEM_Base::getMirror()->getDefaultProperties(static::class) as $property => $value) {
817
+			// don't set instance to null like it was originally,
818
+			// but it's static anyways, and we're ignoring static properties (for now at least)
819
+			if (! isset($static_properties[ $property ])) {
820
+				static::$_instance->{$property} = $value;
821
+			}
822
+		}
823
+		// and then directly call its constructor again, like we would if we were creating a new one
824
+		$arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
825
+		static::$_instance->__construct(...$arguments);
826
+		return self::instance();
827
+	}
828
+
829
+
830
+	/**
831
+	 * Used to set the $_model_query_blog_id static property.
832
+	 *
833
+	 * @param int $blog_id  If provided then will set the blog_id for the models to this id.  If not provided then the
834
+	 *                      value for get_current_blog_id() will be used.
835
+	 */
836
+	public static function set_model_query_blog_id(int $blog_id = 0)
837
+	{
838
+		EEM_Base::$_model_query_blog_id = $blog_id > 0 ? $blog_id : get_current_blog_id();
839
+	}
840
+
841
+
842
+	/**
843
+	 * Returns whatever is set as the internal $model_query_blog_id.
844
+	 *
845
+	 * @return int
846
+	 */
847
+	public static function get_model_query_blog_id()
848
+	{
849
+		return EEM_Base::$_model_query_blog_id;
850
+	}
851
+
852
+
853
+	/**
854
+	 * retrieve the status details from esp_status table as an array IF this model has the status table as a relation.
855
+	 *
856
+	 * @param boolean $translated return localized strings or JUST the array.
857
+	 * @return array
858
+	 * @throws EE_Error
859
+	 * @throws InvalidArgumentException
860
+	 * @throws InvalidDataTypeException
861
+	 * @throws InvalidInterfaceException
862
+	 * @throws ReflectionException
863
+	 */
864
+	public function status_array($translated = false)
865
+	{
866
+		if (! array_key_exists('Status', $this->_model_relations)) {
867
+			return [];
868
+		}
869
+		$model_name   = $this->get_this_model_name();
870
+		$status_type  = str_replace(' ', '_', strtolower(str_replace('_', ' ', $model_name)));
871
+		$stati        = EEM_Status::instance()->get_all([['STS_type' => $status_type]]);
872
+		$status_array = [];
873
+		foreach ($stati as $status) {
874
+			$status_array[ $status->ID() ] = $status->get('STS_code');
875
+		}
876
+		return $translated
877
+			? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
878
+			: $status_array;
879
+	}
880
+
881
+
882
+	/**
883
+	 * Gets all the EE_Base_Class objects which match the $query_params, by querying the DB.
884
+	 *
885
+	 * @param array $query_params             @see
886
+	 *                                        https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
887
+	 *                                        or if you have the development copy of EE you can view this at the path:
888
+	 *                                        /docs/G--Model-System/model-query-params.md
889
+	 * @return EE_Base_Class[]  *note that there is NO option to pass the output type. If you want results different
890
+	 *                                        from EE_Base_Class[], use get_all_wpdb_results(). Array keys are object
891
+	 *                                        IDs (if there is a primary key on the model. if not, numerically indexed)
892
+	 *                                        Some full examples: get 10 transactions which have Scottish attendees:
893
+	 *                                          EEM_Transaction::instance()->get_all(
894
+	 *                                              [
895 895
 *                                                       [
896
-     *                                                      'OR' => [
897
-     *                                                          'Registration.Attendee.ATT_fname'       => ['like', 'Mc%'],
898
-     *                                                          'Registration.Attendee.ATT_fname*other' => ['like', 'Mac%'],
899
-     *                                                      ],
900
-     *                                                  ],
901
-     *                                                  'limit'    => 10,
902
-     *                                                  'group_by' => 'TXN_ID',
903
-     *                                              ]
904
-     *                                          );
905
-     *                                        get all the answers to the question titled "shirt size" for event with id
906
-     *                                        12, ordered by their answer:
907
-     *                                          EEM_Answer::instance()->get_all(
908
-     *                                              [
909
-     *                                                  [
910
-     *                                                      'Question.QST_display_text' => 'shirt size',
911
-     *                                                      'Registration.Event.EVT_ID' => 12,
912
-     *                                                  ],
913
-     *                                                  'order_by' => ['ANS_value' => 'ASC'],
914
-     *                                              ]
915
-     *                                          );
916
- * @throws EE_Error
917
-     * @throws ReflectionException
918
-     */
919
-    public function get_all($query_params = [])
920
-    {
921
-        if (
922
-            isset($query_params['limit'])
923
-            && ! isset($query_params['group_by'])
924
-        ) {
925
-            $query_params['group_by'] = array_keys($this->get_combined_primary_key_fields());
926
-        }
927
-        return $this->_create_objects($this->_get_all_wpdb_results($query_params));
928
-    }
929
-
930
-
931
-    /**
932
-     * Modifies the query parameters so we only get back model objects
933
-     * that "belong" to the current user
934
-     *
935
-     * @param array $query_params @see
936
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
937
-     * @return array @see
938
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
939
-     * @throws ReflectionException
940
-     * @throws ReflectionException
941
-     */
942
-    public function alter_query_params_to_only_include_mine($query_params = [])
943
-    {
944
-        $wp_user_field_name = $this->wp_user_field_name();
945
-        if ($wp_user_field_name) {
946
-            $query_params[0][ $wp_user_field_name ] = get_current_user_id();
947
-        }
948
-        return $query_params;
949
-    }
950
-
951
-
952
-    /**
953
-     * Returns the name of the field's name that points to the WP_User table
954
-     *  on this model (or follows the _model_chain_to_wp_user and uses that model's
955
-     * foreign key to the WP_User table)
956
-     *
957
-     * @return string|boolean string on success, boolean false when there is no
958
-     * foreign key to the WP_User table
959
-     * @throws ReflectionException
960
-     * @throws ReflectionException
961
-     */
962
-    public function wp_user_field_name()
963
-    {
964
-        try {
965
-            if (! empty($this->_model_chain_to_wp_user)) {
966
-                $models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
967
-                $last_model_name              = end($models_to_follow_to_wp_users);
968
-                $model_with_fk_to_wp_users    = EE_Registry::instance()->load_model($last_model_name);
969
-                $model_chain_to_wp_user       = $this->_model_chain_to_wp_user . '.';
970
-            } else {
971
-                $model_with_fk_to_wp_users = $this;
972
-                $model_chain_to_wp_user    = '';
973
-            }
974
-            $wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
975
-            return $model_chain_to_wp_user . $wp_user_field->get_name();
976
-        } catch (EE_Error $e) {
977
-            return false;
978
-        }
979
-    }
980
-
981
-
982
-    /**
983
-     * Returns the _model_chain_to_wp_user string, which indicates which related model
984
-     * (or transiently-related model) has a foreign key to the wp_users table;
985
-     * useful for finding if model objects of this type are 'owned' by the current user.
986
-     * This is an empty string when the foreign key is on this model and when it isn't,
987
-     * but is only non-empty when this model's ownership is indicated by a RELATED model
988
-     * (or transiently-related model)
989
-     *
990
-     * @return string
991
-     */
992
-    public function model_chain_to_wp_user()
993
-    {
994
-        return $this->_model_chain_to_wp_user;
995
-    }
996
-
997
-
998
-    /**
999
-     * Whether this model is 'owned' by a specific wordpress user (even indirectly,
1000
-     * like how registrations don't have a foreign key to wp_users, but the
1001
-     * events they are for are), or is unrelated to wp users.
1002
-     * generally available
1003
-     *
1004
-     * @return boolean
1005
-     */
1006
-    public function is_owned()
1007
-    {
1008
-        if ($this->model_chain_to_wp_user()) {
1009
-            return true;
1010
-        }
1011
-        try {
1012
-            $this->get_foreign_key_to('WP_User');
1013
-            return true;
1014
-        } catch (EE_Error $e) {
1015
-            return false;
1016
-        }
1017
-    }
1018
-
1019
-
1020
-    /**
1021
-     * Used internally to get WPDB results, because other functions, besides get_all, may want to do some queries, but
1022
-     * may want to preserve the WPDB results (eg, update, which first queries to make sure we have all the tables on
1023
-     * the model)
1024
-     *
1025
-     * @param array  $query_params      @see
1026
-     *                                  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1027
-     * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1028
-     * @param mixed  $columns_to_select What columns to select. By default, we select all columns specified by the
1029
-     *                                  fields on the model, and the models we joined to in the query. However, you can
1030
-     *                                  override this and set the select to "*", or a specific column name, like
1031
-     *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1032
-     *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1033
-     *                                  the aliases used to refer to this selection, and values are to be
1034
-     *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1035
-     *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1036
-     * @return array | stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1037
-     * @throws EE_Error
1038
-     * @throws InvalidArgumentException
1039
-     * @throws ReflectionException
1040
-     */
1041
-    protected function _get_all_wpdb_results($query_params = [], $output = ARRAY_A, $columns_to_select = null)
1042
-    {
1043
-        $this->_custom_selections = $this->getCustomSelection($query_params, $columns_to_select);
1044
-        $model_query_info         = $this->_create_model_query_info_carrier($query_params);
1045
-        $select_expressions       = $columns_to_select === null
1046
-            ? $this->_construct_default_select_sql($model_query_info)
1047
-            : '';
1048
-        if ($this->_custom_selections instanceof CustomSelects) {
1049
-            $custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1050
-            $select_expressions .= $select_expressions
1051
-                ? ', ' . $custom_expressions
1052
-                : $custom_expressions;
1053
-        }
1054
-
1055
-        $SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1056
-        return $this->_do_wpdb_query('get_results', [$SQL, $output]);
1057
-    }
1058
-
1059
-
1060
-    /**
1061
-     * Get a CustomSelects object if the $query_params or $columns_to_select allows for it.
1062
-     * Note: $query_params['extra_selects'] will always override any $columns_to_select values. It is the preferred
1063
-     * method of including extra select information.
1064
-     *
1065
-     * @param array             $query_params
1066
-     * @param null|array|string $columns_to_select
1067
-     * @return null|CustomSelects
1068
-     * @throws InvalidArgumentException
1069
-     */
1070
-    protected function getCustomSelection(array $query_params, $columns_to_select = null): ?CustomSelects
1071
-    {
1072
-        if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1073
-            return null;
1074
-        }
1075
-        $selects = $query_params['extra_selects'] ?? $columns_to_select;
1076
-        $selects = is_string($selects)
1077
-            ? explode(',', $selects)
1078
-            : $selects;
1079
-        return new CustomSelects($selects);
1080
-    }
1081
-
1082
-
1083
-    /**
1084
-     * Gets an array of rows from the database just like $wpdb->get_results would,
1085
-     * but you can use the model query params to more easily
1086
-     * take care of joins, field preparation etc.
1087
-     *
1088
-     * @param array  $query_params      @see
1089
-     *                                  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1090
-     * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1091
-     * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1092
-     *                                  fields on the model, and the models we joined to in the query. However, you can
1093
-     *                                  override this and set the select to "*", or a specific column name, like
1094
-     *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1095
-     *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1096
-     *                                  the aliases used to refer to this selection, and values are to be
1097
-     *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1098
-     *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1099
-     * @return array|stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1100
-     * @throws EE_Error
1101
-     * @throws ReflectionException
1102
-     */
1103
-    public function get_all_wpdb_results($query_params = [], $output = ARRAY_A, $columns_to_select = null)
1104
-    {
1105
-        return $this->_get_all_wpdb_results($query_params, $output, $columns_to_select);
1106
-    }
1107
-
1108
-
1109
-    /**
1110
-     * For creating a custom select statement
1111
-     *
1112
-     * @param mixed $columns_to_select either a string to be inserted directly as the select statement,
1113
-     *                                 or an array where keys are aliases, and values are arrays where 0=>the selection
1114
-     *                                 SQL, and 1=>is the datatype
1115
-     * @return string
1116
-     * @throws EE_Error
1117
-     */
1118
-    private function _construct_select_from_input($columns_to_select)
1119
-    {
1120
-        if (is_array($columns_to_select)) {
1121
-            $select_sql_array = [];
1122
-            foreach ($columns_to_select as $alias => $selection_and_datatype) {
1123
-                if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1124
-                    throw new EE_Error(
1125
-                        sprintf(
1126
-                            esc_html__(
1127
-                                "Custom selection %s (alias %s) needs to be an array like array('COUNT(REG_ID)','%%d')",
1128
-                                'event_espresso'
1129
-                            ),
1130
-                            $selection_and_datatype,
1131
-                            $alias
1132
-                        )
1133
-                    );
1134
-                }
1135
-                if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1136
-                    throw new EE_Error(
1137
-                        sprintf(
1138
-                            esc_html__(
1139
-                                "Datatype %s (for selection '%s' and alias '%s') is not a valid wpdb datatype (eg %%s)",
1140
-                                'event_espresso'
1141
-                            ),
1142
-                            $selection_and_datatype[1],
1143
-                            $selection_and_datatype[0],
1144
-                            $alias,
1145
-                            implode(', ', $this->_valid_wpdb_data_types)
1146
-                        )
1147
-                    );
1148
-                }
1149
-                $select_sql_array[] = "{$selection_and_datatype[0]} AS $alias";
1150
-            }
1151
-            $columns_to_select_string = implode(', ', $select_sql_array);
1152
-        } else {
1153
-            $columns_to_select_string = $columns_to_select;
1154
-        }
1155
-        return $columns_to_select_string;
1156
-    }
1157
-
1158
-
1159
-    /**
1160
-     * Convenient wrapper for getting the primary key field's name. Eg, on Registration, this would be 'REG_ID'
1161
-     *
1162
-     * @return string
1163
-     * @throws EE_Error
1164
-     */
1165
-    public function primary_key_name()
1166
-    {
1167
-        return $this->get_primary_key_field()->get_name();
1168
-    }
1169
-
1170
-
1171
-    /**
1172
-     * Gets a single item for this model from the DB, given only its ID (or null if none is found).
1173
-     * If there is no primary key on this model, $id is treated as primary key string
1174
-     *
1175
-     * @param mixed $id int or string, depending on the type of the model's primary key
1176
-     * @return EE_Base_Class|mixed|null
1177
-     * @throws EE_Error
1178
-     * @throws ReflectionException
1179
-     */
1180
-    public function get_one_by_ID($id)
1181
-    {
1182
-        // since entities with no ID can still have properties, we need to check the cache for them
1183
-        $cached_value = $this->get_from_entity_map($id);
1184
-        if ($cached_value) {
1185
-            return $cached_value;
1186
-        }
1187
-        // but if no cached property AND no id is passed, just return null
1188
-        if (empty($id)) {
1189
-            return null;
1190
-        }
1191
-        $model_object = $this->get_one(
1192
-            $this->alter_query_params_to_restrict_by_ID(
1193
-                $id,
1194
-                ['default_where_conditions' => EE_Default_Where_Conditions::MINIMUM_ALL]
1195
-            )
1196
-        );
1197
-        $className    = $this->_get_class_name();
1198
-        if ($model_object instanceof $className) {
1199
-            // make sure valid objects get added to the entity map
1200
-            // so that the next call to this method doesn't trigger another trip to the db
1201
-            $this->add_to_entity_map($model_object);
1202
-        }
1203
-        return $model_object;
1204
-    }
1205
-
1206
-
1207
-    /**
1208
-     * Alters query parameters to only get items with this ID are returned.
1209
-     * Takes into account that the ID might be a string produced by EEM_Base::get_index_primary_key_string(),
1210
-     * or could just be a simple primary key ID
1211
-     *
1212
-     * @param int   $id
1213
-     * @param array $query_params
1214
-     * @return array of normal query params, @see
1215
-     *               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1216
-     * @throws EE_Error
1217
-     */
1218
-    public function alter_query_params_to_restrict_by_ID($id, $query_params = [])
1219
-    {
1220
-        if (! isset($query_params[0])) {
1221
-            $query_params[0] = [];
1222
-        }
1223
-        $conditions_from_id = $this->parse_index_primary_key_string($id);
1224
-        if ($conditions_from_id === null) {
1225
-            $query_params[0][ $this->primary_key_name() ] = $id;
1226
-        } else {
1227
-            // no primary key, so the $id must be from the get_index_primary_key_string()
1228
-            $query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
1229
-        }
1230
-        return $query_params;
1231
-    }
1232
-
1233
-
1234
-    /**
1235
-     * Gets a single item for this model from the DB, given the $query_params. Only returns a single class, not an
1236
-     * array. If no item is found, null is returned.
1237
-     *
1238
-     * @param array $query_params like EEM_Base's $query_params variable.
1239
-     * @return EE_Base_Class|EE_Soft_Delete_Base_Class|NULL
1240
-     * @throws EE_Error
1241
-     * @throws ReflectionException
1242
-     */
1243
-    public function get_one($query_params = [])
1244
-    {
1245
-        if (! is_array($query_params)) {
1246
-            EE_Error::doing_it_wrong(
1247
-                'EEM_Base::get_one',
1248
-                sprintf(
1249
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1250
-                    gettype($query_params)
1251
-                ),
1252
-                '4.6.0'
1253
-            );
1254
-            $query_params = [];
1255
-        }
1256
-        $query_params['limit'] = 1;
1257
-        $items                 = $this->get_all($query_params);
1258
-        if (empty($items)) {
1259
-            return null;
1260
-        }
1261
-        return array_shift($items);
1262
-    }
1263
-
1264
-
1265
-    /**
1266
-     * Returns the next x number of items in sequence from the given value as
1267
-     * found in the database matching the given query conditions.
1268
-     *
1269
-     * @param mixed $current_field_value    Value used for the reference point.
1270
-     * @param null  $field_to_order_by      What field is used for the
1271
-     *                                      reference point.
1272
-     * @param int   $limit                  How many to return.
1273
-     * @param array $query_params           Extra conditions on the query.
1274
-     * @param null  $columns_to_select      If left null, then an array of
1275
-     *                                      EE_Base_Class objects is returned,
1276
-     *                                      otherwise you can indicate just the
1277
-     *                                      columns you want returned.
1278
-     * @return EE_Base_Class[]|array
1279
-     * @throws EE_Error
1280
-     * @throws ReflectionException
1281
-     */
1282
-    public function next_x(
1283
-        $current_field_value,
1284
-        $field_to_order_by = null,
1285
-        $limit = 1,
1286
-        $query_params = [],
1287
-        $columns_to_select = null
1288
-    ) {
1289
-        return $this->_get_consecutive(
1290
-            $current_field_value,
1291
-            '>',
1292
-            $field_to_order_by,
1293
-            $limit,
1294
-            $query_params,
1295
-            $columns_to_select
1296
-        );
1297
-    }
1298
-
1299
-
1300
-    /**
1301
-     * Returns the previous x number of items in sequence from the given value
1302
-     * as found in the database matching the given query conditions.
1303
-     *
1304
-     * @param mixed $current_field_value    Value used for the reference point.
1305
-     * @param null  $field_to_order_by      What field is used for the
1306
-     *                                      reference point.
1307
-     * @param int   $limit                  How many to return.
1308
-     * @param array $query_params           Extra conditions on the query.
1309
-     * @param null  $columns_to_select      If left null, then an array of
1310
-     *                                      EE_Base_Class objects is returned,
1311
-     *                                      otherwise you can indicate just the
1312
-     *                                      columns you want returned.
1313
-     * @return EE_Base_Class[]|array
1314
-     * @throws EE_Error
1315
-     * @throws ReflectionException
1316
-     */
1317
-    public function previous_x(
1318
-        $current_field_value,
1319
-        $field_to_order_by = null,
1320
-        $limit = 1,
1321
-        $query_params = [],
1322
-        $columns_to_select = null
1323
-    ) {
1324
-        return $this->_get_consecutive(
1325
-            $current_field_value,
1326
-            '<',
1327
-            $field_to_order_by,
1328
-            $limit,
1329
-            $query_params,
1330
-            $columns_to_select
1331
-        );
1332
-    }
1333
-
1334
-
1335
-    /**
1336
-     * Returns the next item in sequence from the given value as found in the
1337
-     * database matching the given query conditions.
1338
-     *
1339
-     * @param mixed $current_field_value    Value used for the reference point.
1340
-     * @param null  $field_to_order_by      What field is used for the
1341
-     *                                      reference point.
1342
-     * @param array $query_params           Extra conditions on the query.
1343
-     * @param null  $columns_to_select      If left null, then an EE_Base_Class
1344
-     *                                      object is returned, otherwise you
1345
-     *                                      can indicate just the columns you
1346
-     *                                      want and a single array indexed by
1347
-     *                                      the columns will be returned.
1348
-     * @return EE_Base_Class|null|array()
1349
-     * @throws EE_Error
1350
-     * @throws ReflectionException
1351
-     */
1352
-    public function next(
1353
-        $current_field_value,
1354
-        $field_to_order_by = null,
1355
-        $query_params = [],
1356
-        $columns_to_select = null
1357
-    ) {
1358
-        $results = $this->_get_consecutive(
1359
-            $current_field_value,
1360
-            '>',
1361
-            $field_to_order_by,
1362
-            1,
1363
-            $query_params,
1364
-            $columns_to_select
1365
-        );
1366
-        return empty($results)
1367
-            ? null
1368
-            : reset($results);
1369
-    }
1370
-
1371
-
1372
-    /**
1373
-     * Returns the previous item in sequence from the given value as found in
1374
-     * the database matching the given query conditions.
1375
-     *
1376
-     * @param mixed $current_field_value    Value used for the reference point.
1377
-     * @param null  $field_to_order_by      What field is used for the
1378
-     *                                      reference point.
1379
-     * @param array $query_params           Extra conditions on the query.
1380
-     * @param null  $columns_to_select      If left null, then an EE_Base_Class
1381
-     *                                      object is returned, otherwise you
1382
-     *                                      can indicate just the columns you
1383
-     *                                      want and a single array indexed by
1384
-     *                                      the columns will be returned.
1385
-     * @return EE_Base_Class|null|array()
1386
-     * @throws EE_Error
1387
-     * @throws ReflectionException
1388
-     */
1389
-    public function previous(
1390
-        $current_field_value,
1391
-        $field_to_order_by = null,
1392
-        $query_params = [],
1393
-        $columns_to_select = null
1394
-    ) {
1395
-        $results = $this->_get_consecutive(
1396
-            $current_field_value,
1397
-            '<',
1398
-            $field_to_order_by,
1399
-            1,
1400
-            $query_params,
1401
-            $columns_to_select
1402
-        );
1403
-        return empty($results)
1404
-            ? null
1405
-            : reset($results);
1406
-    }
1407
-
1408
-
1409
-    /**
1410
-     * Returns the a consecutive number of items in sequence from the given
1411
-     * value as found in the database matching the given query conditions.
1412
-     *
1413
-     * @param mixed  $current_field_value   Value used for the reference point.
1414
-     * @param string $operand               What operand is used for the sequence.
1415
-     * @param string $field_to_order_by     What field is used for the reference point.
1416
-     * @param int    $limit                 How many to return.
1417
-     * @param array  $query_params          Extra conditions on the query.
1418
-     * @param null   $columns_to_select     If left null, then an array of EE_Base_Class objects is returned,
1419
-     *                                      otherwise you can indicate just the columns you want returned.
1420
-     * @return EE_Base_Class[]|array
1421
-     * @throws EE_Error
1422
-     * @throws ReflectionException
1423
-     */
1424
-    protected function _get_consecutive(
1425
-        $current_field_value,
1426
-        $operand = '>',
1427
-        $field_to_order_by = null,
1428
-        $limit = 1,
1429
-        $query_params = [],
1430
-        $columns_to_select = null
1431
-    ) {
1432
-        // if $field_to_order_by is empty then let's assume we're ordering by the primary key.
1433
-        if (empty($field_to_order_by)) {
1434
-            if ($this->has_primary_key_field()) {
1435
-                $field_to_order_by = $this->get_primary_key_field()->get_name();
1436
-            } else {
1437
-                if (defined('WP_DEBUG') && WP_DEBUG) {
1438
-                    throw new EE_Error(
1439
-                        esc_html__(
1440
-                            'EEM_Base::_get_consecutive() has been called with no $field_to_order_by argument and there is no primary key on the field.  Please provide the field you would like to use as the base for retrieving the next item(s).',
1441
-                            'event_espresso'
1442
-                        )
1443
-                    );
1444
-                }
1445
-                EE_Error::add_error(
1446
-                    esc_html__('There was an error with the query.', 'event_espresso'),
1447
-                    __FILE__,
1448
-                    __FUNCTION__,
1449
-                    __LINE__
1450
-                );
1451
-                return [];
1452
-            }
1453
-        }
1454
-        if (! is_array($query_params)) {
1455
-            EE_Error::doing_it_wrong(
1456
-                'EEM_Base::_get_consecutive',
1457
-                sprintf(
1458
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1459
-                    gettype($query_params)
1460
-                ),
1461
-                '4.6.0'
1462
-            );
1463
-            $query_params = [];
1464
-        }
1465
-        // let's add the where query param for consecutive look up.
1466
-        $query_params[0][ $field_to_order_by ] = [$operand, $current_field_value];
1467
-        $query_params['limit']                 = $limit;
1468
-        // set direction
1469
-        $incoming_orderby         = isset($query_params['order_by'])
1470
-            ? (array) $query_params['order_by']
1471
-            : [];
1472
-        $query_params['order_by'] = $operand === '>'
1473
-            ? [$field_to_order_by => 'ASC'] + $incoming_orderby
1474
-            : [$field_to_order_by => 'DESC'] + $incoming_orderby;
1475
-        // if $columns_to_select is empty then that means we're returning EE_Base_Class objects
1476
-        if (empty($columns_to_select)) {
1477
-            return $this->get_all($query_params);
1478
-        }
1479
-        // getting just the fields
1480
-        return $this->_get_all_wpdb_results($query_params, ARRAY_A, $columns_to_select);
1481
-    }
1482
-
1483
-
1484
-    /**
1485
-     * This sets the _timezone property after model object has been instantiated.
1486
-     *
1487
-     * @param string|null $timezone valid PHP DateTimeZone timezone string
1488
-     * @throws Exception
1489
-     */
1490
-    public function set_timezone(?string $timezone = '')
1491
-    {
1492
-        if (! $timezone) {
1493
-            return;
1494
-        }
1495
-        $this->_timezone = $timezone;
1496
-        // note we need to loop through relations and set the timezone on those objects as well.
1497
-        foreach ($this->_model_relations as $relation) {
1498
-            $relation->set_timezone($timezone);
1499
-        }
1500
-        // and finally we do the same for any datetime fields
1501
-        foreach ($this->_fields as $field) {
1502
-            if ($field instanceof EE_Datetime_Field) {
1503
-                $field->set_timezone($timezone);
1504
-            }
1505
-        }
1506
-    }
1507
-
1508
-
1509
-    /**
1510
-     * This just returns whatever is set for the current timezone.
1511
-     *
1512
-     * @access public
1513
-     * @return string
1514
-     * @throws Exception
1515
-     */
1516
-    public function get_timezone()
1517
-    {
1518
-        // first validate if timezone is set.  If not, then let's set it be whatever is set on the model fields.
1519
-        if (empty($this->_timezone)) {
1520
-            foreach ($this->_fields as $field) {
1521
-                if ($field instanceof EE_Datetime_Field) {
1522
-                    $this->set_timezone($field->get_timezone());
1523
-                    break;
1524
-                }
1525
-            }
1526
-        }
1527
-        // if timezone STILL empty then return the default timezone for the site.
1528
-        if (empty($this->_timezone)) {
1529
-            $this->set_timezone(EEH_DTT_Helper::get_timezone());
1530
-        }
1531
-        return $this->_timezone;
1532
-    }
1533
-
1534
-
1535
-    /**
1536
-     * This returns the date formats set for the given field name and also ensures that
1537
-     * $this->_timezone property is set correctly.
1538
-     *
1539
-     * @param string $field_name The name of the field the formats are being retrieved for.
1540
-     * @param bool   $pretty     Whether to return the pretty formats (true) or not (false).
1541
-     * @return array formats in an array with the date format first, and the time format last.
1542
-     * @throws EE_Error   If the given field_name is not of the EE_Datetime_Field type.
1543
-     * @since 4.6.x
1544
-     */
1545
-    public function get_formats_for($field_name, $pretty = false)
1546
-    {
1547
-        $field_settings = $this->field_settings_for($field_name);
1548
-        // if not a valid EE_Datetime_Field then throw error
1549
-        if (! $field_settings instanceof EE_Datetime_Field) {
1550
-            throw new EE_Error(
1551
-                sprintf(
1552
-                    esc_html__(
1553
-                        'The field sent into EEM_Base::get_formats_for (%s) is not registered as a EE_Datetime_Field. Please check the spelling and make sure you are submitting the right field name to retrieve date_formats for.',
1554
-                        'event_espresso'
1555
-                    ),
1556
-                    $field_name
1557
-                )
1558
-            );
1559
-        }
1560
-        // while we are here, let's make sure the timezone internally in EEM_Base matches what is stored on
1561
-        // the field.
1562
-        $this->_timezone = (string) $field_settings->get_timezone();
1563
-        return [$field_settings->get_date_format($pretty), $field_settings->get_time_format($pretty)];
1564
-    }
1565
-
1566
-
1567
-    /**
1568
-     * This returns the current time in a format setup for a query on this model.
1569
-     * Usage of this method makes it easier to setup queries against EE_Datetime_Field columns because
1570
-     * it will return:
1571
-     *  - a formatted string in the timezone and format currently set on the EE_Datetime_Field for the given field for
1572
-     *  NOW
1573
-     *  - or a unix timestamp (equivalent to time())
1574
-     * Note: When requesting a formatted string, if the date or time format doesn't include seconds, for example,
1575
-     * the time returned, because it uses that format, will also NOT include seconds. For this reason, if you want
1576
-     * the time returned to be the current time down to the exact second, set $timestamp to true.
1577
-     *
1578
-     * @param string $field_name       The field the current time is needed for.
1579
-     * @param bool   $timestamp        True means to return a unix timestamp. Otherwise a
1580
-     *                                 formatted string matching the set format for the field in the set timezone will
1581
-     *                                 be returned.
1582
-     * @param string $what             Whether to return the string in just the time format, the date format, or both.
1583
-     * @return int|string  If the given field_name is not of the EE_Datetime_Field type, then an EE_Error
1584
-     *                                 exception is triggered.
1585
-     * @throws EE_Error    If the given field_name is not of the EE_Datetime_Field type.
1586
-     * @throws Exception
1587
-     * @since 4.6.x
1588
-     */
1589
-    public function current_time_for_query($field_name, $timestamp = false, $what = 'both')
1590
-    {
1591
-        $formats  = $this->get_formats_for($field_name);
1592
-        $DateTime = new DateTime("now", new DateTimeZone($this->_timezone));
1593
-        if ($timestamp) {
1594
-            return $DateTime->format('U');
1595
-        }
1596
-        // not returning timestamp, so return formatted string in timezone.
1597
-        switch ($what) {
1598
-            case 'time':
1599
-                return $DateTime->format($formats[1]);
1600
-            case 'date':
1601
-                return $DateTime->format($formats[0]);
1602
-            default:
1603
-                return $DateTime->format(implode(' ', $formats));
1604
-        }
1605
-    }
1606
-
1607
-
1608
-    /**
1609
-     * This receives a time string for a given field and ensures
1610
-     * that it is set up to match what the internal settings for the model are.
1611
-     * Returns a DateTime object.
1612
-     * Note: a gotcha for when you send in unix timestamp.  Remember a unix timestamp is already timezone agnostic,
1613
-     * (functionally the equivalent of UTC+0).
1614
-     * So when you send it in, whatever timezone string you include is ignored.
1615
-     *
1616
-     * @param string      $field_name      The field being setup.
1617
-     * @param string      $timestring      The date time string being used.
1618
-     * @param string      $incoming_format The format for the time string.
1619
-     * @param string|null $timezone_string By default, it is assumed the incoming time string is in timezone for
1620
-     *                                     the blog.  If this is not the case, then it can be specified here.  If
1621
-     *                                     incoming format is
1622
-     *                                     'U', this is ignored.
1623
-     * @return DbSafeDateTime
1624
-     * @throws EE_Error
1625
-     * @throws Exception
1626
-     */
1627
-    public function convert_datetime_for_query(
1628
-        string $field_name,
1629
-        string $timestring,
1630
-        string $incoming_format,
1631
-        ?string $timezone_string = ''
1632
-    ): DbSafeDateTime {
1633
-        // just using this to ensure the timezone is set correctly internally
1634
-        $this->get_formats_for($field_name);
1635
-        // load EEH_DTT_Helper
1636
-        $timezone_string     = ! empty($timezone_string) ? $timezone_string : EEH_DTT_Helper::get_timezone();
1637
-        $incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($timezone_string));
1638
-        EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1639
-        return DbSafeDateTime::createFromDateTime($incomingDateTime);
1640
-    }
1641
-
1642
-
1643
-    /**
1644
-     * Gets all the tables comprising this model. Array keys are the table aliases, and values are EE_Table objects
1645
-     *
1646
-     * @return EE_Table_Base[]
1647
-     */
1648
-    public function get_tables()
1649
-    {
1650
-        return $this->_tables;
1651
-    }
1652
-
1653
-
1654
-    /**
1655
-     * Updates all the database entries (in each table for this model) according to $fields_n_values and optionally
1656
-     * also updates all the model objects, where the criteria expressed in $query_params are met..
1657
-     * Also note: if this model has multiple tables, this update verifies all the secondary tables have an entry for
1658
-     * each row (in the primary table) we're trying to update; if not, it inserts an entry in the secondary table. Eg:
1659
-     * if our model has 2 tables: wp_posts (primary), and wp_esp_event (secondary). Let's say we are trying to update a
1660
-     * model object with EVT_ID = 1
1661
-     * (which means where wp_posts has ID = 1, because wp_posts.ID is the primary key's column), which exists, but
1662
-     * there is no entry in wp_esp_event for this entry in wp_posts. So, this update script will insert a row into
1663
-     * wp_esp_event, using any available parameters from $fields_n_values (eg, if "EVT_limit" => 40 is in
1664
-     * $fields_n_values, the new entry in wp_esp_event will set EVT_limit = 40, and use default for other columns which
1665
-     * are not specified)
1666
-     *
1667
-     * @param array   $fields_n_values         keys are model fields (exactly like keys in EEM_Base::_fields, NOT db
1668
-     *                                         columns!), values are strings, ints, floats, and maybe arrays if they
1669
-     *                                         are to be serialized. Basically, the values are what you'd expect to be
1670
-     *                                         values on the model, NOT necessarily what's in the DB. For example, if
1671
-     *                                         we wanted to update only the TXN_details on any Transactions where its
1672
-     *                                         ID=34, we'd use this method as follows:
1673
-     *                                         EEM_Transaction::instance()->update(
1674
-     *                                         array('TXN_details'=>array('detail1'=>'monkey','detail2'=>'banana'),
1675
-     *                                         array(array('TXN_ID'=>34)));
1676
-     * @param array   $query_params            @see
1677
-     *                                         https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1678
-     *                                         Eg, consider updating Question's QST_admin_label field is of type
1679
-     *                                         Simple_HTML. If you use this function to update that field to $new_value
1680
-     *                                         = (note replace 8's with appropriate opening and closing tags in the
1681
-     *                                         following example)"8script8alert('I hack all');8/script88b8boom
1682
-     *                                         baby8/b8", then if you set $values_already_prepared_by_model_object to
1683
-     *                                         TRUE, it is assumed that you've already called
1684
-     *                                         EE_Simple_HTML_Field->prepare_for_set($new_value), which removes the
1685
-     *                                         malicious javascript. However, if
1686
-     *                                         $values_already_prepared_by_model_object is left as FALSE, then
1687
-     *                                         EE_Simple_HTML_Field->prepare_for_set($new_value) will be called on it,
1688
-     *                                         and every other field, before insertion. We provide this parameter
1689
-     *                                         because model objects perform their prepare_for_set function on all
1690
-     *                                         their values, and so don't need to be called again (and in many cases,
1691
-     *                                         shouldn't be called again. Eg: if we escape HTML characters in the
1692
-     *                                         prepare_for_set method...)
1693
-     * @param boolean $keep_model_objs_in_sync if TRUE, makes sure we ALSO update model objects
1694
-     *                                         in this model's entity map according to $fields_n_values that match
1695
-     *                                         $query_params. This obviously has some overhead, so you can disable it
1696
-     *                                         by setting this to FALSE, but be aware that model objects being used
1697
-     *                                         could get out-of-sync with the database
1698
-     * @return int how many rows got updated or FALSE if something went wrong with the query (wp returns FALSE or num
1699
-     *                                         rows affected which *could* include 0 which DOES NOT mean the query was
1700
-     *                                         bad)
1701
-     * @throws EE_Error
1702
-     * @throws ReflectionException
1703
-     */
1704
-    public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1705
-    {
1706
-        if (! is_array($query_params)) {
1707
-            EE_Error::doing_it_wrong(
1708
-                'EEM_Base::update',
1709
-                sprintf(
1710
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1711
-                    gettype($query_params)
1712
-                ),
1713
-                '4.6.0'
1714
-            );
1715
-            $query_params = [];
1716
-        }
1717
-        /**
1718
-         * Action called before a model update call has been made.
1719
-         *
1720
-         * @param EEM_Base $model
1721
-         * @param array    $fields_n_values the updated fields and their new values
1722
-         * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1723
-         */
1724
-        do_action('AHEE__EEM_Base__update__begin', $this, $fields_n_values, $query_params);
1725
-        /**
1726
-         * Filters the fields about to be updated given the query parameters. You can provide the
1727
-         * $query_params to $this->get_all() to find exactly which records will be updated
1728
-         *
1729
-         * @param array    $fields_n_values fields and their new values
1730
-         * @param EEM_Base $model           the model being queried
1731
-         * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1732
-         */
1733
-        $fields_n_values = (array) apply_filters(
1734
-            'FHEE__EEM_Base__update__fields_n_values',
1735
-            $fields_n_values,
1736
-            $this,
1737
-            $query_params
1738
-        );
1739
-        // need to verify that, for any entry we want to update, there are entries in each secondary table.
1740
-        // to do that, for each table, verify that it's PK isn't null.
1741
-        $tables = $this->get_tables();
1742
-        // and if the other tables don't have a row for each table-to-be-updated, we'll insert one with whatever values available in the current update query
1743
-        // NOTE: we should make this code more efficient by NOT querying twice
1744
-        // before the real update, but that needs to first go through ALPHA testing
1745
-        // as it's dangerous. says Mike August 8 2014
1746
-        // we want to make sure the default_where strategy is ignored
1747
-        $this->_ignore_where_strategy = true;
1748
-        $wpdb_select_results          = $this->_get_all_wpdb_results($query_params);
1749
-        foreach ($wpdb_select_results as $wpdb_result) {
1750
-            // type cast stdClass as array
1751
-            $wpdb_result = (array) $wpdb_result;
1752
-            // get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1753
-            if ($this->has_primary_key_field()) {
1754
-                $main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1755
-            } else {
1756
-                // if there's no primary key, we basically can't support having a 2nd table on the model (we could but it would be lots of work)
1757
-                $main_table_pk_value = null;
1758
-            }
1759
-            // if there are more than 1 tables, we'll want to verify that each table for this model has an entry in the other tables
1760
-            // and if the other tables don't have a row for each table-to-be-updated, we'll insert one with whatever values available in the current update query
1761
-            if (count($tables) > 1) {
1762
-                // foreach matching row in the DB, ensure that each table's PK isn't null. If so, there must not be an entry
1763
-                // in that table, and so we'll want to insert one
1764
-                foreach ($tables as $table_obj) {
1765
-                    $this_table_pk_column = $table_obj->get_fully_qualified_pk_column();
1766
-                    // if there is no private key for this table on the results, it means there's no entry
1767
-                    // in this table, right? so insert a row in the current table, using any fields available
1768
-                    if (
1769
-                        ! (array_key_exists($this_table_pk_column, $wpdb_result)
1770
-                           && $wpdb_result[ $this_table_pk_column ])
1771
-                    ) {
1772
-                        $success = $this->_insert_into_specific_table(
1773
-                            $table_obj,
1774
-                            $fields_n_values,
1775
-                            $main_table_pk_value
1776
-                        );
1777
-                        // if we died here, report the error
1778
-                        if (! $success) {
1779
-                            return false;
1780
-                        }
1781
-                    }
1782
-                }
1783
-            }
1784
-            //              //and now check that if we have cached any models by that ID on the model, that
1785
-            //              //they also get updated properly
1786
-            //              $model_object = $this->get_from_entity_map( $main_table_pk_value );
1787
-            //              if( $model_object ){
1788
-            //                  foreach( $fields_n_values as $field => $value ){
1789
-            //                      $model_object->set($field, $value);
1790
-            // let's make sure default_where strategy is followed now
1791
-            $this->_ignore_where_strategy = false;
1792
-        }
1793
-        // if we want to keep model objects in sync, AND
1794
-        // if this wasn't called from a model object (to update itself)
1795
-        // then we want to make sure we keep all the existing
1796
-        // model objects in sync with the db
1797
-        if ($keep_model_objs_in_sync && ! $this->_values_already_prepared_by_model_object) {
1798
-            if ($this->has_primary_key_field()) {
1799
-                $model_objs_affected_ids = $this->get_col($query_params);
1800
-            } else {
1801
-                // we need to select a bunch of columns and then combine them into the the "index primary key string"s
1802
-                $models_affected_key_columns = $this->_get_all_wpdb_results($query_params, ARRAY_A);
1803
-                $model_objs_affected_ids     = [];
1804
-                foreach ($models_affected_key_columns as $row) {
1805
-                    $combined_index_key                             = $this->get_index_primary_key_string($row);
1806
-                    $model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1807
-                }
1808
-            }
1809
-            if (! $model_objs_affected_ids) {
1810
-                // wait wait wait- if nothing was affected let's stop here
1811
-                return 0;
1812
-            }
1813
-            foreach ($model_objs_affected_ids as $id) {
1814
-                $model_obj_in_entity_map = $this->get_from_entity_map($id);
1815
-                if ($model_obj_in_entity_map) {
1816
-                    foreach ($fields_n_values as $field => $new_value) {
1817
-                        $model_obj_in_entity_map->set($field, $new_value);
1818
-                    }
1819
-                }
1820
-            }
1821
-            // if there is a primary key on this model, we can now do a slight optimization
1822
-            if ($this->has_primary_key_field()) {
1823
-                // we already know what we want to update. So let's make the query simpler so it's a little more efficient
1824
-                $query_params = [
1825
-                    [$this->primary_key_name() => ['IN', $model_objs_affected_ids]],
1826
-                    'limit'                    => count($model_objs_affected_ids),
1827
-                    'default_where_conditions' => EE_Default_Where_Conditions::NONE,
1828
-                ];
1829
-            }
1830
-        }
1831
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
1832
-
1833
-        // note: the following query doesn't use _construct_2nd_half_of_select_query()
1834
-        // because it doesn't accept LIMIT, ORDER BY, etc.
1835
-        $rows_affected = $this->_do_wpdb_query(
1836
-            'query',
1837
-            [
1838
-                "UPDATE " . $model_query_info->get_full_join_sql()
1839
-                . " SET " . $this->_construct_update_sql($fields_n_values)
1840
-                . $model_query_info->get_where_sql(),
1841
-            ]
1842
-        );
1843
-
1844
-        /**
1845
-         * Action called after a model update call has been made.
1846
-         *
1847
-         * @param EEM_Base $model
1848
-         * @param array    $fields_n_values the updated fields and their new values
1849
-         * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1850
-         * @param int      $rows_affected
1851
-         */
1852
-        do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1853
-        return $rows_affected;// how many supposedly got updated
1854
-    }
1855
-
1856
-
1857
-    /**
1858
-     * Analogous to $wpdb->get_col, returns a 1-dimensional array where the values
1859
-     * are the values of the field specified (or by default the primary key field)
1860
-     * that matched the query params. Note that you should pass the name of the
1861
-     * model FIELD, not the database table's column name.
1862
-     *
1863
-     * @param array  $query_params
1864
-     * @param string $field_to_select
1865
-     * @return array just like $wpdb->get_col()
1866
-     * @throws EE_Error
1867
-     * @throws ReflectionException
1868
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md for $query_params values
1869
-     */
1870
-    public function get_col($query_params = [], $field_to_select = null)
1871
-    {
1872
-        if ($field_to_select) {
1873
-            $field = $this->field_settings_for($field_to_select);
1874
-        } elseif ($this->has_primary_key_field()) {
1875
-            $field = $this->get_primary_key_field();
1876
-        } else {
1877
-            $field_settings = $this->field_settings();
1878
-            // no primary key, just grab the first column
1879
-            $field = reset($field_settings);
1880
-            // don't need this array now
1881
-            unset($field_settings);
1882
-        }
1883
-        $model_query_info   = $this->_create_model_query_info_carrier($query_params);
1884
-        $select_expressions = $field->get_qualified_column();
1885
-        $SQL                =
1886
-            "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1887
-        return $this->_do_wpdb_query('get_col', [$SQL]);
1888
-    }
1889
-
1890
-
1891
-    /**
1892
-     * Returns a single column value for a single row from the database
1893
-     *
1894
-     * @param array  $query_params    @see
1895
-     *                                https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1896
-     * @param string $field_to_select @see EEM_Base::get_col()
1897
-     * @return string
1898
-     * @throws EE_Error
1899
-     * @throws ReflectionException
1900
-     */
1901
-    public function get_var($query_params = [], $field_to_select = null)
1902
-    {
1903
-        $query_params['limit'] = 1;
1904
-        $col                   = $this->get_col($query_params, $field_to_select);
1905
-        if (! empty($col)) {
1906
-            return reset($col);
1907
-        }
1908
-        return null;
1909
-    }
1910
-
1911
-
1912
-    /**
1913
-     * Makes the SQL for after "UPDATE table_X inner join table_Y..." and before "...WHERE". Eg "Question.name='party
1914
-     * time?', Question.desc='what do you think?',..." Values are filtered through wpdb->prepare to avoid against SQL
1915
-     * injection, but currently no further filtering is done
1916
-     *
1917
-     * @param array $fields_n_values array keys are field names on this model, and values are what those fields should
1918
-     *                               be updated to in the DB
1919
-     * @return string of SQL
1920
-     * @throws EE_Error
1921
-     * @global      $wpdb
1922
-     */
1923
-    public function _construct_update_sql($fields_n_values)
1924
-    {
1925
-        /** @type WPDB $wpdb */
1926
-        global $wpdb;
1927
-        $cols_n_values = [];
1928
-        foreach ($fields_n_values as $field_name => $value) {
1929
-            $field_obj = $this->field_settings_for($field_name);
1930
-            // if the value is NULL, we want to assign the value to that.
1931
-            // wpdb->prepare doesn't really handle that properly
1932
-            $prepared_value  = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1933
-            $value_sql       = $prepared_value === null
1934
-                ? 'NULL'
1935
-                : $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1936
-            $cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1937
-        }
1938
-        return implode(",", $cols_n_values);
1939
-    }
1940
-
1941
-
1942
-    /**
1943
-     * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1944
-     * Performs a HARD delete, meaning the database row should always be removed,
1945
-     * not just have a flag field on it switched
1946
-     * Wrapper for EEM_Base::delete_permanently()
1947
-     *
1948
-     * @param mixed $id
1949
-     * @param bool  $block_deletes whether to allow related model objects to block (prevent) this deletion
1950
-     *                             ie: enforce referential integrity
1951
-     *                             It's advisable to always leave this as TRUE, otherwise you could corrupt your DB
1952
-     * @return int the number of rows deleted
1953
-     * @throws EE_Error
1954
-     * @throws ReflectionException
1955
-     */
1956
-    public function delete_permanently_by_ID($id, $block_deletes = true): int
1957
-    {
1958
-        return $this->delete_permanently(
1959
-            [
1960
-                [$this->get_primary_key_field()->get_name() => $id],
1961
-                'limit' => 1,
1962
-            ],
1963
-            $block_deletes
1964
-        );
1965
-    }
1966
-
1967
-
1968
-    /**
1969
-     * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1970
-     * Wrapper for EEM_Base::delete()
1971
-     *
1972
-     * @param mixed $id
1973
-     * @param bool  $block_deletes whether to allow related model objects to block (prevent) this deletion
1974
-     *                             ie: enforce referential integrity
1975
-     *                             It's advisable to always leave this as TRUE, otherwise you could corrupt your DB
1976
-     * @return int the number of rows deleted
1977
-     * @throws EE_Error
1978
-     * @throws ReflectionException
1979
-     */
1980
-    public function delete_by_ID($id, $block_deletes = true)
1981
-    {
1982
-        return $this->delete(
1983
-            [
1984
-                [$this->get_primary_key_field()->get_name() => $id],
1985
-                'limit' => 1,
1986
-            ],
1987
-            $block_deletes
1988
-        );
1989
-    }
1990
-
1991
-
1992
-    /**
1993
-     * Identical to delete_permanently, but does a "soft" delete if possible,
1994
-     * meaning if the model has a field that indicates its been "trashed" or
1995
-     * "soft deleted", we will just set that instead of actually deleting the rows.
1996
-     *
1997
-     * @param array   $query_params
1998
-     * @param boolean $block_deletes whether to allow related model objects to block (prevent) this deletion
1999
-     *                               ie: enforce referential integrity
2000
-     *                               It's advisable to always leave this as TRUE, otherwise you could corrupt your DB
2001
-     * @return int how many rows got deleted
2002
-     * @throws EE_Error
2003
-     * @throws ReflectionException
2004
-     * @see EEM_Base::delete_permanently
2005
-     */
2006
-    public function delete($query_params, $block_deletes = true)
2007
-    {
2008
-        return $this->delete_permanently($query_params, $block_deletes);
2009
-    }
2010
-
2011
-
2012
-    /**
2013
-     * Deletes the model objects that meet the query params. Note: this method is overridden
2014
-     * in EEM_Soft_Delete_Base so that soft-deleted model objects are instead only flagged
2015
-     * as archived, not actually deleted
2016
-     *
2017
-     * @param array   $query_params
2018
-     * @param boolean $block_deletes  whether to allow related model objects to block (prevent) this deletion
2019
-     *                                ie: enforce referential integrity
2020
-     *                                It's advisable to always leave this as TRUE, otherwise you could corrupt your DB
2021
-     * @return int how many rows got deleted
2022
-     * @throws EE_Error
2023
-     * @throws ReflectionException
2024
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2025
-     */
2026
-    public function delete_permanently($query_params, $block_deletes = true): int
2027
-    {
2028
-        /**
2029
-         * Action called just before performing a real deletion query. You can use the
2030
-         * model and its $query_params to find exactly which items will be deleted
2031
-         *
2032
-         * @param EEM_Base $model
2033
-         * @param array    $query_params  The incoming array of query parameters influencing what gets deleted.
2034
-         * @param bool     $block_deletes @see param description in method phpdoc block.
2035
-         */
2036
-        do_action('AHEE__EEM_Base__delete__begin', $this, $query_params, $block_deletes);
2037
-        // some MySQL databases may be running safe mode, which may restrict
2038
-        // deletion if there is no KEY column used in the WHERE statement of a deletion.
2039
-        // to get around this, we first do a SELECT, get all the IDs, and then run another query
2040
-        // to delete them
2041
-        $items_for_deletion           = $this->_get_all_wpdb_results($query_params);
2042
-        $columns_and_ids_for_deleting = $this->_get_ids_for_delete($items_for_deletion, $block_deletes);
2043
-        $deletion_where_query_part    = $this->_build_query_part_for_deleting_from_columns_and_values(
2044
-            $columns_and_ids_for_deleting
2045
-        );
2046
-        /**
2047
-         * Allows client code to act on the items being deleted before the query is actually executed.
2048
-         * see php doc blocks for more details
2049
-         *
2050
-         * @param EEM_Base $this                         The model instance being acted on.
2051
-         * @param array    $query_params                 The incoming array of query parameters influencing what gets deleted.
2052
-         * @param bool     $block_deletes                @see param description in method phpdoc block.
2053
-         * @param array    $columns_and_ids_for_deleting An array indicating what entities will get removed as
2054
-         *                                               derived from the incoming query parameters.
2055
-         * @see details on the structure of this array in the phpdocs for the `_get_ids_for_delete_method`
2056
-         */
2057
-        do_action(
2058
-            'AHEE__EEM_Base__delete__before_query',
2059
-            $this,
2060
-            $query_params,
2061
-            $block_deletes,
2062
-            $columns_and_ids_for_deleting
2063
-        );
2064
-        $rows_deleted = 0;
2065
-        if ($deletion_where_query_part) {
2066
-            $model_query_info = $this->_create_model_query_info_carrier($query_params);
2067
-            $table_aliases    = array_keys($this->_tables);
2068
-            $SQL              = "DELETE "
2069
-                                . implode(", ", $table_aliases)
2070
-                                . " FROM "
2071
-                                . $model_query_info->get_full_join_sql()
2072
-                                . " WHERE "
2073
-                                . $deletion_where_query_part;
2074
-            $rows_deleted     = $this->_do_wpdb_query('query', [$SQL]);
2075
-        }
2076
-
2077
-        // Next, make sure those items are removed from the entity map; if they could be put into it at all; and if
2078
-        // there was no error with the delete query.
2079
-        if (
2080
-            $this->has_primary_key_field()
2081
-            && $rows_deleted !== false
2082
-            && isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2083
-        ) {
2084
-            $ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2085
-            foreach ($ids_for_removal as $id) {
2086
-                if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2087
-                    unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2088
-                }
2089
-            }
2090
-
2091
-            // delete any extra meta attached to the deleted entities but ONLY if this model is not an instance of
2092
-            // `EEM_Extra_Meta`.  In other words we want to prevent recursion on EEM_Extra_Meta::delete_permanently calls
2093
-            // unnecessarily.  It's very unlikely that users will have assigned Extra Meta to Extra Meta
2094
-            // (although it is possible).
2095
-            // Note this can be skipped by using the provided filter and returning false.
2096
-            if (
2097
-                apply_filters(
2098
-                    'FHEE__EEM_Base__delete_permanently__dont_delete_extra_meta_for_extra_meta',
2099
-                    ! $this instanceof EEM_Extra_Meta,
2100
-                    $this
2101
-                )
2102
-            ) {
2103
-                EEM_Extra_Meta::instance()->delete_permanently(
2104
-                    [
2105
-                        0 => [
2106
-                            'EXM_type' => $this->get_this_model_name(),
2107
-                            'OBJ_ID'   => [
2108
-                                'IN',
2109
-                                $ids_for_removal,
2110
-                            ],
2111
-                        ],
2112
-                    ]
2113
-                );
2114
-            }
2115
-        }
2116
-
2117
-        /**
2118
-         * Action called just after performing a real deletion query. Although at this point the
2119
-         * items should have been deleted
2120
-         *
2121
-         * @param EEM_Base $model
2122
-         * @param array $query_params
2123
-         * @param int   $rows_deleted
2124
-         * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2125
-         */
2126
-        do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2127
-        return (int) $rows_deleted;// how many supposedly got deleted
2128
-    }
2129
-
2130
-
2131
-    /**
2132
-     * Checks all the relations that throw error messages when there are blocking related objects
2133
-     * for related model objects. If there are any related model objects on those relations,
2134
-     * adds an EE_Error, and return true
2135
-     *
2136
-     * @param EE_Base_Class|int $this_model_obj_or_id
2137
-     * @param EE_Base_Class     $ignore_this_model_obj a model object like 'EE_Event', or 'EE_Term_Taxonomy', which
2138
-     *                                                 should be ignored when determining whether there are related
2139
-     *                                                 model objects which block this model object's deletion. Useful
2140
-     *                                                 if you know A is related to B and are considering deleting A,
2141
-     *                                                 but want to see if A has any other objects blocking its deletion
2142
-     *                                                 before removing the relation between A and B
2143
-     * @return boolean
2144
-     * @throws EE_Error
2145
-     * @throws ReflectionException
2146
-     */
2147
-    public function delete_is_blocked_by_related_models($this_model_obj_or_id, $ignore_this_model_obj = null)
2148
-    {
2149
-        // first, if $ignore_this_model_obj was supplied, get its model
2150
-        $ignored_model = $ignore_this_model_obj instanceof EE_Base_Class
2151
-            ? $ignore_this_model_obj->get_model()
2152
-            : null;
2153
-        // now check all the relations of $this_model_obj_or_id and see if there
2154
-        // are any related model objects blocking it?
2155
-        $is_blocked = false;
2156
-        foreach ($this->_model_relations as $relation_name => $relation_obj) {
2157
-            if ($relation_obj->block_delete_if_related_models_exist()) {
2158
-                // if $ignore_this_model_obj was supplied, then for the query
2159
-                // on that model needs to be told to ignore $ignore_this_model_obj
2160
-                if ($ignored_model && $relation_name === $ignored_model->get_this_model_name()) {
2161
-                    $related_model_objects = $relation_obj->get_all_related(
2162
-                        $this_model_obj_or_id,
2163
-                        [
2164
-                            [
2165
-                                $ignored_model->get_primary_key_field()->get_name() => [
2166
-                                    '!=',
2167
-                                    $ignore_this_model_obj->ID(),
2168
-                                ],
2169
-                            ],
2170
-                        ]
2171
-                    );
2172
-                } else {
2173
-                    $related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id);
2174
-                }
2175
-                if ($related_model_objects) {
2176
-                    EE_Error::add_error($relation_obj->get_deletion_error_message(), __FILE__, __FUNCTION__, __LINE__);
2177
-                    $is_blocked = true;
2178
-                }
2179
-            }
2180
-        }
2181
-        return $is_blocked;
2182
-    }
2183
-
2184
-
2185
-    /**
2186
-     * Builds the columns and values for items to delete from the incoming $row_results_for_deleting array.
2187
-     *
2188
-     * @param array $row_results_for_deleting
2189
-     * @param bool  $block_deletes whether to allow related model objects to block (prevent) this deletion
2190
-     *                             ie: enforce referential integrity
2191
-     *                             It's advisable to always leave this as TRUE, otherwise you could corrupt your DB
2192
-     * @return array               The shape of this array depends on whether the model `has_primary_key_field` or not.
2193
-     *                             If the model DOES have a primary_key_field, then the array will be a simple single
2194
-     *                             dimension array where the key is the fully qualified primary key column and
2195
-     *                             the value is an array of ids that will be deleted.
2196
-     *                             Example:
2197
-     *                              [ 'Event.EVT_ID' => [ 1,2,3 ]]
2198
-     *                             If the model DOES NOT have a primary_key_field, then the array will be a
2199
-     *                             two-dimensional array where each element is a group of columns and values that get deleted.
2200
-     *                             Example:
2201
-     *                              [
2202
-     *                                  0 => [
2203
-     *                                      'Term_Relationship.object_id' => 1
2204
-     *                                      'Term_Relationship.term_taxonomy_id' => 5
2205
-     *                                  ],
2206
-     *                                  1 => [
2207
-     *                                      'Term_Relationship.object_id' => 1
2208
-     *                                      'Term_Relationship.term_taxonomy_id' => 6
2209
-     *                                  ]
2210
-     *                              ]
2211
-     * @throws EE_Error
2212
-     * @throws ReflectionException
2213
-     */
2214
-    protected function _get_ids_for_delete(array $row_results_for_deleting, $block_deletes = true)
2215
-    {
2216
-        $ids_to_delete_indexed_by_column = [];
2217
-        if ($this->has_primary_key_field()) {
2218
-            $primary_table = $this->_get_main_table();
2219
-            // following lines are commented out because the variables were not being used
2220
-            // not deleting because unsure if calls were intentionally causing side effects
2221
-            // $primary_table_pk_field          =
2222
-            //     $this->get_field_by_column($primary_table->get_fully_qualified_pk_column());
2223
-            // $other_tables                    = $this->_get_other_tables();
2224
-            $ids_to_delete_indexed_by_column = $query = [];
2225
-            foreach ($row_results_for_deleting as $item_to_delete) {
2226
-                // before we mark this item for deletion,
2227
-                // make sure there's no related entities blocking its deletion (if we're checking)
2228
-                if (
2229
-                    $block_deletes
2230
-                    && $this->delete_is_blocked_by_related_models(
2231
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2232
-                    )
2233
-                ) {
2234
-                    continue;
2235
-                }
2236
-                // primary table deletes
2237
-                if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2238
-                    $ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2239
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2240
-                }
2241
-            }
2242
-        } elseif (count($this->get_combined_primary_key_fields()) > 1) {
2243
-            $fields = $this->get_combined_primary_key_fields();
2244
-            foreach ($row_results_for_deleting as $item_to_delete) {
2245
-                $ids_to_delete_indexed_by_column_for_row = [];
2246
-                foreach ($fields as $cpk_field) {
2247
-                    if ($cpk_field instanceof EE_Model_Field_Base) {
2248
-                        $ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2249
-                            $item_to_delete[ $cpk_field->get_qualified_column() ];
2250
-                    }
2251
-                }
2252
-                $ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
2253
-            }
2254
-        } else {
2255
-            // so there's no primary key and no combined key...
2256
-            // sorry, can't help you
2257
-            throw new EE_Error(
2258
-                sprintf(
2259
-                    esc_html__(
2260
-                        "Cannot delete objects of type %s because there is no primary key NOR combined key",
2261
-                        "event_espresso"
2262
-                    ),
2263
-                    $this->class_name
2264
-                )
2265
-            );
2266
-        }
2267
-        return $ids_to_delete_indexed_by_column;
2268
-    }
2269
-
2270
-
2271
-    /**
2272
-     * This receives an array of columns and values set to be deleted (as prepared by _get_ids_for_delete) and prepares
2273
-     * the corresponding query_part for the query performing the deletion.
2274
-     *
2275
-     * @param array $ids_to_delete_indexed_by_column @see _get_ids_for_delete for how this array might be shaped.
2276
-     * @return string
2277
-     * @throws EE_Error
2278
-     */
2279
-    protected function _build_query_part_for_deleting_from_columns_and_values(array $ids_to_delete_indexed_by_column)
2280
-    {
2281
-        $query_part = '';
2282
-        if (empty($ids_to_delete_indexed_by_column)) {
2283
-            return $query_part;
2284
-        } elseif ($this->has_primary_key_field()) {
2285
-            $query = [];
2286
-            foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2287
-                $query[] = $column . ' IN' . $this->_construct_in_value($ids, $this->_primary_key_field);
2288
-            }
2289
-            $query_part = ! empty($query)
2290
-                ? implode(' AND ', $query)
2291
-                : $query_part;
2292
-        } elseif (count($this->get_combined_primary_key_fields()) > 1) {
2293
-            $ways_to_identify_a_row = [];
2294
-            foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2295
-                $values_for_each_combined_primary_key_for_a_row = [];
2296
-                foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2297
-                    $values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2298
-                }
2299
-                $ways_to_identify_a_row[] = '('
2300
-                                            . implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
2301
-                                            . ')';
2302
-            }
2303
-            $query_part = implode(' OR ', $ways_to_identify_a_row);
2304
-        }
2305
-        return $query_part;
2306
-    }
2307
-
2308
-
2309
-    /**
2310
-     * Gets the model field by the fully qualified name
2311
-     *
2312
-     * @param string $qualified_column_name eg 'Event_CPT.post_name' or $field_obj->get_qualified_column()
2313
-     * @return EE_Model_Field_Base
2314
-     * @throws EE_Error
2315
-     * @throws EE_Error
2316
-     */
2317
-    public function get_field_by_column($qualified_column_name)
2318
-    {
2319
-        foreach ($this->field_settings(true) as $field_name => $field_obj) {
2320
-            if ($field_obj->get_qualified_column() === $qualified_column_name) {
2321
-                return $field_obj;
2322
-            }
2323
-        }
2324
-        throw new EE_Error(
2325
-            sprintf(
2326
-                esc_html__('Could not find a field on the model "%1$s" for qualified column "%2$s"', 'event_espresso'),
2327
-                $this->get_this_model_name(),
2328
-                $qualified_column_name
2329
-            )
2330
-        );
2331
-    }
2332
-
2333
-
2334
-    /**
2335
-     * Count all the rows that match criteria the model query params.
2336
-     * If $field_to_count isn't provided, the model's primary key is used. Otherwise, we count by field_to_count's
2337
-     * column
2338
-     *
2339
-     * @param array  $query_params   @see
2340
-     *                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2341
-     * @param string $field_to_count field on model to count by (not column name)
2342
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2343
-     *                               that by the setting $distinct to TRUE;
2344
-     * @return int
2345
-     * @throws EE_Error
2346
-     * @throws ReflectionException
2347
-     */
2348
-    public function count($query_params = [], $field_to_count = '', $distinct = false)
2349
-    {
2350
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
2351
-        if ($field_to_count) {
2352
-            $field_obj       = $this->field_settings_for($field_to_count);
2353
-            $column_to_count = $field_obj->get_qualified_column();
2354
-        } elseif ($this->has_primary_key_field()) {
2355
-            $pk_field_obj    = $this->get_primary_key_field();
2356
-            $column_to_count = $pk_field_obj->get_qualified_column();
2357
-        } else {
2358
-            // there's no primary key
2359
-            // if we're counting distinct items, and there's no primary key,
2360
-            // we need to list out the columns for distinction;
2361
-            // otherwise we can just use star
2362
-            if ($distinct) {
2363
-                $columns_to_use = [];
2364
-                foreach ($this->get_combined_primary_key_fields() as $field_obj) {
2365
-                    $columns_to_use[] = $field_obj->get_qualified_column();
2366
-                }
2367
-                $column_to_count = implode(',', $columns_to_use);
2368
-            } else {
2369
-                $column_to_count = '*';
2370
-            }
2371
-        }
2372
-        $column_to_count = $distinct
2373
-            ? "DISTINCT " . $column_to_count
2374
-            : $column_to_count;
2375
-        $SQL             =
2376
-            "SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2377
-        return (int) $this->_do_wpdb_query('get_var', [$SQL]);
2378
-    }
2379
-
2380
-
2381
-    /**
2382
-     * Sums up the value of the $field_to_sum (defaults to the primary key, which isn't terribly useful)
2383
-     *
2384
-     * @param array  $query_params @see
2385
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2386
-     * @param string $field_to_sum name of field (array key in $_fields array)
2387
-     * @return float
2388
-     * @throws EE_Error
2389
-     * @throws ReflectionException
2390
-     */
2391
-    public function sum($query_params, $field_to_sum = null)
2392
-    {
2393
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
2394
-        if ($field_to_sum) {
2395
-            $field_obj = $this->field_settings_for($field_to_sum);
2396
-        } else {
2397
-            $field_obj = $this->get_primary_key_field();
2398
-        }
2399
-        $column_to_count = $field_obj->get_qualified_column();
2400
-        $SQL             =
2401
-            "SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2402
-        $return_value    = $this->_do_wpdb_query('get_var', [$SQL]);
2403
-        $data_type       = $field_obj->get_wpdb_data_type();
2404
-        if ($data_type === '%d' || $data_type === '%s') {
2405
-            return (float) $return_value;
2406
-        }
2407
-        // must be %f
2408
-        return (float) $return_value;
2409
-    }
2410
-
2411
-
2412
-    /**
2413
-     * Just calls the specified method on $wpdb with the given arguments
2414
-     * Consolidates a little extra error handling code
2415
-     *
2416
-     * @param string $wpdb_method
2417
-     * @param array  $arguments_to_provide
2418
-     * @return mixed
2419
-     * @throws EE_Error
2420
-     * @global wpdb  $wpdb
2421
-     */
2422
-    protected function _do_wpdb_query($wpdb_method, $arguments_to_provide)
2423
-    {
2424
-        // if we're in maintenance mode level 2, DON'T run any queries
2425
-        // because level 2 indicates the database needs updating and
2426
-        // is probably out of sync with the code
2427
-        if (DbStatus::isOffline()) {
2428
-            throw new RuntimeException(
2429
-                esc_html__(
2430
-                    "Event Espresso Level 2 Maintenance mode is active. That means EE can not run ANY database queries until the necessary migration scripts have run which will take EE out of maintenance mode level 2. Please inform support of this error.",
2431
-                    "event_espresso"
2432
-                )
2433
-            );
2434
-        }
2435
-        /** @type WPDB $wpdb */
2436
-        global $wpdb;
2437
-        if (! method_exists($wpdb, $wpdb_method)) {
2438
-            throw new DomainException(
2439
-                sprintf(
2440
-                    esc_html__(
2441
-                        'There is no method named "%s" on Wordpress\' $wpdb object',
2442
-                        'event_espresso'
2443
-                    ),
2444
-                    $wpdb_method
2445
-                )
2446
-            );
2447
-        }
2448
-        $old_show_errors_value = $wpdb->show_errors;
2449
-        if (defined('WP_DEBUG') && WP_DEBUG) {
2450
-            $wpdb->show_errors(false);
2451
-        }
2452
-        $result = $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2453
-        $this->show_db_query_if_previously_requested($wpdb->last_query);
2454
-        if (defined('WP_DEBUG') && WP_DEBUG) {
2455
-            $wpdb->show_errors($old_show_errors_value);
2456
-            if (! empty($wpdb->last_error)) {
2457
-                throw new EE_Error(sprintf(esc_html__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2458
-            }
2459
-            if ($result === false) {
2460
-                throw new EE_Error(
2461
-                    sprintf(
2462
-                        esc_html__(
2463
-                            'WPDB Error occurred, but no error message was logged by wpdb! The wpdb method called was "%1$s" and the arguments were "%2$s"',
2464
-                            'event_espresso'
2465
-                        ),
2466
-                        $wpdb_method,
2467
-                        var_export($arguments_to_provide, true)
2468
-                    )
2469
-                );
2470
-            }
2471
-        } elseif ($result === false) {
2472
-            EE_Error::add_error(
2473
-                sprintf(
2474
-                    esc_html__(
2475
-                        'A database error has occurred. Turn on WP_DEBUG for more information.||A database error occurred doing wpdb method "%1$s", with arguments "%2$s". The error was "%3$s"',
2476
-                        'event_espresso'
2477
-                    ),
2478
-                    $wpdb_method,
2479
-                    var_export($arguments_to_provide, true),
2480
-                    $wpdb->last_error
2481
-                ),
2482
-                __FILE__,
2483
-                __FUNCTION__,
2484
-                __LINE__
2485
-            );
2486
-        }
2487
-        return $result;
2488
-    }
2489
-
2490
-
2491
-    /**
2492
-     * Attempts to run the indicated WPDB method with the provided arguments,
2493
-     * and if there's an error tries to verify the DB is correct. Uses
2494
-     * the static property EEM_Base::$_db_verification_level to determine whether
2495
-     * we should try to fix the EE core db, the addons, or just give up
2496
-     *
2497
-     * @param string $wpdb_method
2498
-     * @param array  $arguments_to_provide
2499
-     * @return mixed
2500
-     */
2501
-    private function _process_wpdb_query($wpdb_method, $arguments_to_provide)
2502
-    {
2503
-        /** @type WPDB $wpdb */
2504
-        global $wpdb;
2505
-        $wpdb->last_error = null;
2506
-        $result           = call_user_func_array([$wpdb, $wpdb_method], $arguments_to_provide);
2507
-        // was there an error running the query? but we don't care on new activations
2508
-        // (we're going to setup the DB anyway on new activations)
2509
-        if (
2510
-            ($result === false || ! empty($wpdb->last_error))
2511
-            && EE_System::instance()->detect_req_type() !== EE_System::req_type_new_activation
2512
-        ) {
2513
-            switch (EEM_Base::$_db_verification_level) {
2514
-                case EEM_Base::db_verified_none:
2515
-                    // let's double-check core's DB
2516
-                    $error_message = $this->_verify_core_db($wpdb_method, $arguments_to_provide);
2517
-                    break;
2518
-                case EEM_Base::db_verified_core:
2519
-                    // STILL NO LOVE?? verify all the addons too. Maybe they need to be fixed
2520
-                    $error_message = $this->_verify_addons_db($wpdb_method, $arguments_to_provide);
2521
-                    break;
2522
-                case EEM_Base::db_verified_addons:
2523
-                    // ummmm... you in trouble
2524
-                    return $result;
2525
-            }
2526
-            if (! empty($error_message)) {
2527
-                EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2528
-                trigger_error($error_message);
2529
-            }
2530
-            return $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2531
-        }
2532
-        return $result;
2533
-    }
2534
-
2535
-
2536
-    /**
2537
-     * Verifies the EE core database is up-to-date and records that we've done it on
2538
-     * EEM_Base::$_db_verification_level
2539
-     *
2540
-     * @param string $wpdb_method
2541
-     * @param array  $arguments_to_provide
2542
-     * @return string
2543
-     * @throws EE_Error
2544
-     * @throws ReflectionException
2545
-     */
2546
-    private function _verify_core_db($wpdb_method, $arguments_to_provide)
2547
-    {
2548
-        /** @type WPDB $wpdb */
2549
-        global $wpdb;
2550
-        // ok remember that we've already attempted fixing the core db, in case the problem persists
2551
-        EEM_Base::$_db_verification_level = EEM_Base::db_verified_core;
2552
-        $error_message                    = sprintf(
2553
-            esc_html__(
2554
-                'WPDB Error "%1$s" while running wpdb method "%2$s" with arguments %3$s. Automatically attempting to fix EE Core DB',
2555
-                'event_espresso'
2556
-            ),
2557
-            $wpdb->last_error,
2558
-            $wpdb_method,
2559
-            wp_json_encode($arguments_to_provide)
2560
-        );
2561
-        EE_System::instance()->initialize_db_if_no_migrations_required(false, true);
2562
-        return $error_message;
2563
-    }
2564
-
2565
-
2566
-    /**
2567
-     * Verifies the EE addons' database is up-to-date and records that we've done it on
2568
-     * EEM_Base::$_db_verification_level
2569
-     *
2570
-     * @param $wpdb_method
2571
-     * @param $arguments_to_provide
2572
-     * @return string
2573
-     * @throws EE_Error
2574
-     * @throws ReflectionException
2575
-     */
2576
-    private function _verify_addons_db($wpdb_method, $arguments_to_provide)
2577
-    {
2578
-        /** @type WPDB $wpdb */
2579
-        global $wpdb;
2580
-        // ok remember that we've already attempted fixing the addons dbs, in case the problem persists
2581
-        EEM_Base::$_db_verification_level = EEM_Base::db_verified_addons;
2582
-        $error_message                    = sprintf(
2583
-            esc_html__(
2584
-                'WPDB AGAIN: Error "%1$s" while running the same method and arguments as before. Automatically attempting to fix EE Addons DB',
2585
-                'event_espresso'
2586
-            ),
2587
-            $wpdb->last_error,
2588
-            $wpdb_method,
2589
-            wp_json_encode($arguments_to_provide)
2590
-        );
2591
-        EE_System::instance()->initialize_addons();
2592
-        return $error_message;
2593
-    }
2594
-
2595
-
2596
-    /**
2597
-     * In order to avoid repeating this code for the get_all, sum, and count functions, put the code parts
2598
-     * that are identical in here. Returns a string of SQL of everything in a SELECT query except the beginning
2599
-     * SELECT clause, eg " FROM wp_posts AS Event INNER JOIN ... WHERE ... ORDER BY ... LIMIT ... GROUP BY ... HAVING
2600
-     * ..."
2601
-     *
2602
-     * @param EE_Model_Query_Info_Carrier $model_query_info
2603
-     * @return string
2604
-     */
2605
-    private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2606
-    {
2607
-        return " FROM " . $model_query_info->get_full_join_sql() .
2608
-               $model_query_info->get_where_sql() .
2609
-               $model_query_info->get_group_by_sql() .
2610
-               $model_query_info->get_having_sql() .
2611
-               $model_query_info->get_order_by_sql() .
2612
-               $model_query_info->get_limit_sql();
2613
-    }
2614
-
2615
-
2616
-    /**
2617
-     * Set to easily debug the next X queries ran from this model.
2618
-     *
2619
-     * @param int $count
2620
-     */
2621
-    public function show_next_x_db_queries($count = 1)
2622
-    {
2623
-        $this->_show_next_x_db_queries = $count;
2624
-    }
2625
-
2626
-
2627
-    /**
2628
-     * @param $sql_query
2629
-     */
2630
-    public function show_db_query_if_previously_requested($sql_query)
2631
-    {
2632
-        if ($this->_show_next_x_db_queries > 0) {
2633
-            $left = is_admin() ? '12rem' : '2rem';
2634
-            echo "
896
+	 *                                                      'OR' => [
897
+	 *                                                          'Registration.Attendee.ATT_fname'       => ['like', 'Mc%'],
898
+	 *                                                          'Registration.Attendee.ATT_fname*other' => ['like', 'Mac%'],
899
+	 *                                                      ],
900
+	 *                                                  ],
901
+	 *                                                  'limit'    => 10,
902
+	 *                                                  'group_by' => 'TXN_ID',
903
+	 *                                              ]
904
+	 *                                          );
905
+	 *                                        get all the answers to the question titled "shirt size" for event with id
906
+	 *                                        12, ordered by their answer:
907
+	 *                                          EEM_Answer::instance()->get_all(
908
+	 *                                              [
909
+	 *                                                  [
910
+	 *                                                      'Question.QST_display_text' => 'shirt size',
911
+	 *                                                      'Registration.Event.EVT_ID' => 12,
912
+	 *                                                  ],
913
+	 *                                                  'order_by' => ['ANS_value' => 'ASC'],
914
+	 *                                              ]
915
+	 *                                          );
916
+	 * @throws EE_Error
917
+	 * @throws ReflectionException
918
+	 */
919
+	public function get_all($query_params = [])
920
+	{
921
+		if (
922
+			isset($query_params['limit'])
923
+			&& ! isset($query_params['group_by'])
924
+		) {
925
+			$query_params['group_by'] = array_keys($this->get_combined_primary_key_fields());
926
+		}
927
+		return $this->_create_objects($this->_get_all_wpdb_results($query_params));
928
+	}
929
+
930
+
931
+	/**
932
+	 * Modifies the query parameters so we only get back model objects
933
+	 * that "belong" to the current user
934
+	 *
935
+	 * @param array $query_params @see
936
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
937
+	 * @return array @see
938
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
939
+	 * @throws ReflectionException
940
+	 * @throws ReflectionException
941
+	 */
942
+	public function alter_query_params_to_only_include_mine($query_params = [])
943
+	{
944
+		$wp_user_field_name = $this->wp_user_field_name();
945
+		if ($wp_user_field_name) {
946
+			$query_params[0][ $wp_user_field_name ] = get_current_user_id();
947
+		}
948
+		return $query_params;
949
+	}
950
+
951
+
952
+	/**
953
+	 * Returns the name of the field's name that points to the WP_User table
954
+	 *  on this model (or follows the _model_chain_to_wp_user and uses that model's
955
+	 * foreign key to the WP_User table)
956
+	 *
957
+	 * @return string|boolean string on success, boolean false when there is no
958
+	 * foreign key to the WP_User table
959
+	 * @throws ReflectionException
960
+	 * @throws ReflectionException
961
+	 */
962
+	public function wp_user_field_name()
963
+	{
964
+		try {
965
+			if (! empty($this->_model_chain_to_wp_user)) {
966
+				$models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
967
+				$last_model_name              = end($models_to_follow_to_wp_users);
968
+				$model_with_fk_to_wp_users    = EE_Registry::instance()->load_model($last_model_name);
969
+				$model_chain_to_wp_user       = $this->_model_chain_to_wp_user . '.';
970
+			} else {
971
+				$model_with_fk_to_wp_users = $this;
972
+				$model_chain_to_wp_user    = '';
973
+			}
974
+			$wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
975
+			return $model_chain_to_wp_user . $wp_user_field->get_name();
976
+		} catch (EE_Error $e) {
977
+			return false;
978
+		}
979
+	}
980
+
981
+
982
+	/**
983
+	 * Returns the _model_chain_to_wp_user string, which indicates which related model
984
+	 * (or transiently-related model) has a foreign key to the wp_users table;
985
+	 * useful for finding if model objects of this type are 'owned' by the current user.
986
+	 * This is an empty string when the foreign key is on this model and when it isn't,
987
+	 * but is only non-empty when this model's ownership is indicated by a RELATED model
988
+	 * (or transiently-related model)
989
+	 *
990
+	 * @return string
991
+	 */
992
+	public function model_chain_to_wp_user()
993
+	{
994
+		return $this->_model_chain_to_wp_user;
995
+	}
996
+
997
+
998
+	/**
999
+	 * Whether this model is 'owned' by a specific wordpress user (even indirectly,
1000
+	 * like how registrations don't have a foreign key to wp_users, but the
1001
+	 * events they are for are), or is unrelated to wp users.
1002
+	 * generally available
1003
+	 *
1004
+	 * @return boolean
1005
+	 */
1006
+	public function is_owned()
1007
+	{
1008
+		if ($this->model_chain_to_wp_user()) {
1009
+			return true;
1010
+		}
1011
+		try {
1012
+			$this->get_foreign_key_to('WP_User');
1013
+			return true;
1014
+		} catch (EE_Error $e) {
1015
+			return false;
1016
+		}
1017
+	}
1018
+
1019
+
1020
+	/**
1021
+	 * Used internally to get WPDB results, because other functions, besides get_all, may want to do some queries, but
1022
+	 * may want to preserve the WPDB results (eg, update, which first queries to make sure we have all the tables on
1023
+	 * the model)
1024
+	 *
1025
+	 * @param array  $query_params      @see
1026
+	 *                                  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1027
+	 * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1028
+	 * @param mixed  $columns_to_select What columns to select. By default, we select all columns specified by the
1029
+	 *                                  fields on the model, and the models we joined to in the query. However, you can
1030
+	 *                                  override this and set the select to "*", or a specific column name, like
1031
+	 *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1032
+	 *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1033
+	 *                                  the aliases used to refer to this selection, and values are to be
1034
+	 *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1035
+	 *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1036
+	 * @return array | stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1037
+	 * @throws EE_Error
1038
+	 * @throws InvalidArgumentException
1039
+	 * @throws ReflectionException
1040
+	 */
1041
+	protected function _get_all_wpdb_results($query_params = [], $output = ARRAY_A, $columns_to_select = null)
1042
+	{
1043
+		$this->_custom_selections = $this->getCustomSelection($query_params, $columns_to_select);
1044
+		$model_query_info         = $this->_create_model_query_info_carrier($query_params);
1045
+		$select_expressions       = $columns_to_select === null
1046
+			? $this->_construct_default_select_sql($model_query_info)
1047
+			: '';
1048
+		if ($this->_custom_selections instanceof CustomSelects) {
1049
+			$custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1050
+			$select_expressions .= $select_expressions
1051
+				? ', ' . $custom_expressions
1052
+				: $custom_expressions;
1053
+		}
1054
+
1055
+		$SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1056
+		return $this->_do_wpdb_query('get_results', [$SQL, $output]);
1057
+	}
1058
+
1059
+
1060
+	/**
1061
+	 * Get a CustomSelects object if the $query_params or $columns_to_select allows for it.
1062
+	 * Note: $query_params['extra_selects'] will always override any $columns_to_select values. It is the preferred
1063
+	 * method of including extra select information.
1064
+	 *
1065
+	 * @param array             $query_params
1066
+	 * @param null|array|string $columns_to_select
1067
+	 * @return null|CustomSelects
1068
+	 * @throws InvalidArgumentException
1069
+	 */
1070
+	protected function getCustomSelection(array $query_params, $columns_to_select = null): ?CustomSelects
1071
+	{
1072
+		if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1073
+			return null;
1074
+		}
1075
+		$selects = $query_params['extra_selects'] ?? $columns_to_select;
1076
+		$selects = is_string($selects)
1077
+			? explode(',', $selects)
1078
+			: $selects;
1079
+		return new CustomSelects($selects);
1080
+	}
1081
+
1082
+
1083
+	/**
1084
+	 * Gets an array of rows from the database just like $wpdb->get_results would,
1085
+	 * but you can use the model query params to more easily
1086
+	 * take care of joins, field preparation etc.
1087
+	 *
1088
+	 * @param array  $query_params      @see
1089
+	 *                                  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1090
+	 * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1091
+	 * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1092
+	 *                                  fields on the model, and the models we joined to in the query. However, you can
1093
+	 *                                  override this and set the select to "*", or a specific column name, like
1094
+	 *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1095
+	 *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1096
+	 *                                  the aliases used to refer to this selection, and values are to be
1097
+	 *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1098
+	 *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1099
+	 * @return array|stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1100
+	 * @throws EE_Error
1101
+	 * @throws ReflectionException
1102
+	 */
1103
+	public function get_all_wpdb_results($query_params = [], $output = ARRAY_A, $columns_to_select = null)
1104
+	{
1105
+		return $this->_get_all_wpdb_results($query_params, $output, $columns_to_select);
1106
+	}
1107
+
1108
+
1109
+	/**
1110
+	 * For creating a custom select statement
1111
+	 *
1112
+	 * @param mixed $columns_to_select either a string to be inserted directly as the select statement,
1113
+	 *                                 or an array where keys are aliases, and values are arrays where 0=>the selection
1114
+	 *                                 SQL, and 1=>is the datatype
1115
+	 * @return string
1116
+	 * @throws EE_Error
1117
+	 */
1118
+	private function _construct_select_from_input($columns_to_select)
1119
+	{
1120
+		if (is_array($columns_to_select)) {
1121
+			$select_sql_array = [];
1122
+			foreach ($columns_to_select as $alias => $selection_and_datatype) {
1123
+				if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1124
+					throw new EE_Error(
1125
+						sprintf(
1126
+							esc_html__(
1127
+								"Custom selection %s (alias %s) needs to be an array like array('COUNT(REG_ID)','%%d')",
1128
+								'event_espresso'
1129
+							),
1130
+							$selection_and_datatype,
1131
+							$alias
1132
+						)
1133
+					);
1134
+				}
1135
+				if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1136
+					throw new EE_Error(
1137
+						sprintf(
1138
+							esc_html__(
1139
+								"Datatype %s (for selection '%s' and alias '%s') is not a valid wpdb datatype (eg %%s)",
1140
+								'event_espresso'
1141
+							),
1142
+							$selection_and_datatype[1],
1143
+							$selection_and_datatype[0],
1144
+							$alias,
1145
+							implode(', ', $this->_valid_wpdb_data_types)
1146
+						)
1147
+					);
1148
+				}
1149
+				$select_sql_array[] = "{$selection_and_datatype[0]} AS $alias";
1150
+			}
1151
+			$columns_to_select_string = implode(', ', $select_sql_array);
1152
+		} else {
1153
+			$columns_to_select_string = $columns_to_select;
1154
+		}
1155
+		return $columns_to_select_string;
1156
+	}
1157
+
1158
+
1159
+	/**
1160
+	 * Convenient wrapper for getting the primary key field's name. Eg, on Registration, this would be 'REG_ID'
1161
+	 *
1162
+	 * @return string
1163
+	 * @throws EE_Error
1164
+	 */
1165
+	public function primary_key_name()
1166
+	{
1167
+		return $this->get_primary_key_field()->get_name();
1168
+	}
1169
+
1170
+
1171
+	/**
1172
+	 * Gets a single item for this model from the DB, given only its ID (or null if none is found).
1173
+	 * If there is no primary key on this model, $id is treated as primary key string
1174
+	 *
1175
+	 * @param mixed $id int or string, depending on the type of the model's primary key
1176
+	 * @return EE_Base_Class|mixed|null
1177
+	 * @throws EE_Error
1178
+	 * @throws ReflectionException
1179
+	 */
1180
+	public function get_one_by_ID($id)
1181
+	{
1182
+		// since entities with no ID can still have properties, we need to check the cache for them
1183
+		$cached_value = $this->get_from_entity_map($id);
1184
+		if ($cached_value) {
1185
+			return $cached_value;
1186
+		}
1187
+		// but if no cached property AND no id is passed, just return null
1188
+		if (empty($id)) {
1189
+			return null;
1190
+		}
1191
+		$model_object = $this->get_one(
1192
+			$this->alter_query_params_to_restrict_by_ID(
1193
+				$id,
1194
+				['default_where_conditions' => EE_Default_Where_Conditions::MINIMUM_ALL]
1195
+			)
1196
+		);
1197
+		$className    = $this->_get_class_name();
1198
+		if ($model_object instanceof $className) {
1199
+			// make sure valid objects get added to the entity map
1200
+			// so that the next call to this method doesn't trigger another trip to the db
1201
+			$this->add_to_entity_map($model_object);
1202
+		}
1203
+		return $model_object;
1204
+	}
1205
+
1206
+
1207
+	/**
1208
+	 * Alters query parameters to only get items with this ID are returned.
1209
+	 * Takes into account that the ID might be a string produced by EEM_Base::get_index_primary_key_string(),
1210
+	 * or could just be a simple primary key ID
1211
+	 *
1212
+	 * @param int   $id
1213
+	 * @param array $query_params
1214
+	 * @return array of normal query params, @see
1215
+	 *               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1216
+	 * @throws EE_Error
1217
+	 */
1218
+	public function alter_query_params_to_restrict_by_ID($id, $query_params = [])
1219
+	{
1220
+		if (! isset($query_params[0])) {
1221
+			$query_params[0] = [];
1222
+		}
1223
+		$conditions_from_id = $this->parse_index_primary_key_string($id);
1224
+		if ($conditions_from_id === null) {
1225
+			$query_params[0][ $this->primary_key_name() ] = $id;
1226
+		} else {
1227
+			// no primary key, so the $id must be from the get_index_primary_key_string()
1228
+			$query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
1229
+		}
1230
+		return $query_params;
1231
+	}
1232
+
1233
+
1234
+	/**
1235
+	 * Gets a single item for this model from the DB, given the $query_params. Only returns a single class, not an
1236
+	 * array. If no item is found, null is returned.
1237
+	 *
1238
+	 * @param array $query_params like EEM_Base's $query_params variable.
1239
+	 * @return EE_Base_Class|EE_Soft_Delete_Base_Class|NULL
1240
+	 * @throws EE_Error
1241
+	 * @throws ReflectionException
1242
+	 */
1243
+	public function get_one($query_params = [])
1244
+	{
1245
+		if (! is_array($query_params)) {
1246
+			EE_Error::doing_it_wrong(
1247
+				'EEM_Base::get_one',
1248
+				sprintf(
1249
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1250
+					gettype($query_params)
1251
+				),
1252
+				'4.6.0'
1253
+			);
1254
+			$query_params = [];
1255
+		}
1256
+		$query_params['limit'] = 1;
1257
+		$items                 = $this->get_all($query_params);
1258
+		if (empty($items)) {
1259
+			return null;
1260
+		}
1261
+		return array_shift($items);
1262
+	}
1263
+
1264
+
1265
+	/**
1266
+	 * Returns the next x number of items in sequence from the given value as
1267
+	 * found in the database matching the given query conditions.
1268
+	 *
1269
+	 * @param mixed $current_field_value    Value used for the reference point.
1270
+	 * @param null  $field_to_order_by      What field is used for the
1271
+	 *                                      reference point.
1272
+	 * @param int   $limit                  How many to return.
1273
+	 * @param array $query_params           Extra conditions on the query.
1274
+	 * @param null  $columns_to_select      If left null, then an array of
1275
+	 *                                      EE_Base_Class objects is returned,
1276
+	 *                                      otherwise you can indicate just the
1277
+	 *                                      columns you want returned.
1278
+	 * @return EE_Base_Class[]|array
1279
+	 * @throws EE_Error
1280
+	 * @throws ReflectionException
1281
+	 */
1282
+	public function next_x(
1283
+		$current_field_value,
1284
+		$field_to_order_by = null,
1285
+		$limit = 1,
1286
+		$query_params = [],
1287
+		$columns_to_select = null
1288
+	) {
1289
+		return $this->_get_consecutive(
1290
+			$current_field_value,
1291
+			'>',
1292
+			$field_to_order_by,
1293
+			$limit,
1294
+			$query_params,
1295
+			$columns_to_select
1296
+		);
1297
+	}
1298
+
1299
+
1300
+	/**
1301
+	 * Returns the previous x number of items in sequence from the given value
1302
+	 * as found in the database matching the given query conditions.
1303
+	 *
1304
+	 * @param mixed $current_field_value    Value used for the reference point.
1305
+	 * @param null  $field_to_order_by      What field is used for the
1306
+	 *                                      reference point.
1307
+	 * @param int   $limit                  How many to return.
1308
+	 * @param array $query_params           Extra conditions on the query.
1309
+	 * @param null  $columns_to_select      If left null, then an array of
1310
+	 *                                      EE_Base_Class objects is returned,
1311
+	 *                                      otherwise you can indicate just the
1312
+	 *                                      columns you want returned.
1313
+	 * @return EE_Base_Class[]|array
1314
+	 * @throws EE_Error
1315
+	 * @throws ReflectionException
1316
+	 */
1317
+	public function previous_x(
1318
+		$current_field_value,
1319
+		$field_to_order_by = null,
1320
+		$limit = 1,
1321
+		$query_params = [],
1322
+		$columns_to_select = null
1323
+	) {
1324
+		return $this->_get_consecutive(
1325
+			$current_field_value,
1326
+			'<',
1327
+			$field_to_order_by,
1328
+			$limit,
1329
+			$query_params,
1330
+			$columns_to_select
1331
+		);
1332
+	}
1333
+
1334
+
1335
+	/**
1336
+	 * Returns the next item in sequence from the given value as found in the
1337
+	 * database matching the given query conditions.
1338
+	 *
1339
+	 * @param mixed $current_field_value    Value used for the reference point.
1340
+	 * @param null  $field_to_order_by      What field is used for the
1341
+	 *                                      reference point.
1342
+	 * @param array $query_params           Extra conditions on the query.
1343
+	 * @param null  $columns_to_select      If left null, then an EE_Base_Class
1344
+	 *                                      object is returned, otherwise you
1345
+	 *                                      can indicate just the columns you
1346
+	 *                                      want and a single array indexed by
1347
+	 *                                      the columns will be returned.
1348
+	 * @return EE_Base_Class|null|array()
1349
+	 * @throws EE_Error
1350
+	 * @throws ReflectionException
1351
+	 */
1352
+	public function next(
1353
+		$current_field_value,
1354
+		$field_to_order_by = null,
1355
+		$query_params = [],
1356
+		$columns_to_select = null
1357
+	) {
1358
+		$results = $this->_get_consecutive(
1359
+			$current_field_value,
1360
+			'>',
1361
+			$field_to_order_by,
1362
+			1,
1363
+			$query_params,
1364
+			$columns_to_select
1365
+		);
1366
+		return empty($results)
1367
+			? null
1368
+			: reset($results);
1369
+	}
1370
+
1371
+
1372
+	/**
1373
+	 * Returns the previous item in sequence from the given value as found in
1374
+	 * the database matching the given query conditions.
1375
+	 *
1376
+	 * @param mixed $current_field_value    Value used for the reference point.
1377
+	 * @param null  $field_to_order_by      What field is used for the
1378
+	 *                                      reference point.
1379
+	 * @param array $query_params           Extra conditions on the query.
1380
+	 * @param null  $columns_to_select      If left null, then an EE_Base_Class
1381
+	 *                                      object is returned, otherwise you
1382
+	 *                                      can indicate just the columns you
1383
+	 *                                      want and a single array indexed by
1384
+	 *                                      the columns will be returned.
1385
+	 * @return EE_Base_Class|null|array()
1386
+	 * @throws EE_Error
1387
+	 * @throws ReflectionException
1388
+	 */
1389
+	public function previous(
1390
+		$current_field_value,
1391
+		$field_to_order_by = null,
1392
+		$query_params = [],
1393
+		$columns_to_select = null
1394
+	) {
1395
+		$results = $this->_get_consecutive(
1396
+			$current_field_value,
1397
+			'<',
1398
+			$field_to_order_by,
1399
+			1,
1400
+			$query_params,
1401
+			$columns_to_select
1402
+		);
1403
+		return empty($results)
1404
+			? null
1405
+			: reset($results);
1406
+	}
1407
+
1408
+
1409
+	/**
1410
+	 * Returns the a consecutive number of items in sequence from the given
1411
+	 * value as found in the database matching the given query conditions.
1412
+	 *
1413
+	 * @param mixed  $current_field_value   Value used for the reference point.
1414
+	 * @param string $operand               What operand is used for the sequence.
1415
+	 * @param string $field_to_order_by     What field is used for the reference point.
1416
+	 * @param int    $limit                 How many to return.
1417
+	 * @param array  $query_params          Extra conditions on the query.
1418
+	 * @param null   $columns_to_select     If left null, then an array of EE_Base_Class objects is returned,
1419
+	 *                                      otherwise you can indicate just the columns you want returned.
1420
+	 * @return EE_Base_Class[]|array
1421
+	 * @throws EE_Error
1422
+	 * @throws ReflectionException
1423
+	 */
1424
+	protected function _get_consecutive(
1425
+		$current_field_value,
1426
+		$operand = '>',
1427
+		$field_to_order_by = null,
1428
+		$limit = 1,
1429
+		$query_params = [],
1430
+		$columns_to_select = null
1431
+	) {
1432
+		// if $field_to_order_by is empty then let's assume we're ordering by the primary key.
1433
+		if (empty($field_to_order_by)) {
1434
+			if ($this->has_primary_key_field()) {
1435
+				$field_to_order_by = $this->get_primary_key_field()->get_name();
1436
+			} else {
1437
+				if (defined('WP_DEBUG') && WP_DEBUG) {
1438
+					throw new EE_Error(
1439
+						esc_html__(
1440
+							'EEM_Base::_get_consecutive() has been called with no $field_to_order_by argument and there is no primary key on the field.  Please provide the field you would like to use as the base for retrieving the next item(s).',
1441
+							'event_espresso'
1442
+						)
1443
+					);
1444
+				}
1445
+				EE_Error::add_error(
1446
+					esc_html__('There was an error with the query.', 'event_espresso'),
1447
+					__FILE__,
1448
+					__FUNCTION__,
1449
+					__LINE__
1450
+				);
1451
+				return [];
1452
+			}
1453
+		}
1454
+		if (! is_array($query_params)) {
1455
+			EE_Error::doing_it_wrong(
1456
+				'EEM_Base::_get_consecutive',
1457
+				sprintf(
1458
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1459
+					gettype($query_params)
1460
+				),
1461
+				'4.6.0'
1462
+			);
1463
+			$query_params = [];
1464
+		}
1465
+		// let's add the where query param for consecutive look up.
1466
+		$query_params[0][ $field_to_order_by ] = [$operand, $current_field_value];
1467
+		$query_params['limit']                 = $limit;
1468
+		// set direction
1469
+		$incoming_orderby         = isset($query_params['order_by'])
1470
+			? (array) $query_params['order_by']
1471
+			: [];
1472
+		$query_params['order_by'] = $operand === '>'
1473
+			? [$field_to_order_by => 'ASC'] + $incoming_orderby
1474
+			: [$field_to_order_by => 'DESC'] + $incoming_orderby;
1475
+		// if $columns_to_select is empty then that means we're returning EE_Base_Class objects
1476
+		if (empty($columns_to_select)) {
1477
+			return $this->get_all($query_params);
1478
+		}
1479
+		// getting just the fields
1480
+		return $this->_get_all_wpdb_results($query_params, ARRAY_A, $columns_to_select);
1481
+	}
1482
+
1483
+
1484
+	/**
1485
+	 * This sets the _timezone property after model object has been instantiated.
1486
+	 *
1487
+	 * @param string|null $timezone valid PHP DateTimeZone timezone string
1488
+	 * @throws Exception
1489
+	 */
1490
+	public function set_timezone(?string $timezone = '')
1491
+	{
1492
+		if (! $timezone) {
1493
+			return;
1494
+		}
1495
+		$this->_timezone = $timezone;
1496
+		// note we need to loop through relations and set the timezone on those objects as well.
1497
+		foreach ($this->_model_relations as $relation) {
1498
+			$relation->set_timezone($timezone);
1499
+		}
1500
+		// and finally we do the same for any datetime fields
1501
+		foreach ($this->_fields as $field) {
1502
+			if ($field instanceof EE_Datetime_Field) {
1503
+				$field->set_timezone($timezone);
1504
+			}
1505
+		}
1506
+	}
1507
+
1508
+
1509
+	/**
1510
+	 * This just returns whatever is set for the current timezone.
1511
+	 *
1512
+	 * @access public
1513
+	 * @return string
1514
+	 * @throws Exception
1515
+	 */
1516
+	public function get_timezone()
1517
+	{
1518
+		// first validate if timezone is set.  If not, then let's set it be whatever is set on the model fields.
1519
+		if (empty($this->_timezone)) {
1520
+			foreach ($this->_fields as $field) {
1521
+				if ($field instanceof EE_Datetime_Field) {
1522
+					$this->set_timezone($field->get_timezone());
1523
+					break;
1524
+				}
1525
+			}
1526
+		}
1527
+		// if timezone STILL empty then return the default timezone for the site.
1528
+		if (empty($this->_timezone)) {
1529
+			$this->set_timezone(EEH_DTT_Helper::get_timezone());
1530
+		}
1531
+		return $this->_timezone;
1532
+	}
1533
+
1534
+
1535
+	/**
1536
+	 * This returns the date formats set for the given field name and also ensures that
1537
+	 * $this->_timezone property is set correctly.
1538
+	 *
1539
+	 * @param string $field_name The name of the field the formats are being retrieved for.
1540
+	 * @param bool   $pretty     Whether to return the pretty formats (true) or not (false).
1541
+	 * @return array formats in an array with the date format first, and the time format last.
1542
+	 * @throws EE_Error   If the given field_name is not of the EE_Datetime_Field type.
1543
+	 * @since 4.6.x
1544
+	 */
1545
+	public function get_formats_for($field_name, $pretty = false)
1546
+	{
1547
+		$field_settings = $this->field_settings_for($field_name);
1548
+		// if not a valid EE_Datetime_Field then throw error
1549
+		if (! $field_settings instanceof EE_Datetime_Field) {
1550
+			throw new EE_Error(
1551
+				sprintf(
1552
+					esc_html__(
1553
+						'The field sent into EEM_Base::get_formats_for (%s) is not registered as a EE_Datetime_Field. Please check the spelling and make sure you are submitting the right field name to retrieve date_formats for.',
1554
+						'event_espresso'
1555
+					),
1556
+					$field_name
1557
+				)
1558
+			);
1559
+		}
1560
+		// while we are here, let's make sure the timezone internally in EEM_Base matches what is stored on
1561
+		// the field.
1562
+		$this->_timezone = (string) $field_settings->get_timezone();
1563
+		return [$field_settings->get_date_format($pretty), $field_settings->get_time_format($pretty)];
1564
+	}
1565
+
1566
+
1567
+	/**
1568
+	 * This returns the current time in a format setup for a query on this model.
1569
+	 * Usage of this method makes it easier to setup queries against EE_Datetime_Field columns because
1570
+	 * it will return:
1571
+	 *  - a formatted string in the timezone and format currently set on the EE_Datetime_Field for the given field for
1572
+	 *  NOW
1573
+	 *  - or a unix timestamp (equivalent to time())
1574
+	 * Note: When requesting a formatted string, if the date or time format doesn't include seconds, for example,
1575
+	 * the time returned, because it uses that format, will also NOT include seconds. For this reason, if you want
1576
+	 * the time returned to be the current time down to the exact second, set $timestamp to true.
1577
+	 *
1578
+	 * @param string $field_name       The field the current time is needed for.
1579
+	 * @param bool   $timestamp        True means to return a unix timestamp. Otherwise a
1580
+	 *                                 formatted string matching the set format for the field in the set timezone will
1581
+	 *                                 be returned.
1582
+	 * @param string $what             Whether to return the string in just the time format, the date format, or both.
1583
+	 * @return int|string  If the given field_name is not of the EE_Datetime_Field type, then an EE_Error
1584
+	 *                                 exception is triggered.
1585
+	 * @throws EE_Error    If the given field_name is not of the EE_Datetime_Field type.
1586
+	 * @throws Exception
1587
+	 * @since 4.6.x
1588
+	 */
1589
+	public function current_time_for_query($field_name, $timestamp = false, $what = 'both')
1590
+	{
1591
+		$formats  = $this->get_formats_for($field_name);
1592
+		$DateTime = new DateTime("now", new DateTimeZone($this->_timezone));
1593
+		if ($timestamp) {
1594
+			return $DateTime->format('U');
1595
+		}
1596
+		// not returning timestamp, so return formatted string in timezone.
1597
+		switch ($what) {
1598
+			case 'time':
1599
+				return $DateTime->format($formats[1]);
1600
+			case 'date':
1601
+				return $DateTime->format($formats[0]);
1602
+			default:
1603
+				return $DateTime->format(implode(' ', $formats));
1604
+		}
1605
+	}
1606
+
1607
+
1608
+	/**
1609
+	 * This receives a time string for a given field and ensures
1610
+	 * that it is set up to match what the internal settings for the model are.
1611
+	 * Returns a DateTime object.
1612
+	 * Note: a gotcha for when you send in unix timestamp.  Remember a unix timestamp is already timezone agnostic,
1613
+	 * (functionally the equivalent of UTC+0).
1614
+	 * So when you send it in, whatever timezone string you include is ignored.
1615
+	 *
1616
+	 * @param string      $field_name      The field being setup.
1617
+	 * @param string      $timestring      The date time string being used.
1618
+	 * @param string      $incoming_format The format for the time string.
1619
+	 * @param string|null $timezone_string By default, it is assumed the incoming time string is in timezone for
1620
+	 *                                     the blog.  If this is not the case, then it can be specified here.  If
1621
+	 *                                     incoming format is
1622
+	 *                                     'U', this is ignored.
1623
+	 * @return DbSafeDateTime
1624
+	 * @throws EE_Error
1625
+	 * @throws Exception
1626
+	 */
1627
+	public function convert_datetime_for_query(
1628
+		string $field_name,
1629
+		string $timestring,
1630
+		string $incoming_format,
1631
+		?string $timezone_string = ''
1632
+	): DbSafeDateTime {
1633
+		// just using this to ensure the timezone is set correctly internally
1634
+		$this->get_formats_for($field_name);
1635
+		// load EEH_DTT_Helper
1636
+		$timezone_string     = ! empty($timezone_string) ? $timezone_string : EEH_DTT_Helper::get_timezone();
1637
+		$incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($timezone_string));
1638
+		EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1639
+		return DbSafeDateTime::createFromDateTime($incomingDateTime);
1640
+	}
1641
+
1642
+
1643
+	/**
1644
+	 * Gets all the tables comprising this model. Array keys are the table aliases, and values are EE_Table objects
1645
+	 *
1646
+	 * @return EE_Table_Base[]
1647
+	 */
1648
+	public function get_tables()
1649
+	{
1650
+		return $this->_tables;
1651
+	}
1652
+
1653
+
1654
+	/**
1655
+	 * Updates all the database entries (in each table for this model) according to $fields_n_values and optionally
1656
+	 * also updates all the model objects, where the criteria expressed in $query_params are met..
1657
+	 * Also note: if this model has multiple tables, this update verifies all the secondary tables have an entry for
1658
+	 * each row (in the primary table) we're trying to update; if not, it inserts an entry in the secondary table. Eg:
1659
+	 * if our model has 2 tables: wp_posts (primary), and wp_esp_event (secondary). Let's say we are trying to update a
1660
+	 * model object with EVT_ID = 1
1661
+	 * (which means where wp_posts has ID = 1, because wp_posts.ID is the primary key's column), which exists, but
1662
+	 * there is no entry in wp_esp_event for this entry in wp_posts. So, this update script will insert a row into
1663
+	 * wp_esp_event, using any available parameters from $fields_n_values (eg, if "EVT_limit" => 40 is in
1664
+	 * $fields_n_values, the new entry in wp_esp_event will set EVT_limit = 40, and use default for other columns which
1665
+	 * are not specified)
1666
+	 *
1667
+	 * @param array   $fields_n_values         keys are model fields (exactly like keys in EEM_Base::_fields, NOT db
1668
+	 *                                         columns!), values are strings, ints, floats, and maybe arrays if they
1669
+	 *                                         are to be serialized. Basically, the values are what you'd expect to be
1670
+	 *                                         values on the model, NOT necessarily what's in the DB. For example, if
1671
+	 *                                         we wanted to update only the TXN_details on any Transactions where its
1672
+	 *                                         ID=34, we'd use this method as follows:
1673
+	 *                                         EEM_Transaction::instance()->update(
1674
+	 *                                         array('TXN_details'=>array('detail1'=>'monkey','detail2'=>'banana'),
1675
+	 *                                         array(array('TXN_ID'=>34)));
1676
+	 * @param array   $query_params            @see
1677
+	 *                                         https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1678
+	 *                                         Eg, consider updating Question's QST_admin_label field is of type
1679
+	 *                                         Simple_HTML. If you use this function to update that field to $new_value
1680
+	 *                                         = (note replace 8's with appropriate opening and closing tags in the
1681
+	 *                                         following example)"8script8alert('I hack all');8/script88b8boom
1682
+	 *                                         baby8/b8", then if you set $values_already_prepared_by_model_object to
1683
+	 *                                         TRUE, it is assumed that you've already called
1684
+	 *                                         EE_Simple_HTML_Field->prepare_for_set($new_value), which removes the
1685
+	 *                                         malicious javascript. However, if
1686
+	 *                                         $values_already_prepared_by_model_object is left as FALSE, then
1687
+	 *                                         EE_Simple_HTML_Field->prepare_for_set($new_value) will be called on it,
1688
+	 *                                         and every other field, before insertion. We provide this parameter
1689
+	 *                                         because model objects perform their prepare_for_set function on all
1690
+	 *                                         their values, and so don't need to be called again (and in many cases,
1691
+	 *                                         shouldn't be called again. Eg: if we escape HTML characters in the
1692
+	 *                                         prepare_for_set method...)
1693
+	 * @param boolean $keep_model_objs_in_sync if TRUE, makes sure we ALSO update model objects
1694
+	 *                                         in this model's entity map according to $fields_n_values that match
1695
+	 *                                         $query_params. This obviously has some overhead, so you can disable it
1696
+	 *                                         by setting this to FALSE, but be aware that model objects being used
1697
+	 *                                         could get out-of-sync with the database
1698
+	 * @return int how many rows got updated or FALSE if something went wrong with the query (wp returns FALSE or num
1699
+	 *                                         rows affected which *could* include 0 which DOES NOT mean the query was
1700
+	 *                                         bad)
1701
+	 * @throws EE_Error
1702
+	 * @throws ReflectionException
1703
+	 */
1704
+	public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1705
+	{
1706
+		if (! is_array($query_params)) {
1707
+			EE_Error::doing_it_wrong(
1708
+				'EEM_Base::update',
1709
+				sprintf(
1710
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1711
+					gettype($query_params)
1712
+				),
1713
+				'4.6.0'
1714
+			);
1715
+			$query_params = [];
1716
+		}
1717
+		/**
1718
+		 * Action called before a model update call has been made.
1719
+		 *
1720
+		 * @param EEM_Base $model
1721
+		 * @param array    $fields_n_values the updated fields and their new values
1722
+		 * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1723
+		 */
1724
+		do_action('AHEE__EEM_Base__update__begin', $this, $fields_n_values, $query_params);
1725
+		/**
1726
+		 * Filters the fields about to be updated given the query parameters. You can provide the
1727
+		 * $query_params to $this->get_all() to find exactly which records will be updated
1728
+		 *
1729
+		 * @param array    $fields_n_values fields and their new values
1730
+		 * @param EEM_Base $model           the model being queried
1731
+		 * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1732
+		 */
1733
+		$fields_n_values = (array) apply_filters(
1734
+			'FHEE__EEM_Base__update__fields_n_values',
1735
+			$fields_n_values,
1736
+			$this,
1737
+			$query_params
1738
+		);
1739
+		// need to verify that, for any entry we want to update, there are entries in each secondary table.
1740
+		// to do that, for each table, verify that it's PK isn't null.
1741
+		$tables = $this->get_tables();
1742
+		// and if the other tables don't have a row for each table-to-be-updated, we'll insert one with whatever values available in the current update query
1743
+		// NOTE: we should make this code more efficient by NOT querying twice
1744
+		// before the real update, but that needs to first go through ALPHA testing
1745
+		// as it's dangerous. says Mike August 8 2014
1746
+		// we want to make sure the default_where strategy is ignored
1747
+		$this->_ignore_where_strategy = true;
1748
+		$wpdb_select_results          = $this->_get_all_wpdb_results($query_params);
1749
+		foreach ($wpdb_select_results as $wpdb_result) {
1750
+			// type cast stdClass as array
1751
+			$wpdb_result = (array) $wpdb_result;
1752
+			// get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1753
+			if ($this->has_primary_key_field()) {
1754
+				$main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1755
+			} else {
1756
+				// if there's no primary key, we basically can't support having a 2nd table on the model (we could but it would be lots of work)
1757
+				$main_table_pk_value = null;
1758
+			}
1759
+			// if there are more than 1 tables, we'll want to verify that each table for this model has an entry in the other tables
1760
+			// and if the other tables don't have a row for each table-to-be-updated, we'll insert one with whatever values available in the current update query
1761
+			if (count($tables) > 1) {
1762
+				// foreach matching row in the DB, ensure that each table's PK isn't null. If so, there must not be an entry
1763
+				// in that table, and so we'll want to insert one
1764
+				foreach ($tables as $table_obj) {
1765
+					$this_table_pk_column = $table_obj->get_fully_qualified_pk_column();
1766
+					// if there is no private key for this table on the results, it means there's no entry
1767
+					// in this table, right? so insert a row in the current table, using any fields available
1768
+					if (
1769
+						! (array_key_exists($this_table_pk_column, $wpdb_result)
1770
+						   && $wpdb_result[ $this_table_pk_column ])
1771
+					) {
1772
+						$success = $this->_insert_into_specific_table(
1773
+							$table_obj,
1774
+							$fields_n_values,
1775
+							$main_table_pk_value
1776
+						);
1777
+						// if we died here, report the error
1778
+						if (! $success) {
1779
+							return false;
1780
+						}
1781
+					}
1782
+				}
1783
+			}
1784
+			//              //and now check that if we have cached any models by that ID on the model, that
1785
+			//              //they also get updated properly
1786
+			//              $model_object = $this->get_from_entity_map( $main_table_pk_value );
1787
+			//              if( $model_object ){
1788
+			//                  foreach( $fields_n_values as $field => $value ){
1789
+			//                      $model_object->set($field, $value);
1790
+			// let's make sure default_where strategy is followed now
1791
+			$this->_ignore_where_strategy = false;
1792
+		}
1793
+		// if we want to keep model objects in sync, AND
1794
+		// if this wasn't called from a model object (to update itself)
1795
+		// then we want to make sure we keep all the existing
1796
+		// model objects in sync with the db
1797
+		if ($keep_model_objs_in_sync && ! $this->_values_already_prepared_by_model_object) {
1798
+			if ($this->has_primary_key_field()) {
1799
+				$model_objs_affected_ids = $this->get_col($query_params);
1800
+			} else {
1801
+				// we need to select a bunch of columns and then combine them into the the "index primary key string"s
1802
+				$models_affected_key_columns = $this->_get_all_wpdb_results($query_params, ARRAY_A);
1803
+				$model_objs_affected_ids     = [];
1804
+				foreach ($models_affected_key_columns as $row) {
1805
+					$combined_index_key                             = $this->get_index_primary_key_string($row);
1806
+					$model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1807
+				}
1808
+			}
1809
+			if (! $model_objs_affected_ids) {
1810
+				// wait wait wait- if nothing was affected let's stop here
1811
+				return 0;
1812
+			}
1813
+			foreach ($model_objs_affected_ids as $id) {
1814
+				$model_obj_in_entity_map = $this->get_from_entity_map($id);
1815
+				if ($model_obj_in_entity_map) {
1816
+					foreach ($fields_n_values as $field => $new_value) {
1817
+						$model_obj_in_entity_map->set($field, $new_value);
1818
+					}
1819
+				}
1820
+			}
1821
+			// if there is a primary key on this model, we can now do a slight optimization
1822
+			if ($this->has_primary_key_field()) {
1823
+				// we already know what we want to update. So let's make the query simpler so it's a little more efficient
1824
+				$query_params = [
1825
+					[$this->primary_key_name() => ['IN', $model_objs_affected_ids]],
1826
+					'limit'                    => count($model_objs_affected_ids),
1827
+					'default_where_conditions' => EE_Default_Where_Conditions::NONE,
1828
+				];
1829
+			}
1830
+		}
1831
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
1832
+
1833
+		// note: the following query doesn't use _construct_2nd_half_of_select_query()
1834
+		// because it doesn't accept LIMIT, ORDER BY, etc.
1835
+		$rows_affected = $this->_do_wpdb_query(
1836
+			'query',
1837
+			[
1838
+				"UPDATE " . $model_query_info->get_full_join_sql()
1839
+				. " SET " . $this->_construct_update_sql($fields_n_values)
1840
+				. $model_query_info->get_where_sql(),
1841
+			]
1842
+		);
1843
+
1844
+		/**
1845
+		 * Action called after a model update call has been made.
1846
+		 *
1847
+		 * @param EEM_Base $model
1848
+		 * @param array    $fields_n_values the updated fields and their new values
1849
+		 * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1850
+		 * @param int      $rows_affected
1851
+		 */
1852
+		do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1853
+		return $rows_affected;// how many supposedly got updated
1854
+	}
1855
+
1856
+
1857
+	/**
1858
+	 * Analogous to $wpdb->get_col, returns a 1-dimensional array where the values
1859
+	 * are the values of the field specified (or by default the primary key field)
1860
+	 * that matched the query params. Note that you should pass the name of the
1861
+	 * model FIELD, not the database table's column name.
1862
+	 *
1863
+	 * @param array  $query_params
1864
+	 * @param string $field_to_select
1865
+	 * @return array just like $wpdb->get_col()
1866
+	 * @throws EE_Error
1867
+	 * @throws ReflectionException
1868
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md for $query_params values
1869
+	 */
1870
+	public function get_col($query_params = [], $field_to_select = null)
1871
+	{
1872
+		if ($field_to_select) {
1873
+			$field = $this->field_settings_for($field_to_select);
1874
+		} elseif ($this->has_primary_key_field()) {
1875
+			$field = $this->get_primary_key_field();
1876
+		} else {
1877
+			$field_settings = $this->field_settings();
1878
+			// no primary key, just grab the first column
1879
+			$field = reset($field_settings);
1880
+			// don't need this array now
1881
+			unset($field_settings);
1882
+		}
1883
+		$model_query_info   = $this->_create_model_query_info_carrier($query_params);
1884
+		$select_expressions = $field->get_qualified_column();
1885
+		$SQL                =
1886
+			"SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1887
+		return $this->_do_wpdb_query('get_col', [$SQL]);
1888
+	}
1889
+
1890
+
1891
+	/**
1892
+	 * Returns a single column value for a single row from the database
1893
+	 *
1894
+	 * @param array  $query_params    @see
1895
+	 *                                https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1896
+	 * @param string $field_to_select @see EEM_Base::get_col()
1897
+	 * @return string
1898
+	 * @throws EE_Error
1899
+	 * @throws ReflectionException
1900
+	 */
1901
+	public function get_var($query_params = [], $field_to_select = null)
1902
+	{
1903
+		$query_params['limit'] = 1;
1904
+		$col                   = $this->get_col($query_params, $field_to_select);
1905
+		if (! empty($col)) {
1906
+			return reset($col);
1907
+		}
1908
+		return null;
1909
+	}
1910
+
1911
+
1912
+	/**
1913
+	 * Makes the SQL for after "UPDATE table_X inner join table_Y..." and before "...WHERE". Eg "Question.name='party
1914
+	 * time?', Question.desc='what do you think?',..." Values are filtered through wpdb->prepare to avoid against SQL
1915
+	 * injection, but currently no further filtering is done
1916
+	 *
1917
+	 * @param array $fields_n_values array keys are field names on this model, and values are what those fields should
1918
+	 *                               be updated to in the DB
1919
+	 * @return string of SQL
1920
+	 * @throws EE_Error
1921
+	 * @global      $wpdb
1922
+	 */
1923
+	public function _construct_update_sql($fields_n_values)
1924
+	{
1925
+		/** @type WPDB $wpdb */
1926
+		global $wpdb;
1927
+		$cols_n_values = [];
1928
+		foreach ($fields_n_values as $field_name => $value) {
1929
+			$field_obj = $this->field_settings_for($field_name);
1930
+			// if the value is NULL, we want to assign the value to that.
1931
+			// wpdb->prepare doesn't really handle that properly
1932
+			$prepared_value  = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1933
+			$value_sql       = $prepared_value === null
1934
+				? 'NULL'
1935
+				: $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1936
+			$cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1937
+		}
1938
+		return implode(",", $cols_n_values);
1939
+	}
1940
+
1941
+
1942
+	/**
1943
+	 * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1944
+	 * Performs a HARD delete, meaning the database row should always be removed,
1945
+	 * not just have a flag field on it switched
1946
+	 * Wrapper for EEM_Base::delete_permanently()
1947
+	 *
1948
+	 * @param mixed $id
1949
+	 * @param bool  $block_deletes whether to allow related model objects to block (prevent) this deletion
1950
+	 *                             ie: enforce referential integrity
1951
+	 *                             It's advisable to always leave this as TRUE, otherwise you could corrupt your DB
1952
+	 * @return int the number of rows deleted
1953
+	 * @throws EE_Error
1954
+	 * @throws ReflectionException
1955
+	 */
1956
+	public function delete_permanently_by_ID($id, $block_deletes = true): int
1957
+	{
1958
+		return $this->delete_permanently(
1959
+			[
1960
+				[$this->get_primary_key_field()->get_name() => $id],
1961
+				'limit' => 1,
1962
+			],
1963
+			$block_deletes
1964
+		);
1965
+	}
1966
+
1967
+
1968
+	/**
1969
+	 * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1970
+	 * Wrapper for EEM_Base::delete()
1971
+	 *
1972
+	 * @param mixed $id
1973
+	 * @param bool  $block_deletes whether to allow related model objects to block (prevent) this deletion
1974
+	 *                             ie: enforce referential integrity
1975
+	 *                             It's advisable to always leave this as TRUE, otherwise you could corrupt your DB
1976
+	 * @return int the number of rows deleted
1977
+	 * @throws EE_Error
1978
+	 * @throws ReflectionException
1979
+	 */
1980
+	public function delete_by_ID($id, $block_deletes = true)
1981
+	{
1982
+		return $this->delete(
1983
+			[
1984
+				[$this->get_primary_key_field()->get_name() => $id],
1985
+				'limit' => 1,
1986
+			],
1987
+			$block_deletes
1988
+		);
1989
+	}
1990
+
1991
+
1992
+	/**
1993
+	 * Identical to delete_permanently, but does a "soft" delete if possible,
1994
+	 * meaning if the model has a field that indicates its been "trashed" or
1995
+	 * "soft deleted", we will just set that instead of actually deleting the rows.
1996
+	 *
1997
+	 * @param array   $query_params
1998
+	 * @param boolean $block_deletes whether to allow related model objects to block (prevent) this deletion
1999
+	 *                               ie: enforce referential integrity
2000
+	 *                               It's advisable to always leave this as TRUE, otherwise you could corrupt your DB
2001
+	 * @return int how many rows got deleted
2002
+	 * @throws EE_Error
2003
+	 * @throws ReflectionException
2004
+	 * @see EEM_Base::delete_permanently
2005
+	 */
2006
+	public function delete($query_params, $block_deletes = true)
2007
+	{
2008
+		return $this->delete_permanently($query_params, $block_deletes);
2009
+	}
2010
+
2011
+
2012
+	/**
2013
+	 * Deletes the model objects that meet the query params. Note: this method is overridden
2014
+	 * in EEM_Soft_Delete_Base so that soft-deleted model objects are instead only flagged
2015
+	 * as archived, not actually deleted
2016
+	 *
2017
+	 * @param array   $query_params
2018
+	 * @param boolean $block_deletes  whether to allow related model objects to block (prevent) this deletion
2019
+	 *                                ie: enforce referential integrity
2020
+	 *                                It's advisable to always leave this as TRUE, otherwise you could corrupt your DB
2021
+	 * @return int how many rows got deleted
2022
+	 * @throws EE_Error
2023
+	 * @throws ReflectionException
2024
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2025
+	 */
2026
+	public function delete_permanently($query_params, $block_deletes = true): int
2027
+	{
2028
+		/**
2029
+		 * Action called just before performing a real deletion query. You can use the
2030
+		 * model and its $query_params to find exactly which items will be deleted
2031
+		 *
2032
+		 * @param EEM_Base $model
2033
+		 * @param array    $query_params  The incoming array of query parameters influencing what gets deleted.
2034
+		 * @param bool     $block_deletes @see param description in method phpdoc block.
2035
+		 */
2036
+		do_action('AHEE__EEM_Base__delete__begin', $this, $query_params, $block_deletes);
2037
+		// some MySQL databases may be running safe mode, which may restrict
2038
+		// deletion if there is no KEY column used in the WHERE statement of a deletion.
2039
+		// to get around this, we first do a SELECT, get all the IDs, and then run another query
2040
+		// to delete them
2041
+		$items_for_deletion           = $this->_get_all_wpdb_results($query_params);
2042
+		$columns_and_ids_for_deleting = $this->_get_ids_for_delete($items_for_deletion, $block_deletes);
2043
+		$deletion_where_query_part    = $this->_build_query_part_for_deleting_from_columns_and_values(
2044
+			$columns_and_ids_for_deleting
2045
+		);
2046
+		/**
2047
+		 * Allows client code to act on the items being deleted before the query is actually executed.
2048
+		 * see php doc blocks for more details
2049
+		 *
2050
+		 * @param EEM_Base $this                         The model instance being acted on.
2051
+		 * @param array    $query_params                 The incoming array of query parameters influencing what gets deleted.
2052
+		 * @param bool     $block_deletes                @see param description in method phpdoc block.
2053
+		 * @param array    $columns_and_ids_for_deleting An array indicating what entities will get removed as
2054
+		 *                                               derived from the incoming query parameters.
2055
+		 * @see details on the structure of this array in the phpdocs for the `_get_ids_for_delete_method`
2056
+		 */
2057
+		do_action(
2058
+			'AHEE__EEM_Base__delete__before_query',
2059
+			$this,
2060
+			$query_params,
2061
+			$block_deletes,
2062
+			$columns_and_ids_for_deleting
2063
+		);
2064
+		$rows_deleted = 0;
2065
+		if ($deletion_where_query_part) {
2066
+			$model_query_info = $this->_create_model_query_info_carrier($query_params);
2067
+			$table_aliases    = array_keys($this->_tables);
2068
+			$SQL              = "DELETE "
2069
+								. implode(", ", $table_aliases)
2070
+								. " FROM "
2071
+								. $model_query_info->get_full_join_sql()
2072
+								. " WHERE "
2073
+								. $deletion_where_query_part;
2074
+			$rows_deleted     = $this->_do_wpdb_query('query', [$SQL]);
2075
+		}
2076
+
2077
+		// Next, make sure those items are removed from the entity map; if they could be put into it at all; and if
2078
+		// there was no error with the delete query.
2079
+		if (
2080
+			$this->has_primary_key_field()
2081
+			&& $rows_deleted !== false
2082
+			&& isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2083
+		) {
2084
+			$ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2085
+			foreach ($ids_for_removal as $id) {
2086
+				if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2087
+					unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2088
+				}
2089
+			}
2090
+
2091
+			// delete any extra meta attached to the deleted entities but ONLY if this model is not an instance of
2092
+			// `EEM_Extra_Meta`.  In other words we want to prevent recursion on EEM_Extra_Meta::delete_permanently calls
2093
+			// unnecessarily.  It's very unlikely that users will have assigned Extra Meta to Extra Meta
2094
+			// (although it is possible).
2095
+			// Note this can be skipped by using the provided filter and returning false.
2096
+			if (
2097
+				apply_filters(
2098
+					'FHEE__EEM_Base__delete_permanently__dont_delete_extra_meta_for_extra_meta',
2099
+					! $this instanceof EEM_Extra_Meta,
2100
+					$this
2101
+				)
2102
+			) {
2103
+				EEM_Extra_Meta::instance()->delete_permanently(
2104
+					[
2105
+						0 => [
2106
+							'EXM_type' => $this->get_this_model_name(),
2107
+							'OBJ_ID'   => [
2108
+								'IN',
2109
+								$ids_for_removal,
2110
+							],
2111
+						],
2112
+					]
2113
+				);
2114
+			}
2115
+		}
2116
+
2117
+		/**
2118
+		 * Action called just after performing a real deletion query. Although at this point the
2119
+		 * items should have been deleted
2120
+		 *
2121
+		 * @param EEM_Base $model
2122
+		 * @param array $query_params
2123
+		 * @param int   $rows_deleted
2124
+		 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2125
+		 */
2126
+		do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2127
+		return (int) $rows_deleted;// how many supposedly got deleted
2128
+	}
2129
+
2130
+
2131
+	/**
2132
+	 * Checks all the relations that throw error messages when there are blocking related objects
2133
+	 * for related model objects. If there are any related model objects on those relations,
2134
+	 * adds an EE_Error, and return true
2135
+	 *
2136
+	 * @param EE_Base_Class|int $this_model_obj_or_id
2137
+	 * @param EE_Base_Class     $ignore_this_model_obj a model object like 'EE_Event', or 'EE_Term_Taxonomy', which
2138
+	 *                                                 should be ignored when determining whether there are related
2139
+	 *                                                 model objects which block this model object's deletion. Useful
2140
+	 *                                                 if you know A is related to B and are considering deleting A,
2141
+	 *                                                 but want to see if A has any other objects blocking its deletion
2142
+	 *                                                 before removing the relation between A and B
2143
+	 * @return boolean
2144
+	 * @throws EE_Error
2145
+	 * @throws ReflectionException
2146
+	 */
2147
+	public function delete_is_blocked_by_related_models($this_model_obj_or_id, $ignore_this_model_obj = null)
2148
+	{
2149
+		// first, if $ignore_this_model_obj was supplied, get its model
2150
+		$ignored_model = $ignore_this_model_obj instanceof EE_Base_Class
2151
+			? $ignore_this_model_obj->get_model()
2152
+			: null;
2153
+		// now check all the relations of $this_model_obj_or_id and see if there
2154
+		// are any related model objects blocking it?
2155
+		$is_blocked = false;
2156
+		foreach ($this->_model_relations as $relation_name => $relation_obj) {
2157
+			if ($relation_obj->block_delete_if_related_models_exist()) {
2158
+				// if $ignore_this_model_obj was supplied, then for the query
2159
+				// on that model needs to be told to ignore $ignore_this_model_obj
2160
+				if ($ignored_model && $relation_name === $ignored_model->get_this_model_name()) {
2161
+					$related_model_objects = $relation_obj->get_all_related(
2162
+						$this_model_obj_or_id,
2163
+						[
2164
+							[
2165
+								$ignored_model->get_primary_key_field()->get_name() => [
2166
+									'!=',
2167
+									$ignore_this_model_obj->ID(),
2168
+								],
2169
+							],
2170
+						]
2171
+					);
2172
+				} else {
2173
+					$related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id);
2174
+				}
2175
+				if ($related_model_objects) {
2176
+					EE_Error::add_error($relation_obj->get_deletion_error_message(), __FILE__, __FUNCTION__, __LINE__);
2177
+					$is_blocked = true;
2178
+				}
2179
+			}
2180
+		}
2181
+		return $is_blocked;
2182
+	}
2183
+
2184
+
2185
+	/**
2186
+	 * Builds the columns and values for items to delete from the incoming $row_results_for_deleting array.
2187
+	 *
2188
+	 * @param array $row_results_for_deleting
2189
+	 * @param bool  $block_deletes whether to allow related model objects to block (prevent) this deletion
2190
+	 *                             ie: enforce referential integrity
2191
+	 *                             It's advisable to always leave this as TRUE, otherwise you could corrupt your DB
2192
+	 * @return array               The shape of this array depends on whether the model `has_primary_key_field` or not.
2193
+	 *                             If the model DOES have a primary_key_field, then the array will be a simple single
2194
+	 *                             dimension array where the key is the fully qualified primary key column and
2195
+	 *                             the value is an array of ids that will be deleted.
2196
+	 *                             Example:
2197
+	 *                              [ 'Event.EVT_ID' => [ 1,2,3 ]]
2198
+	 *                             If the model DOES NOT have a primary_key_field, then the array will be a
2199
+	 *                             two-dimensional array where each element is a group of columns and values that get deleted.
2200
+	 *                             Example:
2201
+	 *                              [
2202
+	 *                                  0 => [
2203
+	 *                                      'Term_Relationship.object_id' => 1
2204
+	 *                                      'Term_Relationship.term_taxonomy_id' => 5
2205
+	 *                                  ],
2206
+	 *                                  1 => [
2207
+	 *                                      'Term_Relationship.object_id' => 1
2208
+	 *                                      'Term_Relationship.term_taxonomy_id' => 6
2209
+	 *                                  ]
2210
+	 *                              ]
2211
+	 * @throws EE_Error
2212
+	 * @throws ReflectionException
2213
+	 */
2214
+	protected function _get_ids_for_delete(array $row_results_for_deleting, $block_deletes = true)
2215
+	{
2216
+		$ids_to_delete_indexed_by_column = [];
2217
+		if ($this->has_primary_key_field()) {
2218
+			$primary_table = $this->_get_main_table();
2219
+			// following lines are commented out because the variables were not being used
2220
+			// not deleting because unsure if calls were intentionally causing side effects
2221
+			// $primary_table_pk_field          =
2222
+			//     $this->get_field_by_column($primary_table->get_fully_qualified_pk_column());
2223
+			// $other_tables                    = $this->_get_other_tables();
2224
+			$ids_to_delete_indexed_by_column = $query = [];
2225
+			foreach ($row_results_for_deleting as $item_to_delete) {
2226
+				// before we mark this item for deletion,
2227
+				// make sure there's no related entities blocking its deletion (if we're checking)
2228
+				if (
2229
+					$block_deletes
2230
+					&& $this->delete_is_blocked_by_related_models(
2231
+						$item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2232
+					)
2233
+				) {
2234
+					continue;
2235
+				}
2236
+				// primary table deletes
2237
+				if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2238
+					$ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2239
+						$item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2240
+				}
2241
+			}
2242
+		} elseif (count($this->get_combined_primary_key_fields()) > 1) {
2243
+			$fields = $this->get_combined_primary_key_fields();
2244
+			foreach ($row_results_for_deleting as $item_to_delete) {
2245
+				$ids_to_delete_indexed_by_column_for_row = [];
2246
+				foreach ($fields as $cpk_field) {
2247
+					if ($cpk_field instanceof EE_Model_Field_Base) {
2248
+						$ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2249
+							$item_to_delete[ $cpk_field->get_qualified_column() ];
2250
+					}
2251
+				}
2252
+				$ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
2253
+			}
2254
+		} else {
2255
+			// so there's no primary key and no combined key...
2256
+			// sorry, can't help you
2257
+			throw new EE_Error(
2258
+				sprintf(
2259
+					esc_html__(
2260
+						"Cannot delete objects of type %s because there is no primary key NOR combined key",
2261
+						"event_espresso"
2262
+					),
2263
+					$this->class_name
2264
+				)
2265
+			);
2266
+		}
2267
+		return $ids_to_delete_indexed_by_column;
2268
+	}
2269
+
2270
+
2271
+	/**
2272
+	 * This receives an array of columns and values set to be deleted (as prepared by _get_ids_for_delete) and prepares
2273
+	 * the corresponding query_part for the query performing the deletion.
2274
+	 *
2275
+	 * @param array $ids_to_delete_indexed_by_column @see _get_ids_for_delete for how this array might be shaped.
2276
+	 * @return string
2277
+	 * @throws EE_Error
2278
+	 */
2279
+	protected function _build_query_part_for_deleting_from_columns_and_values(array $ids_to_delete_indexed_by_column)
2280
+	{
2281
+		$query_part = '';
2282
+		if (empty($ids_to_delete_indexed_by_column)) {
2283
+			return $query_part;
2284
+		} elseif ($this->has_primary_key_field()) {
2285
+			$query = [];
2286
+			foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2287
+				$query[] = $column . ' IN' . $this->_construct_in_value($ids, $this->_primary_key_field);
2288
+			}
2289
+			$query_part = ! empty($query)
2290
+				? implode(' AND ', $query)
2291
+				: $query_part;
2292
+		} elseif (count($this->get_combined_primary_key_fields()) > 1) {
2293
+			$ways_to_identify_a_row = [];
2294
+			foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2295
+				$values_for_each_combined_primary_key_for_a_row = [];
2296
+				foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2297
+					$values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2298
+				}
2299
+				$ways_to_identify_a_row[] = '('
2300
+											. implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
2301
+											. ')';
2302
+			}
2303
+			$query_part = implode(' OR ', $ways_to_identify_a_row);
2304
+		}
2305
+		return $query_part;
2306
+	}
2307
+
2308
+
2309
+	/**
2310
+	 * Gets the model field by the fully qualified name
2311
+	 *
2312
+	 * @param string $qualified_column_name eg 'Event_CPT.post_name' or $field_obj->get_qualified_column()
2313
+	 * @return EE_Model_Field_Base
2314
+	 * @throws EE_Error
2315
+	 * @throws EE_Error
2316
+	 */
2317
+	public function get_field_by_column($qualified_column_name)
2318
+	{
2319
+		foreach ($this->field_settings(true) as $field_name => $field_obj) {
2320
+			if ($field_obj->get_qualified_column() === $qualified_column_name) {
2321
+				return $field_obj;
2322
+			}
2323
+		}
2324
+		throw new EE_Error(
2325
+			sprintf(
2326
+				esc_html__('Could not find a field on the model "%1$s" for qualified column "%2$s"', 'event_espresso'),
2327
+				$this->get_this_model_name(),
2328
+				$qualified_column_name
2329
+			)
2330
+		);
2331
+	}
2332
+
2333
+
2334
+	/**
2335
+	 * Count all the rows that match criteria the model query params.
2336
+	 * If $field_to_count isn't provided, the model's primary key is used. Otherwise, we count by field_to_count's
2337
+	 * column
2338
+	 *
2339
+	 * @param array  $query_params   @see
2340
+	 *                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2341
+	 * @param string $field_to_count field on model to count by (not column name)
2342
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2343
+	 *                               that by the setting $distinct to TRUE;
2344
+	 * @return int
2345
+	 * @throws EE_Error
2346
+	 * @throws ReflectionException
2347
+	 */
2348
+	public function count($query_params = [], $field_to_count = '', $distinct = false)
2349
+	{
2350
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
2351
+		if ($field_to_count) {
2352
+			$field_obj       = $this->field_settings_for($field_to_count);
2353
+			$column_to_count = $field_obj->get_qualified_column();
2354
+		} elseif ($this->has_primary_key_field()) {
2355
+			$pk_field_obj    = $this->get_primary_key_field();
2356
+			$column_to_count = $pk_field_obj->get_qualified_column();
2357
+		} else {
2358
+			// there's no primary key
2359
+			// if we're counting distinct items, and there's no primary key,
2360
+			// we need to list out the columns for distinction;
2361
+			// otherwise we can just use star
2362
+			if ($distinct) {
2363
+				$columns_to_use = [];
2364
+				foreach ($this->get_combined_primary_key_fields() as $field_obj) {
2365
+					$columns_to_use[] = $field_obj->get_qualified_column();
2366
+				}
2367
+				$column_to_count = implode(',', $columns_to_use);
2368
+			} else {
2369
+				$column_to_count = '*';
2370
+			}
2371
+		}
2372
+		$column_to_count = $distinct
2373
+			? "DISTINCT " . $column_to_count
2374
+			: $column_to_count;
2375
+		$SQL             =
2376
+			"SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2377
+		return (int) $this->_do_wpdb_query('get_var', [$SQL]);
2378
+	}
2379
+
2380
+
2381
+	/**
2382
+	 * Sums up the value of the $field_to_sum (defaults to the primary key, which isn't terribly useful)
2383
+	 *
2384
+	 * @param array  $query_params @see
2385
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2386
+	 * @param string $field_to_sum name of field (array key in $_fields array)
2387
+	 * @return float
2388
+	 * @throws EE_Error
2389
+	 * @throws ReflectionException
2390
+	 */
2391
+	public function sum($query_params, $field_to_sum = null)
2392
+	{
2393
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
2394
+		if ($field_to_sum) {
2395
+			$field_obj = $this->field_settings_for($field_to_sum);
2396
+		} else {
2397
+			$field_obj = $this->get_primary_key_field();
2398
+		}
2399
+		$column_to_count = $field_obj->get_qualified_column();
2400
+		$SQL             =
2401
+			"SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2402
+		$return_value    = $this->_do_wpdb_query('get_var', [$SQL]);
2403
+		$data_type       = $field_obj->get_wpdb_data_type();
2404
+		if ($data_type === '%d' || $data_type === '%s') {
2405
+			return (float) $return_value;
2406
+		}
2407
+		// must be %f
2408
+		return (float) $return_value;
2409
+	}
2410
+
2411
+
2412
+	/**
2413
+	 * Just calls the specified method on $wpdb with the given arguments
2414
+	 * Consolidates a little extra error handling code
2415
+	 *
2416
+	 * @param string $wpdb_method
2417
+	 * @param array  $arguments_to_provide
2418
+	 * @return mixed
2419
+	 * @throws EE_Error
2420
+	 * @global wpdb  $wpdb
2421
+	 */
2422
+	protected function _do_wpdb_query($wpdb_method, $arguments_to_provide)
2423
+	{
2424
+		// if we're in maintenance mode level 2, DON'T run any queries
2425
+		// because level 2 indicates the database needs updating and
2426
+		// is probably out of sync with the code
2427
+		if (DbStatus::isOffline()) {
2428
+			throw new RuntimeException(
2429
+				esc_html__(
2430
+					"Event Espresso Level 2 Maintenance mode is active. That means EE can not run ANY database queries until the necessary migration scripts have run which will take EE out of maintenance mode level 2. Please inform support of this error.",
2431
+					"event_espresso"
2432
+				)
2433
+			);
2434
+		}
2435
+		/** @type WPDB $wpdb */
2436
+		global $wpdb;
2437
+		if (! method_exists($wpdb, $wpdb_method)) {
2438
+			throw new DomainException(
2439
+				sprintf(
2440
+					esc_html__(
2441
+						'There is no method named "%s" on Wordpress\' $wpdb object',
2442
+						'event_espresso'
2443
+					),
2444
+					$wpdb_method
2445
+				)
2446
+			);
2447
+		}
2448
+		$old_show_errors_value = $wpdb->show_errors;
2449
+		if (defined('WP_DEBUG') && WP_DEBUG) {
2450
+			$wpdb->show_errors(false);
2451
+		}
2452
+		$result = $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2453
+		$this->show_db_query_if_previously_requested($wpdb->last_query);
2454
+		if (defined('WP_DEBUG') && WP_DEBUG) {
2455
+			$wpdb->show_errors($old_show_errors_value);
2456
+			if (! empty($wpdb->last_error)) {
2457
+				throw new EE_Error(sprintf(esc_html__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2458
+			}
2459
+			if ($result === false) {
2460
+				throw new EE_Error(
2461
+					sprintf(
2462
+						esc_html__(
2463
+							'WPDB Error occurred, but no error message was logged by wpdb! The wpdb method called was "%1$s" and the arguments were "%2$s"',
2464
+							'event_espresso'
2465
+						),
2466
+						$wpdb_method,
2467
+						var_export($arguments_to_provide, true)
2468
+					)
2469
+				);
2470
+			}
2471
+		} elseif ($result === false) {
2472
+			EE_Error::add_error(
2473
+				sprintf(
2474
+					esc_html__(
2475
+						'A database error has occurred. Turn on WP_DEBUG for more information.||A database error occurred doing wpdb method "%1$s", with arguments "%2$s". The error was "%3$s"',
2476
+						'event_espresso'
2477
+					),
2478
+					$wpdb_method,
2479
+					var_export($arguments_to_provide, true),
2480
+					$wpdb->last_error
2481
+				),
2482
+				__FILE__,
2483
+				__FUNCTION__,
2484
+				__LINE__
2485
+			);
2486
+		}
2487
+		return $result;
2488
+	}
2489
+
2490
+
2491
+	/**
2492
+	 * Attempts to run the indicated WPDB method with the provided arguments,
2493
+	 * and if there's an error tries to verify the DB is correct. Uses
2494
+	 * the static property EEM_Base::$_db_verification_level to determine whether
2495
+	 * we should try to fix the EE core db, the addons, or just give up
2496
+	 *
2497
+	 * @param string $wpdb_method
2498
+	 * @param array  $arguments_to_provide
2499
+	 * @return mixed
2500
+	 */
2501
+	private function _process_wpdb_query($wpdb_method, $arguments_to_provide)
2502
+	{
2503
+		/** @type WPDB $wpdb */
2504
+		global $wpdb;
2505
+		$wpdb->last_error = null;
2506
+		$result           = call_user_func_array([$wpdb, $wpdb_method], $arguments_to_provide);
2507
+		// was there an error running the query? but we don't care on new activations
2508
+		// (we're going to setup the DB anyway on new activations)
2509
+		if (
2510
+			($result === false || ! empty($wpdb->last_error))
2511
+			&& EE_System::instance()->detect_req_type() !== EE_System::req_type_new_activation
2512
+		) {
2513
+			switch (EEM_Base::$_db_verification_level) {
2514
+				case EEM_Base::db_verified_none:
2515
+					// let's double-check core's DB
2516
+					$error_message = $this->_verify_core_db($wpdb_method, $arguments_to_provide);
2517
+					break;
2518
+				case EEM_Base::db_verified_core:
2519
+					// STILL NO LOVE?? verify all the addons too. Maybe they need to be fixed
2520
+					$error_message = $this->_verify_addons_db($wpdb_method, $arguments_to_provide);
2521
+					break;
2522
+				case EEM_Base::db_verified_addons:
2523
+					// ummmm... you in trouble
2524
+					return $result;
2525
+			}
2526
+			if (! empty($error_message)) {
2527
+				EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2528
+				trigger_error($error_message);
2529
+			}
2530
+			return $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2531
+		}
2532
+		return $result;
2533
+	}
2534
+
2535
+
2536
+	/**
2537
+	 * Verifies the EE core database is up-to-date and records that we've done it on
2538
+	 * EEM_Base::$_db_verification_level
2539
+	 *
2540
+	 * @param string $wpdb_method
2541
+	 * @param array  $arguments_to_provide
2542
+	 * @return string
2543
+	 * @throws EE_Error
2544
+	 * @throws ReflectionException
2545
+	 */
2546
+	private function _verify_core_db($wpdb_method, $arguments_to_provide)
2547
+	{
2548
+		/** @type WPDB $wpdb */
2549
+		global $wpdb;
2550
+		// ok remember that we've already attempted fixing the core db, in case the problem persists
2551
+		EEM_Base::$_db_verification_level = EEM_Base::db_verified_core;
2552
+		$error_message                    = sprintf(
2553
+			esc_html__(
2554
+				'WPDB Error "%1$s" while running wpdb method "%2$s" with arguments %3$s. Automatically attempting to fix EE Core DB',
2555
+				'event_espresso'
2556
+			),
2557
+			$wpdb->last_error,
2558
+			$wpdb_method,
2559
+			wp_json_encode($arguments_to_provide)
2560
+		);
2561
+		EE_System::instance()->initialize_db_if_no_migrations_required(false, true);
2562
+		return $error_message;
2563
+	}
2564
+
2565
+
2566
+	/**
2567
+	 * Verifies the EE addons' database is up-to-date and records that we've done it on
2568
+	 * EEM_Base::$_db_verification_level
2569
+	 *
2570
+	 * @param $wpdb_method
2571
+	 * @param $arguments_to_provide
2572
+	 * @return string
2573
+	 * @throws EE_Error
2574
+	 * @throws ReflectionException
2575
+	 */
2576
+	private function _verify_addons_db($wpdb_method, $arguments_to_provide)
2577
+	{
2578
+		/** @type WPDB $wpdb */
2579
+		global $wpdb;
2580
+		// ok remember that we've already attempted fixing the addons dbs, in case the problem persists
2581
+		EEM_Base::$_db_verification_level = EEM_Base::db_verified_addons;
2582
+		$error_message                    = sprintf(
2583
+			esc_html__(
2584
+				'WPDB AGAIN: Error "%1$s" while running the same method and arguments as before. Automatically attempting to fix EE Addons DB',
2585
+				'event_espresso'
2586
+			),
2587
+			$wpdb->last_error,
2588
+			$wpdb_method,
2589
+			wp_json_encode($arguments_to_provide)
2590
+		);
2591
+		EE_System::instance()->initialize_addons();
2592
+		return $error_message;
2593
+	}
2594
+
2595
+
2596
+	/**
2597
+	 * In order to avoid repeating this code for the get_all, sum, and count functions, put the code parts
2598
+	 * that are identical in here. Returns a string of SQL of everything in a SELECT query except the beginning
2599
+	 * SELECT clause, eg " FROM wp_posts AS Event INNER JOIN ... WHERE ... ORDER BY ... LIMIT ... GROUP BY ... HAVING
2600
+	 * ..."
2601
+	 *
2602
+	 * @param EE_Model_Query_Info_Carrier $model_query_info
2603
+	 * @return string
2604
+	 */
2605
+	private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2606
+	{
2607
+		return " FROM " . $model_query_info->get_full_join_sql() .
2608
+			   $model_query_info->get_where_sql() .
2609
+			   $model_query_info->get_group_by_sql() .
2610
+			   $model_query_info->get_having_sql() .
2611
+			   $model_query_info->get_order_by_sql() .
2612
+			   $model_query_info->get_limit_sql();
2613
+	}
2614
+
2615
+
2616
+	/**
2617
+	 * Set to easily debug the next X queries ran from this model.
2618
+	 *
2619
+	 * @param int $count
2620
+	 */
2621
+	public function show_next_x_db_queries($count = 1)
2622
+	{
2623
+		$this->_show_next_x_db_queries = $count;
2624
+	}
2625
+
2626
+
2627
+	/**
2628
+	 * @param $sql_query
2629
+	 */
2630
+	public function show_db_query_if_previously_requested($sql_query)
2631
+	{
2632
+		if ($this->_show_next_x_db_queries > 0) {
2633
+			$left = is_admin() ? '12rem' : '2rem';
2634
+			echo "
2635 2635
             <div class='ee-status-outline ee-status-bg--attention' style='margin: 2rem 2rem 2rem $left;'>
2636 2636
                 " . esc_html($sql_query) . "
2637 2637
             </div>";
2638
-            $this->_show_next_x_db_queries--;
2639
-        }
2640
-    }
2641
-
2642
-
2643
-    /**
2644
-     * Adds a relationship of the correct type between $modelObject and $otherModelObject.
2645
-     * There are the 3 cases:
2646
-     * 'belongsTo' relationship: sets $id_or_obj's foreign_key to be $other_model_id_or_obj's primary_key. If
2647
-     * $otherModelObject has no ID, it is first saved.
2648
-     * 'hasMany' relationship: sets $other_model_id_or_obj's foreign_key to be $id_or_obj's primary_key. If $id_or_obj
2649
-     * has no ID, it is first saved.
2650
-     * 'hasAndBelongsToMany' relationships: checks that there isn't already an entry in the join table, and adds one.
2651
-     * If one of the model Objects has not yet been saved to the database, it is saved before adding the entry in the
2652
-     * join table
2653
-     *
2654
-     * @param EE_Base_Class|int $id_or_obj                        EE_base_Class or ID of $thisModelObject
2655
-     * @param EE_Base_Class|int $other_model_id_or_obj            EE_base_Class or ID of other Model Object
2656
-     * @param string            $relationName                     , key in EEM_Base::_relations
2657
-     *                                                            an attendee to a group, you also want to specify
2658
-     *                                                            which role they will have in that group. So you would
2659
-     *                                                            use this parameter to specify
2660
-     *                                                            array('role-column-name'=>'role-id')
2661
-     * @param array|null        $extra_join_model_fields_n_values This allows you to enter further query params for the
2662
-     *                                                            relation to for relation to methods that allow you to
2663
-     *                                                            further specify extra columns to join by (such as
2664
-     *                                                            HABTM).  Keep in mind that the only acceptable
2665
-     *                                                            query_params is strict "col" => "value" pairs because
2666
-     *                                                            these will be inserted in any new rows created as
2667
-     *                                                            well.
2668
-     * @return EE_Base_Class which was added as a relation. Object referred to by $other_model_id_or_obj
2669
-     * @throws EE_Error
2670
-     */
2671
-    public function add_relationship_to(
2672
-        $id_or_obj,
2673
-        $other_model_id_or_obj,
2674
-        $relationName,
2675
-        $extra_join_model_fields_n_values = []
2676
-    ) {
2677
-        $relation_obj = $this->related_settings_for($relationName);
2678
-        return $relation_obj->add_relation_to($id_or_obj, $other_model_id_or_obj, $extra_join_model_fields_n_values);
2679
-    }
2680
-
2681
-
2682
-    /**
2683
-     * Removes a relationship of the correct type between $modelObject and $otherModelObject.
2684
-     * There are the 3 cases:
2685
-     * 'belongsTo' relationship: sets $modelObject's foreign_key to null, if that field is nullable.Otherwise throws an
2686
-     * error
2687
-     * 'hasMany' relationship: sets $otherModelObject's foreign_key to null,if that field is nullable.Otherwise throws
2688
-     * an error
2689
-     * 'hasAndBelongsToMany' relationships:removes any existing entry in the join table between the two models.
2690
-     *
2691
-     * @param EE_Base_Class|int $id_or_obj             EE_base_Class or ID of $thisModelObject
2692
-     * @param EE_Base_Class|int $other_model_id_or_obj EE_base_Class or ID of other Model Object
2693
-     * @param string            $relationName          key in EEM_Base::_relations
2694
-     * @param array|null        $where_query           This allows you to enter further query params for the relation
2695
-     *                                                 to for relation to methods that allow you to further specify
2696
-     *                                                 extra columns to join by (such as HABTM). Keep in mind that the
2697
-     *                                                 only acceptable query_params is strict "col" => "value" pairs
2698
-     *                                                 because these will be inserted in any new rows created as well.
2699
-     * @return EE_Base_Class
2700
-     * @throws EE_Error
2701
-     */
2702
-    public function remove_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query = [])
2703
-    {
2704
-        $relation_obj = $this->related_settings_for($relationName);
2705
-        return $relation_obj->remove_relation_to($id_or_obj, $other_model_id_or_obj, $where_query);
2706
-    }
2707
-
2708
-
2709
-    /**
2710
-     * @param mixed       $id_or_obj
2711
-     * @param string      $relationName
2712
-     * @param array|null  $where_query_params
2713
-     * @return EE_Base_Class[]
2714
-     * @throws EE_Error
2715
-     * @throws ReflectionException
2716
-     */
2717
-    public function remove_relations($id_or_obj, $relationName, $where_query_params = [])
2718
-    {
2719
-        $relation_obj = $this->related_settings_for($relationName);
2720
-        return $relation_obj->remove_relations($id_or_obj, $where_query_params);
2721
-    }
2722
-
2723
-
2724
-    /**
2725
-     * Gets all the related items of the specified $model_name, using $query_params.
2726
-     * Note: by default, we remove the "default query params"
2727
-     * because we want to get even deleted items etc.
2728
-     *
2729
-     * @param mixed       $id_or_obj    EE_Base_Class child or its ID
2730
-     * @param string      $model_name   like 'Event', 'Registration', etc. always singular
2731
-     * @param array|null  $query_params @see
2732
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2733
-     * @return EE_Base_Class[]
2734
-     * @throws EE_Error
2735
-     * @throws ReflectionException
2736
-     */
2737
-    public function get_all_related($id_or_obj, $model_name, ?array $query_params = [])
2738
-    {
2739
-        $model_obj         = $this->ensure_is_obj($id_or_obj);
2740
-        $relation_settings = $this->related_settings_for($model_name);
2741
-        return $relation_settings->get_all_related($model_obj, $query_params);
2742
-    }
2743
-
2744
-
2745
-    /**
2746
-     * Deletes all the model objects across the relation indicated by $model_name
2747
-     * which are related to $id_or_obj which meet the criteria set in $query_params.
2748
-     * However, if the model objects can't be deleted because of blocking related model objects, then
2749
-     * they aren't deleted. (Unless the thing that would have been deleted can be soft-deleted, that still happens).
2750
-     *
2751
-     * @param EE_Base_Class|int|string $id_or_obj
2752
-     * @param string                   $model_name
2753
-     * @param array|null               $query_params
2754
-     * @return int how many deleted
2755
-     * @throws EE_Error
2756
-     * @throws ReflectionException
2757
-     */
2758
-    public function delete_related($id_or_obj, $model_name, $query_params = [])
2759
-    {
2760
-        $model_obj         = $this->ensure_is_obj($id_or_obj);
2761
-        $relation_settings = $this->related_settings_for($model_name);
2762
-        return $relation_settings->delete_all_related($model_obj, $query_params);
2763
-    }
2764
-
2765
-
2766
-    /**
2767
-     * Hard deletes all the model objects across the relation indicated by $model_name
2768
-     * which are related to $id_or_obj which meet the criteria set in $query_params. If
2769
-     * the model objects can't be hard deleted because of blocking related model objects,
2770
-     * just does a soft-delete on them instead.
2771
-     *
2772
-     * @param EE_Base_Class|int|string $id_or_obj
2773
-     * @param string                   $model_name
2774
-     * @param array|null               $query_params
2775
-     * @return int how many deleted
2776
-     * @throws EE_Error
2777
-     * @throws ReflectionException
2778
-     */
2779
-    public function delete_related_permanently($id_or_obj, $model_name, $query_params = [])
2780
-    {
2781
-        $model_obj         = $this->ensure_is_obj($id_or_obj);
2782
-        $relation_settings = $this->related_settings_for($model_name);
2783
-        return $relation_settings->delete_related_permanently($model_obj, $query_params);
2784
-    }
2785
-
2786
-
2787
-    /**
2788
-     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2789
-     * unless otherwise specified in the $query_params
2790
-     *
2791
-     * @param EE_Base_Class|int|string $id_or_obj
2792
-     * @param string                   $model_name     like 'Event', or 'Registration'
2793
-     * @param array|null               $query_params   @see
2794
-     *                                                 https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2795
-     * @param string                   $field_to_count name of field to count by. By default, uses primary key
2796
-     * @param bool                     $distinct       if we want to only count the distinct values for the column then
2797
-     *                                                 you can trigger that by the setting $distinct to TRUE;
2798
-     * @return int
2799
-     * @throws EE_Error
2800
-     * @throws ReflectionException
2801
-     */
2802
-    public function count_related(
2803
-        $id_or_obj,
2804
-        $model_name,
2805
-        $query_params = [],
2806
-        $field_to_count = null,
2807
-        $distinct = false
2808
-    ) {
2809
-        $related_model = $this->get_related_model_obj($model_name);
2810
-        // we're just going to use the query params on the related model's normal get_all query,
2811
-        // except add a condition to say to match the current mod
2812
-        if (! isset($query_params['default_where_conditions'])) {
2813
-            $query_params['default_where_conditions'] = EE_Default_Where_Conditions::NONE;
2814
-        }
2815
-        $this_model_name                                                 = $this->get_this_model_name();
2816
-        $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2817
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2818
-        return $related_model->count($query_params, $field_to_count, $distinct);
2819
-    }
2820
-
2821
-
2822
-    /**
2823
-     * Instead of getting the related model objects, simply sums up the values of the specified field.
2824
-     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2825
-     *
2826
-     * @param EE_Base_Class|int|string $id_or_obj
2827
-     * @param string                   $model_name   like 'Event', or 'Registration'
2828
-     * @param array|null               $query_params @see
2829
-     *                                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2830
-     * @param string                   $field_to_sum name of field to count by. By default, uses primary key
2831
-     * @return float
2832
-     * @throws EE_Error
2833
-     * @throws ReflectionException
2834
-     */
2835
-    public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2836
-    {
2837
-        $related_model = $this->get_related_model_obj($model_name);
2838
-        if (! is_array($query_params)) {
2839
-            EE_Error::doing_it_wrong(
2840
-                'EEM_Base::sum_related',
2841
-                sprintf(
2842
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
2843
-                    gettype($query_params)
2844
-                ),
2845
-                '4.6.0'
2846
-            );
2847
-            $query_params = [];
2848
-        }
2849
-        // we're just going to use the query params on the related model's normal get_all query,
2850
-        // except add a condition to say to match the current mod
2851
-        if (! isset($query_params['default_where_conditions'])) {
2852
-            $query_params['default_where_conditions'] = EE_Default_Where_Conditions::NONE;
2853
-        }
2854
-        $this_model_name                                                 = $this->get_this_model_name();
2855
-        $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2856
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2857
-        return $related_model->sum($query_params, $field_to_sum);
2858
-    }
2859
-
2860
-
2861
-    /**
2862
-     * Uses $this->_relatedModels info to find the first related model object of relation $relationName to the given
2863
-     * $modelObject
2864
-     *
2865
-     * @param int | EE_Base_Class $id_or_obj        EE_Base_Class child or its ID
2866
-     * @param string              $other_model_name , key in $this->_relatedModels, eg 'Registration', or 'Events'
2867
-     * @param array|null          $query_params     @see
2868
-     *                                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2869
-     * @return EE_Base_Class
2870
-     * @throws EE_Error
2871
-     * @throws ReflectionException
2872
-     */
2873
-    public function get_first_related(EE_Base_Class $id_or_obj, $other_model_name, $query_params)
2874
-    {
2875
-        $query_params['limit'] = 1;
2876
-        $results               = $this->get_all_related($id_or_obj, $other_model_name, $query_params);
2877
-        if ($results) {
2878
-            return array_shift($results);
2879
-        }
2880
-        return null;
2881
-    }
2882
-
2883
-
2884
-    /**
2885
-     * Gets the model's name as it's expected in queries. For example, if this is EEM_Event model, that would be Event
2886
-     *
2887
-     * @return string
2888
-     */
2889
-    public function get_this_model_name()
2890
-    {
2891
-        return str_replace("EEM_", "", get_class($this));
2892
-    }
2893
-
2894
-
2895
-    /**
2896
-     * Gets the model field on this model which is of type EE_Any_Foreign_Model_Name_Field
2897
-     *
2898
-     * @return EE_Any_Foreign_Model_Name_Field
2899
-     * @throws EE_Error
2900
-     */
2901
-    public function get_field_containing_related_model_name()
2902
-    {
2903
-        foreach ($this->field_settings(true) as $field) {
2904
-            if ($field instanceof EE_Any_Foreign_Model_Name_Field) {
2905
-                $field_with_model_name = $field;
2906
-            }
2907
-        }
2908
-        if (! isset($field_with_model_name) || ! $field_with_model_name) {
2909
-            throw new EE_Error(
2910
-                sprintf(
2911
-                    esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
2912
-                    $this->get_this_model_name()
2913
-                )
2914
-            );
2915
-        }
2916
-        return $field_with_model_name;
2917
-    }
2918
-
2919
-
2920
-    /**
2921
-     * Inserts a new entry into the database, for each table.
2922
-     * Note: does not add the item to the entity map because that is done by EE_Base_Class::save() right after this.
2923
-     * If client code uses EEM_Base::insert() directly, then although the item isn't in the entity map,
2924
-     * we also know there is no model object with the newly inserted item's ID at the moment (because
2925
-     * if there were, then they would already be in the DB and this would fail); and in the future if someone
2926
-     * creates a model object with this ID (or grabs it from the DB) then it will be added to the
2927
-     * entity map at that time anyways. SO, no need for EEM_Base::insert ot add to the entity map
2928
-     *
2929
-     * @param array $field_n_values keys are field names, values are their values (in the client code's domain if
2930
-     *                              $values_already_prepared_by_model_object is false, in the model object's domain if
2931
-     *                              $values_already_prepared_by_model_object is true. See comment about this at the top
2932
-     *                              of EEM_Base)
2933
-     * @return int|string new primary key on main table that got inserted
2934
-     * @throws EE_Error
2935
-     * @throws ReflectionException
2936
-     */
2937
-    public function insert($field_n_values)
2938
-    {
2939
-        /**
2940
-         * Filters the fields and their values before inserting an item using the models
2941
-         *
2942
-         * @param array    $fields_n_values keys are the fields and values are their new values
2943
-         * @param EEM_Base $model           the model used
2944
-         */
2945
-        $field_n_values = (array) apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2946
-        if ($this->_satisfies_unique_indexes($field_n_values)) {
2947
-            $main_table = $this->_get_main_table();
2948
-            $new_id     = $this->_insert_into_specific_table($main_table, $field_n_values, false);
2949
-            if ($new_id !== false) {
2950
-                foreach ($this->_get_other_tables() as $other_table) {
2951
-                    $this->_insert_into_specific_table($other_table, $field_n_values, $new_id);
2952
-                }
2953
-            }
2954
-            /**
2955
-             * Done just after attempting to insert a new model object
2956
-             *
2957
-             * @param EEM_Base $model           used
2958
-             * @param array    $fields_n_values fields and their values
2959
-             * @param int|string the              ID of the newly-inserted model object
2960
-             */
2961
-            do_action('AHEE__EEM_Base__insert__end', $this, $field_n_values, $new_id);
2962
-            return $new_id;
2963
-        }
2964
-        return false;
2965
-    }
2966
-
2967
-
2968
-    /**
2969
-     * Checks that the result would satisfy the unique indexes on this model
2970
-     *
2971
-     * @param array  $field_n_values
2972
-     * @param string $action
2973
-     * @return boolean
2974
-     * @throws EE_Error
2975
-     * @throws ReflectionException
2976
-     */
2977
-    protected function _satisfies_unique_indexes(array $field_n_values, $action = 'insert')
2978
-    {
2979
-        foreach ($this->unique_indexes() as $index_name => $index) {
2980
-            $uniqueness_where_params = array_intersect_key($field_n_values, $index->fields());
2981
-            if ($this->exists([$uniqueness_where_params])) {
2982
-                EE_Error::add_error(
2983
-                    sprintf(
2984
-                        esc_html__(
2985
-                            "Could not %s %s. %s uniqueness index failed. Fields %s must form a unique set, but an entry already exists with values %s.",
2986
-                            "event_espresso"
2987
-                        ),
2988
-                        $action,
2989
-                        $this->_get_class_name(),
2990
-                        $index_name,
2991
-                        implode(",", $index->field_names()),
2992
-                        http_build_query($uniqueness_where_params)
2993
-                    ),
2994
-                    __FILE__,
2995
-                    __FUNCTION__,
2996
-                    __LINE__
2997
-                );
2998
-                return false;
2999
-            }
3000
-        }
3001
-        return true;
3002
-    }
3003
-
3004
-
3005
-    /**
3006
-     * Checks the database for an item that conflicts (ie, if this item were
3007
-     * saved to the DB would break some uniqueness requirement, like a primary key
3008
-     * or an index primary key set) with the item specified. $id_obj_or_fields_array
3009
-     * can be either an EE_Base_Class or an array of fields n values
3010
-     *
3011
-     * @param EE_Base_Class|array $obj_or_fields_array
3012
-     * @param boolean             $include_primary_key whether to use the model object's primary key
3013
-     *                                                 when looking for conflicts
3014
-     *                                                 (ie, if false, we ignore the model object's primary key
3015
-     *                                                 when finding "conflicts". If true, it's also considered).
3016
-     *                                                 Only works for INT primary key,
3017
-     *                                                 STRING primary keys cannot be ignored
3018
-     * @return EE_Base_Class|array
3019
-     * @throws EE_Error
3020
-     * @throws ReflectionException
3021
-     */
3022
-    public function get_one_conflicting($obj_or_fields_array, $include_primary_key = true)
3023
-    {
3024
-        if ($obj_or_fields_array instanceof EE_Base_Class) {
3025
-            $fields_n_values = $obj_or_fields_array->model_field_array();
3026
-        } elseif (is_array($obj_or_fields_array)) {
3027
-            $fields_n_values = $obj_or_fields_array;
3028
-        } else {
3029
-            throw new EE_Error(
3030
-                sprintf(
3031
-                    esc_html__(
3032
-                        "%s get_all_conflicting should be called with a model object or an array of field names and values, you provided %d",
3033
-                        "event_espresso"
3034
-                    ),
3035
-                    $this->class_name,
3036
-                    $obj_or_fields_array
3037
-                )
3038
-            );
3039
-        }
3040
-        $query_params = [];
3041
-        if (
3042
-            $this->has_primary_key_field()
3043
-            && ($include_primary_key
3044
-                || $this->get_primary_key_field()
3045
-                   instanceof
3046
-                   EE_Primary_Key_String_Field)
3047
-            && isset($fields_n_values[ $this->primary_key_name() ])
3048
-        ) {
3049
-            $query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
3050
-        }
3051
-        foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
3052
-            $uniqueness_where_params                              =
3053
-                array_intersect_key($fields_n_values, $unique_index->fields());
3054
-            $query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
3055
-        }
3056
-        // if there is nothing to base this search on, then we shouldn't find anything
3057
-        if (empty($query_params)) {
3058
-            return [];
3059
-        }
3060
-        return $this->get_one($query_params);
3061
-    }
3062
-
3063
-
3064
-    /**
3065
-     * Like count, but is optimized and returns a boolean instead of an int
3066
-     *
3067
-     * @param array $query_params
3068
-     * @return boolean
3069
-     * @throws EE_Error
3070
-     * @throws ReflectionException
3071
-     */
3072
-    public function exists($query_params)
3073
-    {
3074
-        $query_params['limit'] = 1;
3075
-        return $this->count($query_params) > 0;
3076
-    }
3077
-
3078
-
3079
-    /**
3080
-     * Wrapper for exists, except ignores default query parameters so we're only considering ID
3081
-     *
3082
-     * @param int|string $id
3083
-     * @return boolean
3084
-     * @throws EE_Error
3085
-     * @throws ReflectionException
3086
-     */
3087
-    public function exists_by_ID($id)
3088
-    {
3089
-        return $this->exists(
3090
-            [
3091
-                'default_where_conditions' => EE_Default_Where_Conditions::NONE,
3092
-                [
3093
-                    $this->primary_key_name() => $id,
3094
-                ],
3095
-            ]
3096
-        );
3097
-    }
3098
-
3099
-
3100
-    /**
3101
-     * Inserts a new row in $table, using the $cols_n_values which apply to that table.
3102
-     * If a $new_id is supplied and if $table is an EE_Other_Table, we assume
3103
-     * we need to add a foreign key column to point to $new_id (which should be the primary key's value
3104
-     * on the main table)
3105
-     * This is protected rather than private because private is not accessible to any child methods and there MAY be
3106
-     * cases where we want to call it directly rather than via insert().
3107
-     *
3108
-     * @access   protected
3109
-     * @param EE_Table_Base $table
3110
-     * @param array         $fields_n_values each key should be in field's keys, and value should be an int, string or
3111
-     *                                       float
3112
-     * @param int           $new_id          for now we assume only int keys
3113
-     * @return int ID of new row inserted, or FALSE on failure
3114
-     * @throws EE_Error
3115
-     * @global WPDB         $wpdb            only used to get the $wpdb->insert_id after performing an insert
3116
-     */
3117
-    protected function _insert_into_specific_table(EE_Table_Base $table, $fields_n_values, $new_id = 0)
3118
-    {
3119
-        global $wpdb;
3120
-        $insertion_col_n_values = [];
3121
-        $format_for_insertion   = [];
3122
-        $fields_on_table        = $this->_get_fields_for_table($table->get_table_alias());
3123
-        foreach ($fields_on_table as $field_obj) {
3124
-            // check if its an auto-incrementing column, in which case we should just leave it to do its autoincrement thing
3125
-            if ($field_obj->is_auto_increment()) {
3126
-                continue;
3127
-            }
3128
-            $prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3129
-            // if the value we want to assign it to is NULL, just don't mention it for the insertion
3130
-            if ($prepared_value !== null) {
3131
-                $insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3132
-                $format_for_insertion[]                                   = $field_obj->get_wpdb_data_type();
3133
-            }
3134
-        }
3135
-        if ($table instanceof EE_Secondary_Table && $new_id) {
3136
-            // its not the main table, so we should have already saved the main table's PK which we just inserted
3137
-            // so add the fk to the main table as a column
3138
-            $insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3139
-            $format_for_insertion[]                              =
3140
-                '%d';// yes right now we're only allowing these foreign keys to be INTs
3141
-        }
3142
-
3143
-        // insert the new entry
3144
-        $result = $this->_do_wpdb_query(
3145
-            'insert',
3146
-            [$table->get_table_name(), $insertion_col_n_values, $format_for_insertion]
3147
-        );
3148
-        if ($result === false) {
3149
-            return false;
3150
-        }
3151
-        // ok, now what do we return for the ID of the newly-inserted thing?
3152
-        if ($this->has_primary_key_field()) {
3153
-            if ($this->get_primary_key_field()->is_auto_increment()) {
3154
-                return $wpdb->insert_id;
3155
-            }
3156
-            // it's not an auto-increment primary key, so
3157
-            // it must have been supplied
3158
-            return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3159
-        }
3160
-        // we can't return a  primary key because there is none. instead return
3161
-        // a unique string indicating this model
3162
-        return $this->get_index_primary_key_string($fields_n_values);
3163
-    }
3164
-
3165
-
3166
-    /**
3167
-     * Prepare the $field_obj 's value in $fields_n_values for use in the database.
3168
-     * If the field doesn't allow NULL, try to use its default. (If it doesn't allow NULL,
3169
-     * and there is no default, we pass it along. WPDB will take care of it)
3170
-     *
3171
-     * @param EE_Model_Field_Base $field_obj
3172
-     * @param array               $fields_n_values
3173
-     * @return mixed string|int|float depending on what the table column will be expecting
3174
-     * @throws EE_Error
3175
-     */
3176
-    protected function _prepare_value_or_use_default($field_obj, $fields_n_values)
3177
-    {
3178
-        $field_name = $field_obj->get_name();
3179
-        // if this field doesn't allow nullable, don't allow it
3180
-        if (! $field_obj->is_nullable() && ! isset($fields_n_values[ $field_name ])) {
3181
-            $fields_n_values[ $field_name ] = $field_obj->get_default_value();
3182
-        }
3183
-        $unprepared_value = $fields_n_values[ $field_name ] ?? null;
3184
-        return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3185
-    }
3186
-
3187
-
3188
-    /**
3189
-     * Consolidates code for preparing  a value supplied to the model for use int eh db. Calls the field's
3190
-     * prepare_for_use_in_db method on the value, and depending on $value_already_prepare_by_model_obj, may also call
3191
-     * the field's prepare_for_set() method.
3192
-     *
3193
-     * @param mixed               $value value in the client code domain if $value_already_prepared_by_model_object is
3194
-     *                                   false, otherwise a value in the model object's domain (see lengthy comment at
3195
-     *                                   top of file)
3196
-     * @param EE_Model_Field_Base $field field which will be doing the preparing of the value. If null, we assume
3197
-     *                                   $value is a custom selection
3198
-     * @return mixed a value ready for use in the database for insertions, updating, or in a where clause
3199
-     */
3200
-    private function _prepare_value_for_use_in_db($value, $field)
3201
-    {
3202
-        if ($field instanceof EE_Model_Field_Base) {
3203
-            // phpcs:disable PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
3204
-            switch ($this->_values_already_prepared_by_model_object) {
3205
-                /** @noinspection PhpMissingBreakStatementInspection */
3206
-                case self::not_prepared_by_model_object:
3207
-                    $value = $field->prepare_for_set($value);
3208
-                // purposefully left out "return"
3209
-                // no break
3210
-                case self::prepared_by_model_object:
3211
-                    /** @noinspection SuspiciousAssignmentsInspection */
3212
-                    $value = $field->prepare_for_use_in_db($value);
3213
-                // no break
3214
-                case self::prepared_for_use_in_db:
3215
-                    // leave the value alone
3216
-            }
3217
-            // phpcs:enable
3218
-        }
3219
-        return $value;
3220
-    }
3221
-
3222
-
3223
-    /**
3224
-     * Returns the main table on this model
3225
-     *
3226
-     * @return EE_Primary_Table
3227
-     * @throws EE_Error
3228
-     */
3229
-    protected function _get_main_table()
3230
-    {
3231
-        foreach ($this->_tables as $table) {
3232
-            if ($table instanceof EE_Primary_Table) {
3233
-                return $table;
3234
-            }
3235
-        }
3236
-        throw new EE_Error(
3237
-            sprintf(
3238
-                esc_html__(
3239
-                    'There are no main tables on %s. They should be added to _tables array in the constructor',
3240
-                    'event_espresso'
3241
-                ),
3242
-                $this->class_name
3243
-            )
3244
-        );
3245
-    }
3246
-
3247
-
3248
-    /**
3249
-     * table
3250
-     * returns EE_Primary_Table table name
3251
-     *
3252
-     * @return string
3253
-     * @throws EE_Error
3254
-     */
3255
-    public function table()
3256
-    {
3257
-        return $this->_get_main_table()->get_table_name();
3258
-    }
3259
-
3260
-
3261
-    /**
3262
-     * table
3263
-     * returns first EE_Secondary_Table table name
3264
-     *
3265
-     * @return string
3266
-     */
3267
-    public function second_table()
3268
-    {
3269
-        // grab second table from tables array
3270
-        $second_table = end($this->_tables);
3271
-        return $second_table instanceof EE_Secondary_Table
3272
-            ? $second_table->get_table_name()
3273
-            : null;
3274
-    }
3275
-
3276
-
3277
-    /**
3278
-     * get_table_obj_by_alias
3279
-     * returns table name given it's alias
3280
-     *
3281
-     * @param string $table_alias
3282
-     * @return EE_Primary_Table | EE_Secondary_Table
3283
-     */
3284
-    public function get_table_obj_by_alias($table_alias = '')
3285
-    {
3286
-        return $this->_tables[ $table_alias ] ?? null;
3287
-    }
3288
-
3289
-
3290
-    /**
3291
-     * Gets all the tables of type EE_Other_Table from EEM_CPT_Basel_Model::_tables
3292
-     *
3293
-     * @return EE_Secondary_Table[]
3294
-     */
3295
-    protected function _get_other_tables()
3296
-    {
3297
-        $other_tables = [];
3298
-        foreach ($this->_tables as $table_alias => $table) {
3299
-            if ($table instanceof EE_Secondary_Table) {
3300
-                $other_tables[ $table_alias ] = $table;
3301
-            }
3302
-        }
3303
-        return $other_tables;
3304
-    }
3305
-
3306
-
3307
-    /**
3308
-     * Finds all the fields that correspond to the given table
3309
-     *
3310
-     * @param string $table_alias , array key in EEM_Base::_tables
3311
-     * @return EE_Model_Field_Base[]
3312
-     */
3313
-    public function _get_fields_for_table($table_alias)
3314
-    {
3315
-        return $this->_fields[ $table_alias ];
3316
-    }
3317
-
3318
-
3319
-    /**
3320
-     * Recurses through all the where parameters, and finds all the related models we'll need
3321
-     * to complete this query. Eg, given where parameters like array('EVT_ID'=>3) from within Event model, we won't
3322
-     * need any related models. But if the array were array('Registrations.REG_ID'=>3), we'd need the related
3323
-     * Registration model. If it were array('Registrations.Transactions.Payments.PAY_ID'=>3), then we'd need the
3324
-     * related Registration, Transaction, and Payment models.
3325
-     *
3326
-     * @param array $query_params @see
3327
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3328
-     * @return EE_Model_Query_Info_Carrier
3329
-     * @throws EE_Error
3330
-     */
3331
-    public function _extract_related_models_from_query($query_params)
3332
-    {
3333
-        $query_info_carrier = new EE_Model_Query_Info_Carrier();
3334
-        if (array_key_exists(0, $query_params)) {
3335
-            $this->_extract_related_models_from_sub_params_array_keys($query_params[0], $query_info_carrier, 0);
3336
-        }
3337
-        if (array_key_exists('group_by', $query_params)) {
3338
-            if (is_array($query_params['group_by'])) {
3339
-                $this->_extract_related_models_from_sub_params_array_values(
3340
-                    $query_params['group_by'],
3341
-                    $query_info_carrier,
3342
-                    'group_by'
3343
-                );
3344
-            } elseif (! empty($query_params['group_by'])) {
3345
-                $this->_extract_related_model_info_from_query_param(
3346
-                    $query_params['group_by'],
3347
-                    $query_info_carrier,
3348
-                    'group_by'
3349
-                );
3350
-            }
3351
-        }
3352
-        if (array_key_exists('having', $query_params)) {
3353
-            $this->_extract_related_models_from_sub_params_array_keys(
3354
-                $query_params[0],
3355
-                $query_info_carrier,
3356
-                'having'
3357
-            );
3358
-        }
3359
-        if (array_key_exists('order_by', $query_params)) {
3360
-            if (is_array($query_params['order_by'])) {
3361
-                $this->_extract_related_models_from_sub_params_array_keys(
3362
-                    $query_params['order_by'],
3363
-                    $query_info_carrier,
3364
-                    'order_by'
3365
-                );
3366
-            } elseif (! empty($query_params['order_by'])) {
3367
-                $this->_extract_related_model_info_from_query_param(
3368
-                    $query_params['order_by'],
3369
-                    $query_info_carrier,
3370
-                    'order_by'
3371
-                );
3372
-            }
3373
-        }
3374
-        if (array_key_exists('force_join', $query_params)) {
3375
-            $this->_extract_related_models_from_sub_params_array_values(
3376
-                $query_params['force_join'],
3377
-                $query_info_carrier,
3378
-                'force_join'
3379
-            );
3380
-        }
3381
-        $this->extractRelatedModelsFromCustomSelects($query_info_carrier);
3382
-        return $query_info_carrier;
3383
-    }
3384
-
3385
-
3386
-    /**
3387
-     * For extracting related models from WHERE (0), HAVING (having), ORDER BY (order_by) or forced joins (force_join)
3388
-     *
3389
-     * @param array                       $sub_query_params
3390
-     * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3391
-     * @param string                      $query_param_type one of $this->_allowed_query_params
3392
-     * @return EE_Model_Query_Info_Carrier
3393
-     * @throws EE_Error
3394
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#-0-where-conditions
3395
-     */
3396
-    private function _extract_related_models_from_sub_params_array_keys(
3397
-        $sub_query_params,
3398
-        EE_Model_Query_Info_Carrier $model_query_info_carrier,
3399
-        $query_param_type
3400
-    ) {
3401
-        if (! empty($sub_query_params)) {
3402
-            $sub_query_params = (array) $sub_query_params;
3403
-            foreach ($sub_query_params as $param => $possibly_array_of_params) {
3404
-                // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3405
-                $this->_extract_related_model_info_from_query_param(
3406
-                    $param,
3407
-                    $model_query_info_carrier,
3408
-                    $query_param_type
3409
-                );
3410
-                // if $possibly_array_of_params is an array, try recursing into it, searching for keys which
3411
-                // indicate needed joins. Eg, array('NOT'=>array('Registration.TXN_ID'=>23)). In this case, we tried
3412
-                // extracting models out of the 'NOT', which obviously wasn't successful, and then we recurse into the value
3413
-                // of array('Registration.TXN_ID'=>23)
3414
-                $query_param_sans_stars =
3415
-                    $this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3416
-                if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3417
-                    if (! is_array($possibly_array_of_params)) {
3418
-                        throw new EE_Error(
3419
-                            sprintf(
3420
-                                esc_html__(
3421
-                                    "You used a special where query param %s, but the value isn't an array of where query params, it's just %s'. It should be an array, eg array('EVT_ID'=>23,'OR'=>array('Venue.VNU_ID'=>32,'Venue.VNU_name'=>'monkey_land'))",
3422
-                                    "event_espresso"
3423
-                                ),
3424
-                                $param,
3425
-                                $possibly_array_of_params
3426
-                            )
3427
-                        );
3428
-                    }
3429
-                    $this->_extract_related_models_from_sub_params_array_keys(
3430
-                        $possibly_array_of_params,
3431
-                        $model_query_info_carrier,
3432
-                        $query_param_type
3433
-                    );
3434
-                } elseif (
3435
-                    $query_param_type === 0 // ie WHERE
3436
-                    && is_array($possibly_array_of_params) // need is_array() check so we don't try to explode a string
3437
-                    && isset($possibly_array_of_params[2])
3438
-                    && $possibly_array_of_params[2]
3439
-                ) {
3440
-                    // then $possible_array_of_params looks something like array('<','DTT_sold',true)
3441
-                    // indicating that $possible_array_of_params[1] is actually a field name,
3442
-                    // from which we should extract query parameters!
3443
-                    if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3444
-                        throw new EE_Error(
3445
-                            sprintf(
3446
-                                esc_html__(
3447
-                                    "Improperly formed query parameter %s. It should be numerically indexed like array('<','DTT_sold',true); but you provided %s",
3448
-                                    "event_espresso"
3449
-                                ),
3450
-                                $query_param_type,
3451
-                                implode(",", $possibly_array_of_params)
3452
-                            )
3453
-                        );
3454
-                    }
3455
-                    $this->_extract_related_model_info_from_query_param(
3456
-                        $possibly_array_of_params[1],
3457
-                        $model_query_info_carrier,
3458
-                        $query_param_type
3459
-                    );
3460
-                }
3461
-            }
3462
-        }
3463
-        return $model_query_info_carrier;
3464
-    }
3465
-
3466
-
3467
-    /**
3468
-     * For extracting related models from forced_joins, where the array values contain the info about what
3469
-     * models to join with. Eg an array like array('Attendee','Price.Price_Type');
3470
-     *
3471
-     * @param array                       $sub_query_params @see
3472
-     *                                                      https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3473
-     * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3474
-     * @param string                      $query_param_type one of $this->_allowed_query_params
3475
-     * @return EE_Model_Query_Info_Carrier
3476
-     * @throws EE_Error
3477
-     */
3478
-    private function _extract_related_models_from_sub_params_array_values(
3479
-        $sub_query_params,
3480
-        EE_Model_Query_Info_Carrier $model_query_info_carrier,
3481
-        $query_param_type
3482
-    ) {
3483
-        if (! empty($sub_query_params)) {
3484
-            if (! is_array($sub_query_params)) {
3485
-                throw new EE_Error(
3486
-                    sprintf(
3487
-                        esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
3488
-                        $sub_query_params
3489
-                    )
3490
-                );
3491
-            }
3492
-            foreach ($sub_query_params as $param) {
3493
-                // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3494
-                $this->_extract_related_model_info_from_query_param(
3495
-                    $param,
3496
-                    $model_query_info_carrier,
3497
-                    $query_param_type
3498
-                );
3499
-            }
3500
-        }
3501
-        return $model_query_info_carrier;
3502
-    }
3503
-
3504
-
3505
-    /**
3506
-     * Extract all the query parts from  model query params
3507
-     * and put into a EEM_Related_Model_Info_Carrier for easy extraction into a query. We create this object
3508
-     * instead of directly constructing the SQL because often we need to extract info from the $query_params
3509
-     * but use them in a different order. Eg, we need to know what models we are querying
3510
-     * before we know what joins to perform. However, we need to know what data types correspond to which fields on
3511
-     * other models before we can finalize the where clause SQL.
3512
-     *
3513
-     * @param array $query_params
3514
-     * @return EE_Model_Query_Info_Carrier
3515
-     * @throws EE_Error
3516
-     * @throws ModelConfigurationException
3517
-     * @throws ReflectionException
3518
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3519
-     */
3520
-    public function _create_model_query_info_carrier($query_params)
3521
-    {
3522
-        if (! is_array($query_params)) {
3523
-            EE_Error::doing_it_wrong(
3524
-                'EEM_Base::_create_model_query_info_carrier',
3525
-                sprintf(
3526
-                    esc_html__(
3527
-                        '$query_params should be an array, you passed a variable of type %s',
3528
-                        'event_espresso'
3529
-                    ),
3530
-                    gettype($query_params)
3531
-                ),
3532
-                '4.6.0'
3533
-            );
3534
-            $query_params = [];
3535
-        }
3536
-        $query_params[0] = $query_params[0] ?? [];
3537
-        // first check if we should alter the query to account for caps or not
3538
-        // because the caps might require us to do extra joins
3539
-        if (isset($query_params['caps']) && $query_params['caps'] !== 'none') {
3540
-            $query_params[0] = array_replace_recursive(
3541
-                $query_params[0],
3542
-                $this->caps_where_conditions($query_params['caps'])
3543
-            );
3544
-        }
3545
-
3546
-        // check if we should alter the query to remove data related to protected
3547
-        // custom post types
3548
-        if (isset($query_params['exclude_protected']) && $query_params['exclude_protected'] === true) {
3549
-            $where_param_key_for_password = $this->modelChainAndPassword();
3550
-            // only include if related to a cpt where no password has been set
3551
-            $query_params[0]['OR*nopassword'] = [
3552
-                $where_param_key_for_password       => '',
3553
-                $where_param_key_for_password . '*' => ['IS_NULL'],
3554
-            ];
3555
-        }
3556
-        $query_object = $this->_extract_related_models_from_query($query_params);
3557
-        // verify where_query_params has NO numeric indexes.... that's simply not how you use it!
3558
-        foreach ($query_params[0] as $key => $value) {
3559
-            if (is_int($key)) {
3560
-                throw new EE_Error(
3561
-                    sprintf(
3562
-                        esc_html__(
3563
-                            "WHERE query params must NOT be numerically-indexed. You provided the array key '%s' for value '%s' while querying model %s. All the query params provided were '%s' Please read documentation on EEM_Base::get_all.",
3564
-                            "event_espresso"
3565
-                        ),
3566
-                        $key,
3567
-                        var_export($value, true),
3568
-                        var_export($query_params, true),
3569
-                        $this->class_name
3570
-                    )
3571
-                );
3572
-            }
3573
-        }
3574
-        if (
3575
-            array_key_exists('default_where_conditions', $query_params)
3576
-            && ! empty($query_params['default_where_conditions'])
3577
-        ) {
3578
-            $use_default_where_conditions = $query_params['default_where_conditions'];
3579
-        } else {
3580
-            $use_default_where_conditions = EE_Default_Where_Conditions::ALL;
3581
-        }
3582
-        $query_params[0] = array_merge(
3583
-            $this->_get_default_where_conditions_for_models_in_query(
3584
-                $query_object,
3585
-                $use_default_where_conditions,
3586
-                $query_params[0]
3587
-            ),
3588
-            $query_params[0]
3589
-        );
3590
-        $query_object->set_where_sql($this->_construct_where_clause($query_params[0]));
3591
-        // if this is a "on_join_limit" then we are limiting on on a specific table in a multi_table join.
3592
-        // So we need to setup a subquery and use that for the main join.
3593
-        // Note for now this only works on the primary table for the model.
3594
-        // So for instance, you could set the limit array like this:
3595
-        // array( 'on_join_limit' => array('Primary_Table_Alias', array(1,10) ) )
3596
-        if (array_key_exists('on_join_limit', $query_params) && ! empty($query_params['on_join_limit'])) {
3597
-            $query_object->set_main_model_join_sql(
3598
-                $this->_construct_limit_join_select(
3599
-                    $query_params['on_join_limit'][0],
3600
-                    $query_params['on_join_limit'][1]
3601
-                )
3602
-            );
3603
-        }
3604
-        // set limit
3605
-        if (array_key_exists('limit', $query_params)) {
3606
-            if (is_array($query_params['limit'])) {
3607
-                if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3608
-                    $e = sprintf(
3609
-                        esc_html__(
3610
-                            "Invalid DB query. You passed '%s' for the LIMIT, but only the following are valid: an integer, string representing an integer, a string like 'int,int', or an array like array(int,int)",
3611
-                            "event_espresso"
3612
-                        ),
3613
-                        http_build_query($query_params['limit'])
3614
-                    );
3615
-                    throw new EE_Error($e . "|" . $e);
3616
-                }
3617
-                // they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3618
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3619
-            } elseif (! empty($query_params['limit'])) {
3620
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3621
-            }
3622
-        }
3623
-        // set order by
3624
-        if (array_key_exists('order_by', $query_params)) {
3625
-            if (is_array($query_params['order_by'])) {
3626
-                // if they're using 'order_by' as an array, they can't use 'order' (because 'order_by' must
3627
-                // specify whether to ascend or descend on each field. Eg 'order_by'=>array('EVT_ID'=>'ASC'). So
3628
-                // including 'order' wouldn't make any sense if 'order_by' has already specified which way to order!
3629
-                if (array_key_exists('order', $query_params)) {
3630
-                    throw new EE_Error(
3631
-                        sprintf(
3632
-                            esc_html__(
3633
-                                "In querying %s, we are using query parameter 'order_by' as an array (keys:%s,values:%s), and so we can't use query parameter 'order' (value %s). You should just use the 'order_by' parameter ",
3634
-                                "event_espresso"
3635
-                            ),
3636
-                            $this->class_name,
3637
-                            implode(", ", array_keys($query_params['order_by'])),
3638
-                            implode(", ", $query_params['order_by']),
3639
-                            $query_params['order']
3640
-                        )
3641
-                    );
3642
-                }
3643
-                $this->_extract_related_models_from_sub_params_array_keys(
3644
-                    $query_params['order_by'],
3645
-                    $query_object,
3646
-                    'order_by'
3647
-                );
3648
-                // assume it's an array of fields to order by
3649
-                $order_array = [];
3650
-                foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3651
-                    $order         = $this->_extract_order($order);
3652
-                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3653
-                }
3654
-                $query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3655
-            } elseif (! empty($query_params['order_by'])) {
3656
-                $this->_extract_related_model_info_from_query_param(
3657
-                    $query_params['order_by'],
3658
-                    $query_object,
3659
-                    'order',
3660
-                    $query_params['order_by']
3661
-                );
3662
-                $order = isset($query_params['order'])
3663
-                    ? $this->_extract_order($query_params['order'])
3664
-                    : 'DESC';
3665
-                $query_object->set_order_by_sql(
3666
-                    " ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3667
-                );
3668
-            }
3669
-        }
3670
-        // if 'order_by' wasn't set, maybe they are just using 'order' on its own?
3671
-        if (
3672
-            ! array_key_exists('order_by', $query_params)
3673
-            && array_key_exists('order', $query_params)
3674
-            && ! empty($query_params['order'])
3675
-        ) {
3676
-            $pk_field = $this->get_primary_key_field();
3677
-            $order    = $this->_extract_order($query_params['order']);
3678
-            $query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3679
-        }
3680
-        // set group by
3681
-        if (array_key_exists('group_by', $query_params)) {
3682
-            if (is_array($query_params['group_by'])) {
3683
-                // it's an array, so assume we'll be grouping by a bunch of stuff
3684
-                $group_by_array = [];
3685
-                foreach ($query_params['group_by'] as $field_name_to_group_by) {
3686
-                    $group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3687
-                }
3688
-                $query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3689
-            } elseif (! empty($query_params['group_by'])) {
3690
-                $query_object->set_group_by_sql(
3691
-                    " GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3692
-                );
3693
-            }
3694
-        }
3695
-        // set having
3696
-        if (array_key_exists('having', $query_params) && $query_params['having']) {
3697
-            $query_object->set_having_sql($this->_construct_having_clause($query_params['having']));
3698
-        }
3699
-        // now, just verify they didn't pass anything wack
3700
-        foreach ($query_params as $query_key => $query_value) {
3701
-            if (! in_array($query_key, $this->_allowed_query_params, true)) {
3702
-                throw new EE_Error(
3703
-                    sprintf(
3704
-                        esc_html__(
3705
-                            "You passed %s as a query parameter to %s, which is illegal! The allowed query parameters are %s",
3706
-                            'event_espresso'
3707
-                        ),
3708
-                        $query_key,
3709
-                        $this->class_name,
3710
-                        //                      print_r( $this->_allowed_query_params, TRUE )
3711
-                        implode(',', $this->_allowed_query_params)
3712
-                    )
3713
-                );
3714
-            }
3715
-        }
3716
-        $main_model_join_sql = $query_object->get_main_model_join_sql();
3717
-        if (empty($main_model_join_sql)) {
3718
-            $query_object->set_main_model_join_sql($this->_construct_internal_join());
3719
-        }
3720
-        return $query_object;
3721
-    }
3722
-
3723
-
3724
-    /**
3725
-     * Gets the where conditions that should be imposed on the query based on the
3726
-     * context (eg reading frontend, backend, edit or delete).
3727
-     *
3728
-     * @param string $context one of EEM_Base::valid_cap_contexts()
3729
-     * @return array @see
3730
-     *                        https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3731
-     * @throws EE_Error
3732
-     */
3733
-    public function caps_where_conditions($context = self::caps_read)
3734
-    {
3735
-        EEM_Base::verify_is_valid_cap_context($context);
3736
-        $cap_where_conditions = [];
3737
-        $cap_restrictions     = $this->caps_missing($context);
3738
-        foreach ($cap_restrictions as $restriction_if_no_cap) {
3739
-            $cap_where_conditions = array_replace_recursive(
3740
-                $cap_where_conditions,
3741
-                $restriction_if_no_cap->get_default_where_conditions()
3742
-            );
3743
-        }
3744
-        return apply_filters(
3745
-            'FHEE__EEM_Base__caps_where_conditions__return',
3746
-            $cap_where_conditions,
3747
-            $this,
3748
-            $context,
3749
-            $cap_restrictions
3750
-        );
3751
-    }
3752
-
3753
-
3754
-    /**
3755
-     * Verifies that $should_be_order_string is in $this->_allowed_order_values,
3756
-     * otherwise throws an exception
3757
-     *
3758
-     * @param string $should_be_order_string
3759
-     * @return string either ASC, asc, DESC or desc
3760
-     * @throws EE_Error
3761
-     */
3762
-    private function _extract_order($should_be_order_string)
3763
-    {
3764
-        if (in_array($should_be_order_string, $this->_allowed_order_values)) {
3765
-            return $should_be_order_string;
3766
-        }
3767
-        throw new EE_Error(
3768
-            sprintf(
3769
-                esc_html__(
3770
-                    "While performing a query on '%s', tried to use '%s' as an order parameter. ",
3771
-                    "event_espresso"
3772
-                ),
3773
-                $this->class_name,
3774
-                $should_be_order_string
3775
-            )
3776
-        );
3777
-    }
3778
-
3779
-
3780
-    /**
3781
-     * Looks at all the models which are included in this query, and asks each
3782
-     * for their universal_where_params, and returns them in the same format as $query_params[0] (where),
3783
-     * so they can be merged
3784
-     *
3785
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
3786
-     * @param string                      $use_default_where_conditions can be 'none','other_models_only', or 'all'.
3787
-     *                                                                  'none' means NO default where conditions will
3788
-     *                                                                  be used AT ALL during this query.
3789
-     *                                                                  'other_models_only' means default where
3790
-     *                                                                  conditions from other models will be used, but
3791
-     *                                                                  not for this primary model. 'all', the default,
3792
-     *                                                                  means default where conditions will apply as
3793
-     *                                                                  normal
3794
-     * @param array                       $where_query_params
3795
-     * @return array
3796
-     * @throws EE_Error
3797
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params
3798
-     *                                                                  .md#0-where-conditions
3799
-     */
3800
-    private function _get_default_where_conditions_for_models_in_query(
3801
-        EE_Model_Query_Info_Carrier $query_info_carrier,
3802
-        $use_default_where_conditions = EE_Default_Where_Conditions::ALL,
3803
-        $where_query_params = []
3804
-    ) {
3805
-        $allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3806
-        if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3807
-            throw new EE_Error(
3808
-                sprintf(
3809
-                    esc_html__(
3810
-                        "You passed an invalid value to the query parameter 'default_where_conditions' of '%s'. Allowed values are %s",
3811
-                        "event_espresso"
3812
-                    ),
3813
-                    $use_default_where_conditions,
3814
-                    implode(", ", $allowed_used_default_where_conditions_values)
3815
-                )
3816
-            );
3817
-        }
3818
-        $universal_query_params = [];
3819
-        if ($this->_should_use_default_where_conditions($use_default_where_conditions, true)) {
3820
-            $universal_query_params = $this->_get_default_where_conditions();
3821
-        } elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, true)) {
3822
-            $universal_query_params = $this->_get_minimum_where_conditions();
3823
-        }
3824
-        foreach ($query_info_carrier->get_model_names_included() as $model_relation_path => $model_name) {
3825
-            $related_model = $this->get_related_model_obj($model_name);
3826
-            if ($this->_should_use_default_where_conditions($use_default_where_conditions, false)) {
3827
-                $related_model_universal_where_params =
3828
-                    $related_model->_get_default_where_conditions($model_relation_path);
3829
-            } elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, false)) {
3830
-                $related_model_universal_where_params =
3831
-                    $related_model->_get_minimum_where_conditions($model_relation_path);
3832
-            } else {
3833
-                // we don't want to add full or even minimum default where conditions from this model, so just continue
3834
-                continue;
3835
-            }
3836
-            $overrides              = $this->_override_defaults_or_make_null_friendly(
3837
-                $related_model_universal_where_params,
3838
-                $where_query_params,
3839
-                $related_model,
3840
-                $model_relation_path
3841
-            );
3842
-            $universal_query_params = EEH_Array::merge_arrays_and_overwrite_keys(
3843
-                $universal_query_params,
3844
-                $overrides
3845
-            );
3846
-        }
3847
-        return $universal_query_params;
3848
-    }
3849
-
3850
-
3851
-    /**
3852
-     * Determines whether we should use default where conditions for the model in question
3853
-     * (this model, or other related models).
3854
-     * Basically, we should use default where conditions on this model if they have requested to use them on all models,
3855
-     * this model only, or to use minimum where conditions on all other models and normal where conditions on this one.
3856
-     * We should use default where conditions on related models when they requested to use default where conditions
3857
-     * on all models, or specifically just on other related models
3858
-     *
3859
-     * @param      $default_where_conditions_value
3860
-     * @param bool $for_this_model false means this is for OTHER related models
3861
-     * @return bool
3862
-     */
3863
-    private function _should_use_default_where_conditions($default_where_conditions_value, $for_this_model = true)
3864
-    {
3865
-        return (
3866
-                   $for_this_model
3867
-                   && in_array(
3868
-                       $default_where_conditions_value,
3869
-                       [
3870
-                           EE_Default_Where_Conditions::ALL,
3871
-                           EE_Default_Where_Conditions::THIS_MODEL_ONLY,
3872
-                           EE_Default_Where_Conditions::MINIMUM_OTHERS,
3873
-                       ],
3874
-                       true
3875
-                   )
3876
-               )
3877
-               || (
3878
-                   ! $for_this_model
3879
-                   && in_array(
3880
-                       $default_where_conditions_value,
3881
-                       [
3882
-                           EE_Default_Where_Conditions::ALL,
3883
-                           EE_Default_Where_Conditions::OTHER_MODELS_ONLY,
3884
-                       ],
3885
-                       true
3886
-                   )
3887
-               );
3888
-    }
3889
-
3890
-
3891
-    /**
3892
-     * Determines whether we should use default minimum conditions for the model in question
3893
-     * (this model, or other related models).
3894
-     * Basically, we should use minimum where conditions on this model only if they requested all models to use minimum
3895
-     * where conditions.
3896
-     * We should use minimum where conditions on related models if they requested to use minimum where conditions
3897
-     * on this model or others
3898
-     *
3899
-     * @param      $default_where_conditions_value
3900
-     * @param bool $for_this_model false means this is for OTHER related models
3901
-     * @return bool
3902
-     */
3903
-    private function _should_use_minimum_where_conditions($default_where_conditions_value, $for_this_model = true)
3904
-    {
3905
-        return (
3906
-                   $for_this_model
3907
-                   && $default_where_conditions_value === EE_Default_Where_Conditions::MINIMUM_ALL
3908
-               )
3909
-               || (
3910
-                   ! $for_this_model
3911
-                   && in_array(
3912
-                       $default_where_conditions_value,
3913
-                       [
3914
-                           EE_Default_Where_Conditions::MINIMUM_OTHERS,
3915
-                           EE_Default_Where_Conditions::MINIMUM_ALL,
3916
-                       ],
3917
-                       true
3918
-                   )
3919
-               );
3920
-    }
3921
-
3922
-
3923
-    /**
3924
-     * Checks if any of the defaults have been overridden. If there are any that AREN'T overridden,
3925
-     * then we also add a special where condition which allows for that model's primary key
3926
-     * to be null (which is important for JOINs. Eg, if you want to see all Events ordered by Venue's name,
3927
-     * then Event's with NO Venue won't appear unless you allow VNU_ID to be NULL)
3928
-     *
3929
-     * @param array    $default_where_conditions
3930
-     * @param array    $provided_where_conditions
3931
-     * @param EEM_Base $model
3932
-     * @param string   $model_relation_path like 'Transaction.Payment.'
3933
-     * @return array @see
3934
-     *                                      https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3935
-     * @throws EE_Error
3936
-     */
3937
-    private function _override_defaults_or_make_null_friendly(
3938
-        $default_where_conditions,
3939
-        $provided_where_conditions,
3940
-        $model,
3941
-        $model_relation_path
3942
-    ) {
3943
-        $null_friendly_where_conditions = [];
3944
-        $none_overridden                = true;
3945
-        $or_condition_key_for_defaults  = 'OR*' . get_class($model);
3946
-        foreach ($default_where_conditions as $key => $val) {
3947
-            if (isset($provided_where_conditions[ $key ])) {
3948
-                $none_overridden = false;
3949
-            } else {
3950
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3951
-            }
3952
-        }
3953
-        if ($none_overridden && $default_where_conditions) {
3954
-            if ($model->has_primary_key_field()) {
3955
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3956
-                                                                                   . "."
3957
-                                                                                   . $model->primary_key_name() ] =
3958
-                    ['IS NULL'];
3959
-            }/*else{
2638
+			$this->_show_next_x_db_queries--;
2639
+		}
2640
+	}
2641
+
2642
+
2643
+	/**
2644
+	 * Adds a relationship of the correct type between $modelObject and $otherModelObject.
2645
+	 * There are the 3 cases:
2646
+	 * 'belongsTo' relationship: sets $id_or_obj's foreign_key to be $other_model_id_or_obj's primary_key. If
2647
+	 * $otherModelObject has no ID, it is first saved.
2648
+	 * 'hasMany' relationship: sets $other_model_id_or_obj's foreign_key to be $id_or_obj's primary_key. If $id_or_obj
2649
+	 * has no ID, it is first saved.
2650
+	 * 'hasAndBelongsToMany' relationships: checks that there isn't already an entry in the join table, and adds one.
2651
+	 * If one of the model Objects has not yet been saved to the database, it is saved before adding the entry in the
2652
+	 * join table
2653
+	 *
2654
+	 * @param EE_Base_Class|int $id_or_obj                        EE_base_Class or ID of $thisModelObject
2655
+	 * @param EE_Base_Class|int $other_model_id_or_obj            EE_base_Class or ID of other Model Object
2656
+	 * @param string            $relationName                     , key in EEM_Base::_relations
2657
+	 *                                                            an attendee to a group, you also want to specify
2658
+	 *                                                            which role they will have in that group. So you would
2659
+	 *                                                            use this parameter to specify
2660
+	 *                                                            array('role-column-name'=>'role-id')
2661
+	 * @param array|null        $extra_join_model_fields_n_values This allows you to enter further query params for the
2662
+	 *                                                            relation to for relation to methods that allow you to
2663
+	 *                                                            further specify extra columns to join by (such as
2664
+	 *                                                            HABTM).  Keep in mind that the only acceptable
2665
+	 *                                                            query_params is strict "col" => "value" pairs because
2666
+	 *                                                            these will be inserted in any new rows created as
2667
+	 *                                                            well.
2668
+	 * @return EE_Base_Class which was added as a relation. Object referred to by $other_model_id_or_obj
2669
+	 * @throws EE_Error
2670
+	 */
2671
+	public function add_relationship_to(
2672
+		$id_or_obj,
2673
+		$other_model_id_or_obj,
2674
+		$relationName,
2675
+		$extra_join_model_fields_n_values = []
2676
+	) {
2677
+		$relation_obj = $this->related_settings_for($relationName);
2678
+		return $relation_obj->add_relation_to($id_or_obj, $other_model_id_or_obj, $extra_join_model_fields_n_values);
2679
+	}
2680
+
2681
+
2682
+	/**
2683
+	 * Removes a relationship of the correct type between $modelObject and $otherModelObject.
2684
+	 * There are the 3 cases:
2685
+	 * 'belongsTo' relationship: sets $modelObject's foreign_key to null, if that field is nullable.Otherwise throws an
2686
+	 * error
2687
+	 * 'hasMany' relationship: sets $otherModelObject's foreign_key to null,if that field is nullable.Otherwise throws
2688
+	 * an error
2689
+	 * 'hasAndBelongsToMany' relationships:removes any existing entry in the join table between the two models.
2690
+	 *
2691
+	 * @param EE_Base_Class|int $id_or_obj             EE_base_Class or ID of $thisModelObject
2692
+	 * @param EE_Base_Class|int $other_model_id_or_obj EE_base_Class or ID of other Model Object
2693
+	 * @param string            $relationName          key in EEM_Base::_relations
2694
+	 * @param array|null        $where_query           This allows you to enter further query params for the relation
2695
+	 *                                                 to for relation to methods that allow you to further specify
2696
+	 *                                                 extra columns to join by (such as HABTM). Keep in mind that the
2697
+	 *                                                 only acceptable query_params is strict "col" => "value" pairs
2698
+	 *                                                 because these will be inserted in any new rows created as well.
2699
+	 * @return EE_Base_Class
2700
+	 * @throws EE_Error
2701
+	 */
2702
+	public function remove_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query = [])
2703
+	{
2704
+		$relation_obj = $this->related_settings_for($relationName);
2705
+		return $relation_obj->remove_relation_to($id_or_obj, $other_model_id_or_obj, $where_query);
2706
+	}
2707
+
2708
+
2709
+	/**
2710
+	 * @param mixed       $id_or_obj
2711
+	 * @param string      $relationName
2712
+	 * @param array|null  $where_query_params
2713
+	 * @return EE_Base_Class[]
2714
+	 * @throws EE_Error
2715
+	 * @throws ReflectionException
2716
+	 */
2717
+	public function remove_relations($id_or_obj, $relationName, $where_query_params = [])
2718
+	{
2719
+		$relation_obj = $this->related_settings_for($relationName);
2720
+		return $relation_obj->remove_relations($id_or_obj, $where_query_params);
2721
+	}
2722
+
2723
+
2724
+	/**
2725
+	 * Gets all the related items of the specified $model_name, using $query_params.
2726
+	 * Note: by default, we remove the "default query params"
2727
+	 * because we want to get even deleted items etc.
2728
+	 *
2729
+	 * @param mixed       $id_or_obj    EE_Base_Class child or its ID
2730
+	 * @param string      $model_name   like 'Event', 'Registration', etc. always singular
2731
+	 * @param array|null  $query_params @see
2732
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2733
+	 * @return EE_Base_Class[]
2734
+	 * @throws EE_Error
2735
+	 * @throws ReflectionException
2736
+	 */
2737
+	public function get_all_related($id_or_obj, $model_name, ?array $query_params = [])
2738
+	{
2739
+		$model_obj         = $this->ensure_is_obj($id_or_obj);
2740
+		$relation_settings = $this->related_settings_for($model_name);
2741
+		return $relation_settings->get_all_related($model_obj, $query_params);
2742
+	}
2743
+
2744
+
2745
+	/**
2746
+	 * Deletes all the model objects across the relation indicated by $model_name
2747
+	 * which are related to $id_or_obj which meet the criteria set in $query_params.
2748
+	 * However, if the model objects can't be deleted because of blocking related model objects, then
2749
+	 * they aren't deleted. (Unless the thing that would have been deleted can be soft-deleted, that still happens).
2750
+	 *
2751
+	 * @param EE_Base_Class|int|string $id_or_obj
2752
+	 * @param string                   $model_name
2753
+	 * @param array|null               $query_params
2754
+	 * @return int how many deleted
2755
+	 * @throws EE_Error
2756
+	 * @throws ReflectionException
2757
+	 */
2758
+	public function delete_related($id_or_obj, $model_name, $query_params = [])
2759
+	{
2760
+		$model_obj         = $this->ensure_is_obj($id_or_obj);
2761
+		$relation_settings = $this->related_settings_for($model_name);
2762
+		return $relation_settings->delete_all_related($model_obj, $query_params);
2763
+	}
2764
+
2765
+
2766
+	/**
2767
+	 * Hard deletes all the model objects across the relation indicated by $model_name
2768
+	 * which are related to $id_or_obj which meet the criteria set in $query_params. If
2769
+	 * the model objects can't be hard deleted because of blocking related model objects,
2770
+	 * just does a soft-delete on them instead.
2771
+	 *
2772
+	 * @param EE_Base_Class|int|string $id_or_obj
2773
+	 * @param string                   $model_name
2774
+	 * @param array|null               $query_params
2775
+	 * @return int how many deleted
2776
+	 * @throws EE_Error
2777
+	 * @throws ReflectionException
2778
+	 */
2779
+	public function delete_related_permanently($id_or_obj, $model_name, $query_params = [])
2780
+	{
2781
+		$model_obj         = $this->ensure_is_obj($id_or_obj);
2782
+		$relation_settings = $this->related_settings_for($model_name);
2783
+		return $relation_settings->delete_related_permanently($model_obj, $query_params);
2784
+	}
2785
+
2786
+
2787
+	/**
2788
+	 * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2789
+	 * unless otherwise specified in the $query_params
2790
+	 *
2791
+	 * @param EE_Base_Class|int|string $id_or_obj
2792
+	 * @param string                   $model_name     like 'Event', or 'Registration'
2793
+	 * @param array|null               $query_params   @see
2794
+	 *                                                 https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2795
+	 * @param string                   $field_to_count name of field to count by. By default, uses primary key
2796
+	 * @param bool                     $distinct       if we want to only count the distinct values for the column then
2797
+	 *                                                 you can trigger that by the setting $distinct to TRUE;
2798
+	 * @return int
2799
+	 * @throws EE_Error
2800
+	 * @throws ReflectionException
2801
+	 */
2802
+	public function count_related(
2803
+		$id_or_obj,
2804
+		$model_name,
2805
+		$query_params = [],
2806
+		$field_to_count = null,
2807
+		$distinct = false
2808
+	) {
2809
+		$related_model = $this->get_related_model_obj($model_name);
2810
+		// we're just going to use the query params on the related model's normal get_all query,
2811
+		// except add a condition to say to match the current mod
2812
+		if (! isset($query_params['default_where_conditions'])) {
2813
+			$query_params['default_where_conditions'] = EE_Default_Where_Conditions::NONE;
2814
+		}
2815
+		$this_model_name                                                 = $this->get_this_model_name();
2816
+		$this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2817
+		$query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2818
+		return $related_model->count($query_params, $field_to_count, $distinct);
2819
+	}
2820
+
2821
+
2822
+	/**
2823
+	 * Instead of getting the related model objects, simply sums up the values of the specified field.
2824
+	 * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2825
+	 *
2826
+	 * @param EE_Base_Class|int|string $id_or_obj
2827
+	 * @param string                   $model_name   like 'Event', or 'Registration'
2828
+	 * @param array|null               $query_params @see
2829
+	 *                                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2830
+	 * @param string                   $field_to_sum name of field to count by. By default, uses primary key
2831
+	 * @return float
2832
+	 * @throws EE_Error
2833
+	 * @throws ReflectionException
2834
+	 */
2835
+	public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2836
+	{
2837
+		$related_model = $this->get_related_model_obj($model_name);
2838
+		if (! is_array($query_params)) {
2839
+			EE_Error::doing_it_wrong(
2840
+				'EEM_Base::sum_related',
2841
+				sprintf(
2842
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
2843
+					gettype($query_params)
2844
+				),
2845
+				'4.6.0'
2846
+			);
2847
+			$query_params = [];
2848
+		}
2849
+		// we're just going to use the query params on the related model's normal get_all query,
2850
+		// except add a condition to say to match the current mod
2851
+		if (! isset($query_params['default_where_conditions'])) {
2852
+			$query_params['default_where_conditions'] = EE_Default_Where_Conditions::NONE;
2853
+		}
2854
+		$this_model_name                                                 = $this->get_this_model_name();
2855
+		$this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2856
+		$query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2857
+		return $related_model->sum($query_params, $field_to_sum);
2858
+	}
2859
+
2860
+
2861
+	/**
2862
+	 * Uses $this->_relatedModels info to find the first related model object of relation $relationName to the given
2863
+	 * $modelObject
2864
+	 *
2865
+	 * @param int | EE_Base_Class $id_or_obj        EE_Base_Class child or its ID
2866
+	 * @param string              $other_model_name , key in $this->_relatedModels, eg 'Registration', or 'Events'
2867
+	 * @param array|null          $query_params     @see
2868
+	 *                                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2869
+	 * @return EE_Base_Class
2870
+	 * @throws EE_Error
2871
+	 * @throws ReflectionException
2872
+	 */
2873
+	public function get_first_related(EE_Base_Class $id_or_obj, $other_model_name, $query_params)
2874
+	{
2875
+		$query_params['limit'] = 1;
2876
+		$results               = $this->get_all_related($id_or_obj, $other_model_name, $query_params);
2877
+		if ($results) {
2878
+			return array_shift($results);
2879
+		}
2880
+		return null;
2881
+	}
2882
+
2883
+
2884
+	/**
2885
+	 * Gets the model's name as it's expected in queries. For example, if this is EEM_Event model, that would be Event
2886
+	 *
2887
+	 * @return string
2888
+	 */
2889
+	public function get_this_model_name()
2890
+	{
2891
+		return str_replace("EEM_", "", get_class($this));
2892
+	}
2893
+
2894
+
2895
+	/**
2896
+	 * Gets the model field on this model which is of type EE_Any_Foreign_Model_Name_Field
2897
+	 *
2898
+	 * @return EE_Any_Foreign_Model_Name_Field
2899
+	 * @throws EE_Error
2900
+	 */
2901
+	public function get_field_containing_related_model_name()
2902
+	{
2903
+		foreach ($this->field_settings(true) as $field) {
2904
+			if ($field instanceof EE_Any_Foreign_Model_Name_Field) {
2905
+				$field_with_model_name = $field;
2906
+			}
2907
+		}
2908
+		if (! isset($field_with_model_name) || ! $field_with_model_name) {
2909
+			throw new EE_Error(
2910
+				sprintf(
2911
+					esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
2912
+					$this->get_this_model_name()
2913
+				)
2914
+			);
2915
+		}
2916
+		return $field_with_model_name;
2917
+	}
2918
+
2919
+
2920
+	/**
2921
+	 * Inserts a new entry into the database, for each table.
2922
+	 * Note: does not add the item to the entity map because that is done by EE_Base_Class::save() right after this.
2923
+	 * If client code uses EEM_Base::insert() directly, then although the item isn't in the entity map,
2924
+	 * we also know there is no model object with the newly inserted item's ID at the moment (because
2925
+	 * if there were, then they would already be in the DB and this would fail); and in the future if someone
2926
+	 * creates a model object with this ID (or grabs it from the DB) then it will be added to the
2927
+	 * entity map at that time anyways. SO, no need for EEM_Base::insert ot add to the entity map
2928
+	 *
2929
+	 * @param array $field_n_values keys are field names, values are their values (in the client code's domain if
2930
+	 *                              $values_already_prepared_by_model_object is false, in the model object's domain if
2931
+	 *                              $values_already_prepared_by_model_object is true. See comment about this at the top
2932
+	 *                              of EEM_Base)
2933
+	 * @return int|string new primary key on main table that got inserted
2934
+	 * @throws EE_Error
2935
+	 * @throws ReflectionException
2936
+	 */
2937
+	public function insert($field_n_values)
2938
+	{
2939
+		/**
2940
+		 * Filters the fields and their values before inserting an item using the models
2941
+		 *
2942
+		 * @param array    $fields_n_values keys are the fields and values are their new values
2943
+		 * @param EEM_Base $model           the model used
2944
+		 */
2945
+		$field_n_values = (array) apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2946
+		if ($this->_satisfies_unique_indexes($field_n_values)) {
2947
+			$main_table = $this->_get_main_table();
2948
+			$new_id     = $this->_insert_into_specific_table($main_table, $field_n_values, false);
2949
+			if ($new_id !== false) {
2950
+				foreach ($this->_get_other_tables() as $other_table) {
2951
+					$this->_insert_into_specific_table($other_table, $field_n_values, $new_id);
2952
+				}
2953
+			}
2954
+			/**
2955
+			 * Done just after attempting to insert a new model object
2956
+			 *
2957
+			 * @param EEM_Base $model           used
2958
+			 * @param array    $fields_n_values fields and their values
2959
+			 * @param int|string the              ID of the newly-inserted model object
2960
+			 */
2961
+			do_action('AHEE__EEM_Base__insert__end', $this, $field_n_values, $new_id);
2962
+			return $new_id;
2963
+		}
2964
+		return false;
2965
+	}
2966
+
2967
+
2968
+	/**
2969
+	 * Checks that the result would satisfy the unique indexes on this model
2970
+	 *
2971
+	 * @param array  $field_n_values
2972
+	 * @param string $action
2973
+	 * @return boolean
2974
+	 * @throws EE_Error
2975
+	 * @throws ReflectionException
2976
+	 */
2977
+	protected function _satisfies_unique_indexes(array $field_n_values, $action = 'insert')
2978
+	{
2979
+		foreach ($this->unique_indexes() as $index_name => $index) {
2980
+			$uniqueness_where_params = array_intersect_key($field_n_values, $index->fields());
2981
+			if ($this->exists([$uniqueness_where_params])) {
2982
+				EE_Error::add_error(
2983
+					sprintf(
2984
+						esc_html__(
2985
+							"Could not %s %s. %s uniqueness index failed. Fields %s must form a unique set, but an entry already exists with values %s.",
2986
+							"event_espresso"
2987
+						),
2988
+						$action,
2989
+						$this->_get_class_name(),
2990
+						$index_name,
2991
+						implode(",", $index->field_names()),
2992
+						http_build_query($uniqueness_where_params)
2993
+					),
2994
+					__FILE__,
2995
+					__FUNCTION__,
2996
+					__LINE__
2997
+				);
2998
+				return false;
2999
+			}
3000
+		}
3001
+		return true;
3002
+	}
3003
+
3004
+
3005
+	/**
3006
+	 * Checks the database for an item that conflicts (ie, if this item were
3007
+	 * saved to the DB would break some uniqueness requirement, like a primary key
3008
+	 * or an index primary key set) with the item specified. $id_obj_or_fields_array
3009
+	 * can be either an EE_Base_Class or an array of fields n values
3010
+	 *
3011
+	 * @param EE_Base_Class|array $obj_or_fields_array
3012
+	 * @param boolean             $include_primary_key whether to use the model object's primary key
3013
+	 *                                                 when looking for conflicts
3014
+	 *                                                 (ie, if false, we ignore the model object's primary key
3015
+	 *                                                 when finding "conflicts". If true, it's also considered).
3016
+	 *                                                 Only works for INT primary key,
3017
+	 *                                                 STRING primary keys cannot be ignored
3018
+	 * @return EE_Base_Class|array
3019
+	 * @throws EE_Error
3020
+	 * @throws ReflectionException
3021
+	 */
3022
+	public function get_one_conflicting($obj_or_fields_array, $include_primary_key = true)
3023
+	{
3024
+		if ($obj_or_fields_array instanceof EE_Base_Class) {
3025
+			$fields_n_values = $obj_or_fields_array->model_field_array();
3026
+		} elseif (is_array($obj_or_fields_array)) {
3027
+			$fields_n_values = $obj_or_fields_array;
3028
+		} else {
3029
+			throw new EE_Error(
3030
+				sprintf(
3031
+					esc_html__(
3032
+						"%s get_all_conflicting should be called with a model object or an array of field names and values, you provided %d",
3033
+						"event_espresso"
3034
+					),
3035
+					$this->class_name,
3036
+					$obj_or_fields_array
3037
+				)
3038
+			);
3039
+		}
3040
+		$query_params = [];
3041
+		if (
3042
+			$this->has_primary_key_field()
3043
+			&& ($include_primary_key
3044
+				|| $this->get_primary_key_field()
3045
+				   instanceof
3046
+				   EE_Primary_Key_String_Field)
3047
+			&& isset($fields_n_values[ $this->primary_key_name() ])
3048
+		) {
3049
+			$query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
3050
+		}
3051
+		foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
3052
+			$uniqueness_where_params                              =
3053
+				array_intersect_key($fields_n_values, $unique_index->fields());
3054
+			$query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
3055
+		}
3056
+		// if there is nothing to base this search on, then we shouldn't find anything
3057
+		if (empty($query_params)) {
3058
+			return [];
3059
+		}
3060
+		return $this->get_one($query_params);
3061
+	}
3062
+
3063
+
3064
+	/**
3065
+	 * Like count, but is optimized and returns a boolean instead of an int
3066
+	 *
3067
+	 * @param array $query_params
3068
+	 * @return boolean
3069
+	 * @throws EE_Error
3070
+	 * @throws ReflectionException
3071
+	 */
3072
+	public function exists($query_params)
3073
+	{
3074
+		$query_params['limit'] = 1;
3075
+		return $this->count($query_params) > 0;
3076
+	}
3077
+
3078
+
3079
+	/**
3080
+	 * Wrapper for exists, except ignores default query parameters so we're only considering ID
3081
+	 *
3082
+	 * @param int|string $id
3083
+	 * @return boolean
3084
+	 * @throws EE_Error
3085
+	 * @throws ReflectionException
3086
+	 */
3087
+	public function exists_by_ID($id)
3088
+	{
3089
+		return $this->exists(
3090
+			[
3091
+				'default_where_conditions' => EE_Default_Where_Conditions::NONE,
3092
+				[
3093
+					$this->primary_key_name() => $id,
3094
+				],
3095
+			]
3096
+		);
3097
+	}
3098
+
3099
+
3100
+	/**
3101
+	 * Inserts a new row in $table, using the $cols_n_values which apply to that table.
3102
+	 * If a $new_id is supplied and if $table is an EE_Other_Table, we assume
3103
+	 * we need to add a foreign key column to point to $new_id (which should be the primary key's value
3104
+	 * on the main table)
3105
+	 * This is protected rather than private because private is not accessible to any child methods and there MAY be
3106
+	 * cases where we want to call it directly rather than via insert().
3107
+	 *
3108
+	 * @access   protected
3109
+	 * @param EE_Table_Base $table
3110
+	 * @param array         $fields_n_values each key should be in field's keys, and value should be an int, string or
3111
+	 *                                       float
3112
+	 * @param int           $new_id          for now we assume only int keys
3113
+	 * @return int ID of new row inserted, or FALSE on failure
3114
+	 * @throws EE_Error
3115
+	 * @global WPDB         $wpdb            only used to get the $wpdb->insert_id after performing an insert
3116
+	 */
3117
+	protected function _insert_into_specific_table(EE_Table_Base $table, $fields_n_values, $new_id = 0)
3118
+	{
3119
+		global $wpdb;
3120
+		$insertion_col_n_values = [];
3121
+		$format_for_insertion   = [];
3122
+		$fields_on_table        = $this->_get_fields_for_table($table->get_table_alias());
3123
+		foreach ($fields_on_table as $field_obj) {
3124
+			// check if its an auto-incrementing column, in which case we should just leave it to do its autoincrement thing
3125
+			if ($field_obj->is_auto_increment()) {
3126
+				continue;
3127
+			}
3128
+			$prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3129
+			// if the value we want to assign it to is NULL, just don't mention it for the insertion
3130
+			if ($prepared_value !== null) {
3131
+				$insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3132
+				$format_for_insertion[]                                   = $field_obj->get_wpdb_data_type();
3133
+			}
3134
+		}
3135
+		if ($table instanceof EE_Secondary_Table && $new_id) {
3136
+			// its not the main table, so we should have already saved the main table's PK which we just inserted
3137
+			// so add the fk to the main table as a column
3138
+			$insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3139
+			$format_for_insertion[]                              =
3140
+				'%d';// yes right now we're only allowing these foreign keys to be INTs
3141
+		}
3142
+
3143
+		// insert the new entry
3144
+		$result = $this->_do_wpdb_query(
3145
+			'insert',
3146
+			[$table->get_table_name(), $insertion_col_n_values, $format_for_insertion]
3147
+		);
3148
+		if ($result === false) {
3149
+			return false;
3150
+		}
3151
+		// ok, now what do we return for the ID of the newly-inserted thing?
3152
+		if ($this->has_primary_key_field()) {
3153
+			if ($this->get_primary_key_field()->is_auto_increment()) {
3154
+				return $wpdb->insert_id;
3155
+			}
3156
+			// it's not an auto-increment primary key, so
3157
+			// it must have been supplied
3158
+			return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3159
+		}
3160
+		// we can't return a  primary key because there is none. instead return
3161
+		// a unique string indicating this model
3162
+		return $this->get_index_primary_key_string($fields_n_values);
3163
+	}
3164
+
3165
+
3166
+	/**
3167
+	 * Prepare the $field_obj 's value in $fields_n_values for use in the database.
3168
+	 * If the field doesn't allow NULL, try to use its default. (If it doesn't allow NULL,
3169
+	 * and there is no default, we pass it along. WPDB will take care of it)
3170
+	 *
3171
+	 * @param EE_Model_Field_Base $field_obj
3172
+	 * @param array               $fields_n_values
3173
+	 * @return mixed string|int|float depending on what the table column will be expecting
3174
+	 * @throws EE_Error
3175
+	 */
3176
+	protected function _prepare_value_or_use_default($field_obj, $fields_n_values)
3177
+	{
3178
+		$field_name = $field_obj->get_name();
3179
+		// if this field doesn't allow nullable, don't allow it
3180
+		if (! $field_obj->is_nullable() && ! isset($fields_n_values[ $field_name ])) {
3181
+			$fields_n_values[ $field_name ] = $field_obj->get_default_value();
3182
+		}
3183
+		$unprepared_value = $fields_n_values[ $field_name ] ?? null;
3184
+		return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3185
+	}
3186
+
3187
+
3188
+	/**
3189
+	 * Consolidates code for preparing  a value supplied to the model for use int eh db. Calls the field's
3190
+	 * prepare_for_use_in_db method on the value, and depending on $value_already_prepare_by_model_obj, may also call
3191
+	 * the field's prepare_for_set() method.
3192
+	 *
3193
+	 * @param mixed               $value value in the client code domain if $value_already_prepared_by_model_object is
3194
+	 *                                   false, otherwise a value in the model object's domain (see lengthy comment at
3195
+	 *                                   top of file)
3196
+	 * @param EE_Model_Field_Base $field field which will be doing the preparing of the value. If null, we assume
3197
+	 *                                   $value is a custom selection
3198
+	 * @return mixed a value ready for use in the database for insertions, updating, or in a where clause
3199
+	 */
3200
+	private function _prepare_value_for_use_in_db($value, $field)
3201
+	{
3202
+		if ($field instanceof EE_Model_Field_Base) {
3203
+			// phpcs:disable PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
3204
+			switch ($this->_values_already_prepared_by_model_object) {
3205
+				/** @noinspection PhpMissingBreakStatementInspection */
3206
+				case self::not_prepared_by_model_object:
3207
+					$value = $field->prepare_for_set($value);
3208
+				// purposefully left out "return"
3209
+				// no break
3210
+				case self::prepared_by_model_object:
3211
+					/** @noinspection SuspiciousAssignmentsInspection */
3212
+					$value = $field->prepare_for_use_in_db($value);
3213
+				// no break
3214
+				case self::prepared_for_use_in_db:
3215
+					// leave the value alone
3216
+			}
3217
+			// phpcs:enable
3218
+		}
3219
+		return $value;
3220
+	}
3221
+
3222
+
3223
+	/**
3224
+	 * Returns the main table on this model
3225
+	 *
3226
+	 * @return EE_Primary_Table
3227
+	 * @throws EE_Error
3228
+	 */
3229
+	protected function _get_main_table()
3230
+	{
3231
+		foreach ($this->_tables as $table) {
3232
+			if ($table instanceof EE_Primary_Table) {
3233
+				return $table;
3234
+			}
3235
+		}
3236
+		throw new EE_Error(
3237
+			sprintf(
3238
+				esc_html__(
3239
+					'There are no main tables on %s. They should be added to _tables array in the constructor',
3240
+					'event_espresso'
3241
+				),
3242
+				$this->class_name
3243
+			)
3244
+		);
3245
+	}
3246
+
3247
+
3248
+	/**
3249
+	 * table
3250
+	 * returns EE_Primary_Table table name
3251
+	 *
3252
+	 * @return string
3253
+	 * @throws EE_Error
3254
+	 */
3255
+	public function table()
3256
+	{
3257
+		return $this->_get_main_table()->get_table_name();
3258
+	}
3259
+
3260
+
3261
+	/**
3262
+	 * table
3263
+	 * returns first EE_Secondary_Table table name
3264
+	 *
3265
+	 * @return string
3266
+	 */
3267
+	public function second_table()
3268
+	{
3269
+		// grab second table from tables array
3270
+		$second_table = end($this->_tables);
3271
+		return $second_table instanceof EE_Secondary_Table
3272
+			? $second_table->get_table_name()
3273
+			: null;
3274
+	}
3275
+
3276
+
3277
+	/**
3278
+	 * get_table_obj_by_alias
3279
+	 * returns table name given it's alias
3280
+	 *
3281
+	 * @param string $table_alias
3282
+	 * @return EE_Primary_Table | EE_Secondary_Table
3283
+	 */
3284
+	public function get_table_obj_by_alias($table_alias = '')
3285
+	{
3286
+		return $this->_tables[ $table_alias ] ?? null;
3287
+	}
3288
+
3289
+
3290
+	/**
3291
+	 * Gets all the tables of type EE_Other_Table from EEM_CPT_Basel_Model::_tables
3292
+	 *
3293
+	 * @return EE_Secondary_Table[]
3294
+	 */
3295
+	protected function _get_other_tables()
3296
+	{
3297
+		$other_tables = [];
3298
+		foreach ($this->_tables as $table_alias => $table) {
3299
+			if ($table instanceof EE_Secondary_Table) {
3300
+				$other_tables[ $table_alias ] = $table;
3301
+			}
3302
+		}
3303
+		return $other_tables;
3304
+	}
3305
+
3306
+
3307
+	/**
3308
+	 * Finds all the fields that correspond to the given table
3309
+	 *
3310
+	 * @param string $table_alias , array key in EEM_Base::_tables
3311
+	 * @return EE_Model_Field_Base[]
3312
+	 */
3313
+	public function _get_fields_for_table($table_alias)
3314
+	{
3315
+		return $this->_fields[ $table_alias ];
3316
+	}
3317
+
3318
+
3319
+	/**
3320
+	 * Recurses through all the where parameters, and finds all the related models we'll need
3321
+	 * to complete this query. Eg, given where parameters like array('EVT_ID'=>3) from within Event model, we won't
3322
+	 * need any related models. But if the array were array('Registrations.REG_ID'=>3), we'd need the related
3323
+	 * Registration model. If it were array('Registrations.Transactions.Payments.PAY_ID'=>3), then we'd need the
3324
+	 * related Registration, Transaction, and Payment models.
3325
+	 *
3326
+	 * @param array $query_params @see
3327
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3328
+	 * @return EE_Model_Query_Info_Carrier
3329
+	 * @throws EE_Error
3330
+	 */
3331
+	public function _extract_related_models_from_query($query_params)
3332
+	{
3333
+		$query_info_carrier = new EE_Model_Query_Info_Carrier();
3334
+		if (array_key_exists(0, $query_params)) {
3335
+			$this->_extract_related_models_from_sub_params_array_keys($query_params[0], $query_info_carrier, 0);
3336
+		}
3337
+		if (array_key_exists('group_by', $query_params)) {
3338
+			if (is_array($query_params['group_by'])) {
3339
+				$this->_extract_related_models_from_sub_params_array_values(
3340
+					$query_params['group_by'],
3341
+					$query_info_carrier,
3342
+					'group_by'
3343
+				);
3344
+			} elseif (! empty($query_params['group_by'])) {
3345
+				$this->_extract_related_model_info_from_query_param(
3346
+					$query_params['group_by'],
3347
+					$query_info_carrier,
3348
+					'group_by'
3349
+				);
3350
+			}
3351
+		}
3352
+		if (array_key_exists('having', $query_params)) {
3353
+			$this->_extract_related_models_from_sub_params_array_keys(
3354
+				$query_params[0],
3355
+				$query_info_carrier,
3356
+				'having'
3357
+			);
3358
+		}
3359
+		if (array_key_exists('order_by', $query_params)) {
3360
+			if (is_array($query_params['order_by'])) {
3361
+				$this->_extract_related_models_from_sub_params_array_keys(
3362
+					$query_params['order_by'],
3363
+					$query_info_carrier,
3364
+					'order_by'
3365
+				);
3366
+			} elseif (! empty($query_params['order_by'])) {
3367
+				$this->_extract_related_model_info_from_query_param(
3368
+					$query_params['order_by'],
3369
+					$query_info_carrier,
3370
+					'order_by'
3371
+				);
3372
+			}
3373
+		}
3374
+		if (array_key_exists('force_join', $query_params)) {
3375
+			$this->_extract_related_models_from_sub_params_array_values(
3376
+				$query_params['force_join'],
3377
+				$query_info_carrier,
3378
+				'force_join'
3379
+			);
3380
+		}
3381
+		$this->extractRelatedModelsFromCustomSelects($query_info_carrier);
3382
+		return $query_info_carrier;
3383
+	}
3384
+
3385
+
3386
+	/**
3387
+	 * For extracting related models from WHERE (0), HAVING (having), ORDER BY (order_by) or forced joins (force_join)
3388
+	 *
3389
+	 * @param array                       $sub_query_params
3390
+	 * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3391
+	 * @param string                      $query_param_type one of $this->_allowed_query_params
3392
+	 * @return EE_Model_Query_Info_Carrier
3393
+	 * @throws EE_Error
3394
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#-0-where-conditions
3395
+	 */
3396
+	private function _extract_related_models_from_sub_params_array_keys(
3397
+		$sub_query_params,
3398
+		EE_Model_Query_Info_Carrier $model_query_info_carrier,
3399
+		$query_param_type
3400
+	) {
3401
+		if (! empty($sub_query_params)) {
3402
+			$sub_query_params = (array) $sub_query_params;
3403
+			foreach ($sub_query_params as $param => $possibly_array_of_params) {
3404
+				// $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3405
+				$this->_extract_related_model_info_from_query_param(
3406
+					$param,
3407
+					$model_query_info_carrier,
3408
+					$query_param_type
3409
+				);
3410
+				// if $possibly_array_of_params is an array, try recursing into it, searching for keys which
3411
+				// indicate needed joins. Eg, array('NOT'=>array('Registration.TXN_ID'=>23)). In this case, we tried
3412
+				// extracting models out of the 'NOT', which obviously wasn't successful, and then we recurse into the value
3413
+				// of array('Registration.TXN_ID'=>23)
3414
+				$query_param_sans_stars =
3415
+					$this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3416
+				if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3417
+					if (! is_array($possibly_array_of_params)) {
3418
+						throw new EE_Error(
3419
+							sprintf(
3420
+								esc_html__(
3421
+									"You used a special where query param %s, but the value isn't an array of where query params, it's just %s'. It should be an array, eg array('EVT_ID'=>23,'OR'=>array('Venue.VNU_ID'=>32,'Venue.VNU_name'=>'monkey_land'))",
3422
+									"event_espresso"
3423
+								),
3424
+								$param,
3425
+								$possibly_array_of_params
3426
+							)
3427
+						);
3428
+					}
3429
+					$this->_extract_related_models_from_sub_params_array_keys(
3430
+						$possibly_array_of_params,
3431
+						$model_query_info_carrier,
3432
+						$query_param_type
3433
+					);
3434
+				} elseif (
3435
+					$query_param_type === 0 // ie WHERE
3436
+					&& is_array($possibly_array_of_params) // need is_array() check so we don't try to explode a string
3437
+					&& isset($possibly_array_of_params[2])
3438
+					&& $possibly_array_of_params[2]
3439
+				) {
3440
+					// then $possible_array_of_params looks something like array('<','DTT_sold',true)
3441
+					// indicating that $possible_array_of_params[1] is actually a field name,
3442
+					// from which we should extract query parameters!
3443
+					if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3444
+						throw new EE_Error(
3445
+							sprintf(
3446
+								esc_html__(
3447
+									"Improperly formed query parameter %s. It should be numerically indexed like array('<','DTT_sold',true); but you provided %s",
3448
+									"event_espresso"
3449
+								),
3450
+								$query_param_type,
3451
+								implode(",", $possibly_array_of_params)
3452
+							)
3453
+						);
3454
+					}
3455
+					$this->_extract_related_model_info_from_query_param(
3456
+						$possibly_array_of_params[1],
3457
+						$model_query_info_carrier,
3458
+						$query_param_type
3459
+					);
3460
+				}
3461
+			}
3462
+		}
3463
+		return $model_query_info_carrier;
3464
+	}
3465
+
3466
+
3467
+	/**
3468
+	 * For extracting related models from forced_joins, where the array values contain the info about what
3469
+	 * models to join with. Eg an array like array('Attendee','Price.Price_Type');
3470
+	 *
3471
+	 * @param array                       $sub_query_params @see
3472
+	 *                                                      https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3473
+	 * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3474
+	 * @param string                      $query_param_type one of $this->_allowed_query_params
3475
+	 * @return EE_Model_Query_Info_Carrier
3476
+	 * @throws EE_Error
3477
+	 */
3478
+	private function _extract_related_models_from_sub_params_array_values(
3479
+		$sub_query_params,
3480
+		EE_Model_Query_Info_Carrier $model_query_info_carrier,
3481
+		$query_param_type
3482
+	) {
3483
+		if (! empty($sub_query_params)) {
3484
+			if (! is_array($sub_query_params)) {
3485
+				throw new EE_Error(
3486
+					sprintf(
3487
+						esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
3488
+						$sub_query_params
3489
+					)
3490
+				);
3491
+			}
3492
+			foreach ($sub_query_params as $param) {
3493
+				// $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3494
+				$this->_extract_related_model_info_from_query_param(
3495
+					$param,
3496
+					$model_query_info_carrier,
3497
+					$query_param_type
3498
+				);
3499
+			}
3500
+		}
3501
+		return $model_query_info_carrier;
3502
+	}
3503
+
3504
+
3505
+	/**
3506
+	 * Extract all the query parts from  model query params
3507
+	 * and put into a EEM_Related_Model_Info_Carrier for easy extraction into a query. We create this object
3508
+	 * instead of directly constructing the SQL because often we need to extract info from the $query_params
3509
+	 * but use them in a different order. Eg, we need to know what models we are querying
3510
+	 * before we know what joins to perform. However, we need to know what data types correspond to which fields on
3511
+	 * other models before we can finalize the where clause SQL.
3512
+	 *
3513
+	 * @param array $query_params
3514
+	 * @return EE_Model_Query_Info_Carrier
3515
+	 * @throws EE_Error
3516
+	 * @throws ModelConfigurationException
3517
+	 * @throws ReflectionException
3518
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3519
+	 */
3520
+	public function _create_model_query_info_carrier($query_params)
3521
+	{
3522
+		if (! is_array($query_params)) {
3523
+			EE_Error::doing_it_wrong(
3524
+				'EEM_Base::_create_model_query_info_carrier',
3525
+				sprintf(
3526
+					esc_html__(
3527
+						'$query_params should be an array, you passed a variable of type %s',
3528
+						'event_espresso'
3529
+					),
3530
+					gettype($query_params)
3531
+				),
3532
+				'4.6.0'
3533
+			);
3534
+			$query_params = [];
3535
+		}
3536
+		$query_params[0] = $query_params[0] ?? [];
3537
+		// first check if we should alter the query to account for caps or not
3538
+		// because the caps might require us to do extra joins
3539
+		if (isset($query_params['caps']) && $query_params['caps'] !== 'none') {
3540
+			$query_params[0] = array_replace_recursive(
3541
+				$query_params[0],
3542
+				$this->caps_where_conditions($query_params['caps'])
3543
+			);
3544
+		}
3545
+
3546
+		// check if we should alter the query to remove data related to protected
3547
+		// custom post types
3548
+		if (isset($query_params['exclude_protected']) && $query_params['exclude_protected'] === true) {
3549
+			$where_param_key_for_password = $this->modelChainAndPassword();
3550
+			// only include if related to a cpt where no password has been set
3551
+			$query_params[0]['OR*nopassword'] = [
3552
+				$where_param_key_for_password       => '',
3553
+				$where_param_key_for_password . '*' => ['IS_NULL'],
3554
+			];
3555
+		}
3556
+		$query_object = $this->_extract_related_models_from_query($query_params);
3557
+		// verify where_query_params has NO numeric indexes.... that's simply not how you use it!
3558
+		foreach ($query_params[0] as $key => $value) {
3559
+			if (is_int($key)) {
3560
+				throw new EE_Error(
3561
+					sprintf(
3562
+						esc_html__(
3563
+							"WHERE query params must NOT be numerically-indexed. You provided the array key '%s' for value '%s' while querying model %s. All the query params provided were '%s' Please read documentation on EEM_Base::get_all.",
3564
+							"event_espresso"
3565
+						),
3566
+						$key,
3567
+						var_export($value, true),
3568
+						var_export($query_params, true),
3569
+						$this->class_name
3570
+					)
3571
+				);
3572
+			}
3573
+		}
3574
+		if (
3575
+			array_key_exists('default_where_conditions', $query_params)
3576
+			&& ! empty($query_params['default_where_conditions'])
3577
+		) {
3578
+			$use_default_where_conditions = $query_params['default_where_conditions'];
3579
+		} else {
3580
+			$use_default_where_conditions = EE_Default_Where_Conditions::ALL;
3581
+		}
3582
+		$query_params[0] = array_merge(
3583
+			$this->_get_default_where_conditions_for_models_in_query(
3584
+				$query_object,
3585
+				$use_default_where_conditions,
3586
+				$query_params[0]
3587
+			),
3588
+			$query_params[0]
3589
+		);
3590
+		$query_object->set_where_sql($this->_construct_where_clause($query_params[0]));
3591
+		// if this is a "on_join_limit" then we are limiting on on a specific table in a multi_table join.
3592
+		// So we need to setup a subquery and use that for the main join.
3593
+		// Note for now this only works on the primary table for the model.
3594
+		// So for instance, you could set the limit array like this:
3595
+		// array( 'on_join_limit' => array('Primary_Table_Alias', array(1,10) ) )
3596
+		if (array_key_exists('on_join_limit', $query_params) && ! empty($query_params['on_join_limit'])) {
3597
+			$query_object->set_main_model_join_sql(
3598
+				$this->_construct_limit_join_select(
3599
+					$query_params['on_join_limit'][0],
3600
+					$query_params['on_join_limit'][1]
3601
+				)
3602
+			);
3603
+		}
3604
+		// set limit
3605
+		if (array_key_exists('limit', $query_params)) {
3606
+			if (is_array($query_params['limit'])) {
3607
+				if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3608
+					$e = sprintf(
3609
+						esc_html__(
3610
+							"Invalid DB query. You passed '%s' for the LIMIT, but only the following are valid: an integer, string representing an integer, a string like 'int,int', or an array like array(int,int)",
3611
+							"event_espresso"
3612
+						),
3613
+						http_build_query($query_params['limit'])
3614
+					);
3615
+					throw new EE_Error($e . "|" . $e);
3616
+				}
3617
+				// they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3618
+				$query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3619
+			} elseif (! empty($query_params['limit'])) {
3620
+				$query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3621
+			}
3622
+		}
3623
+		// set order by
3624
+		if (array_key_exists('order_by', $query_params)) {
3625
+			if (is_array($query_params['order_by'])) {
3626
+				// if they're using 'order_by' as an array, they can't use 'order' (because 'order_by' must
3627
+				// specify whether to ascend or descend on each field. Eg 'order_by'=>array('EVT_ID'=>'ASC'). So
3628
+				// including 'order' wouldn't make any sense if 'order_by' has already specified which way to order!
3629
+				if (array_key_exists('order', $query_params)) {
3630
+					throw new EE_Error(
3631
+						sprintf(
3632
+							esc_html__(
3633
+								"In querying %s, we are using query parameter 'order_by' as an array (keys:%s,values:%s), and so we can't use query parameter 'order' (value %s). You should just use the 'order_by' parameter ",
3634
+								"event_espresso"
3635
+							),
3636
+							$this->class_name,
3637
+							implode(", ", array_keys($query_params['order_by'])),
3638
+							implode(", ", $query_params['order_by']),
3639
+							$query_params['order']
3640
+						)
3641
+					);
3642
+				}
3643
+				$this->_extract_related_models_from_sub_params_array_keys(
3644
+					$query_params['order_by'],
3645
+					$query_object,
3646
+					'order_by'
3647
+				);
3648
+				// assume it's an array of fields to order by
3649
+				$order_array = [];
3650
+				foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3651
+					$order         = $this->_extract_order($order);
3652
+					$order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3653
+				}
3654
+				$query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3655
+			} elseif (! empty($query_params['order_by'])) {
3656
+				$this->_extract_related_model_info_from_query_param(
3657
+					$query_params['order_by'],
3658
+					$query_object,
3659
+					'order',
3660
+					$query_params['order_by']
3661
+				);
3662
+				$order = isset($query_params['order'])
3663
+					? $this->_extract_order($query_params['order'])
3664
+					: 'DESC';
3665
+				$query_object->set_order_by_sql(
3666
+					" ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3667
+				);
3668
+			}
3669
+		}
3670
+		// if 'order_by' wasn't set, maybe they are just using 'order' on its own?
3671
+		if (
3672
+			! array_key_exists('order_by', $query_params)
3673
+			&& array_key_exists('order', $query_params)
3674
+			&& ! empty($query_params['order'])
3675
+		) {
3676
+			$pk_field = $this->get_primary_key_field();
3677
+			$order    = $this->_extract_order($query_params['order']);
3678
+			$query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3679
+		}
3680
+		// set group by
3681
+		if (array_key_exists('group_by', $query_params)) {
3682
+			if (is_array($query_params['group_by'])) {
3683
+				// it's an array, so assume we'll be grouping by a bunch of stuff
3684
+				$group_by_array = [];
3685
+				foreach ($query_params['group_by'] as $field_name_to_group_by) {
3686
+					$group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3687
+				}
3688
+				$query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3689
+			} elseif (! empty($query_params['group_by'])) {
3690
+				$query_object->set_group_by_sql(
3691
+					" GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3692
+				);
3693
+			}
3694
+		}
3695
+		// set having
3696
+		if (array_key_exists('having', $query_params) && $query_params['having']) {
3697
+			$query_object->set_having_sql($this->_construct_having_clause($query_params['having']));
3698
+		}
3699
+		// now, just verify they didn't pass anything wack
3700
+		foreach ($query_params as $query_key => $query_value) {
3701
+			if (! in_array($query_key, $this->_allowed_query_params, true)) {
3702
+				throw new EE_Error(
3703
+					sprintf(
3704
+						esc_html__(
3705
+							"You passed %s as a query parameter to %s, which is illegal! The allowed query parameters are %s",
3706
+							'event_espresso'
3707
+						),
3708
+						$query_key,
3709
+						$this->class_name,
3710
+						//                      print_r( $this->_allowed_query_params, TRUE )
3711
+						implode(',', $this->_allowed_query_params)
3712
+					)
3713
+				);
3714
+			}
3715
+		}
3716
+		$main_model_join_sql = $query_object->get_main_model_join_sql();
3717
+		if (empty($main_model_join_sql)) {
3718
+			$query_object->set_main_model_join_sql($this->_construct_internal_join());
3719
+		}
3720
+		return $query_object;
3721
+	}
3722
+
3723
+
3724
+	/**
3725
+	 * Gets the where conditions that should be imposed on the query based on the
3726
+	 * context (eg reading frontend, backend, edit or delete).
3727
+	 *
3728
+	 * @param string $context one of EEM_Base::valid_cap_contexts()
3729
+	 * @return array @see
3730
+	 *                        https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3731
+	 * @throws EE_Error
3732
+	 */
3733
+	public function caps_where_conditions($context = self::caps_read)
3734
+	{
3735
+		EEM_Base::verify_is_valid_cap_context($context);
3736
+		$cap_where_conditions = [];
3737
+		$cap_restrictions     = $this->caps_missing($context);
3738
+		foreach ($cap_restrictions as $restriction_if_no_cap) {
3739
+			$cap_where_conditions = array_replace_recursive(
3740
+				$cap_where_conditions,
3741
+				$restriction_if_no_cap->get_default_where_conditions()
3742
+			);
3743
+		}
3744
+		return apply_filters(
3745
+			'FHEE__EEM_Base__caps_where_conditions__return',
3746
+			$cap_where_conditions,
3747
+			$this,
3748
+			$context,
3749
+			$cap_restrictions
3750
+		);
3751
+	}
3752
+
3753
+
3754
+	/**
3755
+	 * Verifies that $should_be_order_string is in $this->_allowed_order_values,
3756
+	 * otherwise throws an exception
3757
+	 *
3758
+	 * @param string $should_be_order_string
3759
+	 * @return string either ASC, asc, DESC or desc
3760
+	 * @throws EE_Error
3761
+	 */
3762
+	private function _extract_order($should_be_order_string)
3763
+	{
3764
+		if (in_array($should_be_order_string, $this->_allowed_order_values)) {
3765
+			return $should_be_order_string;
3766
+		}
3767
+		throw new EE_Error(
3768
+			sprintf(
3769
+				esc_html__(
3770
+					"While performing a query on '%s', tried to use '%s' as an order parameter. ",
3771
+					"event_espresso"
3772
+				),
3773
+				$this->class_name,
3774
+				$should_be_order_string
3775
+			)
3776
+		);
3777
+	}
3778
+
3779
+
3780
+	/**
3781
+	 * Looks at all the models which are included in this query, and asks each
3782
+	 * for their universal_where_params, and returns them in the same format as $query_params[0] (where),
3783
+	 * so they can be merged
3784
+	 *
3785
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
3786
+	 * @param string                      $use_default_where_conditions can be 'none','other_models_only', or 'all'.
3787
+	 *                                                                  'none' means NO default where conditions will
3788
+	 *                                                                  be used AT ALL during this query.
3789
+	 *                                                                  'other_models_only' means default where
3790
+	 *                                                                  conditions from other models will be used, but
3791
+	 *                                                                  not for this primary model. 'all', the default,
3792
+	 *                                                                  means default where conditions will apply as
3793
+	 *                                                                  normal
3794
+	 * @param array                       $where_query_params
3795
+	 * @return array
3796
+	 * @throws EE_Error
3797
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params
3798
+	 *                                                                  .md#0-where-conditions
3799
+	 */
3800
+	private function _get_default_where_conditions_for_models_in_query(
3801
+		EE_Model_Query_Info_Carrier $query_info_carrier,
3802
+		$use_default_where_conditions = EE_Default_Where_Conditions::ALL,
3803
+		$where_query_params = []
3804
+	) {
3805
+		$allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3806
+		if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3807
+			throw new EE_Error(
3808
+				sprintf(
3809
+					esc_html__(
3810
+						"You passed an invalid value to the query parameter 'default_where_conditions' of '%s'. Allowed values are %s",
3811
+						"event_espresso"
3812
+					),
3813
+					$use_default_where_conditions,
3814
+					implode(", ", $allowed_used_default_where_conditions_values)
3815
+				)
3816
+			);
3817
+		}
3818
+		$universal_query_params = [];
3819
+		if ($this->_should_use_default_where_conditions($use_default_where_conditions, true)) {
3820
+			$universal_query_params = $this->_get_default_where_conditions();
3821
+		} elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, true)) {
3822
+			$universal_query_params = $this->_get_minimum_where_conditions();
3823
+		}
3824
+		foreach ($query_info_carrier->get_model_names_included() as $model_relation_path => $model_name) {
3825
+			$related_model = $this->get_related_model_obj($model_name);
3826
+			if ($this->_should_use_default_where_conditions($use_default_where_conditions, false)) {
3827
+				$related_model_universal_where_params =
3828
+					$related_model->_get_default_where_conditions($model_relation_path);
3829
+			} elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, false)) {
3830
+				$related_model_universal_where_params =
3831
+					$related_model->_get_minimum_where_conditions($model_relation_path);
3832
+			} else {
3833
+				// we don't want to add full or even minimum default where conditions from this model, so just continue
3834
+				continue;
3835
+			}
3836
+			$overrides              = $this->_override_defaults_or_make_null_friendly(
3837
+				$related_model_universal_where_params,
3838
+				$where_query_params,
3839
+				$related_model,
3840
+				$model_relation_path
3841
+			);
3842
+			$universal_query_params = EEH_Array::merge_arrays_and_overwrite_keys(
3843
+				$universal_query_params,
3844
+				$overrides
3845
+			);
3846
+		}
3847
+		return $universal_query_params;
3848
+	}
3849
+
3850
+
3851
+	/**
3852
+	 * Determines whether we should use default where conditions for the model in question
3853
+	 * (this model, or other related models).
3854
+	 * Basically, we should use default where conditions on this model if they have requested to use them on all models,
3855
+	 * this model only, or to use minimum where conditions on all other models and normal where conditions on this one.
3856
+	 * We should use default where conditions on related models when they requested to use default where conditions
3857
+	 * on all models, or specifically just on other related models
3858
+	 *
3859
+	 * @param      $default_where_conditions_value
3860
+	 * @param bool $for_this_model false means this is for OTHER related models
3861
+	 * @return bool
3862
+	 */
3863
+	private function _should_use_default_where_conditions($default_where_conditions_value, $for_this_model = true)
3864
+	{
3865
+		return (
3866
+				   $for_this_model
3867
+				   && in_array(
3868
+					   $default_where_conditions_value,
3869
+					   [
3870
+						   EE_Default_Where_Conditions::ALL,
3871
+						   EE_Default_Where_Conditions::THIS_MODEL_ONLY,
3872
+						   EE_Default_Where_Conditions::MINIMUM_OTHERS,
3873
+					   ],
3874
+					   true
3875
+				   )
3876
+			   )
3877
+			   || (
3878
+				   ! $for_this_model
3879
+				   && in_array(
3880
+					   $default_where_conditions_value,
3881
+					   [
3882
+						   EE_Default_Where_Conditions::ALL,
3883
+						   EE_Default_Where_Conditions::OTHER_MODELS_ONLY,
3884
+					   ],
3885
+					   true
3886
+				   )
3887
+			   );
3888
+	}
3889
+
3890
+
3891
+	/**
3892
+	 * Determines whether we should use default minimum conditions for the model in question
3893
+	 * (this model, or other related models).
3894
+	 * Basically, we should use minimum where conditions on this model only if they requested all models to use minimum
3895
+	 * where conditions.
3896
+	 * We should use minimum where conditions on related models if they requested to use minimum where conditions
3897
+	 * on this model or others
3898
+	 *
3899
+	 * @param      $default_where_conditions_value
3900
+	 * @param bool $for_this_model false means this is for OTHER related models
3901
+	 * @return bool
3902
+	 */
3903
+	private function _should_use_minimum_where_conditions($default_where_conditions_value, $for_this_model = true)
3904
+	{
3905
+		return (
3906
+				   $for_this_model
3907
+				   && $default_where_conditions_value === EE_Default_Where_Conditions::MINIMUM_ALL
3908
+			   )
3909
+			   || (
3910
+				   ! $for_this_model
3911
+				   && in_array(
3912
+					   $default_where_conditions_value,
3913
+					   [
3914
+						   EE_Default_Where_Conditions::MINIMUM_OTHERS,
3915
+						   EE_Default_Where_Conditions::MINIMUM_ALL,
3916
+					   ],
3917
+					   true
3918
+				   )
3919
+			   );
3920
+	}
3921
+
3922
+
3923
+	/**
3924
+	 * Checks if any of the defaults have been overridden. If there are any that AREN'T overridden,
3925
+	 * then we also add a special where condition which allows for that model's primary key
3926
+	 * to be null (which is important for JOINs. Eg, if you want to see all Events ordered by Venue's name,
3927
+	 * then Event's with NO Venue won't appear unless you allow VNU_ID to be NULL)
3928
+	 *
3929
+	 * @param array    $default_where_conditions
3930
+	 * @param array    $provided_where_conditions
3931
+	 * @param EEM_Base $model
3932
+	 * @param string   $model_relation_path like 'Transaction.Payment.'
3933
+	 * @return array @see
3934
+	 *                                      https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3935
+	 * @throws EE_Error
3936
+	 */
3937
+	private function _override_defaults_or_make_null_friendly(
3938
+		$default_where_conditions,
3939
+		$provided_where_conditions,
3940
+		$model,
3941
+		$model_relation_path
3942
+	) {
3943
+		$null_friendly_where_conditions = [];
3944
+		$none_overridden                = true;
3945
+		$or_condition_key_for_defaults  = 'OR*' . get_class($model);
3946
+		foreach ($default_where_conditions as $key => $val) {
3947
+			if (isset($provided_where_conditions[ $key ])) {
3948
+				$none_overridden = false;
3949
+			} else {
3950
+				$null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3951
+			}
3952
+		}
3953
+		if ($none_overridden && $default_where_conditions) {
3954
+			if ($model->has_primary_key_field()) {
3955
+				$null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3956
+																				   . "."
3957
+																				   . $model->primary_key_name() ] =
3958
+					['IS NULL'];
3959
+			}/*else{
3960 3960
                 //@todo NO PK, use other defaults
3961 3961
             }*/
3962
-        }
3963
-        return $null_friendly_where_conditions;
3964
-    }
3965
-
3966
-
3967
-    /**
3968
-     * Uses the _default_where_conditions_strategy set during __construct() to get
3969
-     * default where conditions on all get_all, update, and delete queries done by this model.
3970
-     * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3971
-     * NOT array('Event_CPT.post_type'=>'esp_event').
3972
-     *
3973
-     * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3974
-     * @return array @see
3975
-     *                                    https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3976
-     * @throws EE_Error
3977
-     * @throws EE_Error
3978
-     */
3979
-    private function _get_default_where_conditions($model_relation_path = '')
3980
-    {
3981
-        if ($this->_ignore_where_strategy) {
3982
-            return [];
3983
-        }
3984
-        return $this->_default_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3985
-    }
3986
-
3987
-
3988
-    /**
3989
-     * Uses the _minimum_where_conditions_strategy set during __construct() to get
3990
-     * minimum where conditions on all get_all, update, and delete queries done by this model.
3991
-     * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3992
-     * NOT array('Event_CPT.post_type'=>'esp_event').
3993
-     * Similar to _get_default_where_conditions
3994
-     *
3995
-     * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3996
-     * @return array @see
3997
-     *                                    https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3998
-     * @throws EE_Error
3999
-     * @throws EE_Error
4000
-     */
4001
-    protected function _get_minimum_where_conditions($model_relation_path = '')
4002
-    {
4003
-        if ($this->_ignore_where_strategy) {
4004
-            return [];
4005
-        }
4006
-        return $this->_minimum_where_conditions_strategy->get_default_where_conditions($model_relation_path);
4007
-    }
4008
-
4009
-
4010
-    /**
4011
-     * Creates the string of SQL for the select part of a select query, everything behind SELECT and before FROM.
4012
-     * Eg, "Event.post_id, Event.post_name,Event_Detail.EVT_ID..."
4013
-     *
4014
-     * @param EE_Model_Query_Info_Carrier $model_query_info
4015
-     * @return string
4016
-     * @throws EE_Error
4017
-     */
4018
-    private function _construct_default_select_sql(EE_Model_Query_Info_Carrier $model_query_info)
4019
-    {
4020
-        $selects = $this->_get_columns_to_select_for_this_model();
4021
-        foreach (
4022
-            $model_query_info->get_model_names_included() as $model_relation_chain => $name_of_other_model_included
4023
-        ) {
4024
-            $other_model_included = $this->get_related_model_obj($name_of_other_model_included);
4025
-            $other_model_selects  = $other_model_included->_get_columns_to_select_for_this_model($model_relation_chain);
4026
-            foreach ($other_model_selects as $key => $value) {
4027
-                $selects[] = $value;
4028
-            }
4029
-        }
4030
-        return implode(", ", $selects);
4031
-    }
4032
-
4033
-
4034
-    /**
4035
-     * Gets an array of columns to select for this model, which are necessary for it to create its objects.
4036
-     * So that's going to be the columns for all the fields on the model
4037
-     *
4038
-     * @param string $model_relation_chain like 'Question.Question_Group.Event'
4039
-     * @return array numerically indexed, values are columns to select and rename, eg "Event.ID AS 'Event.ID'"
4040
-     */
4041
-    public function _get_columns_to_select_for_this_model($model_relation_chain = '')
4042
-    {
4043
-        $fields                                       = $this->field_settings();
4044
-        $selects                                      = [];
4045
-        $table_alias_with_model_relation_chain_prefix =
4046
-            EE_Model_Parser::extract_table_alias_model_relation_chain_prefix(
4047
-                $model_relation_chain,
4048
-                $this->get_this_model_name()
4049
-            );
4050
-        foreach ($fields as $field_obj) {
4051
-            $selects[] = $table_alias_with_model_relation_chain_prefix
4052
-                         . $field_obj->get_table_alias()
4053
-                         . "."
4054
-                         . $field_obj->get_table_column()
4055
-                         . " AS '"
4056
-                         . $table_alias_with_model_relation_chain_prefix
4057
-                         . $field_obj->get_table_alias()
4058
-                         . "."
4059
-                         . $field_obj->get_table_column()
4060
-                         . "'";
4061
-        }
4062
-        // make sure we are also getting the PKs of each table
4063
-        $tables = $this->get_tables();
4064
-        if (count($tables) > 1) {
4065
-            foreach ($tables as $table_obj) {
4066
-                $qualified_pk_column = $table_alias_with_model_relation_chain_prefix
4067
-                                       . $table_obj->get_fully_qualified_pk_column();
4068
-                if (! in_array($qualified_pk_column, $selects)) {
4069
-                    $selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
4070
-                }
4071
-            }
4072
-        }
4073
-        return $selects;
4074
-    }
4075
-
4076
-
4077
-    /**
4078
-     * Given a $query_param like 'Registration.Transaction.TXN_ID', pops off 'Registration.',
4079
-     * gets the join statement for it; gets the data types for it; and passes the remaining 'Transaction.TXN_ID'
4080
-     * onto its related Transaction object to do the same. Returns an EE_Join_And_Data_Types object which contains the
4081
-     * SQL for joining, and the data types
4082
-     *
4083
-     * @param null|string                 $original_query_param
4084
-     * @param string                      $query_param          like Registration.Transaction.TXN_ID
4085
-     * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4086
-     * @param string                      $query_param_type     like Registration.Transaction.TXN_ID
4087
-     *                                                          or 'PAY_ID'. Otherwise, we don't expect there to be a
4088
-     *                                                          column name. We only want model names, eg 'Event.Venue'
4089
-     *                                                          or 'Registration's
4090
-     * @param string                      $original_query_param what it originally was (eg
4091
-     *                                                          Registration.Transaction.TXN_ID). If null, we assume it
4092
-     *                                                          matches $query_param
4093
-     * @return void only modifies the EEM_Related_Model_Info_Carrier passed into it
4094
-     * @throws EE_Error
4095
-     */
4096
-    private function _extract_related_model_info_from_query_param(
4097
-        $query_param,
4098
-        EE_Model_Query_Info_Carrier $passed_in_query_info,
4099
-        $query_param_type,
4100
-        $original_query_param = null
4101
-    ) {
4102
-        if ($original_query_param === null) {
4103
-            $original_query_param = $query_param;
4104
-        }
4105
-        $query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4106
-        // check to see if we have a field on this model
4107
-        $this_model_fields = $this->field_settings(true);
4108
-        if (array_key_exists($query_param, $this_model_fields)) {
4109
-            $field_is_allowed = in_array(
4110
-                $query_param_type,
4111
-                [0, 'where', 'having', 'order_by', 'group_by', 'order', 'custom_selects'],
4112
-                true
4113
-            );
4114
-            if ($field_is_allowed) {
4115
-                return;
4116
-            }
4117
-            throw new EE_Error(
4118
-                sprintf(
4119
-                    esc_html__(
4120
-                        "Using a field name (%s) on model %s is not allowed on this query param type '%s'. Original query param was %s",
4121
-                        "event_espresso"
4122
-                    ),
4123
-                    $query_param,
4124
-                    $this->class_name,
4125
-                    $query_param_type,
4126
-                    $original_query_param
4127
-                )
4128
-            );
4129
-        }
4130
-        // check if this is a special logic query param
4131
-        if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4132
-            $operator_is_allowed = in_array($query_param_type, ['where', 'having', 0, 'custom_selects'], true);
4133
-            if ($operator_is_allowed) {
4134
-                return;
4135
-            }
4136
-            throw new EE_Error(
4137
-                sprintf(
4138
-                    esc_html__(
4139
-                        'Logic query params ("%1$s") are being used incorrectly with the following query param ("%2$s") on model %3$s. %4$sAdditional Info:%4$s%5$s',
4140
-                        'event_espresso'
4141
-                    ),
4142
-                    implode('", "', $this->_logic_query_param_keys),
4143
-                    $query_param,
4144
-                    $this->class_name,
4145
-                    '<br />',
4146
-                    "\t"
4147
-                    . ' $passed_in_query_info = <pre>'
4148
-                    . print_r($passed_in_query_info, true)
4149
-                    . '</pre>'
4150
-                    . "\n\t"
4151
-                    . ' $query_param_type = '
4152
-                    . $query_param_type
4153
-                    . "\n\t"
4154
-                    . ' $original_query_param = '
4155
-                    . $original_query_param
4156
-                )
4157
-            );
4158
-        }
4159
-        // check if it's a custom selection
4160
-        if (
4161
-            $this->_custom_selections instanceof CustomSelects
4162
-            && in_array($query_param, $this->_custom_selections->columnAliases(), true)
4163
-        ) {
4164
-            return;
4165
-        }
4166
-        // check if has a model name at the beginning
4167
-        // and
4168
-        // check if it's a field on a related model
4169
-        if (
4170
-            $this->extractJoinModelFromQueryParams(
4171
-                $passed_in_query_info,
4172
-                $query_param,
4173
-                $original_query_param,
4174
-                $query_param_type
4175
-            )
4176
-        ) {
4177
-            return;
4178
-        }
4179
-
4180
-        // ok so $query_param didn't start with a model name
4181
-        // and we previously confirmed it wasn't a logic query param or field on the current model
4182
-        // it's wack, that's what it is
4183
-        throw new EE_Error(
4184
-            sprintf(
4185
-                esc_html__(
4186
-                    "There is no model named '%s' related to %s. Query param type is %s and original query param is %s",
4187
-                    "event_espresso"
4188
-                ),
4189
-                $query_param,
4190
-                $this->class_name,
4191
-                $query_param_type,
4192
-                $original_query_param
4193
-            )
4194
-        );
4195
-    }
4196
-
4197
-
4198
-    /**
4199
-     * Extracts any possible join model information from the provided possible_join_string.
4200
-     * This method will read the provided $possible_join_string value and determine if there are any possible model
4201
-     * join
4202
-     * parts that should be added to the query.
4203
-     *
4204
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
4205
-     * @param string                      $possible_join_string  Such as Registration.REG_ID, or Registration
4206
-     * @param null|string                 $original_query_param
4207
-     * @param string                      $query_parameter_type  The type for the source of the $possible_join_string
4208
-     *                                                           ('where', 'order_by', 'group_by', 'custom_selects'
4209
-     *                                                           etc.)
4210
-     * @return bool  returns true if a join was added and false if not.
4211
-     * @throws EE_Error
4212
-     */
4213
-    private function extractJoinModelFromQueryParams(
4214
-        EE_Model_Query_Info_Carrier $query_info_carrier,
4215
-        $possible_join_string,
4216
-        $original_query_param,
4217
-        $query_parameter_type
4218
-    ) {
4219
-        foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4220
-            if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4221
-                $this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4222
-                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4223
-                if ($possible_join_string === '') {
4224
-                    // nothing left to $query_param
4225
-                    // we should actually end in a field name, not a model like this!
4226
-                    throw new EE_Error(
4227
-                        sprintf(
4228
-                            esc_html__(
4229
-                                "Query param '%s' (of type %s on model %s) shouldn't end on a period (.) ",
4230
-                                "event_espresso"
4231
-                            ),
4232
-                            $possible_join_string,
4233
-                            $query_parameter_type,
4234
-                            $this->class_name,
4235
-                            $valid_related_model_name
4236
-                        )
4237
-                    );
4238
-                }
4239
-                $related_model_obj = $this->get_related_model_obj($valid_related_model_name);
4240
-                $related_model_obj->_extract_related_model_info_from_query_param(
4241
-                    $possible_join_string,
4242
-                    $query_info_carrier,
4243
-                    $query_parameter_type,
4244
-                    $original_query_param
4245
-                );
4246
-                return true;
4247
-            }
4248
-            if ($possible_join_string === $valid_related_model_name) {
4249
-                $this->_add_join_to_model(
4250
-                    $valid_related_model_name,
4251
-                    $query_info_carrier,
4252
-                    $original_query_param
4253
-                );
4254
-                return true;
4255
-            }
4256
-        }
4257
-        return false;
4258
-    }
4259
-
4260
-
4261
-    /**
4262
-     * Extracts related models from Custom Selects and sets up any joins for those related models.
4263
-     *
4264
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
4265
-     * @throws EE_Error
4266
-     */
4267
-    private function extractRelatedModelsFromCustomSelects(EE_Model_Query_Info_Carrier $query_info_carrier)
4268
-    {
4269
-        if (
4270
-            $this->_custom_selections instanceof CustomSelects
4271
-            && (
4272
-                $this->_custom_selections->type() === CustomSelects::TYPE_STRUCTURED
4273
-                || $this->_custom_selections->type() == CustomSelects::TYPE_COMPLEX
4274
-            )
4275
-        ) {
4276
-            $original_selects = $this->_custom_selections->originalSelects();
4277
-            foreach ($original_selects as $alias => $select_configuration) {
4278
-                $this->extractJoinModelFromQueryParams(
4279
-                    $query_info_carrier,
4280
-                    $select_configuration[0],
4281
-                    $select_configuration[0],
4282
-                    'custom_selects'
4283
-                );
4284
-            }
4285
-        }
4286
-    }
4287
-
4288
-
4289
-    /**
4290
-     * Privately used by _extract_related_model_info_from_query_param to add a join to $model_name
4291
-     * and store it on $passed_in_query_info
4292
-     *
4293
-     * @param string                      $model_name
4294
-     * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4295
-     * @param string                      $original_query_param used to extract the relation chain between the queried
4296
-     *                                                          model and $model_name. Eg, if we are querying Event,
4297
-     *                                                          and are adding a join to 'Payment' with the original
4298
-     *                                                          query param key
4299
-     *                                                          'Registration.Transaction.Payment.PAY_amount', we want
4300
-     *                                                          to extract 'Registration.Transaction.Payment', in case
4301
-     *                                                          Payment wants to add default query params so that it
4302
-     *                                                          will know what models to prepend onto its default query
4303
-     *                                                          params or in case it wants to rename tables (in case
4304
-     *                                                          there are multiple joins to the same table)
4305
-     * @return void
4306
-     * @throws EE_Error
4307
-     */
4308
-    private function _add_join_to_model(
4309
-        $model_name,
4310
-        EE_Model_Query_Info_Carrier $passed_in_query_info,
4311
-        $original_query_param
4312
-    ) {
4313
-        $relation_obj         = $this->related_settings_for($model_name);
4314
-        $model_relation_chain = EE_Model_Parser::extract_model_relation_chain($model_name, $original_query_param);
4315
-        // check if the relation is HABTM, because then we're essentially doing two joins
4316
-        // If so, join first to the JOIN table, and add its data types, and then continue as normal
4317
-        if ($relation_obj instanceof EE_HABTM_Relation) {
4318
-            $join_model_obj = $relation_obj->get_join_model();
4319
-            // replace the model specified with the join model for this relation chain, whi
4320
-            $relation_chain_to_join_model =
4321
-                EE_Model_Parser::replace_model_name_with_join_model_name_in_model_relation_chain(
4322
-                    $model_name,
4323
-                    $join_model_obj->get_this_model_name(),
4324
-                    $model_relation_chain
4325
-                );
4326
-            $passed_in_query_info->merge(
4327
-                new EE_Model_Query_Info_Carrier(
4328
-                    [$relation_chain_to_join_model => $join_model_obj->get_this_model_name()],
4329
-                    $relation_obj->get_join_to_intermediate_model_statement($relation_chain_to_join_model)
4330
-                )
4331
-            );
4332
-        }
4333
-        // now just join to the other table pointed to by the relation object, and add its data types
4334
-        $passed_in_query_info->merge(
4335
-            new EE_Model_Query_Info_Carrier(
4336
-                [$model_relation_chain => $model_name],
4337
-                $relation_obj->get_join_statement($model_relation_chain)
4338
-            )
4339
-        );
4340
-    }
4341
-
4342
-
4343
-    /**
4344
-     * Constructs SQL for where clause, like "WHERE Event.ID = 23 AND Transaction.amount > 100" etc.
4345
-     *
4346
-     * @param array $where_params @see
4347
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4348
-     * @return string of SQL
4349
-     * @throws EE_Error
4350
-     */
4351
-    private function _construct_where_clause($where_params)
4352
-    {
4353
-        $SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4354
-        if ($SQL) {
4355
-            return " WHERE " . $SQL;
4356
-        }
4357
-        return '';
4358
-    }
4359
-
4360
-
4361
-    /**
4362
-     * Just like the _construct_where_clause, except prepends 'HAVING' instead of 'WHERE',
4363
-     * and should be passed HAVING parameters, not WHERE parameters
4364
-     *
4365
-     * @param array $having_params
4366
-     * @return string
4367
-     * @throws EE_Error
4368
-     */
4369
-    private function _construct_having_clause($having_params)
4370
-    {
4371
-        $SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4372
-        if ($SQL) {
4373
-            return " HAVING " . $SQL;
4374
-        }
4375
-        return '';
4376
-    }
4377
-
4378
-
4379
-    /**
4380
-     * Used for creating nested WHERE conditions. Eg "WHERE ! (Event.ID = 3 OR ( Event_Meta.meta_key = 'bob' AND
4381
-     * Event_Meta.meta_value = 'foo'))"
4382
-     *
4383
-     * @param array  $where_params @see
4384
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4385
-     * @param string $glue         joins each subclause together. Should really only be " AND " or " OR "...
4386
-     * @return string of SQL
4387
-     * @throws EE_Error
4388
-     */
4389
-    private function _construct_condition_clause_recursive($where_params, $glue = ' AND')
4390
-    {
4391
-        $where_clauses = [];
4392
-        foreach ($where_params as $query_param => $op_and_value_or_sub_condition) {
4393
-            $query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4394
-            if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4395
-                switch ($query_param) {
4396
-                    case 'not':
4397
-                    case 'NOT':
4398
-                        $where_clauses[] = "! ("
4399
-                                           . $this->_construct_condition_clause_recursive(
4400
-                                               $op_and_value_or_sub_condition,
4401
-                                               $glue
4402
-                                           )
4403
-                                           . ")";
4404
-                        break;
4405
-                    case 'and':
4406
-                    case 'AND':
4407
-                        $where_clauses[] = " ("
4408
-                                           . $this->_construct_condition_clause_recursive(
4409
-                                               $op_and_value_or_sub_condition,
4410
-                                               ' AND '
4411
-                                           )
4412
-                                           . ")";
4413
-                        break;
4414
-                    case 'or':
4415
-                    case 'OR':
4416
-                        $where_clauses[] = " ("
4417
-                                           . $this->_construct_condition_clause_recursive(
4418
-                                               $op_and_value_or_sub_condition,
4419
-                                               ' OR '
4420
-                                           )
4421
-                                           . ")";
4422
-                        break;
4423
-                }
4424
-            } else {
4425
-                $field_obj = $this->_deduce_field_from_query_param($query_param);
4426
-                // if it's not a normal field, maybe it's a custom selection?
4427
-                if (! $field_obj) {
4428
-                    if ($this->_custom_selections instanceof CustomSelects) {
4429
-                        $field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4430
-                    } else {
4431
-                        throw new EE_Error(
4432
-                            sprintf(
4433
-                                esc_html__(
4434
-                                    "%s is neither a valid model field name, nor a custom selection",
4435
-                                    "event_espresso"
4436
-                                ),
4437
-                                $query_param
4438
-                            )
4439
-                        );
4440
-                    }
4441
-                }
4442
-                $op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4443
-                $where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4444
-            }
4445
-        }
4446
-        return $where_clauses
4447
-            ? implode($glue, $where_clauses)
4448
-            : '';
4449
-    }
4450
-
4451
-
4452
-    /**
4453
-     * Takes the input parameter and extract the table name (alias) and column name
4454
-     *
4455
-     * @param string $query_param like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4456
-     * @return string table alias and column name for SQL, eg "Transaction.TXN_ID"
4457
-     * @throws EE_Error
4458
-     */
4459
-    private function _deduce_column_name_from_query_param($query_param)
4460
-    {
4461
-        $field = $this->_deduce_field_from_query_param($query_param);
4462
-        if ($field) {
4463
-            $table_alias_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_from_query_param(
4464
-                $field->get_model_name(),
4465
-                $query_param
4466
-            );
4467
-            return $table_alias_prefix . $field->get_qualified_column();
4468
-        }
4469
-        if (
4470
-            $this->_custom_selections instanceof CustomSelects
4471
-            && in_array($query_param, $this->_custom_selections->columnAliases(), true)
4472
-        ) {
4473
-            // maybe it's custom selection item?
4474
-            // if so, just use it as the "column name"
4475
-            return $query_param;
4476
-        }
4477
-        $custom_select_aliases = $this->_custom_selections instanceof CustomSelects
4478
-            ? implode(',', $this->_custom_selections->columnAliases())
4479
-            : '';
4480
-        throw new EE_Error(
4481
-            sprintf(
4482
-                esc_html__(
4483
-                    "%s is not a valid field on this model, nor a custom selection (%s)",
4484
-                    "event_espresso"
4485
-                ),
4486
-                $query_param,
4487
-                $custom_select_aliases
4488
-            )
4489
-        );
4490
-    }
4491
-
4492
-
4493
-    /**
4494
-     * Removes the * and anything after it from the condition query param key. It is useful to add the * to condition
4495
-     * query param keys (eg, 'OR*', 'EVT_ID') in order for the array keys to still be unique, so that they don't get
4496
-     * overwritten Takes a string like 'Event.EVT_ID*', 'TXN_total**', 'OR*1st', and 'DTT_reg_start*foobar' to
4497
-     * 'Event.EVT_ID', 'TXN_total', 'OR', and 'DTT_reg_start', respectively.
4498
-     *
4499
-     * @param string $condition_query_param_key
4500
-     * @return string
4501
-     */
4502
-    private function _remove_stars_and_anything_after_from_condition_query_param_key($condition_query_param_key)
4503
-    {
4504
-        $pos_of_star = strpos($condition_query_param_key, '*');
4505
-        if ($pos_of_star === false) {
4506
-            return $condition_query_param_key;
4507
-        }
4508
-        return substr($condition_query_param_key, 0, $pos_of_star);
4509
-    }
4510
-
4511
-
4512
-    /**
4513
-     * creates the SQL for the operator and the value in a WHERE clause, eg "< 23" or "LIKE '%monkey%'"
4514
-     *
4515
-     * @param array|string               $op_and_value
4516
-     * @param EE_Model_Field_Base|string $field_obj . If string, should be one of EEM_Base::_valid_wpdb_data_types
4517
-     * @return string
4518
-     * @throws EE_Error
4519
-     */
4520
-    private function _construct_op_and_value($op_and_value, $field_obj)
4521
-    {
4522
-        $operator = '=';
4523
-        $value    = $op_and_value;
4524
-        if (is_array($op_and_value)) {
4525
-            $operator = isset($op_and_value[0])
4526
-                ? $this->_prepare_operator_for_sql($op_and_value[0])
4527
-                : null;
4528
-            if (! $operator) {
4529
-                $php_array_like_string = [];
4530
-                foreach ($op_and_value as $key => $value) {
4531
-                    $value = is_array($value) ? '[' . implode(",", $value) . ']' : $value;
4532
-                    $php_array_like_string[] = "$key=>$value";
4533
-                }
4534
-                throw new EE_Error(
4535
-                    sprintf(
4536
-                        esc_html__(
4537
-                            "You setup a query parameter like you were going to specify an operator, but didn't. You provided '(%s)', but the operator should be at array key index 0 (eg array('>',32))",
4538
-                            "event_espresso"
4539
-                        ),
4540
-                        implode(",", $php_array_like_string)
4541
-                    )
4542
-                );
4543
-            }
4544
-            $value = $op_and_value[1] ?? null;
4545
-        }
4546
-
4547
-        // check to see if the value is actually another field
4548
-        if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2]) {
4549
-            return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4550
-        }
4551
-        if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4552
-            // in this case, the value should be an array, or at least a comma-separated list
4553
-            // it will need to handle a little differently
4554
-            $cleaned_value = $this->_construct_in_value($value, $field_obj);
4555
-            // note: $cleaned_value has already been run through $wpdb->prepare()
4556
-            return $operator . SP . $cleaned_value;
4557
-        }
4558
-        if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4559
-            // the value should be an array with count of two.
4560
-            if (count($value) !== 2) {
4561
-                throw new EE_Error(
4562
-                    sprintf(
4563
-                        esc_html__(
4564
-                            "The '%s' operator must be used with an array of values and there must be exactly TWO values in that array.",
4565
-                            'event_espresso'
4566
-                        ),
4567
-                        "BETWEEN"
4568
-                    )
4569
-                );
4570
-            }
4571
-            $cleaned_value = $this->_construct_between_value($value, $field_obj);
4572
-            return $operator . SP . $cleaned_value;
4573
-        }
4574
-        if (in_array($operator, $this->valid_null_style_operators())) {
4575
-            if ($value !== null) {
4576
-                throw new EE_Error(
4577
-                    sprintf(
4578
-                        esc_html__(
4579
-                            "You attempted to give a value  (%s) while using a NULL-style operator (%s). That isn't valid",
4580
-                            "event_espresso"
4581
-                        ),
4582
-                        $value,
4583
-                        $operator
4584
-                    )
4585
-                );
4586
-            }
4587
-            return $operator;
4588
-        }
4589
-        if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4590
-            // if the operator is 'LIKE', we want to allow percent signs (%) and not
4591
-            // remove other junk. So just treat it as a string.
4592
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4593
-        }
4594
-        if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4595
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4596
-        }
4597
-        if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4598
-            throw new EE_Error(
4599
-                sprintf(
4600
-                    esc_html__(
4601
-                        "Operator '%s' must be used with an array of values, eg 'Registration.REG_ID' => array('%s',array(1,2,3))",
4602
-                        'event_espresso'
4603
-                    ),
4604
-                    $operator,
4605
-                    $operator
4606
-                )
4607
-            );
4608
-        }
4609
-        if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4610
-            throw new EE_Error(
4611
-                sprintf(
4612
-                    esc_html__(
4613
-                        "Operator '%s' must be used with a single value, not an array. Eg 'Registration.REG_ID => array('%s',23))",
4614
-                        'event_espresso'
4615
-                    ),
4616
-                    $operator,
4617
-                    $operator
4618
-                )
4619
-            );
4620
-        }
4621
-        throw new EE_Error(
4622
-            sprintf(
4623
-                esc_html__(
4624
-                    "It appears you've provided some totally invalid query parameters. Operator and value were:'%s', which isn't right at all",
4625
-                    "event_espresso"
4626
-                ),
4627
-                http_build_query($op_and_value)
4628
-            )
4629
-        );
4630
-    }
4631
-
4632
-
4633
-    /**
4634
-     * Creates the operands to be used in a BETWEEN query, eg "'2014-12-31 20:23:33' AND '2015-01-23 12:32:54'"
4635
-     *
4636
-     * @param array                      $values
4637
-     * @param EE_Model_Field_Base|string $field_obj if string, it should be the datatype to be used when querying, eg
4638
-     *                                              '%s'
4639
-     * @return string
4640
-     * @throws EE_Error
4641
-     */
4642
-    public function _construct_between_value($values, $field_obj)
4643
-    {
4644
-        $cleaned_values = [];
4645
-        foreach ($values as $value) {
4646
-            $cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4647
-        }
4648
-        return $cleaned_values[0] . " AND " . $cleaned_values[1];
4649
-    }
4650
-
4651
-
4652
-    /**
4653
-     * Takes an array or a comma-separated list of $values and cleans them
4654
-     * according to $data_type using $wpdb->prepare, and then makes the list a
4655
-     * string surrounded by ( and ). Eg, _construct_in_value(array(1,2,3),'%d') would
4656
-     * return '(1,2,3)'; _construct_in_value("1,2,hack",'%d') would return '(1,2,1)' (assuming
4657
-     * I'm right that a string, when interpreted as a digit, becomes a 1. It might become a 0)
4658
-     *
4659
-     * @param mixed                      $values    array or comma-separated string
4660
-     * @param EE_Model_Field_Base|string $field_obj if string, it should be a wpdb data type like '%s', or '%d'
4661
-     * @return string of SQL to follow an 'IN' or 'NOT IN' operator
4662
-     * @throws EE_Error
4663
-     */
4664
-    public function _construct_in_value($values, $field_obj)
4665
-    {
4666
-        $prepped = [];
4667
-        // check if the value is a CSV list
4668
-        if (is_string($values)) {
4669
-            // in which case, turn it into an array
4670
-            $values = explode(',', $values);
4671
-        }
4672
-        // make sure we only have one of each value in the list
4673
-        $values = array_unique($values);
4674
-        foreach ($values as $value) {
4675
-            $prepped[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4676
-        }
4677
-        // we would just LOVE to leave $cleaned_values as an empty array, and return the value as "()",
4678
-        // but unfortunately that's invalid SQL. So instead we return a string which we KNOW will evaluate to be the empty set
4679
-        // which is effectively equivalent to returning "()". We don't return "(0)" because that only works for auto-incrementing columns
4680
-        if (empty($prepped)) {
4681
-            $all_fields  = $this->field_settings();
4682
-            $first_field = reset($all_fields);
4683
-            $main_table  = $this->_get_main_table();
4684
-            $prepped[]   = "SELECT {$first_field->get_table_column()} FROM {$main_table->get_table_name()} WHERE FALSE";
4685
-        }
4686
-        return '(' . implode(',', $prepped) . ')';
4687
-    }
4688
-
4689
-
4690
-    /**
4691
-     * @param mixed                      $value
4692
-     * @param EE_Model_Field_Base|string $field_obj if string it should be a wpdb data type like '%d'
4693
-     * @return false|null|string
4694
-     * @throws EE_Error
4695
-     */
4696
-    private function _wpdb_prepare_using_field($value, $field_obj)
4697
-    {
4698
-        /** @type WPDB $wpdb */
4699
-        global $wpdb;
4700
-        if ($field_obj instanceof EE_Model_Field_Base) {
4701
-            return $wpdb->prepare(
4702
-                $field_obj->get_wpdb_data_type(),
4703
-                $this->_prepare_value_for_use_in_db($value, $field_obj)
4704
-            );
4705
-        } //$field_obj should really just be a data type
4706
-        if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4707
-            throw new EE_Error(
4708
-                sprintf(
4709
-                    esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
4710
-                    $field_obj,
4711
-                    implode(",", $this->_valid_wpdb_data_types)
4712
-                )
4713
-            );
4714
-        }
4715
-        return $wpdb->prepare($field_obj, $value);
4716
-    }
4717
-
4718
-
4719
-    /**
4720
-     * Takes the input parameter and finds the model field that it indicates.
4721
-     *
4722
-     * @param string $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4723
-     * @return EE_Model_Field_Base
4724
-     * @throws EE_Error
4725
-     */
4726
-    protected function _deduce_field_from_query_param($query_param_name)
4727
-    {
4728
-        // ok, now proceed with deducing which part is the model's name, and which is the field's name
4729
-        // which will help us find the database table and column
4730
-        $query_param_parts = explode(".", $query_param_name);
4731
-        if (empty($query_param_parts)) {
4732
-            throw new EE_Error(
4733
-                sprintf(
4734
-                    esc_html__(
4735
-                        "_extract_column_name is empty when trying to extract column and table name from %s",
4736
-                        'event_espresso'
4737
-                    ),
4738
-                    $query_param_name
4739
-                )
4740
-            );
4741
-        }
4742
-        $number_of_parts       = count($query_param_parts);
4743
-        $last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4744
-        if ($number_of_parts === 1) {
4745
-            $field_name = $last_query_param_part;
4746
-            $model_obj  = $this;
4747
-        } else {// $number_of_parts >= 2
4748
-            // the last part is the column name, and there are only 2parts. therefore...
4749
-            $field_name = $last_query_param_part;
4750
-            $model_obj  = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4751
-        }
4752
-        try {
4753
-            return $model_obj->field_settings_for($field_name);
4754
-        } catch (EE_Error $e) {
4755
-            return null;
4756
-        }
4757
-    }
4758
-
4759
-
4760
-    /**
4761
-     * Given a field's name (ie, a key in $this->field_settings()), uses the EE_Model_Field object to get the table's
4762
-     * alias and column which corresponds to it
4763
-     *
4764
-     * @param string $field_name
4765
-     * @return string
4766
-     * @throws EE_Error
4767
-     */
4768
-    public function _get_qualified_column_for_field($field_name)
4769
-    {
4770
-        $all_fields = $this->field_settings();
4771
-        $field      = $all_fields[ $field_name ] ?? false;
4772
-        if ($field) {
4773
-            return $field->get_qualified_column();
4774
-        }
4775
-        throw new EE_Error(
4776
-            sprintf(
4777
-                esc_html__(
4778
-                    "There is no field titled %s on model %s. Either the query trying to use it is bad, or you need to add it to the list of fields on the model.",
4779
-                    'event_espresso'
4780
-                ),
4781
-                $field_name,
4782
-                $this->class_name
4783
-            )
4784
-        );
4785
-    }
4786
-
4787
-
4788
-    /**
4789
-     * similar to \EEM_Base::_get_qualified_column_for_field() but returns an array with data for ALL fields.
4790
-     * Example usage:
4791
-     * EEM_Ticket::instance()->get_all_wpdb_results(
4792
-     *      array(),
4793
-     *      ARRAY_A,
4794
-     *      EEM_Ticket::instance()->get_qualified_columns_for_all_fields()
4795
-     *  );
4796
-     * is equivalent to
4797
-     *  EEM_Ticket::instance()->get_all_wpdb_results( array(), ARRAY_A, '*' );
4798
-     * and
4799
-     *  EEM_Event::instance()->get_all_wpdb_results(
4800
-     *      array(
4801
-     *          array(
4802
-     *              'Datetime.Ticket.TKT_ID' => array( '<', 100 ),
4803
-     *          ),
4804
-     *          ARRAY_A,
4805
-     *          implode(
4806
-     *              ', ',
4807
-     *              array_merge(
4808
-     *                  EEM_Event::instance()->get_qualified_columns_for_all_fields( '', false ),
4809
-     *                  EEM_Ticket::instance()->get_qualified_columns_for_all_fields( 'Datetime', false )
4810
-     *              )
4811
-     *          )
4812
-     *      )
4813
-     *  );
4814
-     * selects rows from the database, selecting all the event and ticket columns, where the ticket ID is below 100
4815
-     *
4816
-     * @param string $model_relation_chain        the chain of models used to join between the model you want to query
4817
-     *                                            and the one whose fields you are selecting for example: when querying
4818
-     *                                            tickets model and selecting fields from the tickets model you would
4819
-     *                                            leave this parameter empty, because no models are needed to join
4820
-     *                                            between the queried model and the selected one. Likewise when
4821
-     *                                            querying the datetime model and selecting fields from the tickets
4822
-     *                                            model, it would also be left empty, because there is a direct
4823
-     *                                            relation from datetimes to tickets, so no model is needed to join
4824
-     *                                            them together. However, when querying from the event model and
4825
-     *                                            selecting fields from the ticket model, you should provide the string
4826
-     *                                            'Datetime', indicating that the event model must first join to the
4827
-     *                                            datetime model in order to find its relation to ticket model.
4828
-     *                                            Also, when querying from the venue model and selecting fields from
4829
-     *                                            the ticket model, you should provide the string 'Event.Datetime',
4830
-     *                                            indicating you need to join the venue model to the event model,
4831
-     *                                            to the datetime model, in order to find its relation to the ticket
4832
-     *                                            model. This string is used to deduce the prefix that gets added onto
4833
-     *                                            the models' tables qualified columns
4834
-     * @param bool   $return_string               if true, will return a string with qualified column names separated
4835
-     *                                            by ', ' if false, will simply return a numerically indexed array of
4836
-     *                                            qualified column names
4837
-     * @return array|string
4838
-     */
4839
-    public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4840
-    {
4841
-        $table_prefix      = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain)
4842
-                ? ''
4843
-                : '__');
4844
-        $qualified_columns = [];
4845
-        foreach ($this->field_settings() as $field_name => $field) {
4846
-            $qualified_columns[] = $table_prefix . $field->get_qualified_column();
4847
-        }
4848
-        return $return_string
4849
-            ? implode(', ', $qualified_columns)
4850
-            : $qualified_columns;
4851
-    }
4852
-
4853
-
4854
-    /**
4855
-     * constructs the select use on special limit joins
4856
-     * NOTE: for now this has only been tested and will work when the  table alias is for the PRIMARY table. Although
4857
-     * its setup so the select query will be setup on and just doing the special select join off of the primary table
4858
-     * (as that is typically where the limits would be set).
4859
-     *
4860
-     * @param string       $table_alias The table the select is being built for
4861
-     * @param mixed|string $limit       The limit for this select
4862
-     * @return string                The final select join element for the query.
4863
-     * @throws EE_Error
4864
-     * @throws EE_Error
4865
-     */
4866
-    public function _construct_limit_join_select($table_alias, $limit)
4867
-    {
4868
-        $SQL = '';
4869
-        foreach ($this->_tables as $table_obj) {
4870
-            if ($table_obj instanceof EE_Primary_Table) {
4871
-                $SQL .= $table_alias === $table_obj->get_table_alias()
4872
-                    ? $table_obj->get_select_join_limit($limit)
4873
-                    : SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4874
-            } elseif ($table_obj instanceof EE_Secondary_Table) {
4875
-                $SQL .= $table_alias === $table_obj->get_table_alias()
4876
-                    ? $table_obj->get_select_join_limit_join($limit)
4877
-                    : SP . $table_obj->get_join_sql($table_alias) . SP;
4878
-            }
4879
-        }
4880
-        return $SQL;
4881
-    }
4882
-
4883
-
4884
-    /**
4885
-     * Constructs the internal join if there are multiple tables, or simply the table's name and alias
4886
-     * Eg "wp_post AS Event" or "wp_post AS Event INNER JOIN wp_postmeta Event_Meta ON Event.ID = Event_Meta.post_id"
4887
-     *
4888
-     * @return string SQL
4889
-     * @throws EE_Error
4890
-     */
4891
-    public function _construct_internal_join()
4892
-    {
4893
-        $SQL = $this->_get_main_table()->get_table_sql();
4894
-        $SQL .= $this->_construct_internal_join_to_table_with_alias($this->_get_main_table()->get_table_alias());
4895
-        return $SQL;
4896
-    }
4897
-
4898
-
4899
-    /**
4900
-     * Constructs the SQL for joining all the tables on this model.
4901
-     * Normally $alias should be the primary table's alias, but in cases where
4902
-     * we have already joined to a secondary table (eg, the secondary table has a foreign key and is joined before the
4903
-     * primary table) then we should provide that secondary table's alias. Eg, with $alias being the primary table's
4904
-     * alias, this will construct SQL like:
4905
-     * " INNER JOIN wp_esp_secondary_table AS Secondary_Table ON Primary_Table.pk = Secondary_Table.fk".
4906
-     * With $alias being a secondary table's alias, this will construct SQL like:
4907
-     * " INNER JOIN wp_esp_primary_table AS Primary_Table ON Primary_Table.pk = Secondary_Table.fk".
4908
-     *
4909
-     * @param string $alias_prefixed table alias to join to (this table should already be in the FROM SQL clause)
4910
-     * @return string
4911
-     * @throws EE_Error
4912
-     * @throws EE_Error
4913
-     */
4914
-    public function _construct_internal_join_to_table_with_alias($alias_prefixed)
4915
-    {
4916
-        $SQL               = '';
4917
-        $alias_sans_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($alias_prefixed);
4918
-        foreach ($this->_tables as $table_obj) {
4919
-            if ($table_obj instanceof EE_Secondary_Table) {// table is secondary table
4920
-                if ($alias_sans_prefix === $table_obj->get_table_alias()) {
4921
-                    // so we're joining to this table, meaning the table is already in
4922
-                    // the FROM statement, BUT the primary table isn't. So we want
4923
-                    // to add the inverse join sql
4924
-                    $SQL .= $table_obj->get_inverse_join_sql($alias_prefixed);
4925
-                } else {
4926
-                    // just add a regular JOIN to this table from the primary table
4927
-                    $SQL .= $table_obj->get_join_sql($alias_prefixed);
4928
-                }
4929
-            }// if it's a primary table, dont add any SQL. it should already be in the FROM statement
4930
-        }
4931
-        return $SQL;
4932
-    }
4933
-
4934
-
4935
-    /**
4936
-     * Gets an array for storing all the data types on the next-to-be-executed-query.
4937
-     * This should be a growing array of keys being table-columns (eg 'EVT_ID' and 'Event.EVT_ID'), and values being
4938
-     * their data type (eg, '%s', '%d', etc)
4939
-     *
4940
-     * @return array
4941
-     */
4942
-    public function _get_data_types()
4943
-    {
4944
-        $data_types = [];
4945
-        foreach ($this->field_settings() as $field_obj) {
4946
-            // $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4947
-            /** @var $field_obj EE_Model_Field_Base */
4948
-            $data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4949
-        }
4950
-        return $data_types;
4951
-    }
4952
-
4953
-
4954
-    /**
4955
-     * Gets the model object given the relation's name / model's name (eg, 'Event', 'Registration',etc. Always singular)
4956
-     *
4957
-     * @param string $model_name
4958
-     * @return EEM_Base
4959
-     * @throws EE_Error
4960
-     */
4961
-    public function get_related_model_obj($model_name)
4962
-    {
4963
-        $model_classname = "EEM_" . $model_name;
4964
-        if (! class_exists($model_classname)) {
4965
-            throw new EE_Error(
4966
-                sprintf(
4967
-                    esc_html__(
4968
-                        "You specified a related model named %s in your query. No such model exists, if it did, it would have the classname %s",
4969
-                        'event_espresso'
4970
-                    ),
4971
-                    $model_name,
4972
-                    $model_classname
4973
-                )
4974
-            );
4975
-        }
4976
-        return call_user_func($model_classname . "::instance");
4977
-    }
4978
-
4979
-
4980
-    /**
4981
-     * Returns the array of EE_ModelRelations for this model.
4982
-     *
4983
-     * @return EE_Model_Relation_Base[]
4984
-     */
4985
-    public function relation_settings()
4986
-    {
4987
-        return $this->_model_relations;
4988
-    }
4989
-
4990
-
4991
-    /**
4992
-     * Gets all related models that this model BELONGS TO. Handy to know sometimes
4993
-     * because without THOSE models, this model probably doesn't have much purpose.
4994
-     * (Eg, without an event, datetimes have little purpose.)
4995
-     *
4996
-     * @return EE_Belongs_To_Relation[]
4997
-     */
4998
-    public function belongs_to_relations()
4999
-    {
5000
-        $belongs_to_relations = [];
5001
-        foreach ($this->relation_settings() as $model_name => $relation_obj) {
5002
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
5003
-                $belongs_to_relations[ $model_name ] = $relation_obj;
5004
-            }
5005
-        }
5006
-        return $belongs_to_relations;
5007
-    }
5008
-
5009
-
5010
-    /**
5011
-     * Returns the specified EE_Model_Relation, or throws an exception
5012
-     *
5013
-     * @param string $relation_name name of relation, key in $this->_relatedModels
5014
-     * @return EE_Model_Relation_Base
5015
-     * @throws EE_Error
5016
-     */
5017
-    public function related_settings_for($relation_name)
5018
-    {
5019
-        $relatedModels = $this->relation_settings();
5020
-        if (! array_key_exists($relation_name, $relatedModels)) {
5021
-            throw new EE_Error(
5022
-                sprintf(
5023
-                    esc_html__(
5024
-                        'Cannot get %s related to %s. There is no model relation of that type. There is, however, %s...',
5025
-                        'event_espresso'
5026
-                    ),
5027
-                    $relation_name,
5028
-                    $this->_get_class_name(),
5029
-                    implode(', ', array_keys($relatedModels))
5030
-                )
5031
-            );
5032
-        }
5033
-        return $relatedModels[ $relation_name ];
5034
-    }
5035
-
5036
-
5037
-    /**
5038
-     * A convenience method for getting a specific field's settings, instead of getting all field settings for all
5039
-     * fields
5040
-     *
5041
-     * @param string  $fieldName
5042
-     * @param boolean $include_db_only_fields
5043
-     * @return EE_Model_Field_Base
5044
-     * @throws EE_Error
5045
-     */
5046
-    public function field_settings_for($fieldName, $include_db_only_fields = true)
5047
-    {
5048
-        $fieldSettings = $this->field_settings($include_db_only_fields);
5049
-        if (! array_key_exists($fieldName, $fieldSettings)) {
5050
-            throw new EE_Error(
5051
-                sprintf(
5052
-                    esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
5053
-                    $fieldName,
5054
-                    $this->class_name
5055
-                )
5056
-            );
5057
-        }
5058
-        return $fieldSettings[ $fieldName ];
5059
-    }
5060
-
5061
-
5062
-    /**
5063
-     * Checks if this field exists on this model
5064
-     *
5065
-     * @param string $fieldName a key in the model's _field_settings array
5066
-     * @return boolean
5067
-     */
5068
-    public function has_field($fieldName)
5069
-    {
5070
-        $fieldSettings = $this->field_settings(true);
5071
-        if (isset($fieldSettings[ $fieldName ])) {
5072
-            return true;
5073
-        }
5074
-        return false;
5075
-    }
5076
-
5077
-
5078
-    /**
5079
-     * Returns whether this model has a relation to the specified model
5080
-     *
5081
-     * @param string $relation_name possibly one of the keys in the relation_settings array
5082
-     * @return boolean
5083
-     */
5084
-    public function has_relation($relation_name)
5085
-    {
5086
-        $relations = $this->relation_settings();
5087
-        if (isset($relations[ $relation_name ])) {
5088
-            return true;
5089
-        }
5090
-        return false;
5091
-    }
5092
-
5093
-
5094
-    /**
5095
-     * gets the field object of type 'primary_key' from the fieldsSettings attribute.
5096
-     * Eg, on EE_Answer that would be ANS_ID field object
5097
-     *
5098
-     * @param $field_obj
5099
-     * @return boolean
5100
-     */
5101
-    public function is_primary_key_field($field_obj): bool
5102
-    {
5103
-        return $field_obj instanceof EE_Primary_Key_Field_Base;
5104
-    }
5105
-
5106
-
5107
-    /**
5108
-     * gets the field object of type 'primary_key' from the fieldsSettings attribute.
5109
-     * Eg, on EE_Answer that would be ANS_ID field object
5110
-     *
5111
-     * @return EE_Primary_Key_Field_Base
5112
-     * @throws EE_Error
5113
-     */
5114
-    public function get_primary_key_field()
5115
-    {
5116
-        if ($this->_primary_key_field === null) {
5117
-            foreach ($this->field_settings(true) as $field_obj) {
5118
-                if ($this->is_primary_key_field($field_obj)) {
5119
-                    $this->_primary_key_field = $field_obj;
5120
-                    break;
5121
-                }
5122
-            }
5123
-            if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5124
-                throw new EE_Error(
5125
-                    sprintf(
5126
-                        esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
5127
-                        $this->class_name
5128
-                    )
5129
-                );
5130
-            }
5131
-        }
5132
-        return $this->_primary_key_field;
5133
-    }
5134
-
5135
-
5136
-    /**
5137
-     * Returns whether not there is a primary key on this model.
5138
-     * Internally does some caching.
5139
-     *
5140
-     * @return boolean
5141
-     */
5142
-    public function has_primary_key_field()
5143
-    {
5144
-        if ($this->_has_primary_key_field === null) {
5145
-            try {
5146
-                $this->get_primary_key_field();
5147
-                $this->_has_primary_key_field = true;
5148
-            } catch (EE_Error $e) {
5149
-                $this->_has_primary_key_field = false;
5150
-            }
5151
-        }
5152
-        return $this->_has_primary_key_field;
5153
-    }
5154
-
5155
-
5156
-    /**
5157
-     * Finds the first field of type $field_class_name.
5158
-     *
5159
-     * @param string $field_class_name class name of field that you want to find. Eg, EE_Datetime_Field,
5160
-     *                                 EE_Foreign_Key_Field, etc
5161
-     * @return EE_Model_Field_Base or null if none is found
5162
-     */
5163
-    public function get_a_field_of_type($field_class_name)
5164
-    {
5165
-        foreach ($this->field_settings() as $field) {
5166
-            if ($field instanceof $field_class_name) {
5167
-                return $field;
5168
-            }
5169
-        }
5170
-        return null;
5171
-    }
5172
-
5173
-
5174
-    /**
5175
-     * Gets a foreign key field pointing to model.
5176
-     *
5177
-     * @param string $model_name eg Event, Registration, not EEM_Event
5178
-     * @return EE_Foreign_Key_Field_Base
5179
-     * @throws EE_Error
5180
-     */
5181
-    public function get_foreign_key_to($model_name)
5182
-    {
5183
-        if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5184
-            foreach ($this->field_settings() as $field) {
5185
-                if (
5186
-                    $field instanceof EE_Foreign_Key_Field_Base
5187
-                    && in_array($model_name, $field->get_model_names_pointed_to())
5188
-                ) {
5189
-                    $this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5190
-                    break;
5191
-                }
5192
-            }
5193
-            if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5194
-                throw new EE_Error(
5195
-                    sprintf(
5196
-                        esc_html__(
5197
-                            "There is no foreign key field pointing to model %s on model %s",
5198
-                            'event_espresso'
5199
-                        ),
5200
-                        $model_name,
5201
-                        $this->class_name
5202
-                    )
5203
-                );
5204
-            }
5205
-        }
5206
-        return $this->_cache_foreign_key_to_fields[ $model_name ];
5207
-    }
5208
-
5209
-
5210
-    /**
5211
-     * Gets the table name (including $wpdb->prefix) for the table alias
5212
-     *
5213
-     * @param string $table_alias eg Event, Event_Meta, Registration, Transaction, but maybe
5214
-     *                            a table alias with a model chain prefix, like 'Venue__Event_Venue___Event_Meta'.
5215
-     *                            Either one works
5216
-     * @return string
5217
-     */
5218
-    public function get_table_for_alias($table_alias)
5219
-    {
5220
-        $table_alias_sans_model_relation_chain_prefix =
5221
-            EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5222
-        return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5223
-    }
5224
-
5225
-
5226
-    /**
5227
-     * Returns a flat array of all field son this model, instead of organizing them
5228
-     * by table_alias as they are in the constructor.
5229
-     *
5230
-     * @param bool $include_db_only_fields flag indicating whether to include the db-only fields
5231
-     * @return EE_Model_Field_Base[] where the keys are the field's name
5232
-     */
5233
-    public function field_settings($include_db_only_fields = false)
5234
-    {
5235
-        if ($include_db_only_fields) {
5236
-            if ($this->_cached_fields === null) {
5237
-                $this->_cached_fields = [];
5238
-                foreach ($this->_fields as $fields_corresponding_to_table) {
5239
-                    foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5240
-                        $this->_cached_fields[ $field_name ] = $field_obj;
5241
-                    }
5242
-                }
5243
-            }
5244
-            return $this->_cached_fields;
5245
-        }
5246
-        if ($this->_cached_fields_non_db_only === null) {
5247
-            $this->_cached_fields_non_db_only = [];
5248
-            foreach ($this->_fields as $fields_corresponding_to_table) {
5249
-                foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5250
-                    /** @var $field_obj EE_Model_Field_Base */
5251
-                    if (! $field_obj->is_db_only_field()) {
5252
-                        $this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5253
-                    }
5254
-                }
5255
-            }
5256
-        }
5257
-        return $this->_cached_fields_non_db_only;
5258
-    }
5259
-
5260
-
5261
-    /**
5262
-     *        cycle though array of attendees and create objects out of each item
5263
-     *
5264
-     * @access        private
5265
-     * @param array $rows        of results of $wpdb->get_results($query,ARRAY_A)
5266
-     * @return EE_Base_Class[] array keys are primary keys (if there is a primary key on the model. if not,
5267
-     *                           numerically indexed)
5268
-     * @throws EE_Error
5269
-     * @throws ReflectionException
5270
-     */
5271
-    protected function _create_objects($rows = [])
5272
-    {
5273
-        $array_of_objects = [];
5274
-        if (empty($rows)) {
5275
-            return [];
5276
-        }
5277
-        $count_if_model_has_no_primary_key = 0;
5278
-        $has_primary_key                   = $this->has_primary_key_field();
5279
-        $primary_key_field                 = $has_primary_key
5280
-            ? $this->get_primary_key_field()
5281
-            : null;
5282
-        foreach ((array) $rows as $row) {
5283
-            if (empty($row)) {
5284
-                // wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5285
-                return [];
5286
-            }
5287
-            // check if we've already set this object in the results array,
5288
-            // in which case there's no need to process it further (again)
5289
-            if ($has_primary_key) {
5290
-                $table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5291
-                    $row,
5292
-                    $primary_key_field->get_qualified_column(),
5293
-                    $primary_key_field->get_table_column()
5294
-                );
5295
-                if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5296
-                    continue;
5297
-                }
5298
-            }
5299
-            $classInstance = $this->instantiate_class_from_array_or_object($row);
5300
-            if (! $classInstance) {
5301
-                throw new EE_Error(
5302
-                    sprintf(
5303
-                        esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
5304
-                        $this->get_this_model_name(),
5305
-                        http_build_query($row)
5306
-                    )
5307
-                );
5308
-            }
5309
-            // set the timezone on the instantiated objects
5310
-            $classInstance->set_timezone($this->_timezone);
5311
-            // make sure if there is any timezone setting present that we set the timezone for the object
5312
-            $key                      = $has_primary_key
5313
-                ? $classInstance->ID()
5314
-                : $count_if_model_has_no_primary_key++;
5315
-            $array_of_objects[ $key ] = $classInstance;
5316
-            // also, for all the relations of type BelongsTo, see if we can cache
5317
-            // those related models
5318
-            // (we could do this for other relations too, but if there are conditions
5319
-            // that filtered out some fo the results, then we'd be caching an incomplete set
5320
-            // so it requires a little more thought than just caching them immediately...)
5321
-            foreach ($this->_model_relations as $modelName => $relation_obj) {
5322
-                if ($relation_obj instanceof EE_Belongs_To_Relation) {
5323
-                    // check if this model's INFO is present. If so, cache it on the model
5324
-                    $other_model           = $relation_obj->get_other_model();
5325
-                    $other_model_obj_maybe = $other_model->instantiate_class_from_array_or_object($row);
5326
-                    // if we managed to make a model object from the results, cache it on the main model object
5327
-                    if ($other_model_obj_maybe) {
5328
-                        // set timezone on these other model objects if they are present
5329
-                        $other_model_obj_maybe->set_timezone($this->_timezone);
5330
-                        $classInstance->cache($modelName, $other_model_obj_maybe);
5331
-                    }
5332
-                }
5333
-            }
5334
-            // also, if this was a custom select query, let's see if there are any results for the custom select fields
5335
-            // and add them to the object as well.  We'll convert according to the set data_type if there's any set for
5336
-            // the field in the CustomSelects object
5337
-            if ($this->_custom_selections instanceof CustomSelects) {
5338
-                $classInstance->setCustomSelectsValues(
5339
-                    $this->getValuesForCustomSelectAliasesFromResults($row)
5340
-                );
5341
-            }
5342
-        }
5343
-        return $array_of_objects;
5344
-    }
5345
-
5346
-
5347
-    /**
5348
-     * This will parse a given row of results from the db and see if any keys in the results match an alias within the
5349
-     * current CustomSelects object. This will be used to build an array of values indexed by those keys.
5350
-     *
5351
-     * @param array $db_results_row
5352
-     * @return array
5353
-     */
5354
-    protected function getValuesForCustomSelectAliasesFromResults(array $db_results_row)
5355
-    {
5356
-        $results = [];
5357
-        if ($this->_custom_selections instanceof CustomSelects) {
5358
-            foreach ($this->_custom_selections->columnAliases() as $alias) {
5359
-                if (isset($db_results_row[ $alias ])) {
5360
-                    $results[ $alias ] = $this->convertValueToDataType(
5361
-                        $db_results_row[ $alias ],
5362
-                        $this->_custom_selections->getDataTypeForAlias($alias)
5363
-                    );
5364
-                }
5365
-            }
5366
-        }
5367
-        return $results;
5368
-    }
5369
-
5370
-
5371
-    /**
5372
-     * This will set the value for the given alias
5373
-     *
5374
-     * @param string $value
5375
-     * @param string $datatype (one of %d, %s, %f)
5376
-     * @return int|string|float (int for %d, string for %s, float for %f)
5377
-     */
5378
-    protected function convertValueToDataType($value, $datatype)
5379
-    {
5380
-        switch ($datatype) {
5381
-            case '%f':
5382
-                return (float) $value;
5383
-            case '%d':
5384
-                return (int) $value;
5385
-            default:
5386
-                return (string) $value;
5387
-        }
5388
-    }
5389
-
5390
-
5391
-    /**
5392
-     * The purpose of this method is to allow us to create a model object that is not in the db that holds default
5393
-     * values. A typical example of where this is used is when creating a new item and the initial load of a form.  We
5394
-     * dont' necessarily want to test for if the object is present but just assume it is BUT load the defaults from the
5395
-     * object (as set in the model_field!).
5396
-     *
5397
-     * @return EE_Base_Class single EE_Base_Class object with default values for the properties.
5398
-     * @throws EE_Error
5399
-     * @throws ReflectionException
5400
-     */
5401
-    public function create_default_object()
5402
-    {
5403
-        $this_model_fields_and_values = [];
5404
-        // setup the row using default values;
5405
-        foreach ($this->field_settings() as $field_name => $field_obj) {
5406
-            $this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5407
-        }
5408
-        $className = $this->_get_class_name();
5409
-        return EE_Registry::instance()->load_class($className, [$this_model_fields_and_values], false, false);
5410
-    }
5411
-
5412
-
5413
-    /**
5414
-     * @param mixed $cols_n_values either an array of where each key is the name of a field, and the value is its value
5415
-     *                             or an stdClass where each property is the name of a column,
5416
-     * @return EE_Base_Class
5417
-     * @throws EE_Error
5418
-     * @throws ReflectionException
5419
-     */
5420
-    public function instantiate_class_from_array_or_object($cols_n_values)
5421
-    {
5422
-        if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5423
-            $cols_n_values = get_object_vars($cols_n_values);
5424
-        }
5425
-        $primary_key = null;
5426
-        // make sure the array only has keys that are fields/columns on this model
5427
-        $this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5428
-        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5429
-            $primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5430
-        }
5431
-        $className = $this->_get_class_name();
5432
-        // check we actually found results that we can use to build our model object
5433
-        // if not, return null
5434
-        if ($this->has_primary_key_field()) {
5435
-            if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5436
-                return null;
5437
-            }
5438
-        } elseif ($this->unique_indexes()) {
5439
-            $first_column = reset($this_model_fields_n_values);
5440
-            if (empty($first_column)) {
5441
-                return null;
5442
-            }
5443
-        }
5444
-        // if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5445
-        if ($primary_key) {
5446
-            $classInstance = $this->get_from_entity_map($primary_key);
5447
-            if (! $classInstance) {
5448
-                $classInstance = EE_Registry::instance()
5449
-                                            ->load_class(
5450
-                                                $className,
5451
-                                                [$this_model_fields_n_values, $this->_timezone],
5452
-                                                true,
5453
-                                                false
5454
-                                            );
5455
-                // add this new object to the entity map
5456
-                $classInstance = $this->add_to_entity_map($classInstance);
5457
-            }
5458
-        } else {
5459
-            $classInstance = EE_Registry::instance()->load_class(
5460
-                $className,
5461
-                [$this_model_fields_n_values, $this->_timezone],
5462
-                true,
5463
-                false
5464
-            );
5465
-        }
5466
-        return $classInstance;
5467
-    }
5468
-
5469
-
5470
-    /**
5471
-     * Gets the model object from the  entity map if it exists
5472
-     *
5473
-     * @param int|string $id the ID of the model object
5474
-     * @return EE_Base_Class
5475
-     */
5476
-    public function get_from_entity_map($id)
5477
-    {
5478
-        return $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] ?? null;
5479
-    }
5480
-
5481
-
5482
-    /**
5483
-     * add_to_entity_map
5484
-     * Adds the object to the model's entity mappings
5485
-     *        Effectively tells the models "Hey, this model object is the most up-to-date representation of the data,
5486
-     *        and for the remainder of the request, it's even more up-to-date than what's in the database.
5487
-     *        So, if the database doesn't agree with what's in the entity mapper, ignore the database"
5488
-     *        If the database gets updated directly and you want the entity mapper to reflect that change,
5489
-     *        then this method should be called immediately after the update query
5490
-     * Note: The map is indexed by whatever the current blog id is set (via EEM_Base::$_model_query_blog_id).  This is
5491
-     * so on multisite, the entity map is specific to the query being done for a specific site.
5492
-     *
5493
-     * @param EE_Base_Class $object
5494
-     * @return EE_Base_Class
5495
-     * @throws EE_Error
5496
-     * @throws ReflectionException
5497
-     */
5498
-    public function add_to_entity_map(EE_Base_Class $object)
5499
-    {
5500
-        $className = $this->_get_class_name();
5501
-        if (! $object instanceof $className) {
5502
-            throw new EE_Error(
5503
-                sprintf(
5504
-                    esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
5505
-                    is_object($object)
5506
-                        ? get_class($object)
5507
-                        : $object,
5508
-                    $className
5509
-                )
5510
-            );
5511
-        }
5512
-
5513
-        if (! $object->ID()) {
5514
-            throw new EE_Error(
5515
-                sprintf(
5516
-                    esc_html__(
5517
-                        "You tried storing a model object with NO ID in the %s entity mapper.",
5518
-                        "event_espresso"
5519
-                    ),
5520
-                    $this->class_name
5521
-                )
5522
-            );
5523
-        }
5524
-        // double check it's not already there
5525
-        $classInstance = $this->get_from_entity_map($object->ID());
5526
-        if ($classInstance) {
5527
-            return $classInstance;
5528
-        }
5529
-        $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5530
-        return $object;
5531
-    }
5532
-
5533
-
5534
-    /**
5535
-     * if a valid identifier is provided, then that entity is unset from the entity map,
5536
-     * if no identifier is provided, then the entire entity map is emptied
5537
-     *
5538
-     * @param int|string $id the ID of the model object
5539
-     * @return boolean
5540
-     */
5541
-    public function clear_entity_map($id = null)
5542
-    {
5543
-        if (empty($id)) {
5544
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ] = [];
5545
-            return true;
5546
-        }
5547
-        if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5548
-            unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5549
-            return true;
5550
-        }
5551
-        return false;
5552
-    }
5553
-
5554
-
5555
-    /**
5556
-     * Public wrapper for _deduce_fields_n_values_from_cols_n_values.
5557
-     * Given an array where keys are column (or column alias) names and values,
5558
-     * returns an array of their corresponding field names and database values
5559
-     *
5560
-     * @param array $cols_n_values
5561
-     * @return array
5562
-     * @throws EE_Error
5563
-     * @throws ReflectionException
5564
-     */
5565
-    public function deduce_fields_n_values_from_cols_n_values(array $cols_n_values): array
5566
-    {
5567
-        return $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5568
-    }
5569
-
5570
-
5571
-    /**
5572
-     * _deduce_fields_n_values_from_cols_n_values
5573
-     * Given an array where keys are column (or column alias) names and values,
5574
-     * returns an array of their corresponding field names and database values
5575
-     *
5576
-     * @param array|stdClass $cols_n_values
5577
-     * @return array
5578
-     * @throws EE_Error
5579
-     * @throws ReflectionException
5580
-     */
5581
-    protected function _deduce_fields_n_values_from_cols_n_values($cols_n_values): array
5582
-    {
5583
-        if ($cols_n_values instanceof stdClass) {
5584
-            $cols_n_values = get_object_vars($cols_n_values);
5585
-        }
5586
-        $this_model_fields_n_values = [];
5587
-        foreach ($this->get_tables() as $table_alias => $table_obj) {
5588
-            $table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5589
-                $cols_n_values,
5590
-                $table_obj->get_fully_qualified_pk_column(),
5591
-                $table_obj->get_pk_column()
5592
-            );
5593
-            // there is a primary key on this table and its not set. Use defaults for all its columns
5594
-            if ($table_pk_value === null && $table_obj->get_pk_column()) {
5595
-                foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5596
-                    if (! $field_obj->is_db_only_field()) {
5597
-                        // prepare field as if its coming from db
5598
-                        $prepared_value                            =
5599
-                            $field_obj->prepare_for_set($field_obj->get_default_value());
5600
-                        $this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5601
-                    }
5602
-                }
5603
-            } else {
5604
-                // the table's rows existed. Use their values
5605
-                foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5606
-                    if (! $field_obj->is_db_only_field()) {
5607
-                        $this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5608
-                            $cols_n_values,
5609
-                            $field_obj->get_qualified_column(),
5610
-                            $field_obj->get_table_column()
5611
-                        );
5612
-                    }
5613
-                }
5614
-            }
5615
-        }
5616
-        return $this_model_fields_n_values;
5617
-    }
5618
-
5619
-
5620
-    /**
5621
-     * @param $cols_n_values
5622
-     * @param $qualified_column
5623
-     * @param $regular_column
5624
-     * @return null
5625
-     * @throws EE_Error
5626
-     * @throws ReflectionException
5627
-     */
5628
-    protected function _get_column_value_with_table_alias_or_not($cols_n_values, $qualified_column, $regular_column)
5629
-    {
5630
-        $value = null;
5631
-        // ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5632
-        // does the field on the model relate to this column retrieved from the db?
5633
-        // or is it a db-only field? (not relating to the model)
5634
-        if (isset($cols_n_values[ $qualified_column ])) {
5635
-            $value = $cols_n_values[ $qualified_column ];
5636
-        } elseif (isset($cols_n_values[ $regular_column ])) {
5637
-            $value = $cols_n_values[ $regular_column ];
5638
-        } elseif (! empty($this->foreign_key_aliases)) {
5639
-            // no PK?  ok check if there is a foreign key alias set for this table
5640
-            // then check if that alias exists in the incoming data
5641
-            // AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5642
-            foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5643
-                if ($PK_column === $qualified_column && !empty($cols_n_values[ $FK_alias ])) {
5644
-                    $value = $cols_n_values[ $FK_alias ];
5645
-                    [$pk_class] = explode('.', $PK_column);
5646
-                    $pk_model_name = "EEM_{$pk_class}";
5647
-                    /** @var EEM_Base $pk_model */
5648
-                    $pk_model = EE_Registry::instance()->load_model($pk_model_name);
5649
-                    if ($pk_model instanceof EEM_Base) {
5650
-                        // make sure object is pulled from db and added to entity map
5651
-                        $pk_model->get_one_by_ID($value);
5652
-                    }
5653
-                    break;
5654
-                }
5655
-            }
5656
-        }
5657
-        return $value;
5658
-    }
5659
-
5660
-
5661
-    /**
5662
-     * refresh_entity_map_from_db
5663
-     * Makes sure the model object in the entity map at $id assumes the values
5664
-     * of the database (opposite of EE_base_Class::save())
5665
-     *
5666
-     * @param int|string $id
5667
-     * @return EE_Base_Class|EE_Soft_Delete_Base_Class|mixed|null
5668
-     * @throws EE_Error
5669
-     * @throws ReflectionException
5670
-     */
5671
-    public function refresh_entity_map_from_db($id)
5672
-    {
5673
-        $obj_in_map = $this->get_from_entity_map($id);
5674
-        if ($obj_in_map) {
5675
-            $wpdb_results = $this->_get_all_wpdb_results(
5676
-                [[$this->get_primary_key_field()->get_name() => $id], 'limit' => 1]
5677
-            );
5678
-            if ($wpdb_results && is_array($wpdb_results)) {
5679
-                $one_row = reset($wpdb_results);
5680
-                foreach ($this->_deduce_fields_n_values_from_cols_n_values($one_row) as $field_name => $db_value) {
5681
-                    $obj_in_map->set_from_db($field_name, $db_value);
5682
-                }
5683
-                // clear the cache of related model objects
5684
-                foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5685
-                    $obj_in_map->clear_cache($relation_name, null, true);
5686
-                }
5687
-            }
5688
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5689
-            return $obj_in_map;
5690
-        }
5691
-        return $this->get_one_by_ID($id);
5692
-    }
5693
-
5694
-
5695
-    /**
5696
-     * refresh_entity_map_with
5697
-     * Leaves the entry in the entity map alone, but updates it to match the provided
5698
-     * $replacing_model_obj (which we assume to be its equivalent but somehow NOT in the entity map).
5699
-     * This is useful if you have a model object you want to make authoritative over what's in the entity map currently.
5700
-     * Note: The old $replacing_model_obj should now be destroyed as it's now un-authoritative
5701
-     *
5702
-     * @param int|string    $id
5703
-     * @param EE_Base_Class $replacing_model_obj
5704
-     * @return EE_Base_Class
5705
-     * @throws EE_Error
5706
-     * @throws ReflectionException
5707
-     */
5708
-    public function refresh_entity_map_with($id, $replacing_model_obj)
5709
-    {
5710
-        $obj_in_map = $this->get_from_entity_map($id);
5711
-        if ($obj_in_map) {
5712
-            if ($replacing_model_obj instanceof EE_Base_Class) {
5713
-                foreach ($replacing_model_obj->model_field_array() as $field_name => $value) {
5714
-                    $obj_in_map->set($field_name, $value);
5715
-                }
5716
-                // make the model object in the entity map's cache match the $replacing_model_obj
5717
-                foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5718
-                    $obj_in_map->clear_cache($relation_name, null, true);
5719
-                    foreach ($replacing_model_obj->get_all_from_cache($relation_name) as $cache_id => $cached_obj) {
5720
-                        $obj_in_map->cache($relation_name, $cached_obj, $cache_id);
5721
-                    }
5722
-                }
5723
-            }
5724
-            return $obj_in_map;
5725
-        }
5726
-        $this->add_to_entity_map($replacing_model_obj);
5727
-        return $replacing_model_obj;
5728
-    }
5729
-
5730
-
5731
-    /**
5732
-     * Gets the EE class that corresponds to this model. Eg, for EEM_Answer that
5733
-     * would be EE_Answer.To import that class, you'd just add ".class.php" to the name, like so
5734
-     * require_once($this->_getClassName().".class.php");
5735
-     *
5736
-     * @return string
5737
-     */
5738
-    private function _get_class_name()
5739
-    {
5740
-        return "EE_" . $this->get_this_model_name();
5741
-    }
5742
-
5743
-
5744
-    /**
5745
-     * Get the name of the items this model represents, for the quantity specified. Eg,
5746
-     * if $quantity==1, on EEM_Event, it would 'Event' (internationalized), otherwise
5747
-     * it would be 'Events'.
5748
-     *
5749
-     * @param int|float|null $quantity
5750
-     * @return string
5751
-     */
5752
-    public function item_name($quantity = 1): string
5753
-    {
5754
-        $quantity = floor($quantity);
5755
-        return apply_filters(
5756
-            'FHEE__EEM_Base__item_name__plural_or_singular',
5757
-            $quantity > 1
5758
-                ? $this->plural_item
5759
-                : $this->singular_item,
5760
-            $quantity,
5761
-            $this->plural_item,
5762
-            $this->singular_item
5763
-        );
5764
-    }
5765
-
5766
-
5767
-    /**
5768
-     * Very handy general function to allow for plugins to extend any child of EE_TempBase.
5769
-     * If a method is called on a child of EE_TempBase that doesn't exist, this function is called
5770
-     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
5771
-     * requiring a plugin to extend the EE_TempBase (which works fine is there's only 1 plugin, but when will that
5772
-     * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
5773
-     * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
5774
-     * was called, and an array of the original arguments passed to the function. Whatever their callback function
5775
-     * returns will be returned by this function. Example: in functions.php (or in a plugin):
5776
-     * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
5777
-     * my_callback($previousReturnValue,EE_TempBase $object,$argsArray){
5778
-     * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
5779
-     *        return $previousReturnValue.$returnString;
5780
-     * }
5781
-     * require('EEM_Answer.model.php');
5782
-     * echo EEM_Answer::instance()->my_callback('monkeys',100);
5783
-     * // will output "you called my_callback! and passed args:monkeys,100"
5784
-     *
5785
-     * @param string $methodName name of method which was called on a child of EE_TempBase, but which
5786
-     * @param array  $args       array of original arguments passed to the function
5787
-     * @return mixed whatever the plugin which calls add_filter decides
5788
-     * @throws EE_Error
5789
-     */
5790
-    public function __call($methodName, $args)
5791
-    {
5792
-        $className = $this->class_name;
5793
-        $tagName   = "FHEE__{$className}__{$methodName}";
5794
-        if (! has_filter($tagName)) {
5795
-            throw new EE_Error(
5796
-                sprintf(
5797
-                    esc_html__(
5798
-                        'Method %1$s on model %2$s does not exist! You can create one with the following code in functions.php or in a plugin: %4$s function my_callback(%4$s \$previousReturnValue, EEM_Base \$object\ $argsArray=NULL ){%4$s     /*function body*/%4$s      return \$whatever;%4$s }%4$s add_filter( \'%3$s\', \'my_callback\', 10, 3 );',
5799
-                        'event_espresso'
5800
-                    ),
5801
-                    $methodName,
5802
-                    $className,
5803
-                    $tagName,
5804
-                    '<br />'
5805
-                )
5806
-            );
5807
-        }
5808
-        return apply_filters($tagName, null, $this, $args);
5809
-    }
5810
-
5811
-
5812
-    /**
5813
-     * Ensures $base_class_obj_or_id is of the EE_Base_Class child that corresponds ot this model.
5814
-     * If not, assumes its an ID, and uses $this->get_one_by_ID() to get the EE_Base_Class.
5815
-     *
5816
-     * @param EE_Base_Class|string|int $base_class_obj_or_id either:
5817
-     *                                                       the EE_Base_Class object that corresponds to this Model,
5818
-     *                                                       the object's class name
5819
-     *                                                       or object's ID
5820
-     * @param boolean                  $ensure_is_in_db      if set, we will also verify this model object
5821
-     *                                                       exists in the database. If it does not, we add it
5822
-     * @return EE_Base_Class
5823
-     * @throws EE_Error
5824
-     * @throws ReflectionException
5825
-     */
5826
-    public function ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db = false)
5827
-    {
5828
-        $className = $this->_get_class_name();
5829
-        if ($base_class_obj_or_id instanceof $className) {
5830
-            $model_object = $base_class_obj_or_id;
5831
-        } else {
5832
-            $primary_key_field = $this->get_primary_key_field();
5833
-            if (
5834
-                $primary_key_field instanceof EE_Primary_Key_Int_Field
5835
-                && (
5836
-                    is_int($base_class_obj_or_id)
5837
-                    || is_string($base_class_obj_or_id)
5838
-                )
5839
-            ) {
5840
-                // assume it's an ID.
5841
-                // either a proper integer or a string representing an integer (eg "101" instead of 101)
5842
-                $model_object = $this->get_one_by_ID($base_class_obj_or_id);
5843
-            } elseif (
5844
-                $primary_key_field instanceof EE_Primary_Key_String_Field
5845
-                && is_string($base_class_obj_or_id)
5846
-            ) {
5847
-                // assume it's a string representation of the object
5848
-                $model_object = $this->get_one_by_ID($base_class_obj_or_id);
5849
-            } else {
5850
-                throw new EE_Error(
5851
-                    sprintf(
5852
-                        esc_html__(
5853
-                            "'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5854
-                            'event_espresso'
5855
-                        ),
5856
-                        $base_class_obj_or_id,
5857
-                        $this->_get_class_name(),
5858
-                        print_r($base_class_obj_or_id, true)
5859
-                    )
5860
-                );
5861
-            }
5862
-        }
5863
-        if ($ensure_is_in_db && $model_object instanceof EE_Base_Class && $model_object->ID() !== null) {
5864
-            $model_object->save();
5865
-        }
5866
-        return $model_object;
5867
-    }
5868
-
5869
-
5870
-    /**
5871
-     * Similar to ensure_is_obj(), this method makes sure $base_class_obj_or_id
5872
-     * is a value of the this model's primary key. If it's an EE_Base_Class child,
5873
-     * returns it ID.
5874
-     *
5875
-     * @param EE_Base_Class|int|string $base_class_obj_or_id
5876
-     * @return int|string depending on the type of this model object's ID
5877
-     * @throws EE_Error
5878
-     * @throws ReflectionException
5879
-     */
5880
-    public function ensure_is_ID($base_class_obj_or_id)
5881
-    {
5882
-        $className = $this->_get_class_name();
5883
-        if ($base_class_obj_or_id instanceof $className) {
5884
-            /** @var $base_class_obj_or_id EE_Base_Class */
5885
-            $id = $base_class_obj_or_id->ID();
5886
-        } elseif (is_int($base_class_obj_or_id)) {
5887
-            // assume it's an ID
5888
-            $id = $base_class_obj_or_id;
5889
-        } elseif (is_string($base_class_obj_or_id)) {
5890
-            // assume its a string representation of the object
5891
-            $id = $base_class_obj_or_id;
5892
-        } else {
5893
-            throw new EE_Error(
5894
-                sprintf(
5895
-                    esc_html__(
5896
-                        "'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5897
-                        'event_espresso'
5898
-                    ),
5899
-                    $base_class_obj_or_id,
5900
-                    $this->_get_class_name(),
5901
-                    print_r($base_class_obj_or_id, true)
5902
-                )
5903
-            );
5904
-        }
5905
-        return $id;
5906
-    }
5907
-
5908
-
5909
-    /**
5910
-     * Sets whether the values passed to the model (eg, values in WHERE, values in INSERT, UPDATE, etc)
5911
-     * have already been ran through the appropriate model field's prepare_for_use_in_db method. IE, they have
5912
-     * been sanitized and converted into the appropriate domain.
5913
-     * Usually the only place you'll want to change the default (which is to assume values have NOT been sanitized by
5914
-     * the model object/model field) is when making a method call from WITHIN a model object, which has direct access
5915
-     * to its sanitized values. Note: after changing this setting, you should set it back to its previous value (using
5916
-     * get_assumption_concerning_values_already_prepared_by_model_object()) eg.
5917
-     * $EVT = EEM_Event::instance(); $old_setting =
5918
-     * $EVT->get_assumption_concerning_values_already_prepared_by_model_object();
5919
-     * $EVT->assume_values_already_prepared_by_model_object(true);
5920
-     * $EVT->update(array('foo'=>'bar'),array(array('foo'=>'monkey')));
5921
-     * $EVT->assume_values_already_prepared_by_model_object($old_setting);
5922
-     *
5923
-     * @param int $values_already_prepared like one of the constants on EEM_Base
5924
-     * @return void
5925
-     */
5926
-    public function assume_values_already_prepared_by_model_object(
5927
-        $values_already_prepared = self::not_prepared_by_model_object
5928
-    ) {
5929
-        $this->_values_already_prepared_by_model_object = $values_already_prepared;
5930
-    }
5931
-
5932
-
5933
-    /**
5934
-     * Read comments for assume_values_already_prepared_by_model_object()
5935
-     *
5936
-     * @return int
5937
-     */
5938
-    public function get_assumption_concerning_values_already_prepared_by_model_object()
5939
-    {
5940
-        return $this->_values_already_prepared_by_model_object;
5941
-    }
5942
-
5943
-
5944
-    /**
5945
-     * Gets all the indexes on this model
5946
-     *
5947
-     * @return EE_Index[]
5948
-     */
5949
-    public function indexes()
5950
-    {
5951
-        return $this->_indexes;
5952
-    }
5953
-
5954
-
5955
-    /**
5956
-     * Gets all the Unique Indexes on this model
5957
-     *
5958
-     * @return EE_Unique_Index[]
5959
-     */
5960
-    public function unique_indexes()
5961
-    {
5962
-        $unique_indexes = [];
5963
-        foreach ($this->_indexes as $name => $index) {
5964
-            if ($index instanceof EE_Unique_Index) {
5965
-                $unique_indexes [ $name ] = $index;
5966
-            }
5967
-        }
5968
-        return $unique_indexes;
5969
-    }
5970
-
5971
-
5972
-    /**
5973
-     * Gets all the fields which, when combined, make the primary key.
5974
-     * This is usually just an array with 1 element (the primary key), but in cases
5975
-     * where there is no primary key, it's a combination of fields as defined
5976
-     * on a primary index
5977
-     *
5978
-     * @return EE_Model_Field_Base[] indexed by the field's name
5979
-     * @throws EE_Error
5980
-     */
5981
-    public function get_combined_primary_key_fields()
5982
-    {
5983
-        foreach ($this->indexes() as $index) {
5984
-            if ($index instanceof EE_Primary_Key_Index) {
5985
-                return $index->fields();
5986
-            }
5987
-        }
5988
-        return [$this->primary_key_name() => $this->get_primary_key_field()];
5989
-    }
5990
-
5991
-
5992
-    /**
5993
-     * Used to build a primary key string (when the model has no primary key),
5994
-     * which can be used a unique string to identify this model object.
5995
-     *
5996
-     * @param array $fields_n_values keys are field names, values are their values.
5997
-     *                               Note: if you have results from `EEM_Base::get_all_wpdb_results()`, you need to
5998
-     *                               run it through `EEM_Base::deduce_fields_n_values_from_cols_n_values()`
5999
-     *                               before passing it to this function (that will convert it from columns-n-values
6000
-     *                               to field-names-n-values).
6001
-     * @return string
6002
-     * @throws EE_Error
6003
-     */
6004
-    public function get_index_primary_key_string($fields_n_values)
6005
-    {
6006
-        $cols_n_values_for_primary_key_index = array_intersect_key(
6007
-            $fields_n_values,
6008
-            $this->get_combined_primary_key_fields()
6009
-        );
6010
-        return http_build_query($cols_n_values_for_primary_key_index);
6011
-    }
6012
-
6013
-
6014
-    /**
6015
-     * Gets the field values from the primary key string
6016
-     *
6017
-     * @param string $index_primary_key_string
6018
-     * @return null|array
6019
-     * @throws EE_Error
6020
-     * @see EEM_Base::get_combined_primary_key_fields() and EEM_Base::get_index_primary_key_string()
6021
-     */
6022
-    public function parse_index_primary_key_string($index_primary_key_string)
6023
-    {
6024
-        $key_fields = $this->get_combined_primary_key_fields();
6025
-        // check all of them are in the $id
6026
-        $key_vals_in_combined_pk = [];
6027
-        parse_str($index_primary_key_string, $key_vals_in_combined_pk);
6028
-        foreach ($key_fields as $key_field_name => $field_obj) {
6029
-            if (! isset($key_vals_in_combined_pk[ $key_field_name ])) {
6030
-                return null;
6031
-            }
6032
-        }
6033
-        return $key_vals_in_combined_pk;
6034
-    }
6035
-
6036
-
6037
-    /**
6038
-     * verifies that an array of key-value pairs for model fields has a key
6039
-     * for each field comprising the primary key index
6040
-     *
6041
-     * @param array $key_vals
6042
-     * @return boolean
6043
-     * @throws EE_Error
6044
-     */
6045
-    public function has_all_combined_primary_key_fields($key_vals)
6046
-    {
6047
-        $keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
6048
-        foreach ($keys_it_should_have as $key) {
6049
-            if (! isset($key_vals[ $key ])) {
6050
-                return false;
6051
-            }
6052
-        }
6053
-        return true;
6054
-    }
6055
-
6056
-
6057
-    /**
6058
-     * Finds all model objects in the DB that appear to be a copy of $model_object_or_attributes_array.
6059
-     * We consider something to be a copy if all the attributes match (except the ID, of course).
6060
-     *
6061
-     * @param array|EE_Base_Class $model_object_or_attributes_array If its an array, it's field-value pairs
6062
-     * @param array               $query_params                     @see
6063
-     *                                                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
6064
-     * @throws EE_Error
6065
-     * @throws ReflectionException
6066
-     * @return EE_Base_Class[] Array keys are object IDs (if there is a primary key on the model. if not, numerically
6067
-     *                                                              indexed)
6068
-     */
6069
-    public function get_all_copies($model_object_or_attributes_array, $query_params = [])
6070
-    {
6071
-        if ($model_object_or_attributes_array instanceof EE_Base_Class) {
6072
-            $attributes_array = $model_object_or_attributes_array->model_field_array();
6073
-        } elseif (is_array($model_object_or_attributes_array)) {
6074
-            $attributes_array = $model_object_or_attributes_array;
6075
-        } else {
6076
-            throw new EE_Error(
6077
-                sprintf(
6078
-                    esc_html__(
6079
-                        "get_all_copies should be provided with either a model object or an array of field-value-pairs, but was given %s",
6080
-                        "event_espresso"
6081
-                    ),
6082
-                    $model_object_or_attributes_array
6083
-                )
6084
-            );
6085
-        }
6086
-        // even copies obviously won't have the same ID, so remove the primary key
6087
-        // from the WHERE conditions for finding copies (if there is a primary key, of course)
6088
-        if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
6089
-            unset($attributes_array[ $this->primary_key_name() ]);
6090
-        }
6091
-        if (isset($query_params[0])) {
6092
-            $query_params[0] = array_merge($attributes_array, $query_params);
6093
-        } else {
6094
-            $query_params[0] = $attributes_array;
6095
-        }
6096
-        return $this->get_all($query_params);
6097
-    }
6098
-
6099
-
6100
-    /**
6101
-     * Gets the first copy we find. See get_all_copies for more details
6102
-     *
6103
-     * @param mixed EE_Base_Class | array        $model_object_or_attributes_array
6104
-     * @param array $query_params
6105
-     * @return EE_Base_Class
6106
-     * @throws EE_Error
6107
-     * @throws ReflectionException
6108
-     */
6109
-    public function get_one_copy($model_object_or_attributes_array, $query_params = [])
6110
-    {
6111
-        if (! is_array($query_params)) {
6112
-            EE_Error::doing_it_wrong(
6113
-                'EEM_Base::get_one_copy',
6114
-                sprintf(
6115
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
6116
-                    gettype($query_params)
6117
-                ),
6118
-                '4.6.0'
6119
-            );
6120
-            $query_params = [];
6121
-        }
6122
-        $query_params['limit'] = 1;
6123
-        $copies                = $this->get_all_copies($model_object_or_attributes_array, $query_params);
6124
-        if (is_array($copies)) {
6125
-            return array_shift($copies);
6126
-        }
6127
-        return null;
6128
-    }
6129
-
6130
-
6131
-    /**
6132
-     * Updates the item with the specified id. Ignores default query parameters because
6133
-     * we have specified the ID, and its assumed we KNOW what we're doing
6134
-     *
6135
-     * @param array      $fields_n_values keys are field names, values are their new values
6136
-     * @param int|string $id              the value of the primary key to update
6137
-     * @return int number of rows updated
6138
-     * @throws EE_Error
6139
-     * @throws ReflectionException
6140
-     */
6141
-    public function update_by_ID($fields_n_values, $id)
6142
-    {
6143
-        $query_params = [
6144
-            0                          => [$this->get_primary_key_field()->get_name() => $id],
6145
-            'default_where_conditions' => EE_Default_Where_Conditions::OTHER_MODELS_ONLY,
6146
-        ];
6147
-        return $this->update($fields_n_values, $query_params);
6148
-    }
6149
-
6150
-
6151
-    /**
6152
-     * Changes an operator which was supplied to the models into one usable in SQL
6153
-     *
6154
-     * @param string $operator_supplied
6155
-     * @return string an operator which can be used in SQL
6156
-     * @throws EE_Error
6157
-     */
6158
-    private function _prepare_operator_for_sql($operator_supplied)
6159
-    {
6160
-        $sql_operator = $this->_valid_operators[ $operator_supplied ] ?? null;
6161
-        if ($sql_operator) {
6162
-            return $sql_operator;
6163
-        }
6164
-        throw new EE_Error(
6165
-            sprintf(
6166
-                esc_html__(
6167
-                    "The operator '%s' is not in the list of valid operators: %s",
6168
-                    "event_espresso"
6169
-                ),
6170
-                $operator_supplied,
6171
-                implode(",", array_keys($this->_valid_operators))
6172
-            )
6173
-        );
6174
-    }
6175
-
6176
-
6177
-    /**
6178
-     * Gets the valid operators
6179
-     *
6180
-     * @return array keys are accepted strings, values are the SQL they are converted to
6181
-     */
6182
-    public function valid_operators()
6183
-    {
6184
-        return $this->_valid_operators;
6185
-    }
6186
-
6187
-
6188
-    /**
6189
-     * Gets the between-style operators (take 2 arguments).
6190
-     *
6191
-     * @return array keys are accepted strings, values are the SQL they are converted to
6192
-     */
6193
-    public function valid_between_style_operators()
6194
-    {
6195
-        return array_intersect(
6196
-            $this->valid_operators(),
6197
-            $this->_between_style_operators
6198
-        );
6199
-    }
6200
-
6201
-
6202
-    /**
6203
-     * Gets the "like"-style operators (take a single argument, but it may contain wildcards)
6204
-     *
6205
-     * @return array keys are accepted strings, values are the SQL they are converted to
6206
-     */
6207
-    public function valid_like_style_operators()
6208
-    {
6209
-        return array_intersect(
6210
-            $this->valid_operators(),
6211
-            $this->_like_style_operators
6212
-        );
6213
-    }
6214
-
6215
-
6216
-    /**
6217
-     * Gets the "in"-style operators
6218
-     *
6219
-     * @return array keys are accepted strings, values are the SQL they are converted to
6220
-     */
6221
-    public function valid_in_style_operators()
6222
-    {
6223
-        return array_intersect(
6224
-            $this->valid_operators(),
6225
-            $this->_in_style_operators
6226
-        );
6227
-    }
6228
-
6229
-
6230
-    /**
6231
-     * Gets the "null"-style operators (accept no arguments)
6232
-     *
6233
-     * @return array keys are accepted strings, values are the SQL they are converted to
6234
-     */
6235
-    public function valid_null_style_operators()
6236
-    {
6237
-        return array_intersect(
6238
-            $this->valid_operators(),
6239
-            $this->_null_style_operators
6240
-        );
6241
-    }
6242
-
6243
-
6244
-    /**
6245
-     * Gets an array where keys are the primary keys and values are their 'names'
6246
-     * (as determined by the model object's name() function, which is often overridden)
6247
-     *
6248
-     * @param array $query_params like get_all's
6249
-     * @return string[]
6250
-     * @throws EE_Error
6251
-     * @throws ReflectionException
6252
-     */
6253
-    public function get_all_names($query_params = [])
6254
-    {
6255
-        $objs  = $this->get_all($query_params);
6256
-        $names = [];
6257
-        foreach ($objs as $obj) {
6258
-            $names[ $obj->ID() ] = $obj->name();
6259
-        }
6260
-        return $names;
6261
-    }
6262
-
6263
-
6264
-    /**
6265
-     * Gets an array of primary keys from the model objects. If you acquired the model objects
6266
-     * using EEM_Base::get_all() you don't need to call this (and probably shouldn't because
6267
-     * this is duplicated effort and reduces efficiency) you would be better to use
6268
-     * array_keys() on $model_objects.
6269
-     *
6270
-     * @param \EE_Base_Class[] $model_objects
6271
-     * @param boolean          $filter_out_empty_ids if a model object has an ID of '' or 0, don't bother including it
6272
-     *                                               in the returned array
6273
-     * @return array
6274
-     * @throws EE_Error
6275
-     * @throws ReflectionException
6276
-     */
6277
-    public function get_IDs($model_objects, $filter_out_empty_ids = false)
6278
-    {
6279
-        if (! $this->has_primary_key_field()) {
6280
-            if (defined('WP_DEBUG') && WP_DEBUG) {
6281
-                EE_Error::add_error(
6282
-                    esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
6283
-                    __FILE__,
6284
-                    __FUNCTION__,
6285
-                    __LINE__
6286
-                );
6287
-            }
6288
-        }
6289
-        $IDs = [];
6290
-        foreach ($model_objects as $model_object) {
6291
-            $id = $model_object->ID();
6292
-            if (! $id) {
6293
-                if ($filter_out_empty_ids) {
6294
-                    continue;
6295
-                }
6296
-                if (defined('WP_DEBUG') && WP_DEBUG) {
6297
-                    EE_Error::add_error(
6298
-                        esc_html__(
6299
-                            'Called %1$s on a model object that has no ID and so probably hasn\'t been saved to the database',
6300
-                            'event_espresso'
6301
-                        ),
6302
-                        __FILE__,
6303
-                        __FUNCTION__,
6304
-                        __LINE__
6305
-                    );
6306
-                }
6307
-            }
6308
-            $IDs[] = $id;
6309
-        }
6310
-        return $IDs;
6311
-    }
6312
-
6313
-
6314
-    /**
6315
-     * Returns the string used in capabilities relating to this model. If there
6316
-     * are no capabilities that relate to this model returns false
6317
-     *
6318
-     * @return string|false
6319
-     */
6320
-    public function cap_slug()
6321
-    {
6322
-        return apply_filters('FHEE__EEM_Base__cap_slug', $this->_caps_slug, $this);
6323
-    }
6324
-
6325
-
6326
-    /**
6327
-     * Returns the capability-restrictions array (@param string $context
6328
-     *
6329
-     * @return EE_Default_Where_Conditions[] indexed by associated capability
6330
-     * @throws EE_Error
6331
-     * @see EEM_Base::_cap_restrictions).
6332
-     *      If $context is provided (which should be set to one of EEM_Base::valid_cap_contexts())
6333
-     *      only returns the cap restrictions array in that context (ie, the array
6334
-     *      at that key)
6335
-     */
6336
-    public function cap_restrictions($context = EEM_Base::caps_read)
6337
-    {
6338
-        EEM_Base::verify_is_valid_cap_context($context);
6339
-        // check if we ought to run the restriction generator first
6340
-        if (
6341
-            isset($this->_cap_restriction_generators[ $context ])
6342
-            && $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6343
-            && ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6344
-        ) {
6345
-            $this->_cap_restrictions[ $context ] = array_merge(
6346
-                $this->_cap_restrictions[ $context ],
6347
-                $this->_cap_restriction_generators[ $context ]->generate_restrictions()
6348
-            );
6349
-        }
6350
-        // and make sure we've finalized the construction of each restriction
6351
-        foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6352
-            if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6353
-                $where_conditions_obj->_finalize_construct($this);
6354
-            }
6355
-        }
6356
-        return $this->_cap_restrictions[ $context ];
6357
-    }
6358
-
6359
-
6360
-    /**
6361
-     * Indicating whether this model thinks its a wp core model
6362
-     *
6363
-     * @return boolean
6364
-     */
6365
-    public function is_wp_core_model()
6366
-    {
6367
-        return $this->_wp_core_model;
6368
-    }
6369
-
6370
-
6371
-    /**
6372
-     * Gets all the caps that are missing which impose a restriction on
6373
-     * queries made in this context
6374
-     *
6375
-     * @param string $context one of EEM_Base::caps_ constants
6376
-     * @return EE_Default_Where_Conditions[] indexed by capability name
6377
-     * @throws EE_Error
6378
-     */
6379
-    public function caps_missing($context = EEM_Base::caps_read)
6380
-    {
6381
-        $missing_caps     = [];
6382
-        $cap_restrictions = $this->cap_restrictions($context);
6383
-        foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6384
-            if (
6385
-                ! EE_Capabilities::instance()
6386
-                                 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6387
-            ) {
6388
-                $missing_caps[ $cap ] = $restriction_if_no_cap;
6389
-            }
6390
-        }
6391
-        return $missing_caps;
6392
-    }
6393
-
6394
-
6395
-    /**
6396
-     * Gets the mapping from capability contexts to action strings used in capability names
6397
-     *
6398
-     * @return array keys are one of EEM_Base::valid_cap_contexts(), and values are usually
6399
-     * one of 'read', 'edit', or 'delete'
6400
-     */
6401
-    public function cap_contexts_to_cap_action_map()
6402
-    {
6403
-        return apply_filters(
6404
-            'FHEE__EEM_Base__cap_contexts_to_cap_action_map',
6405
-            $this->_cap_contexts_to_cap_action_map,
6406
-            $this
6407
-        );
6408
-    }
6409
-
6410
-
6411
-    /**
6412
-     * Gets the action string for the specified capability context
6413
-     *
6414
-     * @param string $context
6415
-     * @return string one of EEM_Base::cap_contexts_to_cap_action_map() values
6416
-     * @throws EE_Error
6417
-     */
6418
-    public function cap_action_for_context($context)
6419
-    {
6420
-        $mapping = $this->cap_contexts_to_cap_action_map();
6421
-        if (isset($mapping[ $context ])) {
6422
-            return $mapping[ $context ];
6423
-        }
6424
-        if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6425
-            return $action;
6426
-        }
6427
-        throw new EE_Error(
6428
-            sprintf(
6429
-                esc_html__(
6430
-                    'Cannot find capability restrictions for context "%1$s", allowed values are:%2$s',
6431
-                    'event_espresso'
6432
-                ),
6433
-                $context,
6434
-                implode(',', array_keys($this->cap_contexts_to_cap_action_map()))
6435
-            )
6436
-        );
6437
-    }
6438
-
6439
-
6440
-    /**
6441
-     * Returns all the capability contexts which are valid when querying models
6442
-     *
6443
-     * @return array
6444
-     */
6445
-    public static function valid_cap_contexts(): array
6446
-    {
6447
-        return (array) apply_filters(
6448
-            'FHEE__EEM_Base__valid_cap_contexts',
6449
-            [
6450
-                self::caps_read,
6451
-                self::caps_read_admin,
6452
-                self::caps_edit,
6453
-                self::caps_delete,
6454
-            ]
6455
-        );
6456
-    }
6457
-
6458
-
6459
-    /**
6460
-     * Returns all valid options for 'default_where_conditions'
6461
-     *
6462
-     * @return array
6463
-     */
6464
-    public static function valid_default_where_conditions(): array
6465
-    {
6466
-        return [
6467
-            EE_Default_Where_Conditions::ALL,
6468
-            EE_Default_Where_Conditions::THIS_MODEL_ONLY,
6469
-            EE_Default_Where_Conditions::OTHER_MODELS_ONLY,
6470
-            EE_Default_Where_Conditions::MINIMUM_ALL,
6471
-            EE_Default_Where_Conditions::MINIMUM_OTHERS,
6472
-            EE_Default_Where_Conditions::NONE,
6473
-        ];
6474
-    }
6475
-
6476
-    // public static function default_where_conditions_full
6477
-
6478
-
6479
-    /**
6480
-     * Verifies $context is one of EEM_Base::valid_cap_contexts(), if not it throws an exception
6481
-     *
6482
-     * @param string $context
6483
-     * @return bool
6484
-     * @throws EE_Error
6485
-     */
6486
-    public static function verify_is_valid_cap_context($context): bool
6487
-    {
6488
-        $valid_cap_contexts = EEM_Base::valid_cap_contexts();
6489
-        if (in_array($context, $valid_cap_contexts)) {
6490
-            return true;
6491
-        }
6492
-        throw new EE_Error(
6493
-            sprintf(
6494
-                esc_html__(
6495
-                    'Context "%1$s" passed into model "%2$s" is not a valid context. They are: %3$s',
6496
-                    'event_espresso'
6497
-                ),
6498
-                $context,
6499
-                'EEM_Base',
6500
-                implode(',', $valid_cap_contexts)
6501
-            )
6502
-        );
6503
-    }
6504
-
6505
-
6506
-    /**
6507
-     * Clears all the models field caches. This is only useful when a sub-class
6508
-     * might have added a field or something and these caches might be invalidated
6509
-     */
6510
-    protected function _invalidate_field_caches()
6511
-    {
6512
-        $this->_cache_foreign_key_to_fields = [];
6513
-        $this->_cached_fields               = null;
6514
-        $this->_cached_fields_non_db_only   = null;
6515
-    }
6516
-
6517
-
6518
-    /**
6519
-     * Gets the list of all the where query param keys that relate to logic instead of field names
6520
-     * (eg "and", "or", "not").
6521
-     *
6522
-     * @return array
6523
-     */
6524
-    public function logic_query_param_keys(): array
6525
-    {
6526
-        return $this->_logic_query_param_keys;
6527
-    }
6528
-
6529
-
6530
-    /**
6531
-     * Determines whether the where query param array key is for a logic query param.
6532
-     * Eg 'OR', 'not*', and 'and*because-i-say-so' should all return true, whereas
6533
-     * 'ATT_fname', 'EVT_name*not-you-or-me', and 'ORG_name' should return false
6534
-     *
6535
-     * @param $query_param_key
6536
-     * @return bool
6537
-     */
6538
-    public function is_logic_query_param_key($query_param_key): bool
6539
-    {
6540
-        foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6541
-            if (
6542
-                $query_param_key === $logic_query_param_key
6543
-                || strpos($query_param_key, $logic_query_param_key . '*') === 0
6544
-            ) {
6545
-                return true;
6546
-            }
6547
-        }
6548
-        return false;
6549
-    }
6550
-
6551
-
6552
-    /**
6553
-     * Returns true if this model has a password field on it (regardless of whether that password field has any content)
6554
-     *
6555
-     * @return boolean
6556
-     * @since 4.9.74.p
6557
-     */
6558
-    public function hasPassword(): bool
6559
-    {
6560
-        // if we don't yet know if there's a password field, find out and remember it for next time.
6561
-        if ($this->has_password_field === null) {
6562
-            $password_field           = $this->getPasswordField();
6563
-            $this->has_password_field = $password_field instanceof EE_Password_Field;
6564
-        }
6565
-        return $this->has_password_field;
6566
-    }
6567
-
6568
-
6569
-    /**
6570
-     * Returns the password field on this model, if there is one
6571
-     *
6572
-     * @return EE_Password_Field|null
6573
-     * @since 4.9.74.p
6574
-     */
6575
-    public function getPasswordField()
6576
-    {
6577
-        // if we definetely already know there is a password field or not (because has_password_field is true or false)
6578
-        // there's no need to search for it. If we don't know yet, then find out
6579
-        if ($this->has_password_field === null && $this->password_field === null) {
6580
-            $this->password_field = $this->get_a_field_of_type('EE_Password_Field');
6581
-        }
6582
-        // don't bother setting has_password_field because that's hasPassword()'s job.
6583
-        return $this->password_field;
6584
-    }
6585
-
6586
-
6587
-    /**
6588
-     * Returns the list of field (as EE_Model_Field_Bases) that are protected by the password
6589
-     *
6590
-     * @return EE_Model_Field_Base[]
6591
-     * @throws EE_Error
6592
-     * @since 4.9.74.p
6593
-     */
6594
-    public function getPasswordProtectedFields()
6595
-    {
6596
-        $password_field = $this->getPasswordField();
6597
-        $fields         = [];
6598
-        if ($password_field instanceof EE_Password_Field) {
6599
-            $field_names = $password_field->protectedFields();
6600
-            foreach ($field_names as $field_name) {
6601
-                $fields[ $field_name ] = $this->field_settings_for($field_name);
6602
-            }
6603
-        }
6604
-        return $fields;
6605
-    }
6606
-
6607
-
6608
-    /**
6609
-     * Checks if the current user can perform the requested action on this model
6610
-     *
6611
-     * @param string              $cap_to_check one of the array keys from _cap_contexts_to_cap_action_map
6612
-     * @param EE_Base_Class|array $model_obj_or_fields_n_values
6613
-     * @return bool
6614
-     * @throws EE_Error
6615
-     * @throws InvalidArgumentException
6616
-     * @throws InvalidDataTypeException
6617
-     * @throws InvalidInterfaceException
6618
-     * @throws ReflectionException
6619
-     * @throws UnexpectedEntityException
6620
-     * @since 4.9.74.p
6621
-     */
6622
-    public function currentUserCan($cap_to_check, $model_obj_or_fields_n_values)
6623
-    {
6624
-        if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6625
-            $model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6626
-        }
6627
-        if (! is_array($model_obj_or_fields_n_values)) {
6628
-            throw new UnexpectedEntityException(
6629
-                $model_obj_or_fields_n_values,
6630
-                'EE_Base_Class',
6631
-                sprintf(
6632
-                    esc_html__(
6633
-                        '%1$s must be passed an `EE_Base_Class or an array of fields names with their values. You passed in something different.',
6634
-                        'event_espresso'
6635
-                    ),
6636
-                    __FUNCTION__
6637
-                )
6638
-            );
6639
-        }
6640
-        return $this->exists(
6641
-            $this->alter_query_params_to_restrict_by_ID(
6642
-                $this->get_index_primary_key_string($model_obj_or_fields_n_values),
6643
-                [
6644
-                    'default_where_conditions' => 'none',
6645
-                    'caps'                     => $cap_to_check,
6646
-                ]
6647
-            )
6648
-        );
6649
-    }
6650
-
6651
-
6652
-    /**
6653
-     * Returns the query param where conditions key to the password affecting this model.
6654
-     * Eg on EEM_Event this would just be "password", on EEM_Datetime this would be "Event.password", etc.
6655
-     *
6656
-     * @return null|string
6657
-     * @throws EE_Error
6658
-     * @throws InvalidArgumentException
6659
-     * @throws InvalidDataTypeException
6660
-     * @throws InvalidInterfaceException
6661
-     * @throws ModelConfigurationException
6662
-     * @throws ReflectionException
6663
-     * @since 4.9.74.p
6664
-     */
6665
-    public function modelChainAndPassword()
6666
-    {
6667
-        if ($this->model_chain_to_password === null) {
6668
-            throw new ModelConfigurationException(
6669
-                $this,
6670
-                esc_html_x(
6671
-                // @codingStandardsIgnoreStart
6672
-                    'Cannot exclude protected data because the model has not specified which model has the password.',
6673
-                    // @codingStandardsIgnoreEnd
6674
-                    '1: model name',
6675
-                    'event_espresso'
6676
-                )
6677
-            );
6678
-        }
6679
-        if ($this->model_chain_to_password === '') {
6680
-            $model_with_password = $this;
6681
-        } else {
6682
-            if ($pos_of_period = strrpos($this->model_chain_to_password, '.')) {
6683
-                $last_model_in_chain = substr($this->model_chain_to_password, $pos_of_period + 1);
6684
-            } else {
6685
-                $last_model_in_chain = $this->model_chain_to_password;
6686
-            }
6687
-            $model_with_password = EE_Registry::instance()->load_model($last_model_in_chain);
6688
-        }
6689
-
6690
-        $password_field = $model_with_password->getPasswordField();
6691
-        if ($password_field instanceof EE_Password_Field) {
6692
-            $password_field_name = $password_field->get_name();
6693
-        } else {
6694
-            throw new ModelConfigurationException(
6695
-                $this,
6696
-                sprintf(
6697
-                    esc_html_x(
6698
-                        'This model claims related model "%1$s" should have a password field on it, but none was found. The model relation chain is "%2$s"',
6699
-                        '1: model name, 2: special string',
6700
-                        'event_espresso'
6701
-                    ),
6702
-                    $model_with_password->get_this_model_name(),
6703
-                    $this->model_chain_to_password
6704
-                )
6705
-            );
6706
-        }
6707
-        return (
6708
-               $this->model_chain_to_password
6709
-                   ? $this->model_chain_to_password . '.'
6710
-                   : ''
6711
-               ) . $password_field_name;
6712
-    }
6713
-
6714
-
6715
-    /**
6716
-     * Returns true if there is a password on a related model which restricts access to some of this model's rows,
6717
-     * or if this model itself has a password affecting access to some of its other fields.
6718
-     *
6719
-     * @return boolean
6720
-     * @since 4.9.74.p
6721
-     */
6722
-    public function restrictedByRelatedModelPassword(): bool
6723
-    {
6724
-        return $this->model_chain_to_password !== null;
6725
-    }
3962
+		}
3963
+		return $null_friendly_where_conditions;
3964
+	}
3965
+
3966
+
3967
+	/**
3968
+	 * Uses the _default_where_conditions_strategy set during __construct() to get
3969
+	 * default where conditions on all get_all, update, and delete queries done by this model.
3970
+	 * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3971
+	 * NOT array('Event_CPT.post_type'=>'esp_event').
3972
+	 *
3973
+	 * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3974
+	 * @return array @see
3975
+	 *                                    https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3976
+	 * @throws EE_Error
3977
+	 * @throws EE_Error
3978
+	 */
3979
+	private function _get_default_where_conditions($model_relation_path = '')
3980
+	{
3981
+		if ($this->_ignore_where_strategy) {
3982
+			return [];
3983
+		}
3984
+		return $this->_default_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3985
+	}
3986
+
3987
+
3988
+	/**
3989
+	 * Uses the _minimum_where_conditions_strategy set during __construct() to get
3990
+	 * minimum where conditions on all get_all, update, and delete queries done by this model.
3991
+	 * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3992
+	 * NOT array('Event_CPT.post_type'=>'esp_event').
3993
+	 * Similar to _get_default_where_conditions
3994
+	 *
3995
+	 * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3996
+	 * @return array @see
3997
+	 *                                    https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3998
+	 * @throws EE_Error
3999
+	 * @throws EE_Error
4000
+	 */
4001
+	protected function _get_minimum_where_conditions($model_relation_path = '')
4002
+	{
4003
+		if ($this->_ignore_where_strategy) {
4004
+			return [];
4005
+		}
4006
+		return $this->_minimum_where_conditions_strategy->get_default_where_conditions($model_relation_path);
4007
+	}
4008
+
4009
+
4010
+	/**
4011
+	 * Creates the string of SQL for the select part of a select query, everything behind SELECT and before FROM.
4012
+	 * Eg, "Event.post_id, Event.post_name,Event_Detail.EVT_ID..."
4013
+	 *
4014
+	 * @param EE_Model_Query_Info_Carrier $model_query_info
4015
+	 * @return string
4016
+	 * @throws EE_Error
4017
+	 */
4018
+	private function _construct_default_select_sql(EE_Model_Query_Info_Carrier $model_query_info)
4019
+	{
4020
+		$selects = $this->_get_columns_to_select_for_this_model();
4021
+		foreach (
4022
+			$model_query_info->get_model_names_included() as $model_relation_chain => $name_of_other_model_included
4023
+		) {
4024
+			$other_model_included = $this->get_related_model_obj($name_of_other_model_included);
4025
+			$other_model_selects  = $other_model_included->_get_columns_to_select_for_this_model($model_relation_chain);
4026
+			foreach ($other_model_selects as $key => $value) {
4027
+				$selects[] = $value;
4028
+			}
4029
+		}
4030
+		return implode(", ", $selects);
4031
+	}
4032
+
4033
+
4034
+	/**
4035
+	 * Gets an array of columns to select for this model, which are necessary for it to create its objects.
4036
+	 * So that's going to be the columns for all the fields on the model
4037
+	 *
4038
+	 * @param string $model_relation_chain like 'Question.Question_Group.Event'
4039
+	 * @return array numerically indexed, values are columns to select and rename, eg "Event.ID AS 'Event.ID'"
4040
+	 */
4041
+	public function _get_columns_to_select_for_this_model($model_relation_chain = '')
4042
+	{
4043
+		$fields                                       = $this->field_settings();
4044
+		$selects                                      = [];
4045
+		$table_alias_with_model_relation_chain_prefix =
4046
+			EE_Model_Parser::extract_table_alias_model_relation_chain_prefix(
4047
+				$model_relation_chain,
4048
+				$this->get_this_model_name()
4049
+			);
4050
+		foreach ($fields as $field_obj) {
4051
+			$selects[] = $table_alias_with_model_relation_chain_prefix
4052
+						 . $field_obj->get_table_alias()
4053
+						 . "."
4054
+						 . $field_obj->get_table_column()
4055
+						 . " AS '"
4056
+						 . $table_alias_with_model_relation_chain_prefix
4057
+						 . $field_obj->get_table_alias()
4058
+						 . "."
4059
+						 . $field_obj->get_table_column()
4060
+						 . "'";
4061
+		}
4062
+		// make sure we are also getting the PKs of each table
4063
+		$tables = $this->get_tables();
4064
+		if (count($tables) > 1) {
4065
+			foreach ($tables as $table_obj) {
4066
+				$qualified_pk_column = $table_alias_with_model_relation_chain_prefix
4067
+									   . $table_obj->get_fully_qualified_pk_column();
4068
+				if (! in_array($qualified_pk_column, $selects)) {
4069
+					$selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
4070
+				}
4071
+			}
4072
+		}
4073
+		return $selects;
4074
+	}
4075
+
4076
+
4077
+	/**
4078
+	 * Given a $query_param like 'Registration.Transaction.TXN_ID', pops off 'Registration.',
4079
+	 * gets the join statement for it; gets the data types for it; and passes the remaining 'Transaction.TXN_ID'
4080
+	 * onto its related Transaction object to do the same. Returns an EE_Join_And_Data_Types object which contains the
4081
+	 * SQL for joining, and the data types
4082
+	 *
4083
+	 * @param null|string                 $original_query_param
4084
+	 * @param string                      $query_param          like Registration.Transaction.TXN_ID
4085
+	 * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4086
+	 * @param string                      $query_param_type     like Registration.Transaction.TXN_ID
4087
+	 *                                                          or 'PAY_ID'. Otherwise, we don't expect there to be a
4088
+	 *                                                          column name. We only want model names, eg 'Event.Venue'
4089
+	 *                                                          or 'Registration's
4090
+	 * @param string                      $original_query_param what it originally was (eg
4091
+	 *                                                          Registration.Transaction.TXN_ID). If null, we assume it
4092
+	 *                                                          matches $query_param
4093
+	 * @return void only modifies the EEM_Related_Model_Info_Carrier passed into it
4094
+	 * @throws EE_Error
4095
+	 */
4096
+	private function _extract_related_model_info_from_query_param(
4097
+		$query_param,
4098
+		EE_Model_Query_Info_Carrier $passed_in_query_info,
4099
+		$query_param_type,
4100
+		$original_query_param = null
4101
+	) {
4102
+		if ($original_query_param === null) {
4103
+			$original_query_param = $query_param;
4104
+		}
4105
+		$query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4106
+		// check to see if we have a field on this model
4107
+		$this_model_fields = $this->field_settings(true);
4108
+		if (array_key_exists($query_param, $this_model_fields)) {
4109
+			$field_is_allowed = in_array(
4110
+				$query_param_type,
4111
+				[0, 'where', 'having', 'order_by', 'group_by', 'order', 'custom_selects'],
4112
+				true
4113
+			);
4114
+			if ($field_is_allowed) {
4115
+				return;
4116
+			}
4117
+			throw new EE_Error(
4118
+				sprintf(
4119
+					esc_html__(
4120
+						"Using a field name (%s) on model %s is not allowed on this query param type '%s'. Original query param was %s",
4121
+						"event_espresso"
4122
+					),
4123
+					$query_param,
4124
+					$this->class_name,
4125
+					$query_param_type,
4126
+					$original_query_param
4127
+				)
4128
+			);
4129
+		}
4130
+		// check if this is a special logic query param
4131
+		if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4132
+			$operator_is_allowed = in_array($query_param_type, ['where', 'having', 0, 'custom_selects'], true);
4133
+			if ($operator_is_allowed) {
4134
+				return;
4135
+			}
4136
+			throw new EE_Error(
4137
+				sprintf(
4138
+					esc_html__(
4139
+						'Logic query params ("%1$s") are being used incorrectly with the following query param ("%2$s") on model %3$s. %4$sAdditional Info:%4$s%5$s',
4140
+						'event_espresso'
4141
+					),
4142
+					implode('", "', $this->_logic_query_param_keys),
4143
+					$query_param,
4144
+					$this->class_name,
4145
+					'<br />',
4146
+					"\t"
4147
+					. ' $passed_in_query_info = <pre>'
4148
+					. print_r($passed_in_query_info, true)
4149
+					. '</pre>'
4150
+					. "\n\t"
4151
+					. ' $query_param_type = '
4152
+					. $query_param_type
4153
+					. "\n\t"
4154
+					. ' $original_query_param = '
4155
+					. $original_query_param
4156
+				)
4157
+			);
4158
+		}
4159
+		// check if it's a custom selection
4160
+		if (
4161
+			$this->_custom_selections instanceof CustomSelects
4162
+			&& in_array($query_param, $this->_custom_selections->columnAliases(), true)
4163
+		) {
4164
+			return;
4165
+		}
4166
+		// check if has a model name at the beginning
4167
+		// and
4168
+		// check if it's a field on a related model
4169
+		if (
4170
+			$this->extractJoinModelFromQueryParams(
4171
+				$passed_in_query_info,
4172
+				$query_param,
4173
+				$original_query_param,
4174
+				$query_param_type
4175
+			)
4176
+		) {
4177
+			return;
4178
+		}
4179
+
4180
+		// ok so $query_param didn't start with a model name
4181
+		// and we previously confirmed it wasn't a logic query param or field on the current model
4182
+		// it's wack, that's what it is
4183
+		throw new EE_Error(
4184
+			sprintf(
4185
+				esc_html__(
4186
+					"There is no model named '%s' related to %s. Query param type is %s and original query param is %s",
4187
+					"event_espresso"
4188
+				),
4189
+				$query_param,
4190
+				$this->class_name,
4191
+				$query_param_type,
4192
+				$original_query_param
4193
+			)
4194
+		);
4195
+	}
4196
+
4197
+
4198
+	/**
4199
+	 * Extracts any possible join model information from the provided possible_join_string.
4200
+	 * This method will read the provided $possible_join_string value and determine if there are any possible model
4201
+	 * join
4202
+	 * parts that should be added to the query.
4203
+	 *
4204
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
4205
+	 * @param string                      $possible_join_string  Such as Registration.REG_ID, or Registration
4206
+	 * @param null|string                 $original_query_param
4207
+	 * @param string                      $query_parameter_type  The type for the source of the $possible_join_string
4208
+	 *                                                           ('where', 'order_by', 'group_by', 'custom_selects'
4209
+	 *                                                           etc.)
4210
+	 * @return bool  returns true if a join was added and false if not.
4211
+	 * @throws EE_Error
4212
+	 */
4213
+	private function extractJoinModelFromQueryParams(
4214
+		EE_Model_Query_Info_Carrier $query_info_carrier,
4215
+		$possible_join_string,
4216
+		$original_query_param,
4217
+		$query_parameter_type
4218
+	) {
4219
+		foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4220
+			if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4221
+				$this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4222
+				$possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4223
+				if ($possible_join_string === '') {
4224
+					// nothing left to $query_param
4225
+					// we should actually end in a field name, not a model like this!
4226
+					throw new EE_Error(
4227
+						sprintf(
4228
+							esc_html__(
4229
+								"Query param '%s' (of type %s on model %s) shouldn't end on a period (.) ",
4230
+								"event_espresso"
4231
+							),
4232
+							$possible_join_string,
4233
+							$query_parameter_type,
4234
+							$this->class_name,
4235
+							$valid_related_model_name
4236
+						)
4237
+					);
4238
+				}
4239
+				$related_model_obj = $this->get_related_model_obj($valid_related_model_name);
4240
+				$related_model_obj->_extract_related_model_info_from_query_param(
4241
+					$possible_join_string,
4242
+					$query_info_carrier,
4243
+					$query_parameter_type,
4244
+					$original_query_param
4245
+				);
4246
+				return true;
4247
+			}
4248
+			if ($possible_join_string === $valid_related_model_name) {
4249
+				$this->_add_join_to_model(
4250
+					$valid_related_model_name,
4251
+					$query_info_carrier,
4252
+					$original_query_param
4253
+				);
4254
+				return true;
4255
+			}
4256
+		}
4257
+		return false;
4258
+	}
4259
+
4260
+
4261
+	/**
4262
+	 * Extracts related models from Custom Selects and sets up any joins for those related models.
4263
+	 *
4264
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
4265
+	 * @throws EE_Error
4266
+	 */
4267
+	private function extractRelatedModelsFromCustomSelects(EE_Model_Query_Info_Carrier $query_info_carrier)
4268
+	{
4269
+		if (
4270
+			$this->_custom_selections instanceof CustomSelects
4271
+			&& (
4272
+				$this->_custom_selections->type() === CustomSelects::TYPE_STRUCTURED
4273
+				|| $this->_custom_selections->type() == CustomSelects::TYPE_COMPLEX
4274
+			)
4275
+		) {
4276
+			$original_selects = $this->_custom_selections->originalSelects();
4277
+			foreach ($original_selects as $alias => $select_configuration) {
4278
+				$this->extractJoinModelFromQueryParams(
4279
+					$query_info_carrier,
4280
+					$select_configuration[0],
4281
+					$select_configuration[0],
4282
+					'custom_selects'
4283
+				);
4284
+			}
4285
+		}
4286
+	}
4287
+
4288
+
4289
+	/**
4290
+	 * Privately used by _extract_related_model_info_from_query_param to add a join to $model_name
4291
+	 * and store it on $passed_in_query_info
4292
+	 *
4293
+	 * @param string                      $model_name
4294
+	 * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4295
+	 * @param string                      $original_query_param used to extract the relation chain between the queried
4296
+	 *                                                          model and $model_name. Eg, if we are querying Event,
4297
+	 *                                                          and are adding a join to 'Payment' with the original
4298
+	 *                                                          query param key
4299
+	 *                                                          'Registration.Transaction.Payment.PAY_amount', we want
4300
+	 *                                                          to extract 'Registration.Transaction.Payment', in case
4301
+	 *                                                          Payment wants to add default query params so that it
4302
+	 *                                                          will know what models to prepend onto its default query
4303
+	 *                                                          params or in case it wants to rename tables (in case
4304
+	 *                                                          there are multiple joins to the same table)
4305
+	 * @return void
4306
+	 * @throws EE_Error
4307
+	 */
4308
+	private function _add_join_to_model(
4309
+		$model_name,
4310
+		EE_Model_Query_Info_Carrier $passed_in_query_info,
4311
+		$original_query_param
4312
+	) {
4313
+		$relation_obj         = $this->related_settings_for($model_name);
4314
+		$model_relation_chain = EE_Model_Parser::extract_model_relation_chain($model_name, $original_query_param);
4315
+		// check if the relation is HABTM, because then we're essentially doing two joins
4316
+		// If so, join first to the JOIN table, and add its data types, and then continue as normal
4317
+		if ($relation_obj instanceof EE_HABTM_Relation) {
4318
+			$join_model_obj = $relation_obj->get_join_model();
4319
+			// replace the model specified with the join model for this relation chain, whi
4320
+			$relation_chain_to_join_model =
4321
+				EE_Model_Parser::replace_model_name_with_join_model_name_in_model_relation_chain(
4322
+					$model_name,
4323
+					$join_model_obj->get_this_model_name(),
4324
+					$model_relation_chain
4325
+				);
4326
+			$passed_in_query_info->merge(
4327
+				new EE_Model_Query_Info_Carrier(
4328
+					[$relation_chain_to_join_model => $join_model_obj->get_this_model_name()],
4329
+					$relation_obj->get_join_to_intermediate_model_statement($relation_chain_to_join_model)
4330
+				)
4331
+			);
4332
+		}
4333
+		// now just join to the other table pointed to by the relation object, and add its data types
4334
+		$passed_in_query_info->merge(
4335
+			new EE_Model_Query_Info_Carrier(
4336
+				[$model_relation_chain => $model_name],
4337
+				$relation_obj->get_join_statement($model_relation_chain)
4338
+			)
4339
+		);
4340
+	}
4341
+
4342
+
4343
+	/**
4344
+	 * Constructs SQL for where clause, like "WHERE Event.ID = 23 AND Transaction.amount > 100" etc.
4345
+	 *
4346
+	 * @param array $where_params @see
4347
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4348
+	 * @return string of SQL
4349
+	 * @throws EE_Error
4350
+	 */
4351
+	private function _construct_where_clause($where_params)
4352
+	{
4353
+		$SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4354
+		if ($SQL) {
4355
+			return " WHERE " . $SQL;
4356
+		}
4357
+		return '';
4358
+	}
4359
+
4360
+
4361
+	/**
4362
+	 * Just like the _construct_where_clause, except prepends 'HAVING' instead of 'WHERE',
4363
+	 * and should be passed HAVING parameters, not WHERE parameters
4364
+	 *
4365
+	 * @param array $having_params
4366
+	 * @return string
4367
+	 * @throws EE_Error
4368
+	 */
4369
+	private function _construct_having_clause($having_params)
4370
+	{
4371
+		$SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4372
+		if ($SQL) {
4373
+			return " HAVING " . $SQL;
4374
+		}
4375
+		return '';
4376
+	}
4377
+
4378
+
4379
+	/**
4380
+	 * Used for creating nested WHERE conditions. Eg "WHERE ! (Event.ID = 3 OR ( Event_Meta.meta_key = 'bob' AND
4381
+	 * Event_Meta.meta_value = 'foo'))"
4382
+	 *
4383
+	 * @param array  $where_params @see
4384
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4385
+	 * @param string $glue         joins each subclause together. Should really only be " AND " or " OR "...
4386
+	 * @return string of SQL
4387
+	 * @throws EE_Error
4388
+	 */
4389
+	private function _construct_condition_clause_recursive($where_params, $glue = ' AND')
4390
+	{
4391
+		$where_clauses = [];
4392
+		foreach ($where_params as $query_param => $op_and_value_or_sub_condition) {
4393
+			$query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4394
+			if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4395
+				switch ($query_param) {
4396
+					case 'not':
4397
+					case 'NOT':
4398
+						$where_clauses[] = "! ("
4399
+										   . $this->_construct_condition_clause_recursive(
4400
+											   $op_and_value_or_sub_condition,
4401
+											   $glue
4402
+										   )
4403
+										   . ")";
4404
+						break;
4405
+					case 'and':
4406
+					case 'AND':
4407
+						$where_clauses[] = " ("
4408
+										   . $this->_construct_condition_clause_recursive(
4409
+											   $op_and_value_or_sub_condition,
4410
+											   ' AND '
4411
+										   )
4412
+										   . ")";
4413
+						break;
4414
+					case 'or':
4415
+					case 'OR':
4416
+						$where_clauses[] = " ("
4417
+										   . $this->_construct_condition_clause_recursive(
4418
+											   $op_and_value_or_sub_condition,
4419
+											   ' OR '
4420
+										   )
4421
+										   . ")";
4422
+						break;
4423
+				}
4424
+			} else {
4425
+				$field_obj = $this->_deduce_field_from_query_param($query_param);
4426
+				// if it's not a normal field, maybe it's a custom selection?
4427
+				if (! $field_obj) {
4428
+					if ($this->_custom_selections instanceof CustomSelects) {
4429
+						$field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4430
+					} else {
4431
+						throw new EE_Error(
4432
+							sprintf(
4433
+								esc_html__(
4434
+									"%s is neither a valid model field name, nor a custom selection",
4435
+									"event_espresso"
4436
+								),
4437
+								$query_param
4438
+							)
4439
+						);
4440
+					}
4441
+				}
4442
+				$op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4443
+				$where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4444
+			}
4445
+		}
4446
+		return $where_clauses
4447
+			? implode($glue, $where_clauses)
4448
+			: '';
4449
+	}
4450
+
4451
+
4452
+	/**
4453
+	 * Takes the input parameter and extract the table name (alias) and column name
4454
+	 *
4455
+	 * @param string $query_param like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4456
+	 * @return string table alias and column name for SQL, eg "Transaction.TXN_ID"
4457
+	 * @throws EE_Error
4458
+	 */
4459
+	private function _deduce_column_name_from_query_param($query_param)
4460
+	{
4461
+		$field = $this->_deduce_field_from_query_param($query_param);
4462
+		if ($field) {
4463
+			$table_alias_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_from_query_param(
4464
+				$field->get_model_name(),
4465
+				$query_param
4466
+			);
4467
+			return $table_alias_prefix . $field->get_qualified_column();
4468
+		}
4469
+		if (
4470
+			$this->_custom_selections instanceof CustomSelects
4471
+			&& in_array($query_param, $this->_custom_selections->columnAliases(), true)
4472
+		) {
4473
+			// maybe it's custom selection item?
4474
+			// if so, just use it as the "column name"
4475
+			return $query_param;
4476
+		}
4477
+		$custom_select_aliases = $this->_custom_selections instanceof CustomSelects
4478
+			? implode(',', $this->_custom_selections->columnAliases())
4479
+			: '';
4480
+		throw new EE_Error(
4481
+			sprintf(
4482
+				esc_html__(
4483
+					"%s is not a valid field on this model, nor a custom selection (%s)",
4484
+					"event_espresso"
4485
+				),
4486
+				$query_param,
4487
+				$custom_select_aliases
4488
+			)
4489
+		);
4490
+	}
4491
+
4492
+
4493
+	/**
4494
+	 * Removes the * and anything after it from the condition query param key. It is useful to add the * to condition
4495
+	 * query param keys (eg, 'OR*', 'EVT_ID') in order for the array keys to still be unique, so that they don't get
4496
+	 * overwritten Takes a string like 'Event.EVT_ID*', 'TXN_total**', 'OR*1st', and 'DTT_reg_start*foobar' to
4497
+	 * 'Event.EVT_ID', 'TXN_total', 'OR', and 'DTT_reg_start', respectively.
4498
+	 *
4499
+	 * @param string $condition_query_param_key
4500
+	 * @return string
4501
+	 */
4502
+	private function _remove_stars_and_anything_after_from_condition_query_param_key($condition_query_param_key)
4503
+	{
4504
+		$pos_of_star = strpos($condition_query_param_key, '*');
4505
+		if ($pos_of_star === false) {
4506
+			return $condition_query_param_key;
4507
+		}
4508
+		return substr($condition_query_param_key, 0, $pos_of_star);
4509
+	}
4510
+
4511
+
4512
+	/**
4513
+	 * creates the SQL for the operator and the value in a WHERE clause, eg "< 23" or "LIKE '%monkey%'"
4514
+	 *
4515
+	 * @param array|string               $op_and_value
4516
+	 * @param EE_Model_Field_Base|string $field_obj . If string, should be one of EEM_Base::_valid_wpdb_data_types
4517
+	 * @return string
4518
+	 * @throws EE_Error
4519
+	 */
4520
+	private function _construct_op_and_value($op_and_value, $field_obj)
4521
+	{
4522
+		$operator = '=';
4523
+		$value    = $op_and_value;
4524
+		if (is_array($op_and_value)) {
4525
+			$operator = isset($op_and_value[0])
4526
+				? $this->_prepare_operator_for_sql($op_and_value[0])
4527
+				: null;
4528
+			if (! $operator) {
4529
+				$php_array_like_string = [];
4530
+				foreach ($op_and_value as $key => $value) {
4531
+					$value = is_array($value) ? '[' . implode(",", $value) . ']' : $value;
4532
+					$php_array_like_string[] = "$key=>$value";
4533
+				}
4534
+				throw new EE_Error(
4535
+					sprintf(
4536
+						esc_html__(
4537
+							"You setup a query parameter like you were going to specify an operator, but didn't. You provided '(%s)', but the operator should be at array key index 0 (eg array('>',32))",
4538
+							"event_espresso"
4539
+						),
4540
+						implode(",", $php_array_like_string)
4541
+					)
4542
+				);
4543
+			}
4544
+			$value = $op_and_value[1] ?? null;
4545
+		}
4546
+
4547
+		// check to see if the value is actually another field
4548
+		if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2]) {
4549
+			return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4550
+		}
4551
+		if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4552
+			// in this case, the value should be an array, or at least a comma-separated list
4553
+			// it will need to handle a little differently
4554
+			$cleaned_value = $this->_construct_in_value($value, $field_obj);
4555
+			// note: $cleaned_value has already been run through $wpdb->prepare()
4556
+			return $operator . SP . $cleaned_value;
4557
+		}
4558
+		if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4559
+			// the value should be an array with count of two.
4560
+			if (count($value) !== 2) {
4561
+				throw new EE_Error(
4562
+					sprintf(
4563
+						esc_html__(
4564
+							"The '%s' operator must be used with an array of values and there must be exactly TWO values in that array.",
4565
+							'event_espresso'
4566
+						),
4567
+						"BETWEEN"
4568
+					)
4569
+				);
4570
+			}
4571
+			$cleaned_value = $this->_construct_between_value($value, $field_obj);
4572
+			return $operator . SP . $cleaned_value;
4573
+		}
4574
+		if (in_array($operator, $this->valid_null_style_operators())) {
4575
+			if ($value !== null) {
4576
+				throw new EE_Error(
4577
+					sprintf(
4578
+						esc_html__(
4579
+							"You attempted to give a value  (%s) while using a NULL-style operator (%s). That isn't valid",
4580
+							"event_espresso"
4581
+						),
4582
+						$value,
4583
+						$operator
4584
+					)
4585
+				);
4586
+			}
4587
+			return $operator;
4588
+		}
4589
+		if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4590
+			// if the operator is 'LIKE', we want to allow percent signs (%) and not
4591
+			// remove other junk. So just treat it as a string.
4592
+			return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4593
+		}
4594
+		if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4595
+			return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4596
+		}
4597
+		if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4598
+			throw new EE_Error(
4599
+				sprintf(
4600
+					esc_html__(
4601
+						"Operator '%s' must be used with an array of values, eg 'Registration.REG_ID' => array('%s',array(1,2,3))",
4602
+						'event_espresso'
4603
+					),
4604
+					$operator,
4605
+					$operator
4606
+				)
4607
+			);
4608
+		}
4609
+		if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4610
+			throw new EE_Error(
4611
+				sprintf(
4612
+					esc_html__(
4613
+						"Operator '%s' must be used with a single value, not an array. Eg 'Registration.REG_ID => array('%s',23))",
4614
+						'event_espresso'
4615
+					),
4616
+					$operator,
4617
+					$operator
4618
+				)
4619
+			);
4620
+		}
4621
+		throw new EE_Error(
4622
+			sprintf(
4623
+				esc_html__(
4624
+					"It appears you've provided some totally invalid query parameters. Operator and value were:'%s', which isn't right at all",
4625
+					"event_espresso"
4626
+				),
4627
+				http_build_query($op_and_value)
4628
+			)
4629
+		);
4630
+	}
4631
+
4632
+
4633
+	/**
4634
+	 * Creates the operands to be used in a BETWEEN query, eg "'2014-12-31 20:23:33' AND '2015-01-23 12:32:54'"
4635
+	 *
4636
+	 * @param array                      $values
4637
+	 * @param EE_Model_Field_Base|string $field_obj if string, it should be the datatype to be used when querying, eg
4638
+	 *                                              '%s'
4639
+	 * @return string
4640
+	 * @throws EE_Error
4641
+	 */
4642
+	public function _construct_between_value($values, $field_obj)
4643
+	{
4644
+		$cleaned_values = [];
4645
+		foreach ($values as $value) {
4646
+			$cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4647
+		}
4648
+		return $cleaned_values[0] . " AND " . $cleaned_values[1];
4649
+	}
4650
+
4651
+
4652
+	/**
4653
+	 * Takes an array or a comma-separated list of $values and cleans them
4654
+	 * according to $data_type using $wpdb->prepare, and then makes the list a
4655
+	 * string surrounded by ( and ). Eg, _construct_in_value(array(1,2,3),'%d') would
4656
+	 * return '(1,2,3)'; _construct_in_value("1,2,hack",'%d') would return '(1,2,1)' (assuming
4657
+	 * I'm right that a string, when interpreted as a digit, becomes a 1. It might become a 0)
4658
+	 *
4659
+	 * @param mixed                      $values    array or comma-separated string
4660
+	 * @param EE_Model_Field_Base|string $field_obj if string, it should be a wpdb data type like '%s', or '%d'
4661
+	 * @return string of SQL to follow an 'IN' or 'NOT IN' operator
4662
+	 * @throws EE_Error
4663
+	 */
4664
+	public function _construct_in_value($values, $field_obj)
4665
+	{
4666
+		$prepped = [];
4667
+		// check if the value is a CSV list
4668
+		if (is_string($values)) {
4669
+			// in which case, turn it into an array
4670
+			$values = explode(',', $values);
4671
+		}
4672
+		// make sure we only have one of each value in the list
4673
+		$values = array_unique($values);
4674
+		foreach ($values as $value) {
4675
+			$prepped[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4676
+		}
4677
+		// we would just LOVE to leave $cleaned_values as an empty array, and return the value as "()",
4678
+		// but unfortunately that's invalid SQL. So instead we return a string which we KNOW will evaluate to be the empty set
4679
+		// which is effectively equivalent to returning "()". We don't return "(0)" because that only works for auto-incrementing columns
4680
+		if (empty($prepped)) {
4681
+			$all_fields  = $this->field_settings();
4682
+			$first_field = reset($all_fields);
4683
+			$main_table  = $this->_get_main_table();
4684
+			$prepped[]   = "SELECT {$first_field->get_table_column()} FROM {$main_table->get_table_name()} WHERE FALSE";
4685
+		}
4686
+		return '(' . implode(',', $prepped) . ')';
4687
+	}
4688
+
4689
+
4690
+	/**
4691
+	 * @param mixed                      $value
4692
+	 * @param EE_Model_Field_Base|string $field_obj if string it should be a wpdb data type like '%d'
4693
+	 * @return false|null|string
4694
+	 * @throws EE_Error
4695
+	 */
4696
+	private function _wpdb_prepare_using_field($value, $field_obj)
4697
+	{
4698
+		/** @type WPDB $wpdb */
4699
+		global $wpdb;
4700
+		if ($field_obj instanceof EE_Model_Field_Base) {
4701
+			return $wpdb->prepare(
4702
+				$field_obj->get_wpdb_data_type(),
4703
+				$this->_prepare_value_for_use_in_db($value, $field_obj)
4704
+			);
4705
+		} //$field_obj should really just be a data type
4706
+		if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4707
+			throw new EE_Error(
4708
+				sprintf(
4709
+					esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
4710
+					$field_obj,
4711
+					implode(",", $this->_valid_wpdb_data_types)
4712
+				)
4713
+			);
4714
+		}
4715
+		return $wpdb->prepare($field_obj, $value);
4716
+	}
4717
+
4718
+
4719
+	/**
4720
+	 * Takes the input parameter and finds the model field that it indicates.
4721
+	 *
4722
+	 * @param string $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4723
+	 * @return EE_Model_Field_Base
4724
+	 * @throws EE_Error
4725
+	 */
4726
+	protected function _deduce_field_from_query_param($query_param_name)
4727
+	{
4728
+		// ok, now proceed with deducing which part is the model's name, and which is the field's name
4729
+		// which will help us find the database table and column
4730
+		$query_param_parts = explode(".", $query_param_name);
4731
+		if (empty($query_param_parts)) {
4732
+			throw new EE_Error(
4733
+				sprintf(
4734
+					esc_html__(
4735
+						"_extract_column_name is empty when trying to extract column and table name from %s",
4736
+						'event_espresso'
4737
+					),
4738
+					$query_param_name
4739
+				)
4740
+			);
4741
+		}
4742
+		$number_of_parts       = count($query_param_parts);
4743
+		$last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4744
+		if ($number_of_parts === 1) {
4745
+			$field_name = $last_query_param_part;
4746
+			$model_obj  = $this;
4747
+		} else {// $number_of_parts >= 2
4748
+			// the last part is the column name, and there are only 2parts. therefore...
4749
+			$field_name = $last_query_param_part;
4750
+			$model_obj  = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4751
+		}
4752
+		try {
4753
+			return $model_obj->field_settings_for($field_name);
4754
+		} catch (EE_Error $e) {
4755
+			return null;
4756
+		}
4757
+	}
4758
+
4759
+
4760
+	/**
4761
+	 * Given a field's name (ie, a key in $this->field_settings()), uses the EE_Model_Field object to get the table's
4762
+	 * alias and column which corresponds to it
4763
+	 *
4764
+	 * @param string $field_name
4765
+	 * @return string
4766
+	 * @throws EE_Error
4767
+	 */
4768
+	public function _get_qualified_column_for_field($field_name)
4769
+	{
4770
+		$all_fields = $this->field_settings();
4771
+		$field      = $all_fields[ $field_name ] ?? false;
4772
+		if ($field) {
4773
+			return $field->get_qualified_column();
4774
+		}
4775
+		throw new EE_Error(
4776
+			sprintf(
4777
+				esc_html__(
4778
+					"There is no field titled %s on model %s. Either the query trying to use it is bad, or you need to add it to the list of fields on the model.",
4779
+					'event_espresso'
4780
+				),
4781
+				$field_name,
4782
+				$this->class_name
4783
+			)
4784
+		);
4785
+	}
4786
+
4787
+
4788
+	/**
4789
+	 * similar to \EEM_Base::_get_qualified_column_for_field() but returns an array with data for ALL fields.
4790
+	 * Example usage:
4791
+	 * EEM_Ticket::instance()->get_all_wpdb_results(
4792
+	 *      array(),
4793
+	 *      ARRAY_A,
4794
+	 *      EEM_Ticket::instance()->get_qualified_columns_for_all_fields()
4795
+	 *  );
4796
+	 * is equivalent to
4797
+	 *  EEM_Ticket::instance()->get_all_wpdb_results( array(), ARRAY_A, '*' );
4798
+	 * and
4799
+	 *  EEM_Event::instance()->get_all_wpdb_results(
4800
+	 *      array(
4801
+	 *          array(
4802
+	 *              'Datetime.Ticket.TKT_ID' => array( '<', 100 ),
4803
+	 *          ),
4804
+	 *          ARRAY_A,
4805
+	 *          implode(
4806
+	 *              ', ',
4807
+	 *              array_merge(
4808
+	 *                  EEM_Event::instance()->get_qualified_columns_for_all_fields( '', false ),
4809
+	 *                  EEM_Ticket::instance()->get_qualified_columns_for_all_fields( 'Datetime', false )
4810
+	 *              )
4811
+	 *          )
4812
+	 *      )
4813
+	 *  );
4814
+	 * selects rows from the database, selecting all the event and ticket columns, where the ticket ID is below 100
4815
+	 *
4816
+	 * @param string $model_relation_chain        the chain of models used to join between the model you want to query
4817
+	 *                                            and the one whose fields you are selecting for example: when querying
4818
+	 *                                            tickets model and selecting fields from the tickets model you would
4819
+	 *                                            leave this parameter empty, because no models are needed to join
4820
+	 *                                            between the queried model and the selected one. Likewise when
4821
+	 *                                            querying the datetime model and selecting fields from the tickets
4822
+	 *                                            model, it would also be left empty, because there is a direct
4823
+	 *                                            relation from datetimes to tickets, so no model is needed to join
4824
+	 *                                            them together. However, when querying from the event model and
4825
+	 *                                            selecting fields from the ticket model, you should provide the string
4826
+	 *                                            'Datetime', indicating that the event model must first join to the
4827
+	 *                                            datetime model in order to find its relation to ticket model.
4828
+	 *                                            Also, when querying from the venue model and selecting fields from
4829
+	 *                                            the ticket model, you should provide the string 'Event.Datetime',
4830
+	 *                                            indicating you need to join the venue model to the event model,
4831
+	 *                                            to the datetime model, in order to find its relation to the ticket
4832
+	 *                                            model. This string is used to deduce the prefix that gets added onto
4833
+	 *                                            the models' tables qualified columns
4834
+	 * @param bool   $return_string               if true, will return a string with qualified column names separated
4835
+	 *                                            by ', ' if false, will simply return a numerically indexed array of
4836
+	 *                                            qualified column names
4837
+	 * @return array|string
4838
+	 */
4839
+	public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4840
+	{
4841
+		$table_prefix      = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain)
4842
+				? ''
4843
+				: '__');
4844
+		$qualified_columns = [];
4845
+		foreach ($this->field_settings() as $field_name => $field) {
4846
+			$qualified_columns[] = $table_prefix . $field->get_qualified_column();
4847
+		}
4848
+		return $return_string
4849
+			? implode(', ', $qualified_columns)
4850
+			: $qualified_columns;
4851
+	}
4852
+
4853
+
4854
+	/**
4855
+	 * constructs the select use on special limit joins
4856
+	 * NOTE: for now this has only been tested and will work when the  table alias is for the PRIMARY table. Although
4857
+	 * its setup so the select query will be setup on and just doing the special select join off of the primary table
4858
+	 * (as that is typically where the limits would be set).
4859
+	 *
4860
+	 * @param string       $table_alias The table the select is being built for
4861
+	 * @param mixed|string $limit       The limit for this select
4862
+	 * @return string                The final select join element for the query.
4863
+	 * @throws EE_Error
4864
+	 * @throws EE_Error
4865
+	 */
4866
+	public function _construct_limit_join_select($table_alias, $limit)
4867
+	{
4868
+		$SQL = '';
4869
+		foreach ($this->_tables as $table_obj) {
4870
+			if ($table_obj instanceof EE_Primary_Table) {
4871
+				$SQL .= $table_alias === $table_obj->get_table_alias()
4872
+					? $table_obj->get_select_join_limit($limit)
4873
+					: SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4874
+			} elseif ($table_obj instanceof EE_Secondary_Table) {
4875
+				$SQL .= $table_alias === $table_obj->get_table_alias()
4876
+					? $table_obj->get_select_join_limit_join($limit)
4877
+					: SP . $table_obj->get_join_sql($table_alias) . SP;
4878
+			}
4879
+		}
4880
+		return $SQL;
4881
+	}
4882
+
4883
+
4884
+	/**
4885
+	 * Constructs the internal join if there are multiple tables, or simply the table's name and alias
4886
+	 * Eg "wp_post AS Event" or "wp_post AS Event INNER JOIN wp_postmeta Event_Meta ON Event.ID = Event_Meta.post_id"
4887
+	 *
4888
+	 * @return string SQL
4889
+	 * @throws EE_Error
4890
+	 */
4891
+	public function _construct_internal_join()
4892
+	{
4893
+		$SQL = $this->_get_main_table()->get_table_sql();
4894
+		$SQL .= $this->_construct_internal_join_to_table_with_alias($this->_get_main_table()->get_table_alias());
4895
+		return $SQL;
4896
+	}
4897
+
4898
+
4899
+	/**
4900
+	 * Constructs the SQL for joining all the tables on this model.
4901
+	 * Normally $alias should be the primary table's alias, but in cases where
4902
+	 * we have already joined to a secondary table (eg, the secondary table has a foreign key and is joined before the
4903
+	 * primary table) then we should provide that secondary table's alias. Eg, with $alias being the primary table's
4904
+	 * alias, this will construct SQL like:
4905
+	 * " INNER JOIN wp_esp_secondary_table AS Secondary_Table ON Primary_Table.pk = Secondary_Table.fk".
4906
+	 * With $alias being a secondary table's alias, this will construct SQL like:
4907
+	 * " INNER JOIN wp_esp_primary_table AS Primary_Table ON Primary_Table.pk = Secondary_Table.fk".
4908
+	 *
4909
+	 * @param string $alias_prefixed table alias to join to (this table should already be in the FROM SQL clause)
4910
+	 * @return string
4911
+	 * @throws EE_Error
4912
+	 * @throws EE_Error
4913
+	 */
4914
+	public function _construct_internal_join_to_table_with_alias($alias_prefixed)
4915
+	{
4916
+		$SQL               = '';
4917
+		$alias_sans_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($alias_prefixed);
4918
+		foreach ($this->_tables as $table_obj) {
4919
+			if ($table_obj instanceof EE_Secondary_Table) {// table is secondary table
4920
+				if ($alias_sans_prefix === $table_obj->get_table_alias()) {
4921
+					// so we're joining to this table, meaning the table is already in
4922
+					// the FROM statement, BUT the primary table isn't. So we want
4923
+					// to add the inverse join sql
4924
+					$SQL .= $table_obj->get_inverse_join_sql($alias_prefixed);
4925
+				} else {
4926
+					// just add a regular JOIN to this table from the primary table
4927
+					$SQL .= $table_obj->get_join_sql($alias_prefixed);
4928
+				}
4929
+			}// if it's a primary table, dont add any SQL. it should already be in the FROM statement
4930
+		}
4931
+		return $SQL;
4932
+	}
4933
+
4934
+
4935
+	/**
4936
+	 * Gets an array for storing all the data types on the next-to-be-executed-query.
4937
+	 * This should be a growing array of keys being table-columns (eg 'EVT_ID' and 'Event.EVT_ID'), and values being
4938
+	 * their data type (eg, '%s', '%d', etc)
4939
+	 *
4940
+	 * @return array
4941
+	 */
4942
+	public function _get_data_types()
4943
+	{
4944
+		$data_types = [];
4945
+		foreach ($this->field_settings() as $field_obj) {
4946
+			// $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4947
+			/** @var $field_obj EE_Model_Field_Base */
4948
+			$data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4949
+		}
4950
+		return $data_types;
4951
+	}
4952
+
4953
+
4954
+	/**
4955
+	 * Gets the model object given the relation's name / model's name (eg, 'Event', 'Registration',etc. Always singular)
4956
+	 *
4957
+	 * @param string $model_name
4958
+	 * @return EEM_Base
4959
+	 * @throws EE_Error
4960
+	 */
4961
+	public function get_related_model_obj($model_name)
4962
+	{
4963
+		$model_classname = "EEM_" . $model_name;
4964
+		if (! class_exists($model_classname)) {
4965
+			throw new EE_Error(
4966
+				sprintf(
4967
+					esc_html__(
4968
+						"You specified a related model named %s in your query. No such model exists, if it did, it would have the classname %s",
4969
+						'event_espresso'
4970
+					),
4971
+					$model_name,
4972
+					$model_classname
4973
+				)
4974
+			);
4975
+		}
4976
+		return call_user_func($model_classname . "::instance");
4977
+	}
4978
+
4979
+
4980
+	/**
4981
+	 * Returns the array of EE_ModelRelations for this model.
4982
+	 *
4983
+	 * @return EE_Model_Relation_Base[]
4984
+	 */
4985
+	public function relation_settings()
4986
+	{
4987
+		return $this->_model_relations;
4988
+	}
4989
+
4990
+
4991
+	/**
4992
+	 * Gets all related models that this model BELONGS TO. Handy to know sometimes
4993
+	 * because without THOSE models, this model probably doesn't have much purpose.
4994
+	 * (Eg, without an event, datetimes have little purpose.)
4995
+	 *
4996
+	 * @return EE_Belongs_To_Relation[]
4997
+	 */
4998
+	public function belongs_to_relations()
4999
+	{
5000
+		$belongs_to_relations = [];
5001
+		foreach ($this->relation_settings() as $model_name => $relation_obj) {
5002
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
5003
+				$belongs_to_relations[ $model_name ] = $relation_obj;
5004
+			}
5005
+		}
5006
+		return $belongs_to_relations;
5007
+	}
5008
+
5009
+
5010
+	/**
5011
+	 * Returns the specified EE_Model_Relation, or throws an exception
5012
+	 *
5013
+	 * @param string $relation_name name of relation, key in $this->_relatedModels
5014
+	 * @return EE_Model_Relation_Base
5015
+	 * @throws EE_Error
5016
+	 */
5017
+	public function related_settings_for($relation_name)
5018
+	{
5019
+		$relatedModels = $this->relation_settings();
5020
+		if (! array_key_exists($relation_name, $relatedModels)) {
5021
+			throw new EE_Error(
5022
+				sprintf(
5023
+					esc_html__(
5024
+						'Cannot get %s related to %s. There is no model relation of that type. There is, however, %s...',
5025
+						'event_espresso'
5026
+					),
5027
+					$relation_name,
5028
+					$this->_get_class_name(),
5029
+					implode(', ', array_keys($relatedModels))
5030
+				)
5031
+			);
5032
+		}
5033
+		return $relatedModels[ $relation_name ];
5034
+	}
5035
+
5036
+
5037
+	/**
5038
+	 * A convenience method for getting a specific field's settings, instead of getting all field settings for all
5039
+	 * fields
5040
+	 *
5041
+	 * @param string  $fieldName
5042
+	 * @param boolean $include_db_only_fields
5043
+	 * @return EE_Model_Field_Base
5044
+	 * @throws EE_Error
5045
+	 */
5046
+	public function field_settings_for($fieldName, $include_db_only_fields = true)
5047
+	{
5048
+		$fieldSettings = $this->field_settings($include_db_only_fields);
5049
+		if (! array_key_exists($fieldName, $fieldSettings)) {
5050
+			throw new EE_Error(
5051
+				sprintf(
5052
+					esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
5053
+					$fieldName,
5054
+					$this->class_name
5055
+				)
5056
+			);
5057
+		}
5058
+		return $fieldSettings[ $fieldName ];
5059
+	}
5060
+
5061
+
5062
+	/**
5063
+	 * Checks if this field exists on this model
5064
+	 *
5065
+	 * @param string $fieldName a key in the model's _field_settings array
5066
+	 * @return boolean
5067
+	 */
5068
+	public function has_field($fieldName)
5069
+	{
5070
+		$fieldSettings = $this->field_settings(true);
5071
+		if (isset($fieldSettings[ $fieldName ])) {
5072
+			return true;
5073
+		}
5074
+		return false;
5075
+	}
5076
+
5077
+
5078
+	/**
5079
+	 * Returns whether this model has a relation to the specified model
5080
+	 *
5081
+	 * @param string $relation_name possibly one of the keys in the relation_settings array
5082
+	 * @return boolean
5083
+	 */
5084
+	public function has_relation($relation_name)
5085
+	{
5086
+		$relations = $this->relation_settings();
5087
+		if (isset($relations[ $relation_name ])) {
5088
+			return true;
5089
+		}
5090
+		return false;
5091
+	}
5092
+
5093
+
5094
+	/**
5095
+	 * gets the field object of type 'primary_key' from the fieldsSettings attribute.
5096
+	 * Eg, on EE_Answer that would be ANS_ID field object
5097
+	 *
5098
+	 * @param $field_obj
5099
+	 * @return boolean
5100
+	 */
5101
+	public function is_primary_key_field($field_obj): bool
5102
+	{
5103
+		return $field_obj instanceof EE_Primary_Key_Field_Base;
5104
+	}
5105
+
5106
+
5107
+	/**
5108
+	 * gets the field object of type 'primary_key' from the fieldsSettings attribute.
5109
+	 * Eg, on EE_Answer that would be ANS_ID field object
5110
+	 *
5111
+	 * @return EE_Primary_Key_Field_Base
5112
+	 * @throws EE_Error
5113
+	 */
5114
+	public function get_primary_key_field()
5115
+	{
5116
+		if ($this->_primary_key_field === null) {
5117
+			foreach ($this->field_settings(true) as $field_obj) {
5118
+				if ($this->is_primary_key_field($field_obj)) {
5119
+					$this->_primary_key_field = $field_obj;
5120
+					break;
5121
+				}
5122
+			}
5123
+			if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5124
+				throw new EE_Error(
5125
+					sprintf(
5126
+						esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
5127
+						$this->class_name
5128
+					)
5129
+				);
5130
+			}
5131
+		}
5132
+		return $this->_primary_key_field;
5133
+	}
5134
+
5135
+
5136
+	/**
5137
+	 * Returns whether not there is a primary key on this model.
5138
+	 * Internally does some caching.
5139
+	 *
5140
+	 * @return boolean
5141
+	 */
5142
+	public function has_primary_key_field()
5143
+	{
5144
+		if ($this->_has_primary_key_field === null) {
5145
+			try {
5146
+				$this->get_primary_key_field();
5147
+				$this->_has_primary_key_field = true;
5148
+			} catch (EE_Error $e) {
5149
+				$this->_has_primary_key_field = false;
5150
+			}
5151
+		}
5152
+		return $this->_has_primary_key_field;
5153
+	}
5154
+
5155
+
5156
+	/**
5157
+	 * Finds the first field of type $field_class_name.
5158
+	 *
5159
+	 * @param string $field_class_name class name of field that you want to find. Eg, EE_Datetime_Field,
5160
+	 *                                 EE_Foreign_Key_Field, etc
5161
+	 * @return EE_Model_Field_Base or null if none is found
5162
+	 */
5163
+	public function get_a_field_of_type($field_class_name)
5164
+	{
5165
+		foreach ($this->field_settings() as $field) {
5166
+			if ($field instanceof $field_class_name) {
5167
+				return $field;
5168
+			}
5169
+		}
5170
+		return null;
5171
+	}
5172
+
5173
+
5174
+	/**
5175
+	 * Gets a foreign key field pointing to model.
5176
+	 *
5177
+	 * @param string $model_name eg Event, Registration, not EEM_Event
5178
+	 * @return EE_Foreign_Key_Field_Base
5179
+	 * @throws EE_Error
5180
+	 */
5181
+	public function get_foreign_key_to($model_name)
5182
+	{
5183
+		if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5184
+			foreach ($this->field_settings() as $field) {
5185
+				if (
5186
+					$field instanceof EE_Foreign_Key_Field_Base
5187
+					&& in_array($model_name, $field->get_model_names_pointed_to())
5188
+				) {
5189
+					$this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5190
+					break;
5191
+				}
5192
+			}
5193
+			if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5194
+				throw new EE_Error(
5195
+					sprintf(
5196
+						esc_html__(
5197
+							"There is no foreign key field pointing to model %s on model %s",
5198
+							'event_espresso'
5199
+						),
5200
+						$model_name,
5201
+						$this->class_name
5202
+					)
5203
+				);
5204
+			}
5205
+		}
5206
+		return $this->_cache_foreign_key_to_fields[ $model_name ];
5207
+	}
5208
+
5209
+
5210
+	/**
5211
+	 * Gets the table name (including $wpdb->prefix) for the table alias
5212
+	 *
5213
+	 * @param string $table_alias eg Event, Event_Meta, Registration, Transaction, but maybe
5214
+	 *                            a table alias with a model chain prefix, like 'Venue__Event_Venue___Event_Meta'.
5215
+	 *                            Either one works
5216
+	 * @return string
5217
+	 */
5218
+	public function get_table_for_alias($table_alias)
5219
+	{
5220
+		$table_alias_sans_model_relation_chain_prefix =
5221
+			EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5222
+		return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5223
+	}
5224
+
5225
+
5226
+	/**
5227
+	 * Returns a flat array of all field son this model, instead of organizing them
5228
+	 * by table_alias as they are in the constructor.
5229
+	 *
5230
+	 * @param bool $include_db_only_fields flag indicating whether to include the db-only fields
5231
+	 * @return EE_Model_Field_Base[] where the keys are the field's name
5232
+	 */
5233
+	public function field_settings($include_db_only_fields = false)
5234
+	{
5235
+		if ($include_db_only_fields) {
5236
+			if ($this->_cached_fields === null) {
5237
+				$this->_cached_fields = [];
5238
+				foreach ($this->_fields as $fields_corresponding_to_table) {
5239
+					foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5240
+						$this->_cached_fields[ $field_name ] = $field_obj;
5241
+					}
5242
+				}
5243
+			}
5244
+			return $this->_cached_fields;
5245
+		}
5246
+		if ($this->_cached_fields_non_db_only === null) {
5247
+			$this->_cached_fields_non_db_only = [];
5248
+			foreach ($this->_fields as $fields_corresponding_to_table) {
5249
+				foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5250
+					/** @var $field_obj EE_Model_Field_Base */
5251
+					if (! $field_obj->is_db_only_field()) {
5252
+						$this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5253
+					}
5254
+				}
5255
+			}
5256
+		}
5257
+		return $this->_cached_fields_non_db_only;
5258
+	}
5259
+
5260
+
5261
+	/**
5262
+	 *        cycle though array of attendees and create objects out of each item
5263
+	 *
5264
+	 * @access        private
5265
+	 * @param array $rows        of results of $wpdb->get_results($query,ARRAY_A)
5266
+	 * @return EE_Base_Class[] array keys are primary keys (if there is a primary key on the model. if not,
5267
+	 *                           numerically indexed)
5268
+	 * @throws EE_Error
5269
+	 * @throws ReflectionException
5270
+	 */
5271
+	protected function _create_objects($rows = [])
5272
+	{
5273
+		$array_of_objects = [];
5274
+		if (empty($rows)) {
5275
+			return [];
5276
+		}
5277
+		$count_if_model_has_no_primary_key = 0;
5278
+		$has_primary_key                   = $this->has_primary_key_field();
5279
+		$primary_key_field                 = $has_primary_key
5280
+			? $this->get_primary_key_field()
5281
+			: null;
5282
+		foreach ((array) $rows as $row) {
5283
+			if (empty($row)) {
5284
+				// wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5285
+				return [];
5286
+			}
5287
+			// check if we've already set this object in the results array,
5288
+			// in which case there's no need to process it further (again)
5289
+			if ($has_primary_key) {
5290
+				$table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5291
+					$row,
5292
+					$primary_key_field->get_qualified_column(),
5293
+					$primary_key_field->get_table_column()
5294
+				);
5295
+				if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5296
+					continue;
5297
+				}
5298
+			}
5299
+			$classInstance = $this->instantiate_class_from_array_or_object($row);
5300
+			if (! $classInstance) {
5301
+				throw new EE_Error(
5302
+					sprintf(
5303
+						esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
5304
+						$this->get_this_model_name(),
5305
+						http_build_query($row)
5306
+					)
5307
+				);
5308
+			}
5309
+			// set the timezone on the instantiated objects
5310
+			$classInstance->set_timezone($this->_timezone);
5311
+			// make sure if there is any timezone setting present that we set the timezone for the object
5312
+			$key                      = $has_primary_key
5313
+				? $classInstance->ID()
5314
+				: $count_if_model_has_no_primary_key++;
5315
+			$array_of_objects[ $key ] = $classInstance;
5316
+			// also, for all the relations of type BelongsTo, see if we can cache
5317
+			// those related models
5318
+			// (we could do this for other relations too, but if there are conditions
5319
+			// that filtered out some fo the results, then we'd be caching an incomplete set
5320
+			// so it requires a little more thought than just caching them immediately...)
5321
+			foreach ($this->_model_relations as $modelName => $relation_obj) {
5322
+				if ($relation_obj instanceof EE_Belongs_To_Relation) {
5323
+					// check if this model's INFO is present. If so, cache it on the model
5324
+					$other_model           = $relation_obj->get_other_model();
5325
+					$other_model_obj_maybe = $other_model->instantiate_class_from_array_or_object($row);
5326
+					// if we managed to make a model object from the results, cache it on the main model object
5327
+					if ($other_model_obj_maybe) {
5328
+						// set timezone on these other model objects if they are present
5329
+						$other_model_obj_maybe->set_timezone($this->_timezone);
5330
+						$classInstance->cache($modelName, $other_model_obj_maybe);
5331
+					}
5332
+				}
5333
+			}
5334
+			// also, if this was a custom select query, let's see if there are any results for the custom select fields
5335
+			// and add them to the object as well.  We'll convert according to the set data_type if there's any set for
5336
+			// the field in the CustomSelects object
5337
+			if ($this->_custom_selections instanceof CustomSelects) {
5338
+				$classInstance->setCustomSelectsValues(
5339
+					$this->getValuesForCustomSelectAliasesFromResults($row)
5340
+				);
5341
+			}
5342
+		}
5343
+		return $array_of_objects;
5344
+	}
5345
+
5346
+
5347
+	/**
5348
+	 * This will parse a given row of results from the db and see if any keys in the results match an alias within the
5349
+	 * current CustomSelects object. This will be used to build an array of values indexed by those keys.
5350
+	 *
5351
+	 * @param array $db_results_row
5352
+	 * @return array
5353
+	 */
5354
+	protected function getValuesForCustomSelectAliasesFromResults(array $db_results_row)
5355
+	{
5356
+		$results = [];
5357
+		if ($this->_custom_selections instanceof CustomSelects) {
5358
+			foreach ($this->_custom_selections->columnAliases() as $alias) {
5359
+				if (isset($db_results_row[ $alias ])) {
5360
+					$results[ $alias ] = $this->convertValueToDataType(
5361
+						$db_results_row[ $alias ],
5362
+						$this->_custom_selections->getDataTypeForAlias($alias)
5363
+					);
5364
+				}
5365
+			}
5366
+		}
5367
+		return $results;
5368
+	}
5369
+
5370
+
5371
+	/**
5372
+	 * This will set the value for the given alias
5373
+	 *
5374
+	 * @param string $value
5375
+	 * @param string $datatype (one of %d, %s, %f)
5376
+	 * @return int|string|float (int for %d, string for %s, float for %f)
5377
+	 */
5378
+	protected function convertValueToDataType($value, $datatype)
5379
+	{
5380
+		switch ($datatype) {
5381
+			case '%f':
5382
+				return (float) $value;
5383
+			case '%d':
5384
+				return (int) $value;
5385
+			default:
5386
+				return (string) $value;
5387
+		}
5388
+	}
5389
+
5390
+
5391
+	/**
5392
+	 * The purpose of this method is to allow us to create a model object that is not in the db that holds default
5393
+	 * values. A typical example of where this is used is when creating a new item and the initial load of a form.  We
5394
+	 * dont' necessarily want to test for if the object is present but just assume it is BUT load the defaults from the
5395
+	 * object (as set in the model_field!).
5396
+	 *
5397
+	 * @return EE_Base_Class single EE_Base_Class object with default values for the properties.
5398
+	 * @throws EE_Error
5399
+	 * @throws ReflectionException
5400
+	 */
5401
+	public function create_default_object()
5402
+	{
5403
+		$this_model_fields_and_values = [];
5404
+		// setup the row using default values;
5405
+		foreach ($this->field_settings() as $field_name => $field_obj) {
5406
+			$this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5407
+		}
5408
+		$className = $this->_get_class_name();
5409
+		return EE_Registry::instance()->load_class($className, [$this_model_fields_and_values], false, false);
5410
+	}
5411
+
5412
+
5413
+	/**
5414
+	 * @param mixed $cols_n_values either an array of where each key is the name of a field, and the value is its value
5415
+	 *                             or an stdClass where each property is the name of a column,
5416
+	 * @return EE_Base_Class
5417
+	 * @throws EE_Error
5418
+	 * @throws ReflectionException
5419
+	 */
5420
+	public function instantiate_class_from_array_or_object($cols_n_values)
5421
+	{
5422
+		if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5423
+			$cols_n_values = get_object_vars($cols_n_values);
5424
+		}
5425
+		$primary_key = null;
5426
+		// make sure the array only has keys that are fields/columns on this model
5427
+		$this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5428
+		if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5429
+			$primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5430
+		}
5431
+		$className = $this->_get_class_name();
5432
+		// check we actually found results that we can use to build our model object
5433
+		// if not, return null
5434
+		if ($this->has_primary_key_field()) {
5435
+			if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5436
+				return null;
5437
+			}
5438
+		} elseif ($this->unique_indexes()) {
5439
+			$first_column = reset($this_model_fields_n_values);
5440
+			if (empty($first_column)) {
5441
+				return null;
5442
+			}
5443
+		}
5444
+		// if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5445
+		if ($primary_key) {
5446
+			$classInstance = $this->get_from_entity_map($primary_key);
5447
+			if (! $classInstance) {
5448
+				$classInstance = EE_Registry::instance()
5449
+											->load_class(
5450
+												$className,
5451
+												[$this_model_fields_n_values, $this->_timezone],
5452
+												true,
5453
+												false
5454
+											);
5455
+				// add this new object to the entity map
5456
+				$classInstance = $this->add_to_entity_map($classInstance);
5457
+			}
5458
+		} else {
5459
+			$classInstance = EE_Registry::instance()->load_class(
5460
+				$className,
5461
+				[$this_model_fields_n_values, $this->_timezone],
5462
+				true,
5463
+				false
5464
+			);
5465
+		}
5466
+		return $classInstance;
5467
+	}
5468
+
5469
+
5470
+	/**
5471
+	 * Gets the model object from the  entity map if it exists
5472
+	 *
5473
+	 * @param int|string $id the ID of the model object
5474
+	 * @return EE_Base_Class
5475
+	 */
5476
+	public function get_from_entity_map($id)
5477
+	{
5478
+		return $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] ?? null;
5479
+	}
5480
+
5481
+
5482
+	/**
5483
+	 * add_to_entity_map
5484
+	 * Adds the object to the model's entity mappings
5485
+	 *        Effectively tells the models "Hey, this model object is the most up-to-date representation of the data,
5486
+	 *        and for the remainder of the request, it's even more up-to-date than what's in the database.
5487
+	 *        So, if the database doesn't agree with what's in the entity mapper, ignore the database"
5488
+	 *        If the database gets updated directly and you want the entity mapper to reflect that change,
5489
+	 *        then this method should be called immediately after the update query
5490
+	 * Note: The map is indexed by whatever the current blog id is set (via EEM_Base::$_model_query_blog_id).  This is
5491
+	 * so on multisite, the entity map is specific to the query being done for a specific site.
5492
+	 *
5493
+	 * @param EE_Base_Class $object
5494
+	 * @return EE_Base_Class
5495
+	 * @throws EE_Error
5496
+	 * @throws ReflectionException
5497
+	 */
5498
+	public function add_to_entity_map(EE_Base_Class $object)
5499
+	{
5500
+		$className = $this->_get_class_name();
5501
+		if (! $object instanceof $className) {
5502
+			throw new EE_Error(
5503
+				sprintf(
5504
+					esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
5505
+					is_object($object)
5506
+						? get_class($object)
5507
+						: $object,
5508
+					$className
5509
+				)
5510
+			);
5511
+		}
5512
+
5513
+		if (! $object->ID()) {
5514
+			throw new EE_Error(
5515
+				sprintf(
5516
+					esc_html__(
5517
+						"You tried storing a model object with NO ID in the %s entity mapper.",
5518
+						"event_espresso"
5519
+					),
5520
+					$this->class_name
5521
+				)
5522
+			);
5523
+		}
5524
+		// double check it's not already there
5525
+		$classInstance = $this->get_from_entity_map($object->ID());
5526
+		if ($classInstance) {
5527
+			return $classInstance;
5528
+		}
5529
+		$this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5530
+		return $object;
5531
+	}
5532
+
5533
+
5534
+	/**
5535
+	 * if a valid identifier is provided, then that entity is unset from the entity map,
5536
+	 * if no identifier is provided, then the entire entity map is emptied
5537
+	 *
5538
+	 * @param int|string $id the ID of the model object
5539
+	 * @return boolean
5540
+	 */
5541
+	public function clear_entity_map($id = null)
5542
+	{
5543
+		if (empty($id)) {
5544
+			$this->_entity_map[ EEM_Base::$_model_query_blog_id ] = [];
5545
+			return true;
5546
+		}
5547
+		if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5548
+			unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5549
+			return true;
5550
+		}
5551
+		return false;
5552
+	}
5553
+
5554
+
5555
+	/**
5556
+	 * Public wrapper for _deduce_fields_n_values_from_cols_n_values.
5557
+	 * Given an array where keys are column (or column alias) names and values,
5558
+	 * returns an array of their corresponding field names and database values
5559
+	 *
5560
+	 * @param array $cols_n_values
5561
+	 * @return array
5562
+	 * @throws EE_Error
5563
+	 * @throws ReflectionException
5564
+	 */
5565
+	public function deduce_fields_n_values_from_cols_n_values(array $cols_n_values): array
5566
+	{
5567
+		return $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5568
+	}
5569
+
5570
+
5571
+	/**
5572
+	 * _deduce_fields_n_values_from_cols_n_values
5573
+	 * Given an array where keys are column (or column alias) names and values,
5574
+	 * returns an array of their corresponding field names and database values
5575
+	 *
5576
+	 * @param array|stdClass $cols_n_values
5577
+	 * @return array
5578
+	 * @throws EE_Error
5579
+	 * @throws ReflectionException
5580
+	 */
5581
+	protected function _deduce_fields_n_values_from_cols_n_values($cols_n_values): array
5582
+	{
5583
+		if ($cols_n_values instanceof stdClass) {
5584
+			$cols_n_values = get_object_vars($cols_n_values);
5585
+		}
5586
+		$this_model_fields_n_values = [];
5587
+		foreach ($this->get_tables() as $table_alias => $table_obj) {
5588
+			$table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5589
+				$cols_n_values,
5590
+				$table_obj->get_fully_qualified_pk_column(),
5591
+				$table_obj->get_pk_column()
5592
+			);
5593
+			// there is a primary key on this table and its not set. Use defaults for all its columns
5594
+			if ($table_pk_value === null && $table_obj->get_pk_column()) {
5595
+				foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5596
+					if (! $field_obj->is_db_only_field()) {
5597
+						// prepare field as if its coming from db
5598
+						$prepared_value                            =
5599
+							$field_obj->prepare_for_set($field_obj->get_default_value());
5600
+						$this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5601
+					}
5602
+				}
5603
+			} else {
5604
+				// the table's rows existed. Use their values
5605
+				foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5606
+					if (! $field_obj->is_db_only_field()) {
5607
+						$this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5608
+							$cols_n_values,
5609
+							$field_obj->get_qualified_column(),
5610
+							$field_obj->get_table_column()
5611
+						);
5612
+					}
5613
+				}
5614
+			}
5615
+		}
5616
+		return $this_model_fields_n_values;
5617
+	}
5618
+
5619
+
5620
+	/**
5621
+	 * @param $cols_n_values
5622
+	 * @param $qualified_column
5623
+	 * @param $regular_column
5624
+	 * @return null
5625
+	 * @throws EE_Error
5626
+	 * @throws ReflectionException
5627
+	 */
5628
+	protected function _get_column_value_with_table_alias_or_not($cols_n_values, $qualified_column, $regular_column)
5629
+	{
5630
+		$value = null;
5631
+		// ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5632
+		// does the field on the model relate to this column retrieved from the db?
5633
+		// or is it a db-only field? (not relating to the model)
5634
+		if (isset($cols_n_values[ $qualified_column ])) {
5635
+			$value = $cols_n_values[ $qualified_column ];
5636
+		} elseif (isset($cols_n_values[ $regular_column ])) {
5637
+			$value = $cols_n_values[ $regular_column ];
5638
+		} elseif (! empty($this->foreign_key_aliases)) {
5639
+			// no PK?  ok check if there is a foreign key alias set for this table
5640
+			// then check if that alias exists in the incoming data
5641
+			// AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5642
+			foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5643
+				if ($PK_column === $qualified_column && !empty($cols_n_values[ $FK_alias ])) {
5644
+					$value = $cols_n_values[ $FK_alias ];
5645
+					[$pk_class] = explode('.', $PK_column);
5646
+					$pk_model_name = "EEM_{$pk_class}";
5647
+					/** @var EEM_Base $pk_model */
5648
+					$pk_model = EE_Registry::instance()->load_model($pk_model_name);
5649
+					if ($pk_model instanceof EEM_Base) {
5650
+						// make sure object is pulled from db and added to entity map
5651
+						$pk_model->get_one_by_ID($value);
5652
+					}
5653
+					break;
5654
+				}
5655
+			}
5656
+		}
5657
+		return $value;
5658
+	}
5659
+
5660
+
5661
+	/**
5662
+	 * refresh_entity_map_from_db
5663
+	 * Makes sure the model object in the entity map at $id assumes the values
5664
+	 * of the database (opposite of EE_base_Class::save())
5665
+	 *
5666
+	 * @param int|string $id
5667
+	 * @return EE_Base_Class|EE_Soft_Delete_Base_Class|mixed|null
5668
+	 * @throws EE_Error
5669
+	 * @throws ReflectionException
5670
+	 */
5671
+	public function refresh_entity_map_from_db($id)
5672
+	{
5673
+		$obj_in_map = $this->get_from_entity_map($id);
5674
+		if ($obj_in_map) {
5675
+			$wpdb_results = $this->_get_all_wpdb_results(
5676
+				[[$this->get_primary_key_field()->get_name() => $id], 'limit' => 1]
5677
+			);
5678
+			if ($wpdb_results && is_array($wpdb_results)) {
5679
+				$one_row = reset($wpdb_results);
5680
+				foreach ($this->_deduce_fields_n_values_from_cols_n_values($one_row) as $field_name => $db_value) {
5681
+					$obj_in_map->set_from_db($field_name, $db_value);
5682
+				}
5683
+				// clear the cache of related model objects
5684
+				foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5685
+					$obj_in_map->clear_cache($relation_name, null, true);
5686
+				}
5687
+			}
5688
+			$this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5689
+			return $obj_in_map;
5690
+		}
5691
+		return $this->get_one_by_ID($id);
5692
+	}
5693
+
5694
+
5695
+	/**
5696
+	 * refresh_entity_map_with
5697
+	 * Leaves the entry in the entity map alone, but updates it to match the provided
5698
+	 * $replacing_model_obj (which we assume to be its equivalent but somehow NOT in the entity map).
5699
+	 * This is useful if you have a model object you want to make authoritative over what's in the entity map currently.
5700
+	 * Note: The old $replacing_model_obj should now be destroyed as it's now un-authoritative
5701
+	 *
5702
+	 * @param int|string    $id
5703
+	 * @param EE_Base_Class $replacing_model_obj
5704
+	 * @return EE_Base_Class
5705
+	 * @throws EE_Error
5706
+	 * @throws ReflectionException
5707
+	 */
5708
+	public function refresh_entity_map_with($id, $replacing_model_obj)
5709
+	{
5710
+		$obj_in_map = $this->get_from_entity_map($id);
5711
+		if ($obj_in_map) {
5712
+			if ($replacing_model_obj instanceof EE_Base_Class) {
5713
+				foreach ($replacing_model_obj->model_field_array() as $field_name => $value) {
5714
+					$obj_in_map->set($field_name, $value);
5715
+				}
5716
+				// make the model object in the entity map's cache match the $replacing_model_obj
5717
+				foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5718
+					$obj_in_map->clear_cache($relation_name, null, true);
5719
+					foreach ($replacing_model_obj->get_all_from_cache($relation_name) as $cache_id => $cached_obj) {
5720
+						$obj_in_map->cache($relation_name, $cached_obj, $cache_id);
5721
+					}
5722
+				}
5723
+			}
5724
+			return $obj_in_map;
5725
+		}
5726
+		$this->add_to_entity_map($replacing_model_obj);
5727
+		return $replacing_model_obj;
5728
+	}
5729
+
5730
+
5731
+	/**
5732
+	 * Gets the EE class that corresponds to this model. Eg, for EEM_Answer that
5733
+	 * would be EE_Answer.To import that class, you'd just add ".class.php" to the name, like so
5734
+	 * require_once($this->_getClassName().".class.php");
5735
+	 *
5736
+	 * @return string
5737
+	 */
5738
+	private function _get_class_name()
5739
+	{
5740
+		return "EE_" . $this->get_this_model_name();
5741
+	}
5742
+
5743
+
5744
+	/**
5745
+	 * Get the name of the items this model represents, for the quantity specified. Eg,
5746
+	 * if $quantity==1, on EEM_Event, it would 'Event' (internationalized), otherwise
5747
+	 * it would be 'Events'.
5748
+	 *
5749
+	 * @param int|float|null $quantity
5750
+	 * @return string
5751
+	 */
5752
+	public function item_name($quantity = 1): string
5753
+	{
5754
+		$quantity = floor($quantity);
5755
+		return apply_filters(
5756
+			'FHEE__EEM_Base__item_name__plural_or_singular',
5757
+			$quantity > 1
5758
+				? $this->plural_item
5759
+				: $this->singular_item,
5760
+			$quantity,
5761
+			$this->plural_item,
5762
+			$this->singular_item
5763
+		);
5764
+	}
5765
+
5766
+
5767
+	/**
5768
+	 * Very handy general function to allow for plugins to extend any child of EE_TempBase.
5769
+	 * If a method is called on a child of EE_TempBase that doesn't exist, this function is called
5770
+	 * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
5771
+	 * requiring a plugin to extend the EE_TempBase (which works fine is there's only 1 plugin, but when will that
5772
+	 * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
5773
+	 * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
5774
+	 * was called, and an array of the original arguments passed to the function. Whatever their callback function
5775
+	 * returns will be returned by this function. Example: in functions.php (or in a plugin):
5776
+	 * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
5777
+	 * my_callback($previousReturnValue,EE_TempBase $object,$argsArray){
5778
+	 * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
5779
+	 *        return $previousReturnValue.$returnString;
5780
+	 * }
5781
+	 * require('EEM_Answer.model.php');
5782
+	 * echo EEM_Answer::instance()->my_callback('monkeys',100);
5783
+	 * // will output "you called my_callback! and passed args:monkeys,100"
5784
+	 *
5785
+	 * @param string $methodName name of method which was called on a child of EE_TempBase, but which
5786
+	 * @param array  $args       array of original arguments passed to the function
5787
+	 * @return mixed whatever the plugin which calls add_filter decides
5788
+	 * @throws EE_Error
5789
+	 */
5790
+	public function __call($methodName, $args)
5791
+	{
5792
+		$className = $this->class_name;
5793
+		$tagName   = "FHEE__{$className}__{$methodName}";
5794
+		if (! has_filter($tagName)) {
5795
+			throw new EE_Error(
5796
+				sprintf(
5797
+					esc_html__(
5798
+						'Method %1$s on model %2$s does not exist! You can create one with the following code in functions.php or in a plugin: %4$s function my_callback(%4$s \$previousReturnValue, EEM_Base \$object\ $argsArray=NULL ){%4$s     /*function body*/%4$s      return \$whatever;%4$s }%4$s add_filter( \'%3$s\', \'my_callback\', 10, 3 );',
5799
+						'event_espresso'
5800
+					),
5801
+					$methodName,
5802
+					$className,
5803
+					$tagName,
5804
+					'<br />'
5805
+				)
5806
+			);
5807
+		}
5808
+		return apply_filters($tagName, null, $this, $args);
5809
+	}
5810
+
5811
+
5812
+	/**
5813
+	 * Ensures $base_class_obj_or_id is of the EE_Base_Class child that corresponds ot this model.
5814
+	 * If not, assumes its an ID, and uses $this->get_one_by_ID() to get the EE_Base_Class.
5815
+	 *
5816
+	 * @param EE_Base_Class|string|int $base_class_obj_or_id either:
5817
+	 *                                                       the EE_Base_Class object that corresponds to this Model,
5818
+	 *                                                       the object's class name
5819
+	 *                                                       or object's ID
5820
+	 * @param boolean                  $ensure_is_in_db      if set, we will also verify this model object
5821
+	 *                                                       exists in the database. If it does not, we add it
5822
+	 * @return EE_Base_Class
5823
+	 * @throws EE_Error
5824
+	 * @throws ReflectionException
5825
+	 */
5826
+	public function ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db = false)
5827
+	{
5828
+		$className = $this->_get_class_name();
5829
+		if ($base_class_obj_or_id instanceof $className) {
5830
+			$model_object = $base_class_obj_or_id;
5831
+		} else {
5832
+			$primary_key_field = $this->get_primary_key_field();
5833
+			if (
5834
+				$primary_key_field instanceof EE_Primary_Key_Int_Field
5835
+				&& (
5836
+					is_int($base_class_obj_or_id)
5837
+					|| is_string($base_class_obj_or_id)
5838
+				)
5839
+			) {
5840
+				// assume it's an ID.
5841
+				// either a proper integer or a string representing an integer (eg "101" instead of 101)
5842
+				$model_object = $this->get_one_by_ID($base_class_obj_or_id);
5843
+			} elseif (
5844
+				$primary_key_field instanceof EE_Primary_Key_String_Field
5845
+				&& is_string($base_class_obj_or_id)
5846
+			) {
5847
+				// assume it's a string representation of the object
5848
+				$model_object = $this->get_one_by_ID($base_class_obj_or_id);
5849
+			} else {
5850
+				throw new EE_Error(
5851
+					sprintf(
5852
+						esc_html__(
5853
+							"'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5854
+							'event_espresso'
5855
+						),
5856
+						$base_class_obj_or_id,
5857
+						$this->_get_class_name(),
5858
+						print_r($base_class_obj_or_id, true)
5859
+					)
5860
+				);
5861
+			}
5862
+		}
5863
+		if ($ensure_is_in_db && $model_object instanceof EE_Base_Class && $model_object->ID() !== null) {
5864
+			$model_object->save();
5865
+		}
5866
+		return $model_object;
5867
+	}
5868
+
5869
+
5870
+	/**
5871
+	 * Similar to ensure_is_obj(), this method makes sure $base_class_obj_or_id
5872
+	 * is a value of the this model's primary key. If it's an EE_Base_Class child,
5873
+	 * returns it ID.
5874
+	 *
5875
+	 * @param EE_Base_Class|int|string $base_class_obj_or_id
5876
+	 * @return int|string depending on the type of this model object's ID
5877
+	 * @throws EE_Error
5878
+	 * @throws ReflectionException
5879
+	 */
5880
+	public function ensure_is_ID($base_class_obj_or_id)
5881
+	{
5882
+		$className = $this->_get_class_name();
5883
+		if ($base_class_obj_or_id instanceof $className) {
5884
+			/** @var $base_class_obj_or_id EE_Base_Class */
5885
+			$id = $base_class_obj_or_id->ID();
5886
+		} elseif (is_int($base_class_obj_or_id)) {
5887
+			// assume it's an ID
5888
+			$id = $base_class_obj_or_id;
5889
+		} elseif (is_string($base_class_obj_or_id)) {
5890
+			// assume its a string representation of the object
5891
+			$id = $base_class_obj_or_id;
5892
+		} else {
5893
+			throw new EE_Error(
5894
+				sprintf(
5895
+					esc_html__(
5896
+						"'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5897
+						'event_espresso'
5898
+					),
5899
+					$base_class_obj_or_id,
5900
+					$this->_get_class_name(),
5901
+					print_r($base_class_obj_or_id, true)
5902
+				)
5903
+			);
5904
+		}
5905
+		return $id;
5906
+	}
5907
+
5908
+
5909
+	/**
5910
+	 * Sets whether the values passed to the model (eg, values in WHERE, values in INSERT, UPDATE, etc)
5911
+	 * have already been ran through the appropriate model field's prepare_for_use_in_db method. IE, they have
5912
+	 * been sanitized and converted into the appropriate domain.
5913
+	 * Usually the only place you'll want to change the default (which is to assume values have NOT been sanitized by
5914
+	 * the model object/model field) is when making a method call from WITHIN a model object, which has direct access
5915
+	 * to its sanitized values. Note: after changing this setting, you should set it back to its previous value (using
5916
+	 * get_assumption_concerning_values_already_prepared_by_model_object()) eg.
5917
+	 * $EVT = EEM_Event::instance(); $old_setting =
5918
+	 * $EVT->get_assumption_concerning_values_already_prepared_by_model_object();
5919
+	 * $EVT->assume_values_already_prepared_by_model_object(true);
5920
+	 * $EVT->update(array('foo'=>'bar'),array(array('foo'=>'monkey')));
5921
+	 * $EVT->assume_values_already_prepared_by_model_object($old_setting);
5922
+	 *
5923
+	 * @param int $values_already_prepared like one of the constants on EEM_Base
5924
+	 * @return void
5925
+	 */
5926
+	public function assume_values_already_prepared_by_model_object(
5927
+		$values_already_prepared = self::not_prepared_by_model_object
5928
+	) {
5929
+		$this->_values_already_prepared_by_model_object = $values_already_prepared;
5930
+	}
5931
+
5932
+
5933
+	/**
5934
+	 * Read comments for assume_values_already_prepared_by_model_object()
5935
+	 *
5936
+	 * @return int
5937
+	 */
5938
+	public function get_assumption_concerning_values_already_prepared_by_model_object()
5939
+	{
5940
+		return $this->_values_already_prepared_by_model_object;
5941
+	}
5942
+
5943
+
5944
+	/**
5945
+	 * Gets all the indexes on this model
5946
+	 *
5947
+	 * @return EE_Index[]
5948
+	 */
5949
+	public function indexes()
5950
+	{
5951
+		return $this->_indexes;
5952
+	}
5953
+
5954
+
5955
+	/**
5956
+	 * Gets all the Unique Indexes on this model
5957
+	 *
5958
+	 * @return EE_Unique_Index[]
5959
+	 */
5960
+	public function unique_indexes()
5961
+	{
5962
+		$unique_indexes = [];
5963
+		foreach ($this->_indexes as $name => $index) {
5964
+			if ($index instanceof EE_Unique_Index) {
5965
+				$unique_indexes [ $name ] = $index;
5966
+			}
5967
+		}
5968
+		return $unique_indexes;
5969
+	}
5970
+
5971
+
5972
+	/**
5973
+	 * Gets all the fields which, when combined, make the primary key.
5974
+	 * This is usually just an array with 1 element (the primary key), but in cases
5975
+	 * where there is no primary key, it's a combination of fields as defined
5976
+	 * on a primary index
5977
+	 *
5978
+	 * @return EE_Model_Field_Base[] indexed by the field's name
5979
+	 * @throws EE_Error
5980
+	 */
5981
+	public function get_combined_primary_key_fields()
5982
+	{
5983
+		foreach ($this->indexes() as $index) {
5984
+			if ($index instanceof EE_Primary_Key_Index) {
5985
+				return $index->fields();
5986
+			}
5987
+		}
5988
+		return [$this->primary_key_name() => $this->get_primary_key_field()];
5989
+	}
5990
+
5991
+
5992
+	/**
5993
+	 * Used to build a primary key string (when the model has no primary key),
5994
+	 * which can be used a unique string to identify this model object.
5995
+	 *
5996
+	 * @param array $fields_n_values keys are field names, values are their values.
5997
+	 *                               Note: if you have results from `EEM_Base::get_all_wpdb_results()`, you need to
5998
+	 *                               run it through `EEM_Base::deduce_fields_n_values_from_cols_n_values()`
5999
+	 *                               before passing it to this function (that will convert it from columns-n-values
6000
+	 *                               to field-names-n-values).
6001
+	 * @return string
6002
+	 * @throws EE_Error
6003
+	 */
6004
+	public function get_index_primary_key_string($fields_n_values)
6005
+	{
6006
+		$cols_n_values_for_primary_key_index = array_intersect_key(
6007
+			$fields_n_values,
6008
+			$this->get_combined_primary_key_fields()
6009
+		);
6010
+		return http_build_query($cols_n_values_for_primary_key_index);
6011
+	}
6012
+
6013
+
6014
+	/**
6015
+	 * Gets the field values from the primary key string
6016
+	 *
6017
+	 * @param string $index_primary_key_string
6018
+	 * @return null|array
6019
+	 * @throws EE_Error
6020
+	 * @see EEM_Base::get_combined_primary_key_fields() and EEM_Base::get_index_primary_key_string()
6021
+	 */
6022
+	public function parse_index_primary_key_string($index_primary_key_string)
6023
+	{
6024
+		$key_fields = $this->get_combined_primary_key_fields();
6025
+		// check all of them are in the $id
6026
+		$key_vals_in_combined_pk = [];
6027
+		parse_str($index_primary_key_string, $key_vals_in_combined_pk);
6028
+		foreach ($key_fields as $key_field_name => $field_obj) {
6029
+			if (! isset($key_vals_in_combined_pk[ $key_field_name ])) {
6030
+				return null;
6031
+			}
6032
+		}
6033
+		return $key_vals_in_combined_pk;
6034
+	}
6035
+
6036
+
6037
+	/**
6038
+	 * verifies that an array of key-value pairs for model fields has a key
6039
+	 * for each field comprising the primary key index
6040
+	 *
6041
+	 * @param array $key_vals
6042
+	 * @return boolean
6043
+	 * @throws EE_Error
6044
+	 */
6045
+	public function has_all_combined_primary_key_fields($key_vals)
6046
+	{
6047
+		$keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
6048
+		foreach ($keys_it_should_have as $key) {
6049
+			if (! isset($key_vals[ $key ])) {
6050
+				return false;
6051
+			}
6052
+		}
6053
+		return true;
6054
+	}
6055
+
6056
+
6057
+	/**
6058
+	 * Finds all model objects in the DB that appear to be a copy of $model_object_or_attributes_array.
6059
+	 * We consider something to be a copy if all the attributes match (except the ID, of course).
6060
+	 *
6061
+	 * @param array|EE_Base_Class $model_object_or_attributes_array If its an array, it's field-value pairs
6062
+	 * @param array               $query_params                     @see
6063
+	 *                                                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
6064
+	 * @throws EE_Error
6065
+	 * @throws ReflectionException
6066
+	 * @return EE_Base_Class[] Array keys are object IDs (if there is a primary key on the model. if not, numerically
6067
+	 *                                                              indexed)
6068
+	 */
6069
+	public function get_all_copies($model_object_or_attributes_array, $query_params = [])
6070
+	{
6071
+		if ($model_object_or_attributes_array instanceof EE_Base_Class) {
6072
+			$attributes_array = $model_object_or_attributes_array->model_field_array();
6073
+		} elseif (is_array($model_object_or_attributes_array)) {
6074
+			$attributes_array = $model_object_or_attributes_array;
6075
+		} else {
6076
+			throw new EE_Error(
6077
+				sprintf(
6078
+					esc_html__(
6079
+						"get_all_copies should be provided with either a model object or an array of field-value-pairs, but was given %s",
6080
+						"event_espresso"
6081
+					),
6082
+					$model_object_or_attributes_array
6083
+				)
6084
+			);
6085
+		}
6086
+		// even copies obviously won't have the same ID, so remove the primary key
6087
+		// from the WHERE conditions for finding copies (if there is a primary key, of course)
6088
+		if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
6089
+			unset($attributes_array[ $this->primary_key_name() ]);
6090
+		}
6091
+		if (isset($query_params[0])) {
6092
+			$query_params[0] = array_merge($attributes_array, $query_params);
6093
+		} else {
6094
+			$query_params[0] = $attributes_array;
6095
+		}
6096
+		return $this->get_all($query_params);
6097
+	}
6098
+
6099
+
6100
+	/**
6101
+	 * Gets the first copy we find. See get_all_copies for more details
6102
+	 *
6103
+	 * @param mixed EE_Base_Class | array        $model_object_or_attributes_array
6104
+	 * @param array $query_params
6105
+	 * @return EE_Base_Class
6106
+	 * @throws EE_Error
6107
+	 * @throws ReflectionException
6108
+	 */
6109
+	public function get_one_copy($model_object_or_attributes_array, $query_params = [])
6110
+	{
6111
+		if (! is_array($query_params)) {
6112
+			EE_Error::doing_it_wrong(
6113
+				'EEM_Base::get_one_copy',
6114
+				sprintf(
6115
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
6116
+					gettype($query_params)
6117
+				),
6118
+				'4.6.0'
6119
+			);
6120
+			$query_params = [];
6121
+		}
6122
+		$query_params['limit'] = 1;
6123
+		$copies                = $this->get_all_copies($model_object_or_attributes_array, $query_params);
6124
+		if (is_array($copies)) {
6125
+			return array_shift($copies);
6126
+		}
6127
+		return null;
6128
+	}
6129
+
6130
+
6131
+	/**
6132
+	 * Updates the item with the specified id. Ignores default query parameters because
6133
+	 * we have specified the ID, and its assumed we KNOW what we're doing
6134
+	 *
6135
+	 * @param array      $fields_n_values keys are field names, values are their new values
6136
+	 * @param int|string $id              the value of the primary key to update
6137
+	 * @return int number of rows updated
6138
+	 * @throws EE_Error
6139
+	 * @throws ReflectionException
6140
+	 */
6141
+	public function update_by_ID($fields_n_values, $id)
6142
+	{
6143
+		$query_params = [
6144
+			0                          => [$this->get_primary_key_field()->get_name() => $id],
6145
+			'default_where_conditions' => EE_Default_Where_Conditions::OTHER_MODELS_ONLY,
6146
+		];
6147
+		return $this->update($fields_n_values, $query_params);
6148
+	}
6149
+
6150
+
6151
+	/**
6152
+	 * Changes an operator which was supplied to the models into one usable in SQL
6153
+	 *
6154
+	 * @param string $operator_supplied
6155
+	 * @return string an operator which can be used in SQL
6156
+	 * @throws EE_Error
6157
+	 */
6158
+	private function _prepare_operator_for_sql($operator_supplied)
6159
+	{
6160
+		$sql_operator = $this->_valid_operators[ $operator_supplied ] ?? null;
6161
+		if ($sql_operator) {
6162
+			return $sql_operator;
6163
+		}
6164
+		throw new EE_Error(
6165
+			sprintf(
6166
+				esc_html__(
6167
+					"The operator '%s' is not in the list of valid operators: %s",
6168
+					"event_espresso"
6169
+				),
6170
+				$operator_supplied,
6171
+				implode(",", array_keys($this->_valid_operators))
6172
+			)
6173
+		);
6174
+	}
6175
+
6176
+
6177
+	/**
6178
+	 * Gets the valid operators
6179
+	 *
6180
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6181
+	 */
6182
+	public function valid_operators()
6183
+	{
6184
+		return $this->_valid_operators;
6185
+	}
6186
+
6187
+
6188
+	/**
6189
+	 * Gets the between-style operators (take 2 arguments).
6190
+	 *
6191
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6192
+	 */
6193
+	public function valid_between_style_operators()
6194
+	{
6195
+		return array_intersect(
6196
+			$this->valid_operators(),
6197
+			$this->_between_style_operators
6198
+		);
6199
+	}
6200
+
6201
+
6202
+	/**
6203
+	 * Gets the "like"-style operators (take a single argument, but it may contain wildcards)
6204
+	 *
6205
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6206
+	 */
6207
+	public function valid_like_style_operators()
6208
+	{
6209
+		return array_intersect(
6210
+			$this->valid_operators(),
6211
+			$this->_like_style_operators
6212
+		);
6213
+	}
6214
+
6215
+
6216
+	/**
6217
+	 * Gets the "in"-style operators
6218
+	 *
6219
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6220
+	 */
6221
+	public function valid_in_style_operators()
6222
+	{
6223
+		return array_intersect(
6224
+			$this->valid_operators(),
6225
+			$this->_in_style_operators
6226
+		);
6227
+	}
6228
+
6229
+
6230
+	/**
6231
+	 * Gets the "null"-style operators (accept no arguments)
6232
+	 *
6233
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6234
+	 */
6235
+	public function valid_null_style_operators()
6236
+	{
6237
+		return array_intersect(
6238
+			$this->valid_operators(),
6239
+			$this->_null_style_operators
6240
+		);
6241
+	}
6242
+
6243
+
6244
+	/**
6245
+	 * Gets an array where keys are the primary keys and values are their 'names'
6246
+	 * (as determined by the model object's name() function, which is often overridden)
6247
+	 *
6248
+	 * @param array $query_params like get_all's
6249
+	 * @return string[]
6250
+	 * @throws EE_Error
6251
+	 * @throws ReflectionException
6252
+	 */
6253
+	public function get_all_names($query_params = [])
6254
+	{
6255
+		$objs  = $this->get_all($query_params);
6256
+		$names = [];
6257
+		foreach ($objs as $obj) {
6258
+			$names[ $obj->ID() ] = $obj->name();
6259
+		}
6260
+		return $names;
6261
+	}
6262
+
6263
+
6264
+	/**
6265
+	 * Gets an array of primary keys from the model objects. If you acquired the model objects
6266
+	 * using EEM_Base::get_all() you don't need to call this (and probably shouldn't because
6267
+	 * this is duplicated effort and reduces efficiency) you would be better to use
6268
+	 * array_keys() on $model_objects.
6269
+	 *
6270
+	 * @param \EE_Base_Class[] $model_objects
6271
+	 * @param boolean          $filter_out_empty_ids if a model object has an ID of '' or 0, don't bother including it
6272
+	 *                                               in the returned array
6273
+	 * @return array
6274
+	 * @throws EE_Error
6275
+	 * @throws ReflectionException
6276
+	 */
6277
+	public function get_IDs($model_objects, $filter_out_empty_ids = false)
6278
+	{
6279
+		if (! $this->has_primary_key_field()) {
6280
+			if (defined('WP_DEBUG') && WP_DEBUG) {
6281
+				EE_Error::add_error(
6282
+					esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
6283
+					__FILE__,
6284
+					__FUNCTION__,
6285
+					__LINE__
6286
+				);
6287
+			}
6288
+		}
6289
+		$IDs = [];
6290
+		foreach ($model_objects as $model_object) {
6291
+			$id = $model_object->ID();
6292
+			if (! $id) {
6293
+				if ($filter_out_empty_ids) {
6294
+					continue;
6295
+				}
6296
+				if (defined('WP_DEBUG') && WP_DEBUG) {
6297
+					EE_Error::add_error(
6298
+						esc_html__(
6299
+							'Called %1$s on a model object that has no ID and so probably hasn\'t been saved to the database',
6300
+							'event_espresso'
6301
+						),
6302
+						__FILE__,
6303
+						__FUNCTION__,
6304
+						__LINE__
6305
+					);
6306
+				}
6307
+			}
6308
+			$IDs[] = $id;
6309
+		}
6310
+		return $IDs;
6311
+	}
6312
+
6313
+
6314
+	/**
6315
+	 * Returns the string used in capabilities relating to this model. If there
6316
+	 * are no capabilities that relate to this model returns false
6317
+	 *
6318
+	 * @return string|false
6319
+	 */
6320
+	public function cap_slug()
6321
+	{
6322
+		return apply_filters('FHEE__EEM_Base__cap_slug', $this->_caps_slug, $this);
6323
+	}
6324
+
6325
+
6326
+	/**
6327
+	 * Returns the capability-restrictions array (@param string $context
6328
+	 *
6329
+	 * @return EE_Default_Where_Conditions[] indexed by associated capability
6330
+	 * @throws EE_Error
6331
+	 * @see EEM_Base::_cap_restrictions).
6332
+	 *      If $context is provided (which should be set to one of EEM_Base::valid_cap_contexts())
6333
+	 *      only returns the cap restrictions array in that context (ie, the array
6334
+	 *      at that key)
6335
+	 */
6336
+	public function cap_restrictions($context = EEM_Base::caps_read)
6337
+	{
6338
+		EEM_Base::verify_is_valid_cap_context($context);
6339
+		// check if we ought to run the restriction generator first
6340
+		if (
6341
+			isset($this->_cap_restriction_generators[ $context ])
6342
+			&& $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6343
+			&& ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6344
+		) {
6345
+			$this->_cap_restrictions[ $context ] = array_merge(
6346
+				$this->_cap_restrictions[ $context ],
6347
+				$this->_cap_restriction_generators[ $context ]->generate_restrictions()
6348
+			);
6349
+		}
6350
+		// and make sure we've finalized the construction of each restriction
6351
+		foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6352
+			if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6353
+				$where_conditions_obj->_finalize_construct($this);
6354
+			}
6355
+		}
6356
+		return $this->_cap_restrictions[ $context ];
6357
+	}
6358
+
6359
+
6360
+	/**
6361
+	 * Indicating whether this model thinks its a wp core model
6362
+	 *
6363
+	 * @return boolean
6364
+	 */
6365
+	public function is_wp_core_model()
6366
+	{
6367
+		return $this->_wp_core_model;
6368
+	}
6369
+
6370
+
6371
+	/**
6372
+	 * Gets all the caps that are missing which impose a restriction on
6373
+	 * queries made in this context
6374
+	 *
6375
+	 * @param string $context one of EEM_Base::caps_ constants
6376
+	 * @return EE_Default_Where_Conditions[] indexed by capability name
6377
+	 * @throws EE_Error
6378
+	 */
6379
+	public function caps_missing($context = EEM_Base::caps_read)
6380
+	{
6381
+		$missing_caps     = [];
6382
+		$cap_restrictions = $this->cap_restrictions($context);
6383
+		foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6384
+			if (
6385
+				! EE_Capabilities::instance()
6386
+								 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6387
+			) {
6388
+				$missing_caps[ $cap ] = $restriction_if_no_cap;
6389
+			}
6390
+		}
6391
+		return $missing_caps;
6392
+	}
6393
+
6394
+
6395
+	/**
6396
+	 * Gets the mapping from capability contexts to action strings used in capability names
6397
+	 *
6398
+	 * @return array keys are one of EEM_Base::valid_cap_contexts(), and values are usually
6399
+	 * one of 'read', 'edit', or 'delete'
6400
+	 */
6401
+	public function cap_contexts_to_cap_action_map()
6402
+	{
6403
+		return apply_filters(
6404
+			'FHEE__EEM_Base__cap_contexts_to_cap_action_map',
6405
+			$this->_cap_contexts_to_cap_action_map,
6406
+			$this
6407
+		);
6408
+	}
6409
+
6410
+
6411
+	/**
6412
+	 * Gets the action string for the specified capability context
6413
+	 *
6414
+	 * @param string $context
6415
+	 * @return string one of EEM_Base::cap_contexts_to_cap_action_map() values
6416
+	 * @throws EE_Error
6417
+	 */
6418
+	public function cap_action_for_context($context)
6419
+	{
6420
+		$mapping = $this->cap_contexts_to_cap_action_map();
6421
+		if (isset($mapping[ $context ])) {
6422
+			return $mapping[ $context ];
6423
+		}
6424
+		if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6425
+			return $action;
6426
+		}
6427
+		throw new EE_Error(
6428
+			sprintf(
6429
+				esc_html__(
6430
+					'Cannot find capability restrictions for context "%1$s", allowed values are:%2$s',
6431
+					'event_espresso'
6432
+				),
6433
+				$context,
6434
+				implode(',', array_keys($this->cap_contexts_to_cap_action_map()))
6435
+			)
6436
+		);
6437
+	}
6438
+
6439
+
6440
+	/**
6441
+	 * Returns all the capability contexts which are valid when querying models
6442
+	 *
6443
+	 * @return array
6444
+	 */
6445
+	public static function valid_cap_contexts(): array
6446
+	{
6447
+		return (array) apply_filters(
6448
+			'FHEE__EEM_Base__valid_cap_contexts',
6449
+			[
6450
+				self::caps_read,
6451
+				self::caps_read_admin,
6452
+				self::caps_edit,
6453
+				self::caps_delete,
6454
+			]
6455
+		);
6456
+	}
6457
+
6458
+
6459
+	/**
6460
+	 * Returns all valid options for 'default_where_conditions'
6461
+	 *
6462
+	 * @return array
6463
+	 */
6464
+	public static function valid_default_where_conditions(): array
6465
+	{
6466
+		return [
6467
+			EE_Default_Where_Conditions::ALL,
6468
+			EE_Default_Where_Conditions::THIS_MODEL_ONLY,
6469
+			EE_Default_Where_Conditions::OTHER_MODELS_ONLY,
6470
+			EE_Default_Where_Conditions::MINIMUM_ALL,
6471
+			EE_Default_Where_Conditions::MINIMUM_OTHERS,
6472
+			EE_Default_Where_Conditions::NONE,
6473
+		];
6474
+	}
6475
+
6476
+	// public static function default_where_conditions_full
6477
+
6478
+
6479
+	/**
6480
+	 * Verifies $context is one of EEM_Base::valid_cap_contexts(), if not it throws an exception
6481
+	 *
6482
+	 * @param string $context
6483
+	 * @return bool
6484
+	 * @throws EE_Error
6485
+	 */
6486
+	public static function verify_is_valid_cap_context($context): bool
6487
+	{
6488
+		$valid_cap_contexts = EEM_Base::valid_cap_contexts();
6489
+		if (in_array($context, $valid_cap_contexts)) {
6490
+			return true;
6491
+		}
6492
+		throw new EE_Error(
6493
+			sprintf(
6494
+				esc_html__(
6495
+					'Context "%1$s" passed into model "%2$s" is not a valid context. They are: %3$s',
6496
+					'event_espresso'
6497
+				),
6498
+				$context,
6499
+				'EEM_Base',
6500
+				implode(',', $valid_cap_contexts)
6501
+			)
6502
+		);
6503
+	}
6504
+
6505
+
6506
+	/**
6507
+	 * Clears all the models field caches. This is only useful when a sub-class
6508
+	 * might have added a field or something and these caches might be invalidated
6509
+	 */
6510
+	protected function _invalidate_field_caches()
6511
+	{
6512
+		$this->_cache_foreign_key_to_fields = [];
6513
+		$this->_cached_fields               = null;
6514
+		$this->_cached_fields_non_db_only   = null;
6515
+	}
6516
+
6517
+
6518
+	/**
6519
+	 * Gets the list of all the where query param keys that relate to logic instead of field names
6520
+	 * (eg "and", "or", "not").
6521
+	 *
6522
+	 * @return array
6523
+	 */
6524
+	public function logic_query_param_keys(): array
6525
+	{
6526
+		return $this->_logic_query_param_keys;
6527
+	}
6528
+
6529
+
6530
+	/**
6531
+	 * Determines whether the where query param array key is for a logic query param.
6532
+	 * Eg 'OR', 'not*', and 'and*because-i-say-so' should all return true, whereas
6533
+	 * 'ATT_fname', 'EVT_name*not-you-or-me', and 'ORG_name' should return false
6534
+	 *
6535
+	 * @param $query_param_key
6536
+	 * @return bool
6537
+	 */
6538
+	public function is_logic_query_param_key($query_param_key): bool
6539
+	{
6540
+		foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6541
+			if (
6542
+				$query_param_key === $logic_query_param_key
6543
+				|| strpos($query_param_key, $logic_query_param_key . '*') === 0
6544
+			) {
6545
+				return true;
6546
+			}
6547
+		}
6548
+		return false;
6549
+	}
6550
+
6551
+
6552
+	/**
6553
+	 * Returns true if this model has a password field on it (regardless of whether that password field has any content)
6554
+	 *
6555
+	 * @return boolean
6556
+	 * @since 4.9.74.p
6557
+	 */
6558
+	public function hasPassword(): bool
6559
+	{
6560
+		// if we don't yet know if there's a password field, find out and remember it for next time.
6561
+		if ($this->has_password_field === null) {
6562
+			$password_field           = $this->getPasswordField();
6563
+			$this->has_password_field = $password_field instanceof EE_Password_Field;
6564
+		}
6565
+		return $this->has_password_field;
6566
+	}
6567
+
6568
+
6569
+	/**
6570
+	 * Returns the password field on this model, if there is one
6571
+	 *
6572
+	 * @return EE_Password_Field|null
6573
+	 * @since 4.9.74.p
6574
+	 */
6575
+	public function getPasswordField()
6576
+	{
6577
+		// if we definetely already know there is a password field or not (because has_password_field is true or false)
6578
+		// there's no need to search for it. If we don't know yet, then find out
6579
+		if ($this->has_password_field === null && $this->password_field === null) {
6580
+			$this->password_field = $this->get_a_field_of_type('EE_Password_Field');
6581
+		}
6582
+		// don't bother setting has_password_field because that's hasPassword()'s job.
6583
+		return $this->password_field;
6584
+	}
6585
+
6586
+
6587
+	/**
6588
+	 * Returns the list of field (as EE_Model_Field_Bases) that are protected by the password
6589
+	 *
6590
+	 * @return EE_Model_Field_Base[]
6591
+	 * @throws EE_Error
6592
+	 * @since 4.9.74.p
6593
+	 */
6594
+	public function getPasswordProtectedFields()
6595
+	{
6596
+		$password_field = $this->getPasswordField();
6597
+		$fields         = [];
6598
+		if ($password_field instanceof EE_Password_Field) {
6599
+			$field_names = $password_field->protectedFields();
6600
+			foreach ($field_names as $field_name) {
6601
+				$fields[ $field_name ] = $this->field_settings_for($field_name);
6602
+			}
6603
+		}
6604
+		return $fields;
6605
+	}
6606
+
6607
+
6608
+	/**
6609
+	 * Checks if the current user can perform the requested action on this model
6610
+	 *
6611
+	 * @param string              $cap_to_check one of the array keys from _cap_contexts_to_cap_action_map
6612
+	 * @param EE_Base_Class|array $model_obj_or_fields_n_values
6613
+	 * @return bool
6614
+	 * @throws EE_Error
6615
+	 * @throws InvalidArgumentException
6616
+	 * @throws InvalidDataTypeException
6617
+	 * @throws InvalidInterfaceException
6618
+	 * @throws ReflectionException
6619
+	 * @throws UnexpectedEntityException
6620
+	 * @since 4.9.74.p
6621
+	 */
6622
+	public function currentUserCan($cap_to_check, $model_obj_or_fields_n_values)
6623
+	{
6624
+		if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6625
+			$model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6626
+		}
6627
+		if (! is_array($model_obj_or_fields_n_values)) {
6628
+			throw new UnexpectedEntityException(
6629
+				$model_obj_or_fields_n_values,
6630
+				'EE_Base_Class',
6631
+				sprintf(
6632
+					esc_html__(
6633
+						'%1$s must be passed an `EE_Base_Class or an array of fields names with their values. You passed in something different.',
6634
+						'event_espresso'
6635
+					),
6636
+					__FUNCTION__
6637
+				)
6638
+			);
6639
+		}
6640
+		return $this->exists(
6641
+			$this->alter_query_params_to_restrict_by_ID(
6642
+				$this->get_index_primary_key_string($model_obj_or_fields_n_values),
6643
+				[
6644
+					'default_where_conditions' => 'none',
6645
+					'caps'                     => $cap_to_check,
6646
+				]
6647
+			)
6648
+		);
6649
+	}
6650
+
6651
+
6652
+	/**
6653
+	 * Returns the query param where conditions key to the password affecting this model.
6654
+	 * Eg on EEM_Event this would just be "password", on EEM_Datetime this would be "Event.password", etc.
6655
+	 *
6656
+	 * @return null|string
6657
+	 * @throws EE_Error
6658
+	 * @throws InvalidArgumentException
6659
+	 * @throws InvalidDataTypeException
6660
+	 * @throws InvalidInterfaceException
6661
+	 * @throws ModelConfigurationException
6662
+	 * @throws ReflectionException
6663
+	 * @since 4.9.74.p
6664
+	 */
6665
+	public function modelChainAndPassword()
6666
+	{
6667
+		if ($this->model_chain_to_password === null) {
6668
+			throw new ModelConfigurationException(
6669
+				$this,
6670
+				esc_html_x(
6671
+				// @codingStandardsIgnoreStart
6672
+					'Cannot exclude protected data because the model has not specified which model has the password.',
6673
+					// @codingStandardsIgnoreEnd
6674
+					'1: model name',
6675
+					'event_espresso'
6676
+				)
6677
+			);
6678
+		}
6679
+		if ($this->model_chain_to_password === '') {
6680
+			$model_with_password = $this;
6681
+		} else {
6682
+			if ($pos_of_period = strrpos($this->model_chain_to_password, '.')) {
6683
+				$last_model_in_chain = substr($this->model_chain_to_password, $pos_of_period + 1);
6684
+			} else {
6685
+				$last_model_in_chain = $this->model_chain_to_password;
6686
+			}
6687
+			$model_with_password = EE_Registry::instance()->load_model($last_model_in_chain);
6688
+		}
6689
+
6690
+		$password_field = $model_with_password->getPasswordField();
6691
+		if ($password_field instanceof EE_Password_Field) {
6692
+			$password_field_name = $password_field->get_name();
6693
+		} else {
6694
+			throw new ModelConfigurationException(
6695
+				$this,
6696
+				sprintf(
6697
+					esc_html_x(
6698
+						'This model claims related model "%1$s" should have a password field on it, but none was found. The model relation chain is "%2$s"',
6699
+						'1: model name, 2: special string',
6700
+						'event_espresso'
6701
+					),
6702
+					$model_with_password->get_this_model_name(),
6703
+					$this->model_chain_to_password
6704
+				)
6705
+			);
6706
+		}
6707
+		return (
6708
+			   $this->model_chain_to_password
6709
+				   ? $this->model_chain_to_password . '.'
6710
+				   : ''
6711
+			   ) . $password_field_name;
6712
+	}
6713
+
6714
+
6715
+	/**
6716
+	 * Returns true if there is a password on a related model which restricts access to some of this model's rows,
6717
+	 * or if this model itself has a password affecting access to some of its other fields.
6718
+	 *
6719
+	 * @return boolean
6720
+	 * @since 4.9.74.p
6721
+	 */
6722
+	public function restrictedByRelatedModelPassword(): bool
6723
+	{
6724
+		return $this->model_chain_to_password !== null;
6725
+	}
6726 6726
 }
Please login to merge, or discard this patch.
Spacing   +230 added lines, -230 removed lines patch added patch discarded remove patch
@@ -551,7 +551,7 @@  discard block
 block discarded – undo
551 551
     {
552 552
         $this->class_name = get_class($this);
553 553
         // check that the model has not been loaded too soon
554
-        if (! did_action('AHEE__EE_System__load_espresso_addons')) {
554
+        if ( ! did_action('AHEE__EE_System__load_espresso_addons')) {
555 555
             throw new EE_Error(
556 556
                 sprintf(
557 557
                     esc_html__(
@@ -586,7 +586,7 @@  discard block
 block discarded – undo
586 586
         $this->_fields = (array) apply_filters("FHEE__{$this->class_name}__construct__fields", $this->_fields);
587 587
         $this->_invalidate_field_caches();
588 588
         foreach ($this->_fields as $table_alias => $fields_for_table) {
589
-            if (! array_key_exists($table_alias, $this->_tables)) {
589
+            if ( ! array_key_exists($table_alias, $this->_tables)) {
590 590
                 throw new EE_Error(
591 591
                     sprintf(
592 592
                         esc_html__(
@@ -635,12 +635,12 @@  discard block
 block discarded – undo
635 635
         }
636 636
         $this->set_timezone($timezone);
637 637
         // finalize default where condition strategy, or set default
638
-        if (! $this->_default_where_conditions_strategy) {
638
+        if ( ! $this->_default_where_conditions_strategy) {
639 639
             // nothing was set during child constructor, so set default
640 640
             $this->_default_where_conditions_strategy = new EE_Default_Where_Conditions();
641 641
         }
642 642
         $this->_default_where_conditions_strategy->_finalize_construct($this);
643
-        if (! $this->_minimum_where_conditions_strategy) {
643
+        if ( ! $this->_minimum_where_conditions_strategy) {
644 644
             // nothing was set during child constructor, so set default
645 645
             $this->_minimum_where_conditions_strategy = new EE_Default_Where_Conditions();
646 646
         }
@@ -651,10 +651,10 @@  discard block
 block discarded – undo
651 651
             $this->_caps_slug = EEH_Inflector::pluralize_and_lower($this->get_this_model_name());
652 652
         }
653 653
         // initialize the standard cap restriction generators if none were specified by the child constructor
654
-        if (! empty($this->_cap_restriction_generators)) {
654
+        if ( ! empty($this->_cap_restriction_generators)) {
655 655
             foreach ($this->cap_contexts_to_cap_action_map() as $cap_context => $action) {
656
-                if (! isset($this->_cap_restriction_generators[ $cap_context ])) {
657
-                    $this->_cap_restriction_generators[ $cap_context ] = apply_filters(
656
+                if ( ! isset($this->_cap_restriction_generators[$cap_context])) {
657
+                    $this->_cap_restriction_generators[$cap_context] = apply_filters(
658 658
                         'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
659 659
                         new EE_Restriction_Generator_Protected(),
660 660
                         $cap_context,
@@ -664,12 +664,12 @@  discard block
 block discarded – undo
664 664
             }
665 665
         }
666 666
         // if there are cap restriction generators, use them to make the default cap restrictions
667
-        if (! empty($this->_cap_restriction_generators)) {
667
+        if ( ! empty($this->_cap_restriction_generators)) {
668 668
             foreach ($this->_cap_restriction_generators as $context => $generator_object) {
669
-                if (! $generator_object) {
669
+                if ( ! $generator_object) {
670 670
                     continue;
671 671
                 }
672
-                if (! $generator_object instanceof EE_Restriction_Generator_Base) {
672
+                if ( ! $generator_object instanceof EE_Restriction_Generator_Base) {
673 673
                     throw new EE_Error(
674 674
                         sprintf(
675 675
                             esc_html__(
@@ -682,7 +682,7 @@  discard block
 block discarded – undo
682 682
                     );
683 683
                 }
684 684
                 $action = $this->cap_action_for_context($context);
685
-                if (! $generator_object->construction_finalized()) {
685
+                if ( ! $generator_object->construction_finalized()) {
686 686
                     $generator_object->_construct_finalize($this, $action);
687 687
                 }
688 688
             }
@@ -699,7 +699,7 @@  discard block
 block discarded – undo
699 699
      */
700 700
     protected static function getLoader(): LoaderInterface
701 701
     {
702
-        if (! EEM_Base::$loader instanceof LoaderInterface) {
702
+        if ( ! EEM_Base::$loader instanceof LoaderInterface) {
703 703
             EEM_Base::$loader = LoaderFactory::getLoader();
704 704
         }
705 705
         return EEM_Base::$loader;
@@ -712,7 +712,7 @@  discard block
 block discarded – undo
712 712
      */
713 713
     private static function getMirror(): Mirror
714 714
     {
715
-        if (! EEM_Base::$mirror instanceof Mirror) {
715
+        if ( ! EEM_Base::$mirror instanceof Mirror) {
716 716
             EEM_Base::$mirror = EEM_Base::getLoader()->getShared(Mirror::class);
717 717
         }
718 718
         return EEM_Base::$mirror;
@@ -768,7 +768,7 @@  discard block
 block discarded – undo
768 768
     public static function instance($timezone = '')
769 769
     {
770 770
         // check if instance of Espresso_model already exists
771
-        if (! static::$_instance instanceof static) {
771
+        if ( ! static::$_instance instanceof static) {
772 772
             /**
773 773
              * Set blog id for models to current blog. However we ONLY do this if $_model_query_blog_id is not already set.
774 774
              */
@@ -805,7 +805,7 @@  discard block
 block discarded – undo
805 805
      */
806 806
     public static function reset($timezone = '')
807 807
     {
808
-        if (! static::$_instance instanceof EEM_Base) {
808
+        if ( ! static::$_instance instanceof EEM_Base) {
809 809
             return null;
810 810
         }
811 811
         // Let's NOT swap out the current instance for a new one
@@ -816,7 +816,7 @@  discard block
 block discarded – undo
816 816
         foreach (EEM_Base::getMirror()->getDefaultProperties(static::class) as $property => $value) {
817 817
             // don't set instance to null like it was originally,
818 818
             // but it's static anyways, and we're ignoring static properties (for now at least)
819
-            if (! isset($static_properties[ $property ])) {
819
+            if ( ! isset($static_properties[$property])) {
820 820
                 static::$_instance->{$property} = $value;
821 821
             }
822 822
         }
@@ -863,7 +863,7 @@  discard block
 block discarded – undo
863 863
      */
864 864
     public function status_array($translated = false)
865 865
     {
866
-        if (! array_key_exists('Status', $this->_model_relations)) {
866
+        if ( ! array_key_exists('Status', $this->_model_relations)) {
867 867
             return [];
868 868
         }
869 869
         $model_name   = $this->get_this_model_name();
@@ -871,7 +871,7 @@  discard block
 block discarded – undo
871 871
         $stati        = EEM_Status::instance()->get_all([['STS_type' => $status_type]]);
872 872
         $status_array = [];
873 873
         foreach ($stati as $status) {
874
-            $status_array[ $status->ID() ] = $status->get('STS_code');
874
+            $status_array[$status->ID()] = $status->get('STS_code');
875 875
         }
876 876
         return $translated
877 877
             ? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
@@ -943,7 +943,7 @@  discard block
 block discarded – undo
943 943
     {
944 944
         $wp_user_field_name = $this->wp_user_field_name();
945 945
         if ($wp_user_field_name) {
946
-            $query_params[0][ $wp_user_field_name ] = get_current_user_id();
946
+            $query_params[0][$wp_user_field_name] = get_current_user_id();
947 947
         }
948 948
         return $query_params;
949 949
     }
@@ -962,17 +962,17 @@  discard block
 block discarded – undo
962 962
     public function wp_user_field_name()
963 963
     {
964 964
         try {
965
-            if (! empty($this->_model_chain_to_wp_user)) {
965
+            if ( ! empty($this->_model_chain_to_wp_user)) {
966 966
                 $models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
967 967
                 $last_model_name              = end($models_to_follow_to_wp_users);
968 968
                 $model_with_fk_to_wp_users    = EE_Registry::instance()->load_model($last_model_name);
969
-                $model_chain_to_wp_user       = $this->_model_chain_to_wp_user . '.';
969
+                $model_chain_to_wp_user       = $this->_model_chain_to_wp_user.'.';
970 970
             } else {
971 971
                 $model_with_fk_to_wp_users = $this;
972 972
                 $model_chain_to_wp_user    = '';
973 973
             }
974 974
             $wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
975
-            return $model_chain_to_wp_user . $wp_user_field->get_name();
975
+            return $model_chain_to_wp_user.$wp_user_field->get_name();
976 976
         } catch (EE_Error $e) {
977 977
             return false;
978 978
         }
@@ -1048,11 +1048,11 @@  discard block
 block discarded – undo
1048 1048
         if ($this->_custom_selections instanceof CustomSelects) {
1049 1049
             $custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1050 1050
             $select_expressions .= $select_expressions
1051
-                ? ', ' . $custom_expressions
1051
+                ? ', '.$custom_expressions
1052 1052
                 : $custom_expressions;
1053 1053
         }
1054 1054
 
1055
-        $SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1055
+        $SQL = "SELECT $select_expressions ".$this->_construct_2nd_half_of_select_query($model_query_info);
1056 1056
         return $this->_do_wpdb_query('get_results', [$SQL, $output]);
1057 1057
     }
1058 1058
 
@@ -1069,7 +1069,7 @@  discard block
 block discarded – undo
1069 1069
      */
1070 1070
     protected function getCustomSelection(array $query_params, $columns_to_select = null): ?CustomSelects
1071 1071
     {
1072
-        if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1072
+        if ( ! isset($query_params['extra_selects']) && $columns_to_select === null) {
1073 1073
             return null;
1074 1074
         }
1075 1075
         $selects = $query_params['extra_selects'] ?? $columns_to_select;
@@ -1120,7 +1120,7 @@  discard block
 block discarded – undo
1120 1120
         if (is_array($columns_to_select)) {
1121 1121
             $select_sql_array = [];
1122 1122
             foreach ($columns_to_select as $alias => $selection_and_datatype) {
1123
-                if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1123
+                if ( ! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1124 1124
                     throw new EE_Error(
1125 1125
                         sprintf(
1126 1126
                             esc_html__(
@@ -1132,7 +1132,7 @@  discard block
 block discarded – undo
1132 1132
                         )
1133 1133
                     );
1134 1134
                 }
1135
-                if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1135
+                if ( ! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1136 1136
                     throw new EE_Error(
1137 1137
                         sprintf(
1138 1138
                             esc_html__(
@@ -1194,7 +1194,7 @@  discard block
 block discarded – undo
1194 1194
                 ['default_where_conditions' => EE_Default_Where_Conditions::MINIMUM_ALL]
1195 1195
             )
1196 1196
         );
1197
-        $className    = $this->_get_class_name();
1197
+        $className = $this->_get_class_name();
1198 1198
         if ($model_object instanceof $className) {
1199 1199
             // make sure valid objects get added to the entity map
1200 1200
             // so that the next call to this method doesn't trigger another trip to the db
@@ -1217,12 +1217,12 @@  discard block
 block discarded – undo
1217 1217
      */
1218 1218
     public function alter_query_params_to_restrict_by_ID($id, $query_params = [])
1219 1219
     {
1220
-        if (! isset($query_params[0])) {
1220
+        if ( ! isset($query_params[0])) {
1221 1221
             $query_params[0] = [];
1222 1222
         }
1223 1223
         $conditions_from_id = $this->parse_index_primary_key_string($id);
1224 1224
         if ($conditions_from_id === null) {
1225
-            $query_params[0][ $this->primary_key_name() ] = $id;
1225
+            $query_params[0][$this->primary_key_name()] = $id;
1226 1226
         } else {
1227 1227
             // no primary key, so the $id must be from the get_index_primary_key_string()
1228 1228
             $query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
@@ -1242,7 +1242,7 @@  discard block
 block discarded – undo
1242 1242
      */
1243 1243
     public function get_one($query_params = [])
1244 1244
     {
1245
-        if (! is_array($query_params)) {
1245
+        if ( ! is_array($query_params)) {
1246 1246
             EE_Error::doing_it_wrong(
1247 1247
                 'EEM_Base::get_one',
1248 1248
                 sprintf(
@@ -1451,7 +1451,7 @@  discard block
 block discarded – undo
1451 1451
                 return [];
1452 1452
             }
1453 1453
         }
1454
-        if (! is_array($query_params)) {
1454
+        if ( ! is_array($query_params)) {
1455 1455
             EE_Error::doing_it_wrong(
1456 1456
                 'EEM_Base::_get_consecutive',
1457 1457
                 sprintf(
@@ -1463,7 +1463,7 @@  discard block
 block discarded – undo
1463 1463
             $query_params = [];
1464 1464
         }
1465 1465
         // let's add the where query param for consecutive look up.
1466
-        $query_params[0][ $field_to_order_by ] = [$operand, $current_field_value];
1466
+        $query_params[0][$field_to_order_by] = [$operand, $current_field_value];
1467 1467
         $query_params['limit']                 = $limit;
1468 1468
         // set direction
1469 1469
         $incoming_orderby         = isset($query_params['order_by'])
@@ -1489,7 +1489,7 @@  discard block
 block discarded – undo
1489 1489
      */
1490 1490
     public function set_timezone(?string $timezone = '')
1491 1491
     {
1492
-        if (! $timezone) {
1492
+        if ( ! $timezone) {
1493 1493
             return;
1494 1494
         }
1495 1495
         $this->_timezone = $timezone;
@@ -1546,7 +1546,7 @@  discard block
 block discarded – undo
1546 1546
     {
1547 1547
         $field_settings = $this->field_settings_for($field_name);
1548 1548
         // if not a valid EE_Datetime_Field then throw error
1549
-        if (! $field_settings instanceof EE_Datetime_Field) {
1549
+        if ( ! $field_settings instanceof EE_Datetime_Field) {
1550 1550
             throw new EE_Error(
1551 1551
                 sprintf(
1552 1552
                     esc_html__(
@@ -1633,7 +1633,7 @@  discard block
 block discarded – undo
1633 1633
         // just using this to ensure the timezone is set correctly internally
1634 1634
         $this->get_formats_for($field_name);
1635 1635
         // load EEH_DTT_Helper
1636
-        $timezone_string     = ! empty($timezone_string) ? $timezone_string : EEH_DTT_Helper::get_timezone();
1636
+        $timezone_string = ! empty($timezone_string) ? $timezone_string : EEH_DTT_Helper::get_timezone();
1637 1637
         $incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($timezone_string));
1638 1638
         EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1639 1639
         return DbSafeDateTime::createFromDateTime($incomingDateTime);
@@ -1703,7 +1703,7 @@  discard block
 block discarded – undo
1703 1703
      */
1704 1704
     public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1705 1705
     {
1706
-        if (! is_array($query_params)) {
1706
+        if ( ! is_array($query_params)) {
1707 1707
             EE_Error::doing_it_wrong(
1708 1708
                 'EEM_Base::update',
1709 1709
                 sprintf(
@@ -1751,7 +1751,7 @@  discard block
 block discarded – undo
1751 1751
             $wpdb_result = (array) $wpdb_result;
1752 1752
             // get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1753 1753
             if ($this->has_primary_key_field()) {
1754
-                $main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1754
+                $main_table_pk_value = $wpdb_result[$this->get_primary_key_field()->get_qualified_column()];
1755 1755
             } else {
1756 1756
                 // if there's no primary key, we basically can't support having a 2nd table on the model (we could but it would be lots of work)
1757 1757
                 $main_table_pk_value = null;
@@ -1767,7 +1767,7 @@  discard block
 block discarded – undo
1767 1767
                     // in this table, right? so insert a row in the current table, using any fields available
1768 1768
                     if (
1769 1769
                         ! (array_key_exists($this_table_pk_column, $wpdb_result)
1770
-                           && $wpdb_result[ $this_table_pk_column ])
1770
+                           && $wpdb_result[$this_table_pk_column])
1771 1771
                     ) {
1772 1772
                         $success = $this->_insert_into_specific_table(
1773 1773
                             $table_obj,
@@ -1775,7 +1775,7 @@  discard block
 block discarded – undo
1775 1775
                             $main_table_pk_value
1776 1776
                         );
1777 1777
                         // if we died here, report the error
1778
-                        if (! $success) {
1778
+                        if ( ! $success) {
1779 1779
                             return false;
1780 1780
                         }
1781 1781
                     }
@@ -1803,10 +1803,10 @@  discard block
 block discarded – undo
1803 1803
                 $model_objs_affected_ids     = [];
1804 1804
                 foreach ($models_affected_key_columns as $row) {
1805 1805
                     $combined_index_key                             = $this->get_index_primary_key_string($row);
1806
-                    $model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1806
+                    $model_objs_affected_ids[$combined_index_key] = $combined_index_key;
1807 1807
                 }
1808 1808
             }
1809
-            if (! $model_objs_affected_ids) {
1809
+            if ( ! $model_objs_affected_ids) {
1810 1810
                 // wait wait wait- if nothing was affected let's stop here
1811 1811
                 return 0;
1812 1812
             }
@@ -1835,8 +1835,8 @@  discard block
 block discarded – undo
1835 1835
         $rows_affected = $this->_do_wpdb_query(
1836 1836
             'query',
1837 1837
             [
1838
-                "UPDATE " . $model_query_info->get_full_join_sql()
1839
-                . " SET " . $this->_construct_update_sql($fields_n_values)
1838
+                "UPDATE ".$model_query_info->get_full_join_sql()
1839
+                . " SET ".$this->_construct_update_sql($fields_n_values)
1840 1840
                 . $model_query_info->get_where_sql(),
1841 1841
             ]
1842 1842
         );
@@ -1850,7 +1850,7 @@  discard block
 block discarded – undo
1850 1850
          * @param int      $rows_affected
1851 1851
          */
1852 1852
         do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1853
-        return $rows_affected;// how many supposedly got updated
1853
+        return $rows_affected; // how many supposedly got updated
1854 1854
     }
1855 1855
 
1856 1856
 
@@ -1883,7 +1883,7 @@  discard block
 block discarded – undo
1883 1883
         $model_query_info   = $this->_create_model_query_info_carrier($query_params);
1884 1884
         $select_expressions = $field->get_qualified_column();
1885 1885
         $SQL                =
1886
-            "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1886
+            "SELECT $select_expressions ".$this->_construct_2nd_half_of_select_query($model_query_info);
1887 1887
         return $this->_do_wpdb_query('get_col', [$SQL]);
1888 1888
     }
1889 1889
 
@@ -1902,7 +1902,7 @@  discard block
 block discarded – undo
1902 1902
     {
1903 1903
         $query_params['limit'] = 1;
1904 1904
         $col                   = $this->get_col($query_params, $field_to_select);
1905
-        if (! empty($col)) {
1905
+        if ( ! empty($col)) {
1906 1906
             return reset($col);
1907 1907
         }
1908 1908
         return null;
@@ -1933,7 +1933,7 @@  discard block
 block discarded – undo
1933 1933
             $value_sql       = $prepared_value === null
1934 1934
                 ? 'NULL'
1935 1935
                 : $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1936
-            $cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1936
+            $cols_n_values[] = $field_obj->get_qualified_column()."=".$value_sql;
1937 1937
         }
1938 1938
         return implode(",", $cols_n_values);
1939 1939
     }
@@ -2071,7 +2071,7 @@  discard block
 block discarded – undo
2071 2071
                                 . $model_query_info->get_full_join_sql()
2072 2072
                                 . " WHERE "
2073 2073
                                 . $deletion_where_query_part;
2074
-            $rows_deleted     = $this->_do_wpdb_query('query', [$SQL]);
2074
+            $rows_deleted = $this->_do_wpdb_query('query', [$SQL]);
2075 2075
         }
2076 2076
 
2077 2077
         // Next, make sure those items are removed from the entity map; if they could be put into it at all; and if
@@ -2079,12 +2079,12 @@  discard block
 block discarded – undo
2079 2079
         if (
2080 2080
             $this->has_primary_key_field()
2081 2081
             && $rows_deleted !== false
2082
-            && isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2082
+            && isset($columns_and_ids_for_deleting[$this->get_primary_key_field()->get_qualified_column()])
2083 2083
         ) {
2084
-            $ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2084
+            $ids_for_removal = $columns_and_ids_for_deleting[$this->get_primary_key_field()->get_qualified_column()];
2085 2085
             foreach ($ids_for_removal as $id) {
2086
-                if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2087
-                    unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2086
+                if (isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])) {
2087
+                    unset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id]);
2088 2088
                 }
2089 2089
             }
2090 2090
 
@@ -2124,7 +2124,7 @@  discard block
 block discarded – undo
2124 2124
          * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2125 2125
          */
2126 2126
         do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2127
-        return (int) $rows_deleted;// how many supposedly got deleted
2127
+        return (int) $rows_deleted; // how many supposedly got deleted
2128 2128
     }
2129 2129
 
2130 2130
 
@@ -2228,15 +2228,15 @@  discard block
 block discarded – undo
2228 2228
                 if (
2229 2229
                     $block_deletes
2230 2230
                     && $this->delete_is_blocked_by_related_models(
2231
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2231
+                        $item_to_delete[$primary_table->get_fully_qualified_pk_column()]
2232 2232
                     )
2233 2233
                 ) {
2234 2234
                     continue;
2235 2235
                 }
2236 2236
                 // primary table deletes
2237
-                if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2238
-                    $ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2239
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2237
+                if (isset($item_to_delete[$primary_table->get_fully_qualified_pk_column()])) {
2238
+                    $ids_to_delete_indexed_by_column[$primary_table->get_fully_qualified_pk_column()][] =
2239
+                        $item_to_delete[$primary_table->get_fully_qualified_pk_column()];
2240 2240
                 }
2241 2241
             }
2242 2242
         } elseif (count($this->get_combined_primary_key_fields()) > 1) {
@@ -2245,8 +2245,8 @@  discard block
 block discarded – undo
2245 2245
                 $ids_to_delete_indexed_by_column_for_row = [];
2246 2246
                 foreach ($fields as $cpk_field) {
2247 2247
                     if ($cpk_field instanceof EE_Model_Field_Base) {
2248
-                        $ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2249
-                            $item_to_delete[ $cpk_field->get_qualified_column() ];
2248
+                        $ids_to_delete_indexed_by_column_for_row[$cpk_field->get_qualified_column()] =
2249
+                            $item_to_delete[$cpk_field->get_qualified_column()];
2250 2250
                     }
2251 2251
                 }
2252 2252
                 $ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
@@ -2284,7 +2284,7 @@  discard block
 block discarded – undo
2284 2284
         } elseif ($this->has_primary_key_field()) {
2285 2285
             $query = [];
2286 2286
             foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2287
-                $query[] = $column . ' IN' . $this->_construct_in_value($ids, $this->_primary_key_field);
2287
+                $query[] = $column.' IN'.$this->_construct_in_value($ids, $this->_primary_key_field);
2288 2288
             }
2289 2289
             $query_part = ! empty($query)
2290 2290
                 ? implode(' AND ', $query)
@@ -2294,7 +2294,7 @@  discard block
 block discarded – undo
2294 2294
             foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2295 2295
                 $values_for_each_combined_primary_key_for_a_row = [];
2296 2296
                 foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2297
-                    $values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2297
+                    $values_for_each_combined_primary_key_for_a_row[] = $column.'='.$id;
2298 2298
                 }
2299 2299
                 $ways_to_identify_a_row[] = '('
2300 2300
                                             . implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
@@ -2370,10 +2370,10 @@  discard block
 block discarded – undo
2370 2370
             }
2371 2371
         }
2372 2372
         $column_to_count = $distinct
2373
-            ? "DISTINCT " . $column_to_count
2373
+            ? "DISTINCT ".$column_to_count
2374 2374
             : $column_to_count;
2375 2375
         $SQL             =
2376
-            "SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2376
+            "SELECT COUNT(".$column_to_count.")".$this->_construct_2nd_half_of_select_query($model_query_info);
2377 2377
         return (int) $this->_do_wpdb_query('get_var', [$SQL]);
2378 2378
     }
2379 2379
 
@@ -2398,7 +2398,7 @@  discard block
 block discarded – undo
2398 2398
         }
2399 2399
         $column_to_count = $field_obj->get_qualified_column();
2400 2400
         $SQL             =
2401
-            "SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2401
+            "SELECT SUM(".$column_to_count.")".$this->_construct_2nd_half_of_select_query($model_query_info);
2402 2402
         $return_value    = $this->_do_wpdb_query('get_var', [$SQL]);
2403 2403
         $data_type       = $field_obj->get_wpdb_data_type();
2404 2404
         if ($data_type === '%d' || $data_type === '%s') {
@@ -2434,7 +2434,7 @@  discard block
 block discarded – undo
2434 2434
         }
2435 2435
         /** @type WPDB $wpdb */
2436 2436
         global $wpdb;
2437
-        if (! method_exists($wpdb, $wpdb_method)) {
2437
+        if ( ! method_exists($wpdb, $wpdb_method)) {
2438 2438
             throw new DomainException(
2439 2439
                 sprintf(
2440 2440
                     esc_html__(
@@ -2453,7 +2453,7 @@  discard block
 block discarded – undo
2453 2453
         $this->show_db_query_if_previously_requested($wpdb->last_query);
2454 2454
         if (defined('WP_DEBUG') && WP_DEBUG) {
2455 2455
             $wpdb->show_errors($old_show_errors_value);
2456
-            if (! empty($wpdb->last_error)) {
2456
+            if ( ! empty($wpdb->last_error)) {
2457 2457
                 throw new EE_Error(sprintf(esc_html__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2458 2458
             }
2459 2459
             if ($result === false) {
@@ -2523,7 +2523,7 @@  discard block
 block discarded – undo
2523 2523
                     // ummmm... you in trouble
2524 2524
                     return $result;
2525 2525
             }
2526
-            if (! empty($error_message)) {
2526
+            if ( ! empty($error_message)) {
2527 2527
                 EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2528 2528
                 trigger_error($error_message);
2529 2529
             }
@@ -2604,11 +2604,11 @@  discard block
 block discarded – undo
2604 2604
      */
2605 2605
     private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2606 2606
     {
2607
-        return " FROM " . $model_query_info->get_full_join_sql() .
2608
-               $model_query_info->get_where_sql() .
2609
-               $model_query_info->get_group_by_sql() .
2610
-               $model_query_info->get_having_sql() .
2611
-               $model_query_info->get_order_by_sql() .
2607
+        return " FROM ".$model_query_info->get_full_join_sql().
2608
+               $model_query_info->get_where_sql().
2609
+               $model_query_info->get_group_by_sql().
2610
+               $model_query_info->get_having_sql().
2611
+               $model_query_info->get_order_by_sql().
2612 2612
                $model_query_info->get_limit_sql();
2613 2613
     }
2614 2614
 
@@ -2633,7 +2633,7 @@  discard block
 block discarded – undo
2633 2633
             $left = is_admin() ? '12rem' : '2rem';
2634 2634
             echo "
2635 2635
             <div class='ee-status-outline ee-status-bg--attention' style='margin: 2rem 2rem 2rem $left;'>
2636
-                " . esc_html($sql_query) . "
2636
+                ".esc_html($sql_query)."
2637 2637
             </div>";
2638 2638
             $this->_show_next_x_db_queries--;
2639 2639
         }
@@ -2809,12 +2809,12 @@  discard block
 block discarded – undo
2809 2809
         $related_model = $this->get_related_model_obj($model_name);
2810 2810
         // we're just going to use the query params on the related model's normal get_all query,
2811 2811
         // except add a condition to say to match the current mod
2812
-        if (! isset($query_params['default_where_conditions'])) {
2812
+        if ( ! isset($query_params['default_where_conditions'])) {
2813 2813
             $query_params['default_where_conditions'] = EE_Default_Where_Conditions::NONE;
2814 2814
         }
2815 2815
         $this_model_name                                                 = $this->get_this_model_name();
2816 2816
         $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2817
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2817
+        $query_params[0][$this_model_name.".".$this_pk_field_name] = $id_or_obj;
2818 2818
         return $related_model->count($query_params, $field_to_count, $distinct);
2819 2819
     }
2820 2820
 
@@ -2835,7 +2835,7 @@  discard block
 block discarded – undo
2835 2835
     public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2836 2836
     {
2837 2837
         $related_model = $this->get_related_model_obj($model_name);
2838
-        if (! is_array($query_params)) {
2838
+        if ( ! is_array($query_params)) {
2839 2839
             EE_Error::doing_it_wrong(
2840 2840
                 'EEM_Base::sum_related',
2841 2841
                 sprintf(
@@ -2848,12 +2848,12 @@  discard block
 block discarded – undo
2848 2848
         }
2849 2849
         // we're just going to use the query params on the related model's normal get_all query,
2850 2850
         // except add a condition to say to match the current mod
2851
-        if (! isset($query_params['default_where_conditions'])) {
2851
+        if ( ! isset($query_params['default_where_conditions'])) {
2852 2852
             $query_params['default_where_conditions'] = EE_Default_Where_Conditions::NONE;
2853 2853
         }
2854 2854
         $this_model_name                                                 = $this->get_this_model_name();
2855 2855
         $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2856
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2856
+        $query_params[0][$this_model_name.".".$this_pk_field_name] = $id_or_obj;
2857 2857
         return $related_model->sum($query_params, $field_to_sum);
2858 2858
     }
2859 2859
 
@@ -2905,7 +2905,7 @@  discard block
 block discarded – undo
2905 2905
                 $field_with_model_name = $field;
2906 2906
             }
2907 2907
         }
2908
-        if (! isset($field_with_model_name) || ! $field_with_model_name) {
2908
+        if ( ! isset($field_with_model_name) || ! $field_with_model_name) {
2909 2909
             throw new EE_Error(
2910 2910
                 sprintf(
2911 2911
                     esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
@@ -3044,14 +3044,14 @@  discard block
 block discarded – undo
3044 3044
                 || $this->get_primary_key_field()
3045 3045
                    instanceof
3046 3046
                    EE_Primary_Key_String_Field)
3047
-            && isset($fields_n_values[ $this->primary_key_name() ])
3047
+            && isset($fields_n_values[$this->primary_key_name()])
3048 3048
         ) {
3049
-            $query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
3049
+            $query_params[0]['OR'][$this->primary_key_name()] = $fields_n_values[$this->primary_key_name()];
3050 3050
         }
3051 3051
         foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
3052 3052
             $uniqueness_where_params                              =
3053 3053
                 array_intersect_key($fields_n_values, $unique_index->fields());
3054
-            $query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
3054
+            $query_params[0]['OR']['AND*'.$unique_index_name] = $uniqueness_where_params;
3055 3055
         }
3056 3056
         // if there is nothing to base this search on, then we shouldn't find anything
3057 3057
         if (empty($query_params)) {
@@ -3128,16 +3128,16 @@  discard block
 block discarded – undo
3128 3128
             $prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3129 3129
             // if the value we want to assign it to is NULL, just don't mention it for the insertion
3130 3130
             if ($prepared_value !== null) {
3131
-                $insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3131
+                $insertion_col_n_values[$field_obj->get_table_column()] = $prepared_value;
3132 3132
                 $format_for_insertion[]                                   = $field_obj->get_wpdb_data_type();
3133 3133
             }
3134 3134
         }
3135 3135
         if ($table instanceof EE_Secondary_Table && $new_id) {
3136 3136
             // its not the main table, so we should have already saved the main table's PK which we just inserted
3137 3137
             // so add the fk to the main table as a column
3138
-            $insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3138
+            $insertion_col_n_values[$table->get_fk_on_table()] = $new_id;
3139 3139
             $format_for_insertion[]                              =
3140
-                '%d';// yes right now we're only allowing these foreign keys to be INTs
3140
+                '%d'; // yes right now we're only allowing these foreign keys to be INTs
3141 3141
         }
3142 3142
 
3143 3143
         // insert the new entry
@@ -3155,7 +3155,7 @@  discard block
 block discarded – undo
3155 3155
             }
3156 3156
             // it's not an auto-increment primary key, so
3157 3157
             // it must have been supplied
3158
-            return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3158
+            return $fields_n_values[$this->get_primary_key_field()->get_name()];
3159 3159
         }
3160 3160
         // we can't return a  primary key because there is none. instead return
3161 3161
         // a unique string indicating this model
@@ -3177,10 +3177,10 @@  discard block
 block discarded – undo
3177 3177
     {
3178 3178
         $field_name = $field_obj->get_name();
3179 3179
         // if this field doesn't allow nullable, don't allow it
3180
-        if (! $field_obj->is_nullable() && ! isset($fields_n_values[ $field_name ])) {
3181
-            $fields_n_values[ $field_name ] = $field_obj->get_default_value();
3180
+        if ( ! $field_obj->is_nullable() && ! isset($fields_n_values[$field_name])) {
3181
+            $fields_n_values[$field_name] = $field_obj->get_default_value();
3182 3182
         }
3183
-        $unprepared_value = $fields_n_values[ $field_name ] ?? null;
3183
+        $unprepared_value = $fields_n_values[$field_name] ?? null;
3184 3184
         return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3185 3185
     }
3186 3186
 
@@ -3283,7 +3283,7 @@  discard block
 block discarded – undo
3283 3283
      */
3284 3284
     public function get_table_obj_by_alias($table_alias = '')
3285 3285
     {
3286
-        return $this->_tables[ $table_alias ] ?? null;
3286
+        return $this->_tables[$table_alias] ?? null;
3287 3287
     }
3288 3288
 
3289 3289
 
@@ -3297,7 +3297,7 @@  discard block
 block discarded – undo
3297 3297
         $other_tables = [];
3298 3298
         foreach ($this->_tables as $table_alias => $table) {
3299 3299
             if ($table instanceof EE_Secondary_Table) {
3300
-                $other_tables[ $table_alias ] = $table;
3300
+                $other_tables[$table_alias] = $table;
3301 3301
             }
3302 3302
         }
3303 3303
         return $other_tables;
@@ -3312,7 +3312,7 @@  discard block
 block discarded – undo
3312 3312
      */
3313 3313
     public function _get_fields_for_table($table_alias)
3314 3314
     {
3315
-        return $this->_fields[ $table_alias ];
3315
+        return $this->_fields[$table_alias];
3316 3316
     }
3317 3317
 
3318 3318
 
@@ -3341,7 +3341,7 @@  discard block
 block discarded – undo
3341 3341
                     $query_info_carrier,
3342 3342
                     'group_by'
3343 3343
                 );
3344
-            } elseif (! empty($query_params['group_by'])) {
3344
+            } elseif ( ! empty($query_params['group_by'])) {
3345 3345
                 $this->_extract_related_model_info_from_query_param(
3346 3346
                     $query_params['group_by'],
3347 3347
                     $query_info_carrier,
@@ -3363,7 +3363,7 @@  discard block
 block discarded – undo
3363 3363
                     $query_info_carrier,
3364 3364
                     'order_by'
3365 3365
                 );
3366
-            } elseif (! empty($query_params['order_by'])) {
3366
+            } elseif ( ! empty($query_params['order_by'])) {
3367 3367
                 $this->_extract_related_model_info_from_query_param(
3368 3368
                     $query_params['order_by'],
3369 3369
                     $query_info_carrier,
@@ -3398,7 +3398,7 @@  discard block
 block discarded – undo
3398 3398
         EE_Model_Query_Info_Carrier $model_query_info_carrier,
3399 3399
         $query_param_type
3400 3400
     ) {
3401
-        if (! empty($sub_query_params)) {
3401
+        if ( ! empty($sub_query_params)) {
3402 3402
             $sub_query_params = (array) $sub_query_params;
3403 3403
             foreach ($sub_query_params as $param => $possibly_array_of_params) {
3404 3404
                 // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
@@ -3414,7 +3414,7 @@  discard block
 block discarded – undo
3414 3414
                 $query_param_sans_stars =
3415 3415
                     $this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3416 3416
                 if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3417
-                    if (! is_array($possibly_array_of_params)) {
3417
+                    if ( ! is_array($possibly_array_of_params)) {
3418 3418
                         throw new EE_Error(
3419 3419
                             sprintf(
3420 3420
                                 esc_html__(
@@ -3440,7 +3440,7 @@  discard block
 block discarded – undo
3440 3440
                     // then $possible_array_of_params looks something like array('<','DTT_sold',true)
3441 3441
                     // indicating that $possible_array_of_params[1] is actually a field name,
3442 3442
                     // from which we should extract query parameters!
3443
-                    if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3443
+                    if ( ! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3444 3444
                         throw new EE_Error(
3445 3445
                             sprintf(
3446 3446
                                 esc_html__(
@@ -3480,8 +3480,8 @@  discard block
 block discarded – undo
3480 3480
         EE_Model_Query_Info_Carrier $model_query_info_carrier,
3481 3481
         $query_param_type
3482 3482
     ) {
3483
-        if (! empty($sub_query_params)) {
3484
-            if (! is_array($sub_query_params)) {
3483
+        if ( ! empty($sub_query_params)) {
3484
+            if ( ! is_array($sub_query_params)) {
3485 3485
                 throw new EE_Error(
3486 3486
                     sprintf(
3487 3487
                         esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
@@ -3519,7 +3519,7 @@  discard block
 block discarded – undo
3519 3519
      */
3520 3520
     public function _create_model_query_info_carrier($query_params)
3521 3521
     {
3522
-        if (! is_array($query_params)) {
3522
+        if ( ! is_array($query_params)) {
3523 3523
             EE_Error::doing_it_wrong(
3524 3524
                 'EEM_Base::_create_model_query_info_carrier',
3525 3525
                 sprintf(
@@ -3550,7 +3550,7 @@  discard block
 block discarded – undo
3550 3550
             // only include if related to a cpt where no password has been set
3551 3551
             $query_params[0]['OR*nopassword'] = [
3552 3552
                 $where_param_key_for_password       => '',
3553
-                $where_param_key_for_password . '*' => ['IS_NULL'],
3553
+                $where_param_key_for_password.'*' => ['IS_NULL'],
3554 3554
             ];
3555 3555
         }
3556 3556
         $query_object = $this->_extract_related_models_from_query($query_params);
@@ -3604,7 +3604,7 @@  discard block
 block discarded – undo
3604 3604
         // set limit
3605 3605
         if (array_key_exists('limit', $query_params)) {
3606 3606
             if (is_array($query_params['limit'])) {
3607
-                if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3607
+                if ( ! isset($query_params['limit'][0], $query_params['limit'][1])) {
3608 3608
                     $e = sprintf(
3609 3609
                         esc_html__(
3610 3610
                             "Invalid DB query. You passed '%s' for the LIMIT, but only the following are valid: an integer, string representing an integer, a string like 'int,int', or an array like array(int,int)",
@@ -3612,12 +3612,12 @@  discard block
 block discarded – undo
3612 3612
                         ),
3613 3613
                         http_build_query($query_params['limit'])
3614 3614
                     );
3615
-                    throw new EE_Error($e . "|" . $e);
3615
+                    throw new EE_Error($e."|".$e);
3616 3616
                 }
3617 3617
                 // they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3618
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3619
-            } elseif (! empty($query_params['limit'])) {
3620
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3618
+                $query_object->set_limit_sql(" LIMIT ".$query_params['limit'][0].",".$query_params['limit'][1]);
3619
+            } elseif ( ! empty($query_params['limit'])) {
3620
+                $query_object->set_limit_sql(" LIMIT ".$query_params['limit']);
3621 3621
             }
3622 3622
         }
3623 3623
         // set order by
@@ -3649,10 +3649,10 @@  discard block
 block discarded – undo
3649 3649
                 $order_array = [];
3650 3650
                 foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3651 3651
                     $order         = $this->_extract_order($order);
3652
-                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3652
+                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by).SP.$order;
3653 3653
                 }
3654
-                $query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3655
-            } elseif (! empty($query_params['order_by'])) {
3654
+                $query_object->set_order_by_sql(" ORDER BY ".implode(",", $order_array));
3655
+            } elseif ( ! empty($query_params['order_by'])) {
3656 3656
                 $this->_extract_related_model_info_from_query_param(
3657 3657
                     $query_params['order_by'],
3658 3658
                     $query_object,
@@ -3663,7 +3663,7 @@  discard block
 block discarded – undo
3663 3663
                     ? $this->_extract_order($query_params['order'])
3664 3664
                     : 'DESC';
3665 3665
                 $query_object->set_order_by_sql(
3666
-                    " ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3666
+                    " ORDER BY ".$this->_deduce_column_name_from_query_param($query_params['order_by']).SP.$order
3667 3667
                 );
3668 3668
             }
3669 3669
         }
@@ -3675,7 +3675,7 @@  discard block
 block discarded – undo
3675 3675
         ) {
3676 3676
             $pk_field = $this->get_primary_key_field();
3677 3677
             $order    = $this->_extract_order($query_params['order']);
3678
-            $query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3678
+            $query_object->set_order_by_sql(" ORDER BY ".$pk_field->get_qualified_column().SP.$order);
3679 3679
         }
3680 3680
         // set group by
3681 3681
         if (array_key_exists('group_by', $query_params)) {
@@ -3685,10 +3685,10 @@  discard block
 block discarded – undo
3685 3685
                 foreach ($query_params['group_by'] as $field_name_to_group_by) {
3686 3686
                     $group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3687 3687
                 }
3688
-                $query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3689
-            } elseif (! empty($query_params['group_by'])) {
3688
+                $query_object->set_group_by_sql(" GROUP BY ".implode(", ", $group_by_array));
3689
+            } elseif ( ! empty($query_params['group_by'])) {
3690 3690
                 $query_object->set_group_by_sql(
3691
-                    " GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3691
+                    " GROUP BY ".$this->_deduce_column_name_from_query_param($query_params['group_by'])
3692 3692
                 );
3693 3693
             }
3694 3694
         }
@@ -3698,7 +3698,7 @@  discard block
 block discarded – undo
3698 3698
         }
3699 3699
         // now, just verify they didn't pass anything wack
3700 3700
         foreach ($query_params as $query_key => $query_value) {
3701
-            if (! in_array($query_key, $this->_allowed_query_params, true)) {
3701
+            if ( ! in_array($query_key, $this->_allowed_query_params, true)) {
3702 3702
                 throw new EE_Error(
3703 3703
                     sprintf(
3704 3704
                         esc_html__(
@@ -3803,7 +3803,7 @@  discard block
 block discarded – undo
3803 3803
         $where_query_params = []
3804 3804
     ) {
3805 3805
         $allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3806
-        if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3806
+        if ( ! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3807 3807
             throw new EE_Error(
3808 3808
                 sprintf(
3809 3809
                     esc_html__(
@@ -3833,7 +3833,7 @@  discard block
 block discarded – undo
3833 3833
                 // we don't want to add full or even minimum default where conditions from this model, so just continue
3834 3834
                 continue;
3835 3835
             }
3836
-            $overrides              = $this->_override_defaults_or_make_null_friendly(
3836
+            $overrides = $this->_override_defaults_or_make_null_friendly(
3837 3837
                 $related_model_universal_where_params,
3838 3838
                 $where_query_params,
3839 3839
                 $related_model,
@@ -3942,19 +3942,19 @@  discard block
 block discarded – undo
3942 3942
     ) {
3943 3943
         $null_friendly_where_conditions = [];
3944 3944
         $none_overridden                = true;
3945
-        $or_condition_key_for_defaults  = 'OR*' . get_class($model);
3945
+        $or_condition_key_for_defaults  = 'OR*'.get_class($model);
3946 3946
         foreach ($default_where_conditions as $key => $val) {
3947
-            if (isset($provided_where_conditions[ $key ])) {
3947
+            if (isset($provided_where_conditions[$key])) {
3948 3948
                 $none_overridden = false;
3949 3949
             } else {
3950
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3950
+                $null_friendly_where_conditions[$or_condition_key_for_defaults]['AND'][$key] = $val;
3951 3951
             }
3952 3952
         }
3953 3953
         if ($none_overridden && $default_where_conditions) {
3954 3954
             if ($model->has_primary_key_field()) {
3955
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3955
+                $null_friendly_where_conditions[$or_condition_key_for_defaults][$model_relation_path
3956 3956
                                                                                    . "."
3957
-                                                                                   . $model->primary_key_name() ] =
3957
+                                                                                   . $model->primary_key_name()] =
3958 3958
                     ['IS NULL'];
3959 3959
             }/*else{
3960 3960
                 //@todo NO PK, use other defaults
@@ -4065,7 +4065,7 @@  discard block
 block discarded – undo
4065 4065
             foreach ($tables as $table_obj) {
4066 4066
                 $qualified_pk_column = $table_alias_with_model_relation_chain_prefix
4067 4067
                                        . $table_obj->get_fully_qualified_pk_column();
4068
-                if (! in_array($qualified_pk_column, $selects)) {
4068
+                if ( ! in_array($qualified_pk_column, $selects)) {
4069 4069
                     $selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
4070 4070
                 }
4071 4071
             }
@@ -4217,9 +4217,9 @@  discard block
 block discarded – undo
4217 4217
         $query_parameter_type
4218 4218
     ) {
4219 4219
         foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4220
-            if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4220
+            if (strpos($possible_join_string, $valid_related_model_name.".") === 0) {
4221 4221
                 $this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4222
-                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4222
+                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name."."));
4223 4223
                 if ($possible_join_string === '') {
4224 4224
                     // nothing left to $query_param
4225 4225
                     // we should actually end in a field name, not a model like this!
@@ -4352,7 +4352,7 @@  discard block
 block discarded – undo
4352 4352
     {
4353 4353
         $SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4354 4354
         if ($SQL) {
4355
-            return " WHERE " . $SQL;
4355
+            return " WHERE ".$SQL;
4356 4356
         }
4357 4357
         return '';
4358 4358
     }
@@ -4370,7 +4370,7 @@  discard block
 block discarded – undo
4370 4370
     {
4371 4371
         $SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4372 4372
         if ($SQL) {
4373
-            return " HAVING " . $SQL;
4373
+            return " HAVING ".$SQL;
4374 4374
         }
4375 4375
         return '';
4376 4376
     }
@@ -4424,7 +4424,7 @@  discard block
 block discarded – undo
4424 4424
             } else {
4425 4425
                 $field_obj = $this->_deduce_field_from_query_param($query_param);
4426 4426
                 // if it's not a normal field, maybe it's a custom selection?
4427
-                if (! $field_obj) {
4427
+                if ( ! $field_obj) {
4428 4428
                     if ($this->_custom_selections instanceof CustomSelects) {
4429 4429
                         $field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4430 4430
                     } else {
@@ -4440,7 +4440,7 @@  discard block
 block discarded – undo
4440 4440
                     }
4441 4441
                 }
4442 4442
                 $op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4443
-                $where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4443
+                $where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param).SP.$op_and_value_sql;
4444 4444
             }
4445 4445
         }
4446 4446
         return $where_clauses
@@ -4464,7 +4464,7 @@  discard block
 block discarded – undo
4464 4464
                 $field->get_model_name(),
4465 4465
                 $query_param
4466 4466
             );
4467
-            return $table_alias_prefix . $field->get_qualified_column();
4467
+            return $table_alias_prefix.$field->get_qualified_column();
4468 4468
         }
4469 4469
         if (
4470 4470
             $this->_custom_selections instanceof CustomSelects
@@ -4525,10 +4525,10 @@  discard block
 block discarded – undo
4525 4525
             $operator = isset($op_and_value[0])
4526 4526
                 ? $this->_prepare_operator_for_sql($op_and_value[0])
4527 4527
                 : null;
4528
-            if (! $operator) {
4528
+            if ( ! $operator) {
4529 4529
                 $php_array_like_string = [];
4530 4530
                 foreach ($op_and_value as $key => $value) {
4531
-                    $value = is_array($value) ? '[' . implode(",", $value) . ']' : $value;
4531
+                    $value = is_array($value) ? '['.implode(",", $value).']' : $value;
4532 4532
                     $php_array_like_string[] = "$key=>$value";
4533 4533
                 }
4534 4534
                 throw new EE_Error(
@@ -4546,14 +4546,14 @@  discard block
 block discarded – undo
4546 4546
 
4547 4547
         // check to see if the value is actually another field
4548 4548
         if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2]) {
4549
-            return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4549
+            return $operator.SP.$this->_deduce_column_name_from_query_param($value);
4550 4550
         }
4551 4551
         if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4552 4552
             // in this case, the value should be an array, or at least a comma-separated list
4553 4553
             // it will need to handle a little differently
4554 4554
             $cleaned_value = $this->_construct_in_value($value, $field_obj);
4555 4555
             // note: $cleaned_value has already been run through $wpdb->prepare()
4556
-            return $operator . SP . $cleaned_value;
4556
+            return $operator.SP.$cleaned_value;
4557 4557
         }
4558 4558
         if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4559 4559
             // the value should be an array with count of two.
@@ -4569,7 +4569,7 @@  discard block
 block discarded – undo
4569 4569
                 );
4570 4570
             }
4571 4571
             $cleaned_value = $this->_construct_between_value($value, $field_obj);
4572
-            return $operator . SP . $cleaned_value;
4572
+            return $operator.SP.$cleaned_value;
4573 4573
         }
4574 4574
         if (in_array($operator, $this->valid_null_style_operators())) {
4575 4575
             if ($value !== null) {
@@ -4589,10 +4589,10 @@  discard block
 block discarded – undo
4589 4589
         if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4590 4590
             // if the operator is 'LIKE', we want to allow percent signs (%) and not
4591 4591
             // remove other junk. So just treat it as a string.
4592
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4592
+            return $operator.SP.$this->_wpdb_prepare_using_field($value, '%s');
4593 4593
         }
4594
-        if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4595
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4594
+        if ( ! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4595
+            return $operator.SP.$this->_wpdb_prepare_using_field($value, $field_obj);
4596 4596
         }
4597 4597
         if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4598 4598
             throw new EE_Error(
@@ -4606,7 +4606,7 @@  discard block
 block discarded – undo
4606 4606
                 )
4607 4607
             );
4608 4608
         }
4609
-        if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4609
+        if ( ! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4610 4610
             throw new EE_Error(
4611 4611
                 sprintf(
4612 4612
                     esc_html__(
@@ -4645,7 +4645,7 @@  discard block
 block discarded – undo
4645 4645
         foreach ($values as $value) {
4646 4646
             $cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4647 4647
         }
4648
-        return $cleaned_values[0] . " AND " . $cleaned_values[1];
4648
+        return $cleaned_values[0]." AND ".$cleaned_values[1];
4649 4649
     }
4650 4650
 
4651 4651
 
@@ -4683,7 +4683,7 @@  discard block
 block discarded – undo
4683 4683
             $main_table  = $this->_get_main_table();
4684 4684
             $prepped[]   = "SELECT {$first_field->get_table_column()} FROM {$main_table->get_table_name()} WHERE FALSE";
4685 4685
         }
4686
-        return '(' . implode(',', $prepped) . ')';
4686
+        return '('.implode(',', $prepped).')';
4687 4687
     }
4688 4688
 
4689 4689
 
@@ -4703,7 +4703,7 @@  discard block
 block discarded – undo
4703 4703
                 $this->_prepare_value_for_use_in_db($value, $field_obj)
4704 4704
             );
4705 4705
         } //$field_obj should really just be a data type
4706
-        if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4706
+        if ( ! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4707 4707
             throw new EE_Error(
4708 4708
                 sprintf(
4709 4709
                     esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
@@ -4740,14 +4740,14 @@  discard block
 block discarded – undo
4740 4740
             );
4741 4741
         }
4742 4742
         $number_of_parts       = count($query_param_parts);
4743
-        $last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4743
+        $last_query_param_part = $query_param_parts[count($query_param_parts) - 1];
4744 4744
         if ($number_of_parts === 1) {
4745 4745
             $field_name = $last_query_param_part;
4746 4746
             $model_obj  = $this;
4747 4747
         } else {// $number_of_parts >= 2
4748 4748
             // the last part is the column name, and there are only 2parts. therefore...
4749 4749
             $field_name = $last_query_param_part;
4750
-            $model_obj  = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4750
+            $model_obj  = $this->get_related_model_obj($query_param_parts[$number_of_parts - 2]);
4751 4751
         }
4752 4752
         try {
4753 4753
             return $model_obj->field_settings_for($field_name);
@@ -4768,7 +4768,7 @@  discard block
 block discarded – undo
4768 4768
     public function _get_qualified_column_for_field($field_name)
4769 4769
     {
4770 4770
         $all_fields = $this->field_settings();
4771
-        $field      = $all_fields[ $field_name ] ?? false;
4771
+        $field      = $all_fields[$field_name] ?? false;
4772 4772
         if ($field) {
4773 4773
             return $field->get_qualified_column();
4774 4774
         }
@@ -4838,12 +4838,12 @@  discard block
 block discarded – undo
4838 4838
      */
4839 4839
     public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4840 4840
     {
4841
-        $table_prefix      = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain)
4841
+        $table_prefix      = str_replace('.', '__', $model_relation_chain).(empty($model_relation_chain)
4842 4842
                 ? ''
4843 4843
                 : '__');
4844 4844
         $qualified_columns = [];
4845 4845
         foreach ($this->field_settings() as $field_name => $field) {
4846
-            $qualified_columns[] = $table_prefix . $field->get_qualified_column();
4846
+            $qualified_columns[] = $table_prefix.$field->get_qualified_column();
4847 4847
         }
4848 4848
         return $return_string
4849 4849
             ? implode(', ', $qualified_columns)
@@ -4870,11 +4870,11 @@  discard block
 block discarded – undo
4870 4870
             if ($table_obj instanceof EE_Primary_Table) {
4871 4871
                 $SQL .= $table_alias === $table_obj->get_table_alias()
4872 4872
                     ? $table_obj->get_select_join_limit($limit)
4873
-                    : SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4873
+                    : SP.$table_obj->get_table_name()." AS ".$table_obj->get_table_alias().SP;
4874 4874
             } elseif ($table_obj instanceof EE_Secondary_Table) {
4875 4875
                 $SQL .= $table_alias === $table_obj->get_table_alias()
4876 4876
                     ? $table_obj->get_select_join_limit_join($limit)
4877
-                    : SP . $table_obj->get_join_sql($table_alias) . SP;
4877
+                    : SP.$table_obj->get_join_sql($table_alias).SP;
4878 4878
             }
4879 4879
         }
4880 4880
         return $SQL;
@@ -4945,7 +4945,7 @@  discard block
 block discarded – undo
4945 4945
         foreach ($this->field_settings() as $field_obj) {
4946 4946
             // $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4947 4947
             /** @var $field_obj EE_Model_Field_Base */
4948
-            $data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4948
+            $data_types[$field_obj->get_qualified_column()] = $field_obj->get_wpdb_data_type();
4949 4949
         }
4950 4950
         return $data_types;
4951 4951
     }
@@ -4960,8 +4960,8 @@  discard block
 block discarded – undo
4960 4960
      */
4961 4961
     public function get_related_model_obj($model_name)
4962 4962
     {
4963
-        $model_classname = "EEM_" . $model_name;
4964
-        if (! class_exists($model_classname)) {
4963
+        $model_classname = "EEM_".$model_name;
4964
+        if ( ! class_exists($model_classname)) {
4965 4965
             throw new EE_Error(
4966 4966
                 sprintf(
4967 4967
                     esc_html__(
@@ -4973,7 +4973,7 @@  discard block
 block discarded – undo
4973 4973
                 )
4974 4974
             );
4975 4975
         }
4976
-        return call_user_func($model_classname . "::instance");
4976
+        return call_user_func($model_classname."::instance");
4977 4977
     }
4978 4978
 
4979 4979
 
@@ -5000,7 +5000,7 @@  discard block
 block discarded – undo
5000 5000
         $belongs_to_relations = [];
5001 5001
         foreach ($this->relation_settings() as $model_name => $relation_obj) {
5002 5002
             if ($relation_obj instanceof EE_Belongs_To_Relation) {
5003
-                $belongs_to_relations[ $model_name ] = $relation_obj;
5003
+                $belongs_to_relations[$model_name] = $relation_obj;
5004 5004
             }
5005 5005
         }
5006 5006
         return $belongs_to_relations;
@@ -5017,7 +5017,7 @@  discard block
 block discarded – undo
5017 5017
     public function related_settings_for($relation_name)
5018 5018
     {
5019 5019
         $relatedModels = $this->relation_settings();
5020
-        if (! array_key_exists($relation_name, $relatedModels)) {
5020
+        if ( ! array_key_exists($relation_name, $relatedModels)) {
5021 5021
             throw new EE_Error(
5022 5022
                 sprintf(
5023 5023
                     esc_html__(
@@ -5030,7 +5030,7 @@  discard block
 block discarded – undo
5030 5030
                 )
5031 5031
             );
5032 5032
         }
5033
-        return $relatedModels[ $relation_name ];
5033
+        return $relatedModels[$relation_name];
5034 5034
     }
5035 5035
 
5036 5036
 
@@ -5046,7 +5046,7 @@  discard block
 block discarded – undo
5046 5046
     public function field_settings_for($fieldName, $include_db_only_fields = true)
5047 5047
     {
5048 5048
         $fieldSettings = $this->field_settings($include_db_only_fields);
5049
-        if (! array_key_exists($fieldName, $fieldSettings)) {
5049
+        if ( ! array_key_exists($fieldName, $fieldSettings)) {
5050 5050
             throw new EE_Error(
5051 5051
                 sprintf(
5052 5052
                     esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
@@ -5055,7 +5055,7 @@  discard block
 block discarded – undo
5055 5055
                 )
5056 5056
             );
5057 5057
         }
5058
-        return $fieldSettings[ $fieldName ];
5058
+        return $fieldSettings[$fieldName];
5059 5059
     }
5060 5060
 
5061 5061
 
@@ -5068,7 +5068,7 @@  discard block
 block discarded – undo
5068 5068
     public function has_field($fieldName)
5069 5069
     {
5070 5070
         $fieldSettings = $this->field_settings(true);
5071
-        if (isset($fieldSettings[ $fieldName ])) {
5071
+        if (isset($fieldSettings[$fieldName])) {
5072 5072
             return true;
5073 5073
         }
5074 5074
         return false;
@@ -5084,7 +5084,7 @@  discard block
 block discarded – undo
5084 5084
     public function has_relation($relation_name)
5085 5085
     {
5086 5086
         $relations = $this->relation_settings();
5087
-        if (isset($relations[ $relation_name ])) {
5087
+        if (isset($relations[$relation_name])) {
5088 5088
             return true;
5089 5089
         }
5090 5090
         return false;
@@ -5120,7 +5120,7 @@  discard block
 block discarded – undo
5120 5120
                     break;
5121 5121
                 }
5122 5122
             }
5123
-            if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5123
+            if ( ! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5124 5124
                 throw new EE_Error(
5125 5125
                     sprintf(
5126 5126
                         esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
@@ -5180,17 +5180,17 @@  discard block
 block discarded – undo
5180 5180
      */
5181 5181
     public function get_foreign_key_to($model_name)
5182 5182
     {
5183
-        if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5183
+        if ( ! isset($this->_cache_foreign_key_to_fields[$model_name])) {
5184 5184
             foreach ($this->field_settings() as $field) {
5185 5185
                 if (
5186 5186
                     $field instanceof EE_Foreign_Key_Field_Base
5187 5187
                     && in_array($model_name, $field->get_model_names_pointed_to())
5188 5188
                 ) {
5189
-                    $this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5189
+                    $this->_cache_foreign_key_to_fields[$model_name] = $field;
5190 5190
                     break;
5191 5191
                 }
5192 5192
             }
5193
-            if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5193
+            if ( ! isset($this->_cache_foreign_key_to_fields[$model_name])) {
5194 5194
                 throw new EE_Error(
5195 5195
                     sprintf(
5196 5196
                         esc_html__(
@@ -5203,7 +5203,7 @@  discard block
 block discarded – undo
5203 5203
                 );
5204 5204
             }
5205 5205
         }
5206
-        return $this->_cache_foreign_key_to_fields[ $model_name ];
5206
+        return $this->_cache_foreign_key_to_fields[$model_name];
5207 5207
     }
5208 5208
 
5209 5209
 
@@ -5219,7 +5219,7 @@  discard block
 block discarded – undo
5219 5219
     {
5220 5220
         $table_alias_sans_model_relation_chain_prefix =
5221 5221
             EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5222
-        return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5222
+        return $this->_tables[$table_alias_sans_model_relation_chain_prefix]->get_table_name();
5223 5223
     }
5224 5224
 
5225 5225
 
@@ -5237,7 +5237,7 @@  discard block
 block discarded – undo
5237 5237
                 $this->_cached_fields = [];
5238 5238
                 foreach ($this->_fields as $fields_corresponding_to_table) {
5239 5239
                     foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5240
-                        $this->_cached_fields[ $field_name ] = $field_obj;
5240
+                        $this->_cached_fields[$field_name] = $field_obj;
5241 5241
                     }
5242 5242
                 }
5243 5243
             }
@@ -5248,8 +5248,8 @@  discard block
 block discarded – undo
5248 5248
             foreach ($this->_fields as $fields_corresponding_to_table) {
5249 5249
                 foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5250 5250
                     /** @var $field_obj EE_Model_Field_Base */
5251
-                    if (! $field_obj->is_db_only_field()) {
5252
-                        $this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5251
+                    if ( ! $field_obj->is_db_only_field()) {
5252
+                        $this->_cached_fields_non_db_only[$field_name] = $field_obj;
5253 5253
                     }
5254 5254
                 }
5255 5255
             }
@@ -5292,12 +5292,12 @@  discard block
 block discarded – undo
5292 5292
                     $primary_key_field->get_qualified_column(),
5293 5293
                     $primary_key_field->get_table_column()
5294 5294
                 );
5295
-                if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5295
+                if ($table_pk_value && isset($array_of_objects[$table_pk_value])) {
5296 5296
                     continue;
5297 5297
                 }
5298 5298
             }
5299 5299
             $classInstance = $this->instantiate_class_from_array_or_object($row);
5300
-            if (! $classInstance) {
5300
+            if ( ! $classInstance) {
5301 5301
                 throw new EE_Error(
5302 5302
                     sprintf(
5303 5303
                         esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
@@ -5312,7 +5312,7 @@  discard block
 block discarded – undo
5312 5312
             $key                      = $has_primary_key
5313 5313
                 ? $classInstance->ID()
5314 5314
                 : $count_if_model_has_no_primary_key++;
5315
-            $array_of_objects[ $key ] = $classInstance;
5315
+            $array_of_objects[$key] = $classInstance;
5316 5316
             // also, for all the relations of type BelongsTo, see if we can cache
5317 5317
             // those related models
5318 5318
             // (we could do this for other relations too, but if there are conditions
@@ -5356,9 +5356,9 @@  discard block
 block discarded – undo
5356 5356
         $results = [];
5357 5357
         if ($this->_custom_selections instanceof CustomSelects) {
5358 5358
             foreach ($this->_custom_selections->columnAliases() as $alias) {
5359
-                if (isset($db_results_row[ $alias ])) {
5360
-                    $results[ $alias ] = $this->convertValueToDataType(
5361
-                        $db_results_row[ $alias ],
5359
+                if (isset($db_results_row[$alias])) {
5360
+                    $results[$alias] = $this->convertValueToDataType(
5361
+                        $db_results_row[$alias],
5362 5362
                         $this->_custom_selections->getDataTypeForAlias($alias)
5363 5363
                     );
5364 5364
                 }
@@ -5403,7 +5403,7 @@  discard block
 block discarded – undo
5403 5403
         $this_model_fields_and_values = [];
5404 5404
         // setup the row using default values;
5405 5405
         foreach ($this->field_settings() as $field_name => $field_obj) {
5406
-            $this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5406
+            $this_model_fields_and_values[$field_name] = $field_obj->get_default_value();
5407 5407
         }
5408 5408
         $className = $this->_get_class_name();
5409 5409
         return EE_Registry::instance()->load_class($className, [$this_model_fields_and_values], false, false);
@@ -5419,20 +5419,20 @@  discard block
 block discarded – undo
5419 5419
      */
5420 5420
     public function instantiate_class_from_array_or_object($cols_n_values)
5421 5421
     {
5422
-        if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5422
+        if ( ! is_array($cols_n_values) && is_object($cols_n_values)) {
5423 5423
             $cols_n_values = get_object_vars($cols_n_values);
5424 5424
         }
5425 5425
         $primary_key = null;
5426 5426
         // make sure the array only has keys that are fields/columns on this model
5427 5427
         $this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5428
-        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5429
-            $primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5428
+        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[$this->primary_key_name()])) {
5429
+            $primary_key = $this_model_fields_n_values[$this->primary_key_name()];
5430 5430
         }
5431 5431
         $className = $this->_get_class_name();
5432 5432
         // check we actually found results that we can use to build our model object
5433 5433
         // if not, return null
5434 5434
         if ($this->has_primary_key_field()) {
5435
-            if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5435
+            if (empty($this_model_fields_n_values[$this->primary_key_name()])) {
5436 5436
                 return null;
5437 5437
             }
5438 5438
         } elseif ($this->unique_indexes()) {
@@ -5444,7 +5444,7 @@  discard block
 block discarded – undo
5444 5444
         // if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5445 5445
         if ($primary_key) {
5446 5446
             $classInstance = $this->get_from_entity_map($primary_key);
5447
-            if (! $classInstance) {
5447
+            if ( ! $classInstance) {
5448 5448
                 $classInstance = EE_Registry::instance()
5449 5449
                                             ->load_class(
5450 5450
                                                 $className,
@@ -5475,7 +5475,7 @@  discard block
 block discarded – undo
5475 5475
      */
5476 5476
     public function get_from_entity_map($id)
5477 5477
     {
5478
-        return $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] ?? null;
5478
+        return $this->_entity_map[EEM_Base::$_model_query_blog_id][$id] ?? null;
5479 5479
     }
5480 5480
 
5481 5481
 
@@ -5498,7 +5498,7 @@  discard block
 block discarded – undo
5498 5498
     public function add_to_entity_map(EE_Base_Class $object)
5499 5499
     {
5500 5500
         $className = $this->_get_class_name();
5501
-        if (! $object instanceof $className) {
5501
+        if ( ! $object instanceof $className) {
5502 5502
             throw new EE_Error(
5503 5503
                 sprintf(
5504 5504
                     esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
@@ -5510,7 +5510,7 @@  discard block
 block discarded – undo
5510 5510
             );
5511 5511
         }
5512 5512
 
5513
-        if (! $object->ID()) {
5513
+        if ( ! $object->ID()) {
5514 5514
             throw new EE_Error(
5515 5515
                 sprintf(
5516 5516
                     esc_html__(
@@ -5526,7 +5526,7 @@  discard block
 block discarded – undo
5526 5526
         if ($classInstance) {
5527 5527
             return $classInstance;
5528 5528
         }
5529
-        $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5529
+        $this->_entity_map[EEM_Base::$_model_query_blog_id][$object->ID()] = $object;
5530 5530
         return $object;
5531 5531
     }
5532 5532
 
@@ -5541,11 +5541,11 @@  discard block
 block discarded – undo
5541 5541
     public function clear_entity_map($id = null)
5542 5542
     {
5543 5543
         if (empty($id)) {
5544
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ] = [];
5544
+            $this->_entity_map[EEM_Base::$_model_query_blog_id] = [];
5545 5545
             return true;
5546 5546
         }
5547
-        if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5548
-            unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5547
+        if (isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])) {
5548
+            unset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id]);
5549 5549
             return true;
5550 5550
         }
5551 5551
         return false;
@@ -5593,18 +5593,18 @@  discard block
 block discarded – undo
5593 5593
             // there is a primary key on this table and its not set. Use defaults for all its columns
5594 5594
             if ($table_pk_value === null && $table_obj->get_pk_column()) {
5595 5595
                 foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5596
-                    if (! $field_obj->is_db_only_field()) {
5596
+                    if ( ! $field_obj->is_db_only_field()) {
5597 5597
                         // prepare field as if its coming from db
5598 5598
                         $prepared_value                            =
5599 5599
                             $field_obj->prepare_for_set($field_obj->get_default_value());
5600
-                        $this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5600
+                        $this_model_fields_n_values[$field_name] = $field_obj->prepare_for_use_in_db($prepared_value);
5601 5601
                     }
5602 5602
                 }
5603 5603
             } else {
5604 5604
                 // the table's rows existed. Use their values
5605 5605
                 foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5606
-                    if (! $field_obj->is_db_only_field()) {
5607
-                        $this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5606
+                    if ( ! $field_obj->is_db_only_field()) {
5607
+                        $this_model_fields_n_values[$field_name] = $this->_get_column_value_with_table_alias_or_not(
5608 5608
                             $cols_n_values,
5609 5609
                             $field_obj->get_qualified_column(),
5610 5610
                             $field_obj->get_table_column()
@@ -5631,17 +5631,17 @@  discard block
 block discarded – undo
5631 5631
         // ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5632 5632
         // does the field on the model relate to this column retrieved from the db?
5633 5633
         // or is it a db-only field? (not relating to the model)
5634
-        if (isset($cols_n_values[ $qualified_column ])) {
5635
-            $value = $cols_n_values[ $qualified_column ];
5636
-        } elseif (isset($cols_n_values[ $regular_column ])) {
5637
-            $value = $cols_n_values[ $regular_column ];
5638
-        } elseif (! empty($this->foreign_key_aliases)) {
5634
+        if (isset($cols_n_values[$qualified_column])) {
5635
+            $value = $cols_n_values[$qualified_column];
5636
+        } elseif (isset($cols_n_values[$regular_column])) {
5637
+            $value = $cols_n_values[$regular_column];
5638
+        } elseif ( ! empty($this->foreign_key_aliases)) {
5639 5639
             // no PK?  ok check if there is a foreign key alias set for this table
5640 5640
             // then check if that alias exists in the incoming data
5641 5641
             // AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5642 5642
             foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5643
-                if ($PK_column === $qualified_column && !empty($cols_n_values[ $FK_alias ])) {
5644
-                    $value = $cols_n_values[ $FK_alias ];
5643
+                if ($PK_column === $qualified_column && ! empty($cols_n_values[$FK_alias])) {
5644
+                    $value = $cols_n_values[$FK_alias];
5645 5645
                     [$pk_class] = explode('.', $PK_column);
5646 5646
                     $pk_model_name = "EEM_{$pk_class}";
5647 5647
                     /** @var EEM_Base $pk_model */
@@ -5685,7 +5685,7 @@  discard block
 block discarded – undo
5685 5685
                     $obj_in_map->clear_cache($relation_name, null, true);
5686 5686
                 }
5687 5687
             }
5688
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5688
+            $this->_entity_map[EEM_Base::$_model_query_blog_id][$id] = $obj_in_map;
5689 5689
             return $obj_in_map;
5690 5690
         }
5691 5691
         return $this->get_one_by_ID($id);
@@ -5737,7 +5737,7 @@  discard block
 block discarded – undo
5737 5737
      */
5738 5738
     private function _get_class_name()
5739 5739
     {
5740
-        return "EE_" . $this->get_this_model_name();
5740
+        return "EE_".$this->get_this_model_name();
5741 5741
     }
5742 5742
 
5743 5743
 
@@ -5791,7 +5791,7 @@  discard block
 block discarded – undo
5791 5791
     {
5792 5792
         $className = $this->class_name;
5793 5793
         $tagName   = "FHEE__{$className}__{$methodName}";
5794
-        if (! has_filter($tagName)) {
5794
+        if ( ! has_filter($tagName)) {
5795 5795
             throw new EE_Error(
5796 5796
                 sprintf(
5797 5797
                     esc_html__(
@@ -5962,7 +5962,7 @@  discard block
 block discarded – undo
5962 5962
         $unique_indexes = [];
5963 5963
         foreach ($this->_indexes as $name => $index) {
5964 5964
             if ($index instanceof EE_Unique_Index) {
5965
-                $unique_indexes [ $name ] = $index;
5965
+                $unique_indexes [$name] = $index;
5966 5966
             }
5967 5967
         }
5968 5968
         return $unique_indexes;
@@ -6026,7 +6026,7 @@  discard block
 block discarded – undo
6026 6026
         $key_vals_in_combined_pk = [];
6027 6027
         parse_str($index_primary_key_string, $key_vals_in_combined_pk);
6028 6028
         foreach ($key_fields as $key_field_name => $field_obj) {
6029
-            if (! isset($key_vals_in_combined_pk[ $key_field_name ])) {
6029
+            if ( ! isset($key_vals_in_combined_pk[$key_field_name])) {
6030 6030
                 return null;
6031 6031
             }
6032 6032
         }
@@ -6046,7 +6046,7 @@  discard block
 block discarded – undo
6046 6046
     {
6047 6047
         $keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
6048 6048
         foreach ($keys_it_should_have as $key) {
6049
-            if (! isset($key_vals[ $key ])) {
6049
+            if ( ! isset($key_vals[$key])) {
6050 6050
                 return false;
6051 6051
             }
6052 6052
         }
@@ -6085,8 +6085,8 @@  discard block
 block discarded – undo
6085 6085
         }
6086 6086
         // even copies obviously won't have the same ID, so remove the primary key
6087 6087
         // from the WHERE conditions for finding copies (if there is a primary key, of course)
6088
-        if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
6089
-            unset($attributes_array[ $this->primary_key_name() ]);
6088
+        if ($this->has_primary_key_field() && isset($attributes_array[$this->primary_key_name()])) {
6089
+            unset($attributes_array[$this->primary_key_name()]);
6090 6090
         }
6091 6091
         if (isset($query_params[0])) {
6092 6092
             $query_params[0] = array_merge($attributes_array, $query_params);
@@ -6108,7 +6108,7 @@  discard block
 block discarded – undo
6108 6108
      */
6109 6109
     public function get_one_copy($model_object_or_attributes_array, $query_params = [])
6110 6110
     {
6111
-        if (! is_array($query_params)) {
6111
+        if ( ! is_array($query_params)) {
6112 6112
             EE_Error::doing_it_wrong(
6113 6113
                 'EEM_Base::get_one_copy',
6114 6114
                 sprintf(
@@ -6157,7 +6157,7 @@  discard block
 block discarded – undo
6157 6157
      */
6158 6158
     private function _prepare_operator_for_sql($operator_supplied)
6159 6159
     {
6160
-        $sql_operator = $this->_valid_operators[ $operator_supplied ] ?? null;
6160
+        $sql_operator = $this->_valid_operators[$operator_supplied] ?? null;
6161 6161
         if ($sql_operator) {
6162 6162
             return $sql_operator;
6163 6163
         }
@@ -6255,7 +6255,7 @@  discard block
 block discarded – undo
6255 6255
         $objs  = $this->get_all($query_params);
6256 6256
         $names = [];
6257 6257
         foreach ($objs as $obj) {
6258
-            $names[ $obj->ID() ] = $obj->name();
6258
+            $names[$obj->ID()] = $obj->name();
6259 6259
         }
6260 6260
         return $names;
6261 6261
     }
@@ -6276,7 +6276,7 @@  discard block
 block discarded – undo
6276 6276
      */
6277 6277
     public function get_IDs($model_objects, $filter_out_empty_ids = false)
6278 6278
     {
6279
-        if (! $this->has_primary_key_field()) {
6279
+        if ( ! $this->has_primary_key_field()) {
6280 6280
             if (defined('WP_DEBUG') && WP_DEBUG) {
6281 6281
                 EE_Error::add_error(
6282 6282
                     esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
@@ -6289,7 +6289,7 @@  discard block
 block discarded – undo
6289 6289
         $IDs = [];
6290 6290
         foreach ($model_objects as $model_object) {
6291 6291
             $id = $model_object->ID();
6292
-            if (! $id) {
6292
+            if ( ! $id) {
6293 6293
                 if ($filter_out_empty_ids) {
6294 6294
                     continue;
6295 6295
                 }
@@ -6338,22 +6338,22 @@  discard block
 block discarded – undo
6338 6338
         EEM_Base::verify_is_valid_cap_context($context);
6339 6339
         // check if we ought to run the restriction generator first
6340 6340
         if (
6341
-            isset($this->_cap_restriction_generators[ $context ])
6342
-            && $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6343
-            && ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6341
+            isset($this->_cap_restriction_generators[$context])
6342
+            && $this->_cap_restriction_generators[$context] instanceof EE_Restriction_Generator_Base
6343
+            && ! $this->_cap_restriction_generators[$context]->has_generated_cap_restrictions()
6344 6344
         ) {
6345
-            $this->_cap_restrictions[ $context ] = array_merge(
6346
-                $this->_cap_restrictions[ $context ],
6347
-                $this->_cap_restriction_generators[ $context ]->generate_restrictions()
6345
+            $this->_cap_restrictions[$context] = array_merge(
6346
+                $this->_cap_restrictions[$context],
6347
+                $this->_cap_restriction_generators[$context]->generate_restrictions()
6348 6348
             );
6349 6349
         }
6350 6350
         // and make sure we've finalized the construction of each restriction
6351
-        foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6351
+        foreach ($this->_cap_restrictions[$context] as $where_conditions_obj) {
6352 6352
             if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6353 6353
                 $where_conditions_obj->_finalize_construct($this);
6354 6354
             }
6355 6355
         }
6356
-        return $this->_cap_restrictions[ $context ];
6356
+        return $this->_cap_restrictions[$context];
6357 6357
     }
6358 6358
 
6359 6359
 
@@ -6383,9 +6383,9 @@  discard block
 block discarded – undo
6383 6383
         foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6384 6384
             if (
6385 6385
                 ! EE_Capabilities::instance()
6386
-                                 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6386
+                                 ->current_user_can($cap, $this->get_this_model_name().'_model_applying_caps')
6387 6387
             ) {
6388
-                $missing_caps[ $cap ] = $restriction_if_no_cap;
6388
+                $missing_caps[$cap] = $restriction_if_no_cap;
6389 6389
             }
6390 6390
         }
6391 6391
         return $missing_caps;
@@ -6418,8 +6418,8 @@  discard block
 block discarded – undo
6418 6418
     public function cap_action_for_context($context)
6419 6419
     {
6420 6420
         $mapping = $this->cap_contexts_to_cap_action_map();
6421
-        if (isset($mapping[ $context ])) {
6422
-            return $mapping[ $context ];
6421
+        if (isset($mapping[$context])) {
6422
+            return $mapping[$context];
6423 6423
         }
6424 6424
         if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6425 6425
             return $action;
@@ -6540,7 +6540,7 @@  discard block
 block discarded – undo
6540 6540
         foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6541 6541
             if (
6542 6542
                 $query_param_key === $logic_query_param_key
6543
-                || strpos($query_param_key, $logic_query_param_key . '*') === 0
6543
+                || strpos($query_param_key, $logic_query_param_key.'*') === 0
6544 6544
             ) {
6545 6545
                 return true;
6546 6546
             }
@@ -6598,7 +6598,7 @@  discard block
 block discarded – undo
6598 6598
         if ($password_field instanceof EE_Password_Field) {
6599 6599
             $field_names = $password_field->protectedFields();
6600 6600
             foreach ($field_names as $field_name) {
6601
-                $fields[ $field_name ] = $this->field_settings_for($field_name);
6601
+                $fields[$field_name] = $this->field_settings_for($field_name);
6602 6602
             }
6603 6603
         }
6604 6604
         return $fields;
@@ -6624,7 +6624,7 @@  discard block
 block discarded – undo
6624 6624
         if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6625 6625
             $model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6626 6626
         }
6627
-        if (! is_array($model_obj_or_fields_n_values)) {
6627
+        if ( ! is_array($model_obj_or_fields_n_values)) {
6628 6628
             throw new UnexpectedEntityException(
6629 6629
                 $model_obj_or_fields_n_values,
6630 6630
                 'EE_Base_Class',
@@ -6706,9 +6706,9 @@  discard block
 block discarded – undo
6706 6706
         }
6707 6707
         return (
6708 6708
                $this->model_chain_to_password
6709
-                   ? $this->model_chain_to_password . '.'
6709
+                   ? $this->model_chain_to_password.'.'
6710 6710
                    : ''
6711
-               ) . $password_field_name;
6711
+               ).$password_field_name;
6712 6712
     }
6713 6713
 
6714 6714
 
Please login to merge, or discard this patch.
core/admin/EE_Admin_Page.core.php 1 patch
Indentation   +4234 added lines, -4234 removed lines patch added patch discarded remove patch
@@ -24,4330 +24,4330 @@
 block discarded – undo
24 24
  */
25 25
 abstract class EE_Admin_Page extends EE_Base implements InterminableInterface
26 26
 {
27
-    protected ?EE_Admin_Config $admin_config       = null;
27
+	protected ?EE_Admin_Config $admin_config       = null;
28 28
 
29
-    protected ?EE_Admin_Hooks $_hook_obj          = null;
29
+	protected ?EE_Admin_Hooks $_hook_obj          = null;
30 30
 
31
-    protected ?EE_Admin_List_Table $_list_table_object = null;
31
+	protected ?EE_Admin_List_Table $_list_table_object = null;
32 32
 
33
-    protected ?EE_Capabilities $capabilities       = null;
33
+	protected ?EE_Capabilities $capabilities       = null;
34 34
 
35
-    protected ?EE_Registry $EE                 = null;
35
+	protected ?EE_Registry $EE                 = null;
36 36
 
37
-    protected ?FeatureFlags $feature            = null;
37
+	protected ?FeatureFlags $feature            = null;
38 38
 
39
-    protected ?LoaderInterface $loader             = null;
39
+	protected ?LoaderInterface $loader             = null;
40 40
 
41
-    protected ?RequestInterface $request            = null;
41
+	protected ?RequestInterface $request            = null;
42 42
 
43
-    protected ?WP_Screen $_current_screen    = null;
44
-
45
-    /**
46
-     * @var array
47
-     * @since 5.0.0.p
48
-     */
49
-    private array $publish_post_meta_box_hidden_fields = [];
50
-
51
-    /**
52
-     * some default things shared by all child classes
53
-     *
54
-     * @var string[]
55
-     */
56
-    protected array $_default_espresso_metaboxes = [
57
-        '_espresso_news_post_box',
58
-        '_espresso_links_post_box',
59
-        '_espresso_ratings_request',
60
-        '_espresso_sponsors_post_box',
61
-    ];
62
-
63
-    /**
64
-     * Used to hold default query args for list table routes to help preserve stickiness of filters for carried out
65
-     * actions.
66
-     *
67
-     * @since 4.6.x
68
-     */
69
-    protected array $_default_route_query_args = [];
70
-
71
-    protected array $_labels                   = [];
72
-
73
-    protected array $_nav_tabs                 = [];
74
-
75
-    protected array $_page_config              = [];
76
-
77
-    /**
78
-     * action => method pairs used for routing incoming requests
79
-     *
80
-     * @var array
81
-     */
82
-    protected array $_page_routes   = [];
83
-
84
-    protected array $_req_data      = [];
85
-
86
-    protected array $_route_config  = [];
87
-
88
-    protected array $_template_args = [];
89
-
90
-    protected array $_views         = [];
91
-
92
-    /**
93
-     * yes / no array for admin form fields
94
-     *
95
-     * @var array|array[]
96
-     */
97
-    protected array $_yes_no_values = [];
98
-
99
-    /**
100
-     * this starts at null so we can have no header routes progress through two states.
101
-     */
102
-    protected ?bool $_is_UI_request = null;
103
-
104
-    /**
105
-     * flags whether the given route is a caffeinated route or not.
106
-     */
107
-    protected bool $_is_caf        = false;
108
-
109
-    protected bool $_routing       = false;
110
-
111
-    /**
112
-     * whether initializePage() has run
113
-     *
114
-     * @var bool
115
-     */
116
-    protected bool $initialized = false;
117
-
118
-
119
-    protected string $_admin_base_path      = '';
120
-
121
-    protected string $_admin_base_url       = '';
122
-
123
-    protected string $_admin_page_title     = '';
124
-
125
-    protected string $_column_template_path = '';
126
-
127
-    protected bool $_cpt_route            = false;
128
-
129
-    /**
130
-     * set via request page and action args.
131
-     */
132
-    protected string $_current_page          = '';
133
-
134
-    protected string $_current_page_view_url = '';
135
-
136
-    protected string $_current_view          = '';
137
-
138
-    protected string $_default_nav_tab_name  = 'overview';
139
-
140
-    /**
141
-     * sanitized request action
142
-     */
143
-    protected string $_req_action = '';
144
-
145
-    /**
146
-     * sanitized request action nonce
147
-     */
148
-    protected string $_req_nonce        = '';
149
-
150
-    protected string $_search_btn_label = '';
151
-
152
-    protected string $_template_path    = '';
153
-
154
-    protected string $_view             = '';
155
-
156
-    /**
157
-     * set early within EE_Admin_Init
158
-     *
159
-     * @var string
160
-     */
161
-    protected string $_wp_page_slug = '';
162
-
163
-    /**
164
-     * if the current class is an admin page extension, like: Extend_Events_Admin_Page,
165
-     * then this would be the parent classname: Events_Admin_Page
166
-     *
167
-     * @var string
168
-     */
169
-    public string $base_class_name = '';
170
-
171
-    public string $class_name      = '';
172
-
173
-    /**
174
-     * unprocessed value for the 'action' request param (default '')
175
-     *
176
-     * @var string
177
-     */
178
-    protected string $raw_req_action = '';
179
-
180
-    /**
181
-     * unprocessed value for the 'page' request param (default '')
182
-     *
183
-     * @var string
184
-     */
185
-    protected string $raw_req_page = '';
186
-
187
-    public string $page_folder  = '';
188
-
189
-    public string $page_label   = '';
190
-
191
-    public string $page_slug    = '';
192
-
193
-
194
-    /**
195
-     * the current page route and route config
196
-     *
197
-     * @var array|callable|string|null
198
-     */
199
-    protected $_route = null;
200
-
201
-
202
-    /**
203
-     * @Constructor
204
-     * @param bool $routing indicate whether we want to just load the object and handle routing or just load the object.
205
-     * @throws InvalidArgumentException
206
-     * @throws InvalidDataTypeException
207
-     * @throws InvalidInterfaceException
208
-     * @throws ReflectionException
209
-     */
210
-    public function __construct($routing = true)
211
-    {
212
-        $this->_routing = $routing;
213
-
214
-        $this->loader       = LoaderFactory::getLoader();
215
-        $this->admin_config = $this->loader->getShared(EE_Admin_Config::class);
216
-        $this->feature      = $this->loader->getShared(FeatureFlags::class);
217
-        $this->request      = $this->loader->getShared(RequestInterface::class);
218
-        $this->capabilities = $this->loader->getShared(EE_Capabilities::class);
219
-
220
-        $this->class_name      = get_class($this);
221
-        $this->base_class_name = strpos($this->class_name, 'Extend_') === 0
222
-            ? str_replace('Extend_', '', $this->class_name)
223
-            : '';
224
-
225
-        if (strpos($this->_get_dir(), 'caffeinated') !== false) {
226
-            $this->_is_caf = true;
227
-        }
228
-        $this->_yes_no_values = [
229
-            ['id' => true, 'text' => esc_html__('Yes', 'event_espresso')],
230
-            ['id' => false, 'text' => esc_html__('No', 'event_espresso')],
231
-        ];
232
-        // set the _req_data property.
233
-        $this->_req_data = $this->request->requestParams();
234
-    }
235
-
236
-
237
-    /**
238
-     * @return EE_Admin_Config
239
-     */
240
-    public function adminConfig(): EE_Admin_Config
241
-    {
242
-        return $this->admin_config;
243
-    }
244
-
245
-
246
-    public function capabilities(): EE_Capabilities
247
-    {
248
-        if (! $this->capabilities instanceof EE_Capabilities) {
249
-            $this->capabilities = $this->loader->getShared(EE_Capabilities::class);
250
-        }
251
-        return $this->capabilities;
252
-    }
253
-
254
-
255
-    /**
256
-     * @return FeatureFlags
257
-     */
258
-    public function feature(): FeatureFlags
259
-    {
260
-        return $this->feature;
261
-    }
262
-
263
-
264
-    /**
265
-     * This logic used to be in the constructor, but that caused a chicken <--> egg scenario
266
-     * for child classes that needed to set properties prior to these methods getting called,
267
-     * but also needed the parent class to have its construction completed as well.
268
-     * Bottom line is that constructors should ONLY be used for setting initial properties
269
-     * and any complex initialization logic should only run after instantiation is complete.
270
-     * This method gets called immediately after construction from within
271
-     *      EE_Admin_Page_Init::_initialize_admin_page()
272
-     *
273
-     * @throws EE_Error
274
-     * @throws InvalidArgumentException
275
-     * @throws InvalidDataTypeException
276
-     * @throws InvalidInterfaceException
277
-     * @throws ReflectionException
278
-     * @throws Throwable
279
-     * @since 5.0.0.p
280
-     */
281
-    public function initializePage()
282
-    {
283
-        if ($this->initialized) {
284
-            return;
285
-        }
286
-        // set initial page props (child method)
287
-        $this->_init_page_props();
288
-        // set global defaults
289
-        $this->_set_defaults();
290
-        // set early because incoming requests could be ajax related and we need to register those hooks.
291
-        if ($this->request->isAjax()) {
292
-            $this->_global_ajax_hooks();
293
-            $this->_ajax_hooks();
294
-        }
295
-        // other_page_hooks have to be early too.
296
-        $this->_do_other_page_hooks();
297
-        // set up page dependencies
298
-        $this->_before_page_setup();
299
-        $this->_page_setup();
300
-        $this->initialized = true;
301
-    }
302
-
303
-
304
-    /**
305
-     * _init_page_props
306
-     * Child classes use to set at least the following properties:
307
-     * $page_slug.
308
-     * $page_label.
309
-     *
310
-     * @abstract
311
-     * @return void
312
-     */
313
-    abstract protected function _init_page_props();
314
-
315
-
316
-    /**
317
-     * _ajax_hooks
318
-     * child classes put all their add_action('wp_ajax_{name_of_hook}') hooks in here.
319
-     * Note: within the ajax callback methods.
320
-     *
321
-     * @abstract
322
-     * @return void
323
-     */
324
-    abstract protected function _ajax_hooks();
325
-
326
-
327
-    /**
328
-     * _define_page_props
329
-     * child classes define page properties in here.  Must include at least:
330
-     * $_admin_base_url = base_url for all admin pages
331
-     * $_admin_page_title = default admin_page_title for admin pages
332
-     * $_labels = array of default labels for various automatically generated elements:
333
-     *    array(
334
-     *        'buttons' => array(
335
-     *            'add' => esc_html__('label for add new button'),
336
-     *            'edit' => esc_html__('label for edit button'),
337
-     *            'delete' => esc_html__('label for delete button')
338
-     *            )
339
-     *        )
340
-     *
341
-     * @abstract
342
-     * @return void
343
-     */
344
-    abstract protected function _define_page_props();
345
-
346
-
347
-    /**
348
-     * _set_page_routes
349
-     * child classes use this to define the page routes for all subpages handled by the class.  Page routes are
350
-     * assigned to an action => method pairs in an array and to the $_page_routes property.  Each page route must also
351
-     * have a 'default' route. Here's the format
352
-     * $this->_page_routes = array(
353
-     *        'default' => array(
354
-     *            'func' => '_default_method_handling_route',
355
-     *            'args' => array('array','of','args'),
356
-     *            'noheader' => true, //add this in if this page route is processed before any headers are loaded (i.e.
357
-     *            ajax request, backend processing)
358
-     *            'headers_sent_route'=>'headers_route_reference', //add this if noheader=>true, and you want to load a
359
-     *            headers route after.  The string you enter here should match the defined route reference for a
360
-     *            headers sent route.
361
-     *            'capability' => 'route_capability', //indicate a string for minimum capability required to access
362
-     *            this route.
363
-     *            'obj_id' => 10 // if this route has an object id, then this can include it (used for capability
364
-     *            checks).
365
-     *        ),
366
-     *        'insert_item' => '_method_for_handling_insert_item' //this can be used if all we need to have is a
367
-     *        handling method.
368
-     *        )
369
-     * )
370
-     *
371
-     * @abstract
372
-     * @return void
373
-     */
374
-    abstract protected function _set_page_routes();
375
-
376
-
377
-    /**
378
-     * _set_page_config
379
-     * child classes use this to define the _page_config array for all subpages handled by the class. Each key in the
380
-     * array corresponds to the page_route for the loaded page. Format:
381
-     * $this->_page_config = array(
382
-     *        'default' => array(
383
-     *            'labels' => array(
384
-     *                'buttons' => array(
385
-     *                    'add' => esc_html__('label for adding item'),
386
-     *                    'edit' => esc_html__('label for editing item'),
387
-     *                    'delete' => esc_html__('label for deleting item')
388
-     *                ),
389
-     *                'publishbox' => esc_html__('Localized Title for Publish metabox', 'event_espresso')
390
-     *            ), //optional an array of custom labels for various automatically generated elements to use on the
391
-     *            page. If this isn't present then the defaults will be used as set for the $this->_labels in
392
-     *            _define_page_props() method
393
-     *            'nav' => array(
394
-     *                'label' => esc_html__('Label for Tab', 'event_espresso').
395
-     *                'url' => 'http://someurl', //automatically generated UNLESS you define
396
-     *                'css_class' => 'css-class', //automatically generated UNLESS you define
397
-     *                'order' => 10, //required to indicate tab position.
398
-     *                'persistent' => false //if you want the nav tab to ONLY display when the specific route is
399
-     *                displayed then add this parameter.
400
-     *            'list_table' => 'name_of_list_table' //string for list table class to be loaded for this admin_page.
401
-     *            'metaboxes' => array('metabox1', 'metabox2'), //if present this key indicates we want to load
402
-     *            metaboxes set for eventespresso admin pages.
403
-     *            'has_metaboxes' => true, //this boolean flag can simply be used to indicate if the route will have
404
-     *            metaboxes.  Typically this is used if the 'metaboxes' index is not used because metaboxes are added
405
-     *            later.  We just use this flag to make sure the necessary js gets enqueued on page load.
406
-     *            'has_help_popups' => false //defaults(true) //this boolean flag can simply be used to indicate if the
407
-     *            given route has help popups setup and if it does then we need to make sure thickbox is enqueued.
408
-     *            'columns' => array(4, 2), //this key triggers the setup of a page that uses columns (metaboxes).  The
409
-     *            array indicates the max number of columns (4) and the default number of columns on page load (2).
410
-     *            There is an option in the "screen_options" dropdown that is set up so users can pick what columns they
411
-     *            want to display.
412
-     *            'help_tabs' => array( //this is used for adding help tabs to a page
413
-     *                'tab_id' => array(
414
-     *                    'title' => 'tab_title',
415
-     *                    'filename' => 'name_of_file_containing_content', //this is the primary method for setting
416
-     *                    help tab content.  The fallback if it isn't present is to try the callback.  Filename
417
-     *                    should match a file in the admin folder's "help_tabs" dir (ie..
418
-     *                    events/help_tabs/name_of_file_containing_content.help_tab.php)
419
-     *                    'callback' => 'callback_method_for_content', //if 'filename' isn't present then system will
420
-     *                    attempt to use the callback which should match the name of a method in the class
421
-     *                    ),
422
-     *                'tab2_id' => array(
423
-     *                    'title' => 'tab2 title',
424
-     *                    'filename' => 'file_name_2'
425
-     *                    'callback' => 'callback_method_for_content',
426
-     *                 ),
427
-     *            'help_sidebar' => 'callback_for_sidebar_content', //this is used for setting up the sidebar in the
428
-     *            help tab area on an admin page. @return void
429
-     *
430
-     * @abstract
431
-     */
432
-    abstract protected function _set_page_config();
433
-
434
-
435
-    /**
436
-     * _add_screen_options
437
-     * Child classes can add any extra wp_screen_options within this method using built-in WP functions/methods for
438
-     * doing so. Note child classes can also define _add_screen_options_($this->_current_view) to limit screen options
439
-     * to a particular view.
440
-     *
441
-     * @link   http://chrismarslender.com/wp-tutorials/wordpress-screen-options-tutorial/
442
-     *         see also WP_Screen object documents...
443
-     * @link   http://codex.wordpress.org/Class_Reference/WP_Screen
444
-     * @abstract
445
-     * @return void
446
-     */
447
-    abstract protected function _add_screen_options();
448
-
449
-
450
-    /**
451
-     * _add_feature_pointers
452
-     * Child classes should use this method for implementing any "feature pointers" (using built-in WP styling js).
453
-     * Note child classes can also define _add_feature_pointers_($this->_current_view) to limit screen options to a
454
-     * particular view. Note: this is just a placeholder for now.  Implementation will come down the road See:
455
-     * WP_Internal_Pointers class in wp-admin/includes/template.php for example (it's a final class so can't be
456
-     * extended) also see:
457
-     *
458
-     * @link   http://eamann.com/tech/wordpress-portland/
459
-     * @abstract
460
-     * @return void
461
-     */
462
-    abstract protected function _add_feature_pointers();
463
-
464
-
465
-    /**
466
-     * load_scripts_styles
467
-     * child classes put their wp_enqueue_script and wp_enqueue_style hooks in here for anything they need loaded for
468
-     * their pages/subpages.  Note this is for all pages/subpages of the system.  You can also load only specific
469
-     * scripts/styles per view by putting them in a dynamic function in this format
470
-     * (load_scripts_styles_{$this->_current_view}) which matches your page route (action request arg)
471
-     *
472
-     * @abstract
473
-     * @return void
474
-     */
475
-    abstract public function load_scripts_styles();
476
-
477
-
478
-    /**
479
-     * admin_init
480
-     * Anything that should be set/executed at 'admin_init' WP hook runtime should be put in here.  This will apply to
481
-     * all pages/views loaded by child class.
482
-     *
483
-     * @abstract
484
-     * @return void
485
-     */
486
-    abstract public function admin_init();
487
-
488
-
489
-    /**
490
-     * admin_notices
491
-     * Anything triggered by the 'admin_notices' WP hook should be put in here.  This particular method will apply to
492
-     * all pages/views loaded by child class.
493
-     *
494
-     * @abstract
495
-     * @return void
496
-     */
497
-    abstract public function admin_notices();
498
-
499
-
500
-    /**
501
-     * admin_footer_scripts
502
-     * Anything triggered by the 'admin_print_footer_scripts' WP hook should be put in here. This particular method
503
-     * will apply to all pages/views loaded by child class.
504
-     *
505
-     * @return void
506
-     */
507
-    abstract public function admin_footer_scripts();
508
-
509
-
510
-    /**
511
-     * admin_footer
512
-     * anything triggered by the 'admin_footer' WP action hook should be added to here. This particular method will
513
-     * apply to all pages/views loaded by child class.
514
-     *
515
-     * @return void
516
-     */
517
-    public function admin_footer()
518
-    {
519
-    }
43
+	protected ?WP_Screen $_current_screen    = null;
520 44
 
45
+	/**
46
+	 * @var array
47
+	 * @since 5.0.0.p
48
+	 */
49
+	private array $publish_post_meta_box_hidden_fields = [];
521 50
 
522
-    /**
523
-     * _global_ajax_hooks
524
-     * all global add_action('wp_ajax_{name_of_hook}') hooks in here.
525
-     * Note: within the ajax callback methods.
526
-     *
527
-     * @abstract
528
-     * @return void
529
-     */
530
-    protected function _global_ajax_hooks()
531
-    {
532
-        // for lazy loading of metabox content
533
-        add_action('wp_ajax_espresso-ajax-content', [$this, 'ajax_metabox_content']);
534
-
535
-        add_action(
536
-            'wp_ajax_espresso_hide_status_change_notice',
537
-            [$this, 'hideStatusChangeNotice']
538
-        );
539
-        add_action(
540
-            'wp_ajax_nopriv_espresso_hide_status_change_notice',
541
-            [$this, 'hideStatusChangeNotice']
542
-        );
543
-    }
544
-
545
-
546
-    public function ajax_metabox_content()
547
-    {
548
-        $content_id  = $this->request->getRequestParam('contentid', '');
549
-        $content_url = $this->request->getRequestParam('contenturl', '', DataType::URL);
550
-        EE_Admin_Page::cached_rss_display($content_id, $content_url);
551
-        wp_die();
552
-    }
553
-
554
-
555
-    public function hideStatusChangeNotice()
556
-    {
557
-        $response = [];
558
-        try {
559
-            /** @var StatusChangeNotice $status_change_notice */
560
-            $status_change_notice = $this->loader->getShared(
561
-                'EventEspresso\core\domain\services\admin\notices\status_change\StatusChangeNotice'
562
-            );
563
-            $response['success']  = $status_change_notice->dismiss() > -1;
564
-        } catch (Exception $exception) {
565
-            $response['errors'] = $exception->getMessage();
566
-        }
567
-        wp_send_json($response);
568
-    }
51
+	/**
52
+	 * some default things shared by all child classes
53
+	 *
54
+	 * @var string[]
55
+	 */
56
+	protected array $_default_espresso_metaboxes = [
57
+		'_espresso_news_post_box',
58
+		'_espresso_links_post_box',
59
+		'_espresso_ratings_request',
60
+		'_espresso_sponsors_post_box',
61
+	];
569 62
 
63
+	/**
64
+	 * Used to hold default query args for list table routes to help preserve stickiness of filters for carried out
65
+	 * actions.
66
+	 *
67
+	 * @since 4.6.x
68
+	 */
69
+	protected array $_default_route_query_args = [];
570 70
 
571
-    /**
572
-     * allows extending classes do something specific before the parent constructor runs _page_setup().
573
-     *
574
-     * @return void
575
-     */
576
-    protected function _before_page_setup()
577
-    {
578
-        // default is to do nothing
579
-    }
71
+	protected array $_labels                   = [];
580 72
 
73
+	protected array $_nav_tabs                 = [];
581 74
 
582
-    /**
583
-     * Makes sure any things that need to be loaded early get handled.
584
-     * We also escape early here if the page requested doesn't match the object.
585
-     *
586
-     * @final
587
-     * @return void
588
-     * @throws EE_Error
589
-     * @throws InvalidArgumentException
590
-     * @throws ReflectionException
591
-     * @throws InvalidDataTypeException
592
-     * @throws InvalidInterfaceException
593
-     * @throws Throwable
594
-     */
595
-    final protected function _page_setup()
596
-    {
597
-        // requires?
598
-        // admin_init stuff - global - we're setting this REALLY early
599
-        // so if EE_Admin pages have to hook into other WP pages they can.
600
-        // But keep in mind, not everything is available from the EE_Admin Page object at this point.
601
-        add_action('admin_init', [$this, 'admin_init_global'], 5);
602
-        // next verify if we need to load anything...
603
-        $this->_current_page = $this->request->getRequestParam('page', '', DataType::KEY);
604
-        $this->_current_page = $this->request->getRequestParam('current_page', $this->_current_page, DataType::KEY);
605
-        $this->page_folder   = strtolower(
606
-            str_replace(['_Admin_Page', 'Extend_'], '', $this->class_name)
607
-        );
608
-        global $ee_menu_slugs;
609
-        $ee_menu_slugs = (array) $ee_menu_slugs;
610
-        if (
611
-            ! $this->request->isAjax()
612
-            && (! $this->_current_page || ! isset($ee_menu_slugs[ $this->_current_page ]))
613
-        ) {
614
-            return;
615
-        }
616
-        // because WP List tables have two duplicate select inputs for choosing bulk actions,
617
-        // we need to copy the action from the second to the first
618
-        $action     = $this->request->getRequestParam('action', '-1', DataType::KEY);
619
-        $action2    = $this->request->getRequestParam('action2', '-1', DataType::KEY);
620
-        $action     = $action !== '-1' ? $action : $action2;
621
-        $req_action = $action !== '-1' ? $action : 'default';
622
-
623
-        // if a specific 'route' has been set, and the action is 'default' OR we are doing_ajax
624
-        // then let's use the route as the action.
625
-        // This covers cases where we're coming in from a list table that isn't on the default route.
626
-        $route = $this->request->getRequestParam('route');
627
-        $route = $route !== '-1' ? $route : 'default';
628
-        $this->_req_action = $route && ($req_action === 'default' || $this->request->isAjax())
629
-            ? $route
630
-            : $req_action;
631
-        $this->_current_view = $this->_req_action;
632
-        $this->_req_nonce    = $this->_req_action . '_nonce';
633
-        $this->_define_page_props();
634
-        $this->_current_page_view_url = add_query_arg(
635
-            ['page' => $this->_current_page, 'action' => $this->_current_view],
636
-            $this->_admin_base_url
637
-        );
638
-        // set page configs
639
-        $this->_set_page_routes();
640
-        $this->_set_page_config();
641
-        // let's include any referrer data in our default_query_args for this route for "stickiness".
642
-        if ($this->request->requestParamIsSet('wp_referer')) {
643
-            $wp_referer = $this->request->getRequestParam('wp_referer');
644
-            if ($wp_referer) {
645
-                $this->_default_route_query_args['wp_referer'] = $wp_referer;
646
-            }
647
-        }
648
-        // for CPT and other extended functionality.
649
-        // If there is an _extend_page_config_for_cpt
650
-        // then let's run that to modify all the various page configuration arrays.
651
-        if (method_exists($this, '_extend_page_config_for_cpt')) {
652
-            $this->_extend_page_config_for_cpt();
653
-        }
654
-        // filter routes and page_config so addons can add their stuff. Filtering done per class
655
-        $this->_page_routes = apply_filters(
656
-            'FHEE__' . $this->class_name . '__page_setup__page_routes',
657
-            $this->_page_routes,
658
-            $this
659
-        );
660
-        $this->_page_config = apply_filters(
661
-            'FHEE__' . $this->class_name . '__page_setup__page_config',
662
-            $this->_page_config,
663
-            $this
664
-        );
665
-        if ($this->base_class_name !== '') {
666
-            $this->_page_routes = apply_filters(
667
-                'FHEE__' . $this->base_class_name . '__page_setup__page_routes',
668
-                $this->_page_routes,
669
-                $this
670
-            );
671
-            $this->_page_config = apply_filters(
672
-                'FHEE__' . $this->base_class_name . '__page_setup__page_config',
673
-                $this->_page_config,
674
-                $this
675
-            );
676
-        }
677
-        // if AHEE__EE_Admin_Page__route_admin_request_$this->_current_view method is present
678
-        // then we call it hooked into the AHEE__EE_Admin_Page__route_admin_request action
679
-        if (method_exists($this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view)) {
680
-            add_action(
681
-                'AHEE__EE_Admin_Page__route_admin_request',
682
-                [$this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view],
683
-                10,
684
-                2
685
-            );
686
-        }
687
-        // next route only if routing enabled
688
-        if ($this->_routing && ! $this->request->isAjax()) {
689
-            $this->_verify_routes();
690
-            // next let's just check user_access and kill if no access
691
-            $this->check_user_access();
692
-            if ($this->_is_UI_request) {
693
-                // admin_init stuff - global, all views for this page class, specific view
694
-                add_action('admin_init', [$this, 'admin_init']);
695
-                if (method_exists($this, 'admin_init_' . $this->_current_view)) {
696
-                    add_action('admin_init', [$this, 'admin_init_' . $this->_current_view], 15);
697
-                }
698
-            } else {
699
-                // hijack regular WP loading and route admin request immediately
700
-                @ini_set('memory_limit', apply_filters('admin_memory_limit', WP_MAX_MEMORY_LIMIT));
701
-                $this->route_admin_request();
702
-            }
703
-        }
704
-    }
75
+	protected array $_page_config              = [];
705 76
 
77
+	/**
78
+	 * action => method pairs used for routing incoming requests
79
+	 *
80
+	 * @var array
81
+	 */
82
+	protected array $_page_routes   = [];
706 83
 
707
-    /**
708
-     * Provides a way for related child admin pages to load stuff on the loaded admin page.
709
-     *
710
-     * @return void
711
-     * @throws EE_Error
712
-     */
713
-    private function _do_other_page_hooks()
714
-    {
715
-        $registered_pages = apply_filters('FHEE_do_other_page_hooks_' . $this->page_slug, []);
716
-        foreach ($registered_pages as $page) {
717
-            // now let's set up the file name and class that should be present
718
-            $classname = str_replace('.class.php', '', $page);
719
-            // autoloaders should take care of loading file
720
-            if (! class_exists($classname)) {
721
-                $error_msg[] = sprintf(
722
-                    esc_html__(
723
-                        'Something went wrong with loading the %s admin hooks page.',
724
-                        'event_espresso'
725
-                    ),
726
-                    $page
727
-                );
728
-                $error_msg[] = $error_msg[0]
729
-                               . "\r\n"
730
-                               . sprintf(
731
-                                   esc_html__(
732
-                                       'There is no class in place for the %1$s admin hooks page.%2$sMake sure you have %3$s defined. If this is a non-EE-core admin page then you also must have an autoloader in place for your class',
733
-                                       'event_espresso'
734
-                                   ),
735
-                                   $page,
736
-                                   '<br />',
737
-                                   '<strong>' . $classname . '</strong>'
738
-                               );
739
-                throw new EE_Error(implode('||', $error_msg));
740
-            }
741
-            // don't load the same class twice
742
-            static $loaded = [];
743
-            if (in_array($classname, $loaded, true)) {
744
-                continue;
745
-            }
746
-            $loaded[] = $classname;
747
-            // notice we are passing the instance of this class to the hook object.
748
-            $this->loader->getShared($classname, [$this]);
749
-        }
750
-    }
84
+	protected array $_req_data      = [];
751 85
 
86
+	protected array $_route_config  = [];
752 87
 
753
-    /**
754
-     * @throws ReflectionException
755
-     * @throws EE_Error
756
-     */
757
-    public function load_page_dependencies()
758
-    {
759
-        try {
760
-            $this->_load_page_dependencies();
761
-        } catch (EE_Error $e) {
762
-            $e->get_error();
763
-        }
764
-    }
88
+	protected array $_template_args = [];
765 89
 
90
+	protected array $_views         = [];
766 91
 
767
-    /**
768
-     * load_page_dependencies
769
-     * loads things specific to this page class when it's loaded.  Really helps with efficiency.
770
-     *
771
-     * @return void
772
-     * @throws DomainException
773
-     * @throws EE_Error
774
-     * @throws InvalidArgumentException
775
-     * @throws InvalidDataTypeException
776
-     * @throws InvalidInterfaceException
777
-     * @throws ReflectionException
778
-     */
779
-    protected function _load_page_dependencies()
780
-    {
781
-        // let's set the current_screen and screen options to override what WP set
782
-        $this->_current_screen = get_current_screen();
783
-        // load admin_notices - global, page class, and view specific
784
-        add_action('admin_notices', [$this, 'admin_notices_global'], 5);
785
-        add_action('admin_notices', [$this, 'admin_notices']);
786
-        if (method_exists($this, 'admin_notices_' . $this->_current_view)) {
787
-            add_action('admin_notices', [$this, 'admin_notices_' . $this->_current_view], 15);
788
-        }
789
-        // load network admin_notices - global, page class, and view specific
790
-        add_action('network_admin_notices', [$this, 'network_admin_notices_global'], 5);
791
-        if (method_exists($this, 'network_admin_notices_' . $this->_current_view)) {
792
-            add_action('network_admin_notices', [$this, 'network_admin_notices_' . $this->_current_view]);
793
-        }
794
-        // this will save any per_page screen options if they are present
795
-        $this->_set_per_page_screen_options();
796
-        // setup list table properties
797
-        $this->_set_list_table();
798
-        // child classes can "register" a metabox to be automatically handled via the _page_config array property.
799
-        // However in some cases the metaboxes will need to be added within a route handling callback.
800
-        add_action('add_meta_boxes', [$this, 'addRegisteredMetaBoxes'], 99);
801
-        // hack because promos admin was loading the edited promotion object in the metaboxes callback
802
-        // which should NOT be generated on non-UI requests like POST updates/inserts
803
-        if (
804
-            $this->class_name === 'Promotions_Admin_Page'
805
-            && ($this->_req_action === 'edit' || $this->_req_action === 'create_new')
806
-        ) {
807
-            $this->addRegisteredMetaBoxes();
808
-        }
809
-        $this->_add_screen_columns();
810
-        // add screen options - global, page child class, and view specific
811
-        $this->_add_global_screen_options();
812
-        $this->_add_screen_options();
813
-        $add_screen_options = "_add_screen_options_$this->_current_view";
814
-        if (method_exists($this, $add_screen_options)) {
815
-            $this->{$add_screen_options}();
816
-        }
817
-        // add help tab(s) - set via page_config and qtips.
818
-        $this->_add_help_tabs();
819
-        $this->_add_qtips();
820
-        // add feature_pointers - global, page child class, and view specific
821
-        $this->_add_feature_pointers();
822
-        $this->_add_global_feature_pointers();
823
-        $add_feature_pointer = "_add_feature_pointer_$this->_current_view";
824
-        if (method_exists($this, $add_feature_pointer)) {
825
-            $this->{$add_feature_pointer}();
826
-        }
827
-        // enqueue scripts/styles - global, page class, and view specific
828
-        add_action('admin_enqueue_scripts', [$this, 'admin_footer_scripts_eei18n_js_strings'], 1);
829
-        add_action('admin_enqueue_scripts', [$this, 'load_global_scripts_styles'], 5);
830
-        add_action('admin_enqueue_scripts', [$this, 'load_scripts_styles']);
831
-        if (method_exists($this, "load_scripts_styles_$this->_current_view")) {
832
-            add_action('admin_enqueue_scripts', [$this, "load_scripts_styles_$this->_current_view"], 15);
833
-        }
834
-        // admin_print_footer_scripts - global, page child class, and view specific.
835
-        // NOTE, despite the name, whenever possible, scripts should NOT be loaded using this.
836
-        // In most cases that's doing_it_wrong().  But adding hidden container elements etc.
837
-        // is a good use case. Notice the late priority we're giving these
838
-        add_action('admin_print_footer_scripts', [$this, 'admin_footer_scripts_global'], 99);
839
-        add_action('admin_print_footer_scripts', [$this, 'admin_footer_scripts'], 100);
840
-        if (method_exists($this, "admin_footer_scripts_$this->_current_view")) {
841
-            add_action('admin_print_footer_scripts', [$this, "admin_footer_scripts_$this->_current_view"], 101);
842
-        }
843
-        // admin footer scripts
844
-        add_action('admin_footer', [$this, 'admin_footer_global'], 99);
845
-        add_action('admin_footer', [$this, 'admin_footer'], 100);
846
-        if (method_exists($this, "admin_footer_$this->_current_view")) {
847
-            add_action('admin_footer', [$this, "admin_footer_$this->_current_view"], 101);
848
-        }
849
-        do_action('FHEE__EE_Admin_Page___load_page_dependencies__after_load', $this->page_slug);
850
-        // targeted hook
851
-        do_action(
852
-            "FHEE__EE_Admin_Page___load_page_dependencies__after_load__{$this->page_slug}__$this->_req_action"
853
-        );
854
-    }
92
+	/**
93
+	 * yes / no array for admin form fields
94
+	 *
95
+	 * @var array|array[]
96
+	 */
97
+	protected array $_yes_no_values = [];
855 98
 
99
+	/**
100
+	 * this starts at null so we can have no header routes progress through two states.
101
+	 */
102
+	protected ?bool $_is_UI_request = null;
856 103
 
857
-    /**
858
-     * _set_defaults
859
-     * This sets some global defaults for class properties.
860
-     */
861
-    private function _set_defaults()
862
-    {
863
-        // init template args
864
-        $this->set_template_args(
865
-            [
866
-                'admin_page_header'  => '',
867
-                'admin_page_content' => '',
868
-                'post_body_content'  => '',
869
-                'before_list_table'  => '',
870
-                'after_list_table'   => '',
871
-            ]
872
-        );
873
-    }
104
+	/**
105
+	 * flags whether the given route is a caffeinated route or not.
106
+	 */
107
+	protected bool $_is_caf        = false;
874 108
 
109
+	protected bool $_routing       = false;
875 110
 
876
-    /**
877
-     * route_admin_request
878
-     *
879
-     * @return void
880
-     * @throws InvalidArgumentException
881
-     * @throws InvalidInterfaceException
882
-     * @throws InvalidDataTypeException
883
-     * @throws EE_Error
884
-     * @throws ReflectionException
885
-     * @throws Throwable
886
-     * @see    _route_admin_request()
887
-     */
888
-    public function route_admin_request()
889
-    {
890
-        try {
891
-            $this->_route_admin_request();
892
-        } catch (EE_Error $e) {
893
-            $e->get_error();
894
-        }
895
-    }
896
-
897
-
898
-    public function set_wp_page_slug($wp_page_slug)
899
-    {
900
-        $this->_wp_page_slug = $wp_page_slug;
901
-        // if in network admin then we need to append "-network" to the page slug. Why? Because that's how WP rolls...
902
-        if (is_network_admin()) {
903
-            $this->_wp_page_slug .= '-network';
904
-        }
905
-    }
111
+	/**
112
+	 * whether initializePage() has run
113
+	 *
114
+	 * @var bool
115
+	 */
116
+	protected bool $initialized = false;
906 117
 
907 118
 
908
-    /**
909
-     * _verify_routes
910
-     * All this method does is verify the incoming request and make sure that routes exist for it.  We do this early so
911
-     * we know if we need to drop out.
912
-     *
913
-     * @return bool
914
-     * @throws EE_Error
915
-     */
916
-    protected function _verify_routes(): bool
917
-    {
918
-        if (! $this->_current_page && ! $this->request->isAjax()) {
919
-            return false;
920
-        }
921
-        // check that the page_routes array is not empty
922
-        if (empty($this->_page_routes)) {
923
-            // user error msg
924
-            $error_msg = sprintf(
925
-                esc_html__('No page routes have been set for the %s admin page.', 'event_espresso'),
926
-                $this->_admin_page_title
927
-            );
928
-            // developer error msg
929
-            $error_msg .= '||' . $error_msg
930
-                          . esc_html__(
931
-                              ' Make sure the "set_page_routes()" method exists, and is setting the "_page_routes" array properly.',
932
-                              'event_espresso'
933
-                          );
934
-            throw new EE_Error($error_msg);
935
-        }
936
-        // route 'editpost' routes to CPT 'edit' routes
937
-        $alt_edit_route = $this instanceof EE_Admin_Page_CPT ? $this->cpt_editpost_route : 'edit';
938
-        if (
939
-            $this->_req_action === 'editpost'
940
-            && ! isset($this->_page_routes['editpost'])
941
-            && isset($this->_page_routes[ $alt_edit_route ])
942
-        ) {
943
-            $this->_req_action = $alt_edit_route;
944
-        }
945
-        // and that the requested page route exists
946
-        if (array_key_exists($this->_req_action, $this->_page_routes)) {
947
-            $this->_route        = $this->_page_routes[ $this->_req_action ];
948
-            $this->_route_config = $this->_page_config[ $this->_req_action ] ?? [];
949
-        } else {
950
-            // user error msg
951
-            $error_msg = sprintf(
952
-                esc_html__(
953
-                    'The requested page route does not exist for the %s admin page.',
954
-                    'event_espresso'
955
-                ),
956
-                $this->_admin_page_title
957
-            );
958
-            // developer error msg
959
-            $error_msg .= '||' . $error_msg
960
-                          . sprintf(
961
-                              esc_html__(
962
-                                  ' Create a key in the "_page_routes" array named "%s" and set its value to the appropriate method.',
963
-                                  'event_espresso'
964
-                              ),
965
-                              $this->_req_action
966
-                          );
967
-            throw new EE_Error($error_msg);
968
-        }
969
-        // and that a default route exists
970
-        if (! array_key_exists('default', $this->_page_routes)) {
971
-            // user error msg
972
-            $error_msg = sprintf(
973
-                esc_html__(
974
-                    'A default page route has not been set for the % admin page.',
975
-                    'event_espresso'
976
-                ),
977
-                $this->_admin_page_title
978
-            );
979
-            // developer error msg
980
-            $error_msg .= '||' . $error_msg
981
-                          . esc_html__(
982
-                              ' Create a key in the "_page_routes" array named "default" and set its value to your default page method.',
983
-                              'event_espresso'
984
-                          );
985
-            throw new EE_Error($error_msg);
986
-        }
987
-
988
-        // first lets' catch if the UI request has EVER been set.
989
-        if ($this->_is_UI_request === null) {
990
-            // let's set if this is a UI request or not.
991
-            $this->_is_UI_request = ! $this->request->getRequestParam('noheader', false, DataType::BOOL);
992
-            // wait a minute... we might have a noheader in the route array
993
-            $this->_is_UI_request = ! (isset($this->_route['noheader']) && $this->_route['noheader'])
994
-                ? $this->_is_UI_request
995
-                : false;
996
-        }
997
-        $this->_set_current_labels();
998
-        return true;
999
-    }
119
+	protected string $_admin_base_path      = '';
1000 120
 
121
+	protected string $_admin_base_url       = '';
1001 122
 
1002
-    /**
1003
-     * this method simply verifies a given route and makes sure it's an actual route available for the loaded page
1004
-     *
1005
-     * @param string $route the route name we're verifying
1006
-     * @return bool we'll throw an exception if this isn't a valid route.
1007
-     * @throws EE_Error
1008
-     */
1009
-    protected function _verify_route(string $route): bool
1010
-    {
1011
-        if (array_key_exists($this->_req_action, $this->_page_routes)) {
1012
-            return true;
1013
-        }
1014
-        // user error msg
1015
-        $error_msg = sprintf(
1016
-            esc_html__('The given page route does not exist for the %s admin page.', 'event_espresso'),
1017
-            $this->_admin_page_title
1018
-        );
1019
-        // developer error msg
1020
-        $error_msg .= '||' . $error_msg
1021
-                      . sprintf(
1022
-                          esc_html__(
1023
-                              ' Check the route you are using in your method (%s) and make sure it matches a route set in your "_page_routes" array property',
1024
-                              'event_espresso'
1025
-                          ),
1026
-                          $route
1027
-                      );
1028
-        throw new EE_Error($error_msg);
1029
-    }
123
+	protected string $_admin_page_title     = '';
1030 124
 
125
+	protected string $_column_template_path = '';
1031 126
 
1032
-    /**
1033
-     * perform nonce verification
1034
-     * This method has be encapsulated here so that any ajax requests that bypass normal routes can verify their nonces
1035
-     * using this method (and save retyping!)
1036
-     *
1037
-     * @param string $nonce     The nonce sent
1038
-     * @param string $nonce_ref The nonce reference string (name0)
1039
-     * @return void
1040
-     * @throws EE_Error
1041
-     * @throws InvalidArgumentException
1042
-     * @throws InvalidDataTypeException
1043
-     * @throws InvalidInterfaceException
1044
-     */
1045
-    protected function _verify_nonce(string $nonce = '', string $nonce_ref = '')
1046
-    {
1047
-        $nonce = $nonce ?: $this->request->getRequestParam($this->_req_nonce, '');
1048
-        $nonce_ref = $nonce_ref ?: $this->_req_action;
1049
-        // verify nonce against expected value
1050
-        if (! wp_verify_nonce($nonce, $nonce_ref)) {
1051
-            // these are not the droids you are looking for !!!
1052
-            $msg = sprintf(
1053
-                esc_html__('%sNonce Fail.%s', 'event_espresso'),
1054
-                '<a href="https://www.youtube.com/watch?v=56_S0WeTkzs">',
1055
-                '</a>'
1056
-            );
1057
-            if (WP_DEBUG) {
1058
-                $msg .= "\n  ";
1059
-                $msg .= sprintf(
1060
-                    esc_html__(
1061
-                        'In order to dynamically generate nonces for your actions, use the %s::add_query_args_and_nonce() method. May the Nonce be with you!',
1062
-                        'event_espresso'
1063
-                    ),
1064
-                    __CLASS__
1065
-                );
1066
-            }
1067
-            if (! $this->request->isAjax()) {
1068
-                wp_die($msg);
1069
-            }
1070
-            EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
1071
-            $this->_return_json();
1072
-        }
1073
-    }
127
+	protected bool $_cpt_route            = false;
1074 128
 
129
+	/**
130
+	 * set via request page and action args.
131
+	 */
132
+	protected string $_current_page          = '';
1075 133
 
1076
-    /**
1077
-     * _route_admin_request()
1078
-     * Meat and potatoes of the class.  Basically, this dude checks out what's being requested and sees if there are
1079
-     * some doodads to work the magic and handle the flingjangy. Translation:  Checks if the requested action is listed
1080
-     * in the page routes and then will try to load the corresponding method.
1081
-     *
1082
-     * @return void
1083
-     * @throws EE_Error
1084
-     * @throws InvalidArgumentException
1085
-     * @throws InvalidDataTypeException
1086
-     * @throws InvalidInterfaceException
1087
-     * @throws ReflectionException
1088
-     * @throws Throwable
1089
-     */
1090
-    protected function _route_admin_request()
1091
-    {
1092
-        if (! $this->_is_UI_request) {
1093
-            $this->_verify_routes();
1094
-        }
1095
-        $nonce_check = ! isset($this->_route_config['require_nonce']) || $this->_route_config['require_nonce'];
1096
-        if ($this->_req_action !== 'default' && $nonce_check) {
1097
-            // set nonce from post data
1098
-            $nonce = $this->request->getRequestParam($this->_req_nonce, '');
1099
-            $this->_verify_nonce($nonce, $this->_req_nonce);
1100
-        }
1101
-        // set the nav_tabs array but ONLY if this is  UI_request
1102
-        if ($this->_is_UI_request) {
1103
-            $this->_set_nav_tabs();
1104
-        }
1105
-        // grab callback function
1106
-        $func = $this->_route['func'] ?? $this->_route;
1107
-        // check if callback has args
1108
-        $args = $this->_route['args'] ?? [];
1109
-
1110
-        // action right before calling route
1111
-        // (hook is something like 'AHEE__Registrations_Admin_Page__route_admin_request')
1112
-        if (! did_action('AHEE__EE_Admin_Page__route_admin_request')) {
1113
-            do_action('AHEE__EE_Admin_Page__route_admin_request', $this->_current_view, $this);
1114
-        }
1115
-        // strip _wp_http_referer from the server REQUEST_URI
1116
-        // else it grows in length on every submission due to recursion,
1117
-        // ultimately causing a "Request-URI Too Large" error
1118
-        $this->request->unSetRequestParam('_wp_http_referer');
1119
-        $this->request->unSetServerParam('_wp_http_referer');
1120
-        $cleaner_request_uri = remove_query_arg(
1121
-            '_wp_http_referer',
1122
-            wp_unslash($this->request->getServerParam('REQUEST_URI'))
1123
-        );
1124
-        $this->request->setRequestParam('_wp_http_referer', $cleaner_request_uri, true);
1125
-        $this->request->setServerParam('REQUEST_URI', $cleaner_request_uri, true);
1126
-        $route_callback = [];
1127
-        if (! empty($func)) {
1128
-            if (is_array($func) && is_callable($func)) {
1129
-                $route_callback = $func;
1130
-            } elseif (is_string($func)) {
1131
-                if (strpos($func, '::') !== false) {
1132
-                    $route_callback = explode('::', $func);
1133
-                } elseif (method_exists($this, $func)) {
1134
-                    $route_callback = [$this, $func];
1135
-                } else {
1136
-                    $route_callback = $func;
1137
-                }
1138
-            }
1139
-            [$class, $method] = $route_callback;
1140
-            // is it neither a class method NOR a standalone function?
1141
-            if (! is_callable($route_callback)) {
1142
-                // user error msg
1143
-                $error_msg = esc_html__(
1144
-                    'An error occurred. The  requested page route could not be found.',
1145
-                    'event_espresso'
1146
-                );
1147
-                // developer error msg
1148
-                $error_msg .= '||';
1149
-                $error_msg .= sprintf(
1150
-                    esc_html__(
1151
-                        'Page route "%s" could not be called. Check that the spelling for method names and actions in the "_page_routes" array are all correct.',
1152
-                        'event_espresso'
1153
-                    ),
1154
-                    $method
1155
-                );
1156
-                throw new DomainException($error_msg);
1157
-            }
1158
-            if ($class !== $this && ! in_array($this, $args)) {
1159
-                // send along this admin page object for access by addons.
1160
-                $args['admin_page'] = $this;
1161
-            }
1162
-
1163
-            call_user_func_array($route_callback, $args);
1164
-        }
1165
-        // if we've routed and this route has a no headers route AND a sent_headers_route,
1166
-        // then we need to reset the routing properties to the new route.
1167
-        // now if UI request is FALSE and noheader is true AND we have a headers_sent_route in the route array then let's set UI_request to true because the no header route has a second func after headers have been sent.
1168
-        if (
1169
-            $this->_is_UI_request === false
1170
-            && is_array($this->_route)
1171
-            && ! empty($this->_route['headers_sent_route'])
1172
-        ) {
1173
-            $this->_reset_routing_properties($this->_route['headers_sent_route']);
1174
-        }
1175
-    }
134
+	protected string $_current_page_view_url = '';
1176 135
 
136
+	protected string $_current_view          = '';
1177 137
 
1178
-    /**
1179
-     * This method just allows the resetting of page properties in the case where a no headers
1180
-     * route redirects to a headers route in its route config.
1181
-     *
1182
-     * @param string $new_route New (non header) route to redirect to.
1183
-     * @return void
1184
-     * @throws ReflectionException
1185
-     * @throws InvalidArgumentException
1186
-     * @throws InvalidInterfaceException
1187
-     * @throws InvalidDataTypeException
1188
-     * @throws EE_Error
1189
-     * @throws Throwable
1190
-     * @since   4.3.0
1191
-     */
1192
-    protected function _reset_routing_properties(string $new_route)
1193
-    {
1194
-        $this->_is_UI_request = true;
1195
-        // now we set the current route to whatever the headers_sent_route is set at
1196
-        $this->request->setRequestParam('action', $new_route);
1197
-        // rerun page setup
1198
-        $this->_page_setup();
1199
-    }
138
+	protected string $_default_nav_tab_name  = 'overview';
1200 139
 
140
+	/**
141
+	 * sanitized request action
142
+	 */
143
+	protected string $_req_action = '';
144
+
145
+	/**
146
+	 * sanitized request action nonce
147
+	 */
148
+	protected string $_req_nonce        = '';
149
+
150
+	protected string $_search_btn_label = '';
151
+
152
+	protected string $_template_path    = '';
153
+
154
+	protected string $_view             = '';
1201 155
 
1202
-    /**
1203
-     * _add_query_arg
1204
-     * adds nonce to array of arguments then calls WP add_query_arg function
1205
-     *(internally just uses EEH_URL's function with the same name)
1206
-     *
1207
-     * @param array  $args
1208
-     * @param string $url
1209
-     * @param bool   $sticky                  if true, then the existing Request params will be appended to the
1210
-     *                                        generated url in an associative array indexed by the key 'wp_referer';
1211
-     *                                        Example usage: If the current page is:
1212
-     *                                        http://mydomain.com/wp-admin/admin.php?page=espresso_registrations
1213
-     *                                        &action=default&event_id=20&month_range=March%202015
1214
-     *                                        &_wpnonce=5467821
1215
-     *                                        and you call:
1216
-     *                                        EE_Admin_Page::add_query_args_and_nonce(
1217
-     *                                        array(
1218
-     *                                        'action' => 'resend_something',
1219
-     *                                        'page=>espresso_registrations'
1220
-     *                                        ),
1221
-     *                                        $some_url,
1222
-     *                                        true
1223
-     *                                        );
1224
-     *                                        It will produce a URL in this structure:
1225
-     *                                        http://{$some_url}/?page=espresso_registrations&action=resend_something
1226
-     *                                        &wp_referer[action]=default&wp_referer[event_id]=20&wpreferer[
1227
-     *                                        month_range]=March%202015
1228
-     * @param bool   $exclude_nonce           If true, the nonce will be excluded from the generated nonce.
1229
-     * @param int    $context
1230
-     * @return string
1231
-     */
1232
-    public static function add_query_args_and_nonce(
1233
-        array $args = [],
1234
-        string $url = '',
1235
-        bool $sticky = false,
1236
-        bool $exclude_nonce = false,
1237
-        int $context = EEH_URL::CONTEXT_NONE
1238
-    ): string {
1239
-        // if there is a _wp_http_referer include the values from the request but only if sticky = true
1240
-        if ($sticky) {
1241
-            /** @var RequestInterface $request */
1242
-            $request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
1243
-            $request->unSetRequestParams(['_wp_http_referer', 'wp_referer'], true);
1244
-            $request->unSetServerParam('_wp_http_referer', true);
1245
-            foreach ($request->requestParams() as $key => $value) {
1246
-                // do not add nonces
1247
-                if (strpos($key, 'nonce') !== false) {
1248
-                    continue;
1249
-                }
1250
-                $args[ 'wp_referer[' . $key . ']' ] = is_string($value) ? htmlspecialchars($value) : $value;
1251
-            }
1252
-        }
1253
-        return EEH_URL::add_query_args_and_nonce($args, $url, $exclude_nonce, $context);
1254
-    }
156
+	/**
157
+	 * set early within EE_Admin_Init
158
+	 *
159
+	 * @var string
160
+	 */
161
+	protected string $_wp_page_slug = '';
1255 162
 
163
+	/**
164
+	 * if the current class is an admin page extension, like: Extend_Events_Admin_Page,
165
+	 * then this would be the parent classname: Events_Admin_Page
166
+	 *
167
+	 * @var string
168
+	 */
169
+	public string $base_class_name = '';
1256 170
 
1257
-    /**
1258
-     * This returns a generated link that will load the related help tab.
1259
-     *
1260
-     * @param string $help_tab_id the id for the connected help tab
1261
-     * @param string $icon_style  (optional) include css class for the style you want to use for the help icon.
1262
-     * @param string $help_text   (optional) send help text you want to use for the link if default not to be used
1263
-     * @return string              generated link
1264
-     * @uses EEH_Template::get_help_tab_link()
1265
-     */
1266
-    protected function _get_help_tab_link(string $help_tab_id, string $icon_style = '', string $help_text = ''): string
1267
-    {
1268
-        return EEH_Template::get_help_tab_link(
1269
-            $help_tab_id,
1270
-            $this->page_slug,
1271
-            $this->_req_action,
1272
-            $icon_style,
1273
-            $help_text
1274
-        );
1275
-    }
1276
-
1277
-
1278
-    /**
1279
-     * _add_help_tabs
1280
-     * Note child classes define their help tabs within the page_config array.
1281
-     *
1282
-     * @link   http://codex.wordpress.org/Function_Reference/add_help_tab
1283
-     * @return void
1284
-     * @throws DomainException
1285
-     * @throws EE_Error
1286
-     * @throws ReflectionException
1287
-     */
1288
-    protected function _add_help_tabs()
1289
-    {
1290
-        if (isset($this->_page_config[ $this->_req_action ])) {
1291
-            $config = $this->_page_config[ $this->_req_action ];
1292
-            // let's see if there is a help_sidebar set for the current route and we'll set that up for usage as well.
1293
-            if (is_array($config) && isset($config['help_sidebar'])) {
1294
-                // check that the callback given is valid
1295
-                if (! method_exists($this, $config['help_sidebar'])) {
1296
-                    throw new EE_Error(
1297
-                        sprintf(
1298
-                            esc_html__(
1299
-                                'The _page_config array has a callback set for the "help_sidebar" option.  However the callback given (%s) is not a valid callback.  Double check the spelling and make sure this method exists for the class %s',
1300
-                                'event_espresso'
1301
-                            ),
1302
-                            $config['help_sidebar'],
1303
-                            $this->class_name
1304
-                        )
1305
-                    );
1306
-                }
1307
-                $content = apply_filters(
1308
-                    'FHEE__' . $this->class_name . '__add_help_tabs__help_sidebar',
1309
-                    $this->{$config['help_sidebar']}()
1310
-                );
1311
-                $this->_current_screen->set_help_sidebar($content);
1312
-            }
1313
-            if (! isset($config['help_tabs'])) {
1314
-                return;
1315
-            } //no help tabs for this route
1316
-            foreach ((array) $config['help_tabs'] as $tab_id => $cfg) {
1317
-                // we're here so there ARE help tabs!
1318
-                // make sure we've got what we need
1319
-                if (! isset($cfg['title'])) {
1320
-                    throw new EE_Error(
1321
-                        esc_html__(
1322
-                            'The _page_config array is not set up properly for help tabs.  It is missing a title',
1323
-                            'event_espresso'
1324
-                        )
1325
-                    );
1326
-                }
1327
-                if (! isset($cfg['filename']) && ! isset($cfg['callback']) && ! isset($cfg['content'])) {
1328
-                    throw new EE_Error(
1329
-                        esc_html__(
1330
-                            'The _page_config array is not setup properly for help tabs. It is missing a either a filename reference, or a callback reference or a content reference so there is no way to know the content for the help tab',
1331
-                            'event_espresso'
1332
-                        )
1333
-                    );
1334
-                }
1335
-                // first priority goes to content.
1336
-                if (! empty($cfg['content'])) {
1337
-                    $content = $cfg['content'];
1338
-                    // second priority goes to filename
1339
-                } elseif (! empty($cfg['filename'])) {
1340
-                    $file_path = $this->_get_dir() . '/help_tabs/' . $cfg['filename'] . '.help_tab.php';
1341
-                    // it's possible that the file is located on decaf route (and above sets up for caf route, if this is the case then lets check decaf route too)
1342
-                    $file_path = ! is_readable($file_path) ? EE_ADMIN_PAGES
1343
-                                                             . basename($this->_get_dir())
1344
-                                                             . '/help_tabs/'
1345
-                                                             . $cfg['filename']
1346
-                                                             . '.help_tab.php' : $file_path;
1347
-                    // if file is STILL not readable then let's do an EE_Error so its more graceful than a fatal error.
1348
-                    if (! isset($cfg['callback']) && ! is_readable($file_path)) {
1349
-                        EE_Error::add_error(
1350
-                            sprintf(
1351
-                                esc_html__(
1352
-                                    'The filename given for the help tab %s is not a valid file and there is no other configuration for the tab content.  Please check that the string you set for the help tab on this route (%s) is the correct spelling.  The file should be in %s',
1353
-                                    'event_espresso'
1354
-                                ),
1355
-                                $tab_id,
1356
-                                key($config),
1357
-                                $file_path
1358
-                            ),
1359
-                            __FILE__,
1360
-                            __FUNCTION__,
1361
-                            __LINE__
1362
-                        );
1363
-                        return;
1364
-                    }
1365
-                    $template_args['admin_page_obj'] = $this;
1366
-                    $content                         = EEH_Template::display_template(
1367
-                        $file_path,
1368
-                        $template_args,
1369
-                        true
1370
-                    );
1371
-                } else {
1372
-                    $content = '';
1373
-                }
1374
-                // check if callback is valid
1375
-                if (
1376
-                    empty($content)
1377
-                    && (
1378
-                        ! isset($cfg['callback']) || ! method_exists($this, $cfg['callback'])
1379
-                    )
1380
-                ) {
1381
-                    EE_Error::add_error(
1382
-                        sprintf(
1383
-                            esc_html__(
1384
-                                'The callback given for a %s help tab on this page does not content OR a corresponding method for generating the content.  Check the spelling or make sure the method is present.',
1385
-                                'event_espresso'
1386
-                            ),
1387
-                            $cfg['title']
1388
-                        ),
1389
-                        __FILE__,
1390
-                        __FUNCTION__,
1391
-                        __LINE__
1392
-                    );
1393
-                    return;
1394
-                }
1395
-                // setup config array for help tab method
1396
-                $id  = $this->page_slug . '-' . $this->_req_action . '-' . $tab_id;
1397
-                $_ht = [
1398
-                    'id'       => $id,
1399
-                    'title'    => $cfg['title'],
1400
-                    'callback' => isset($cfg['callback']) && empty($content) ? [$this, $cfg['callback']] : null,
1401
-                    'content'  => $content,
1402
-                ];
1403
-                $this->_current_screen->add_help_tab($_ht);
1404
-            }
1405
-        }
1406
-    }
1407
-
1408
-
1409
-    /**
1410
-     * This simply sets up any qtips that have been defined in the page config
1411
-     *
1412
-     * @return void
1413
-     * @throws ReflectionException
1414
-     * @throws EE_Error
1415
-     */
1416
-    protected function _add_qtips()
1417
-    {
1418
-        if (isset($this->_route_config['qtips'])) {
1419
-            $qtips = (array) $this->_route_config['qtips'];
1420
-            // load qtip loader
1421
-            $path = [
1422
-                $this->_get_dir() . '/qtips/',
1423
-                EE_ADMIN_PAGES . basename($this->_get_dir()) . '/qtips/',
1424
-            ];
1425
-            EEH_Qtip_Loader::instance()->register($qtips, $path);
1426
-        }
1427
-    }
171
+	public string $class_name      = '';
1428 172
 
1429
-
1430
-    /**
1431
-     * _set_nav_tabs
1432
-     * This sets up the nav tabs from the page_routes array.  This method can be overwritten by child classes if you
1433
-     * wish to add additional tabs or modify accordingly.
1434
-     *
1435
-     * @return void
1436
-     * @throws InvalidArgumentException
1437
-     * @throws InvalidInterfaceException
1438
-     * @throws InvalidDataTypeException
1439
-     */
1440
-    protected function _set_nav_tabs()
1441
-    {
1442
-        $i        = 0;
1443
-        $only_tab = count($this->_page_config) < 2;
1444
-        foreach ($this->_page_config as $slug => $config) {
1445
-            if (! is_array($config) || empty($config['nav'])) {
1446
-                continue;
1447
-            }
1448
-            // no nav tab for this config
1449
-            // check for persistent flag
1450
-            if ($slug !== $this->_req_action && isset($config['nav']['persistent']) && ! $config['nav']['persistent']) {
1451
-                // nav tab is only to appear when route requested.
1452
-                continue;
1453
-            }
1454
-            if (! $this->check_user_access($slug, true)) {
1455
-                // no nav tab because current user does not have access.
1456
-                continue;
1457
-            }
1458
-            $css_class = $config['css_class'] ?? '';
1459
-            $css_class .= $only_tab ? ' ee-only-tab' : '';
1460
-            $css_class .= " ee-nav-tab__$slug";
1461
-
1462
-            $this->_nav_tabs[ $slug ] = [
1463
-                'url'       => $config['nav']['url'] ?? EE_Admin_Page::add_query_args_and_nonce(
1464
-                    ['action' => $slug],
1465
-                    $this->_admin_base_url
1466
-                ),
1467
-                'link_text' => $this->navTabLabel($config['nav'], $slug),
1468
-                'css_class' => $this->_req_action === $slug ? $css_class . ' nav-tab-active' : $css_class,
1469
-                'order'     => $config['nav']['order'] ?? $i,
1470
-            ];
1471
-            $i++;
1472
-        }
1473
-        // if $this->_nav_tabs is empty then lets set the default
1474
-        if (empty($this->_nav_tabs)) {
1475
-            $this->_nav_tabs[ $this->_default_nav_tab_name ] = [
1476
-                'url'       => $this->_admin_base_url,
1477
-                'link_text' => ucwords(str_replace('_', ' ', $this->_default_nav_tab_name)),
1478
-                'css_class' => 'nav-tab-active',
1479
-                'order'     => 10,
1480
-            ];
1481
-        }
1482
-        // now let's sort the tabs according to order
1483
-        usort($this->_nav_tabs, [$this, '_sort_nav_tabs']);
1484
-    }
1485
-
1486
-
1487
-    private function navTabLabel(array $nav_tab, string $slug): string
1488
-    {
1489
-        $label = $nav_tab['label'] ?? ucwords(str_replace('_', ' ', $slug));
1490
-        $icon  = $nav_tab['icon'] ?? null;
1491
-        $icon  = $icon ? '<span class="dashicons ' . $icon . '"></span>' : '';
1492
-        return '
173
+	/**
174
+	 * unprocessed value for the 'action' request param (default '')
175
+	 *
176
+	 * @var string
177
+	 */
178
+	protected string $raw_req_action = '';
179
+
180
+	/**
181
+	 * unprocessed value for the 'page' request param (default '')
182
+	 *
183
+	 * @var string
184
+	 */
185
+	protected string $raw_req_page = '';
186
+
187
+	public string $page_folder  = '';
188
+
189
+	public string $page_label   = '';
190
+
191
+	public string $page_slug    = '';
192
+
193
+
194
+	/**
195
+	 * the current page route and route config
196
+	 *
197
+	 * @var array|callable|string|null
198
+	 */
199
+	protected $_route = null;
200
+
201
+
202
+	/**
203
+	 * @Constructor
204
+	 * @param bool $routing indicate whether we want to just load the object and handle routing or just load the object.
205
+	 * @throws InvalidArgumentException
206
+	 * @throws InvalidDataTypeException
207
+	 * @throws InvalidInterfaceException
208
+	 * @throws ReflectionException
209
+	 */
210
+	public function __construct($routing = true)
211
+	{
212
+		$this->_routing = $routing;
213
+
214
+		$this->loader       = LoaderFactory::getLoader();
215
+		$this->admin_config = $this->loader->getShared(EE_Admin_Config::class);
216
+		$this->feature      = $this->loader->getShared(FeatureFlags::class);
217
+		$this->request      = $this->loader->getShared(RequestInterface::class);
218
+		$this->capabilities = $this->loader->getShared(EE_Capabilities::class);
219
+
220
+		$this->class_name      = get_class($this);
221
+		$this->base_class_name = strpos($this->class_name, 'Extend_') === 0
222
+			? str_replace('Extend_', '', $this->class_name)
223
+			: '';
224
+
225
+		if (strpos($this->_get_dir(), 'caffeinated') !== false) {
226
+			$this->_is_caf = true;
227
+		}
228
+		$this->_yes_no_values = [
229
+			['id' => true, 'text' => esc_html__('Yes', 'event_espresso')],
230
+			['id' => false, 'text' => esc_html__('No', 'event_espresso')],
231
+		];
232
+		// set the _req_data property.
233
+		$this->_req_data = $this->request->requestParams();
234
+	}
235
+
236
+
237
+	/**
238
+	 * @return EE_Admin_Config
239
+	 */
240
+	public function adminConfig(): EE_Admin_Config
241
+	{
242
+		return $this->admin_config;
243
+	}
244
+
245
+
246
+	public function capabilities(): EE_Capabilities
247
+	{
248
+		if (! $this->capabilities instanceof EE_Capabilities) {
249
+			$this->capabilities = $this->loader->getShared(EE_Capabilities::class);
250
+		}
251
+		return $this->capabilities;
252
+	}
253
+
254
+
255
+	/**
256
+	 * @return FeatureFlags
257
+	 */
258
+	public function feature(): FeatureFlags
259
+	{
260
+		return $this->feature;
261
+	}
262
+
263
+
264
+	/**
265
+	 * This logic used to be in the constructor, but that caused a chicken <--> egg scenario
266
+	 * for child classes that needed to set properties prior to these methods getting called,
267
+	 * but also needed the parent class to have its construction completed as well.
268
+	 * Bottom line is that constructors should ONLY be used for setting initial properties
269
+	 * and any complex initialization logic should only run after instantiation is complete.
270
+	 * This method gets called immediately after construction from within
271
+	 *      EE_Admin_Page_Init::_initialize_admin_page()
272
+	 *
273
+	 * @throws EE_Error
274
+	 * @throws InvalidArgumentException
275
+	 * @throws InvalidDataTypeException
276
+	 * @throws InvalidInterfaceException
277
+	 * @throws ReflectionException
278
+	 * @throws Throwable
279
+	 * @since 5.0.0.p
280
+	 */
281
+	public function initializePage()
282
+	{
283
+		if ($this->initialized) {
284
+			return;
285
+		}
286
+		// set initial page props (child method)
287
+		$this->_init_page_props();
288
+		// set global defaults
289
+		$this->_set_defaults();
290
+		// set early because incoming requests could be ajax related and we need to register those hooks.
291
+		if ($this->request->isAjax()) {
292
+			$this->_global_ajax_hooks();
293
+			$this->_ajax_hooks();
294
+		}
295
+		// other_page_hooks have to be early too.
296
+		$this->_do_other_page_hooks();
297
+		// set up page dependencies
298
+		$this->_before_page_setup();
299
+		$this->_page_setup();
300
+		$this->initialized = true;
301
+	}
302
+
303
+
304
+	/**
305
+	 * _init_page_props
306
+	 * Child classes use to set at least the following properties:
307
+	 * $page_slug.
308
+	 * $page_label.
309
+	 *
310
+	 * @abstract
311
+	 * @return void
312
+	 */
313
+	abstract protected function _init_page_props();
314
+
315
+
316
+	/**
317
+	 * _ajax_hooks
318
+	 * child classes put all their add_action('wp_ajax_{name_of_hook}') hooks in here.
319
+	 * Note: within the ajax callback methods.
320
+	 *
321
+	 * @abstract
322
+	 * @return void
323
+	 */
324
+	abstract protected function _ajax_hooks();
325
+
326
+
327
+	/**
328
+	 * _define_page_props
329
+	 * child classes define page properties in here.  Must include at least:
330
+	 * $_admin_base_url = base_url for all admin pages
331
+	 * $_admin_page_title = default admin_page_title for admin pages
332
+	 * $_labels = array of default labels for various automatically generated elements:
333
+	 *    array(
334
+	 *        'buttons' => array(
335
+	 *            'add' => esc_html__('label for add new button'),
336
+	 *            'edit' => esc_html__('label for edit button'),
337
+	 *            'delete' => esc_html__('label for delete button')
338
+	 *            )
339
+	 *        )
340
+	 *
341
+	 * @abstract
342
+	 * @return void
343
+	 */
344
+	abstract protected function _define_page_props();
345
+
346
+
347
+	/**
348
+	 * _set_page_routes
349
+	 * child classes use this to define the page routes for all subpages handled by the class.  Page routes are
350
+	 * assigned to an action => method pairs in an array and to the $_page_routes property.  Each page route must also
351
+	 * have a 'default' route. Here's the format
352
+	 * $this->_page_routes = array(
353
+	 *        'default' => array(
354
+	 *            'func' => '_default_method_handling_route',
355
+	 *            'args' => array('array','of','args'),
356
+	 *            'noheader' => true, //add this in if this page route is processed before any headers are loaded (i.e.
357
+	 *            ajax request, backend processing)
358
+	 *            'headers_sent_route'=>'headers_route_reference', //add this if noheader=>true, and you want to load a
359
+	 *            headers route after.  The string you enter here should match the defined route reference for a
360
+	 *            headers sent route.
361
+	 *            'capability' => 'route_capability', //indicate a string for minimum capability required to access
362
+	 *            this route.
363
+	 *            'obj_id' => 10 // if this route has an object id, then this can include it (used for capability
364
+	 *            checks).
365
+	 *        ),
366
+	 *        'insert_item' => '_method_for_handling_insert_item' //this can be used if all we need to have is a
367
+	 *        handling method.
368
+	 *        )
369
+	 * )
370
+	 *
371
+	 * @abstract
372
+	 * @return void
373
+	 */
374
+	abstract protected function _set_page_routes();
375
+
376
+
377
+	/**
378
+	 * _set_page_config
379
+	 * child classes use this to define the _page_config array for all subpages handled by the class. Each key in the
380
+	 * array corresponds to the page_route for the loaded page. Format:
381
+	 * $this->_page_config = array(
382
+	 *        'default' => array(
383
+	 *            'labels' => array(
384
+	 *                'buttons' => array(
385
+	 *                    'add' => esc_html__('label for adding item'),
386
+	 *                    'edit' => esc_html__('label for editing item'),
387
+	 *                    'delete' => esc_html__('label for deleting item')
388
+	 *                ),
389
+	 *                'publishbox' => esc_html__('Localized Title for Publish metabox', 'event_espresso')
390
+	 *            ), //optional an array of custom labels for various automatically generated elements to use on the
391
+	 *            page. If this isn't present then the defaults will be used as set for the $this->_labels in
392
+	 *            _define_page_props() method
393
+	 *            'nav' => array(
394
+	 *                'label' => esc_html__('Label for Tab', 'event_espresso').
395
+	 *                'url' => 'http://someurl', //automatically generated UNLESS you define
396
+	 *                'css_class' => 'css-class', //automatically generated UNLESS you define
397
+	 *                'order' => 10, //required to indicate tab position.
398
+	 *                'persistent' => false //if you want the nav tab to ONLY display when the specific route is
399
+	 *                displayed then add this parameter.
400
+	 *            'list_table' => 'name_of_list_table' //string for list table class to be loaded for this admin_page.
401
+	 *            'metaboxes' => array('metabox1', 'metabox2'), //if present this key indicates we want to load
402
+	 *            metaboxes set for eventespresso admin pages.
403
+	 *            'has_metaboxes' => true, //this boolean flag can simply be used to indicate if the route will have
404
+	 *            metaboxes.  Typically this is used if the 'metaboxes' index is not used because metaboxes are added
405
+	 *            later.  We just use this flag to make sure the necessary js gets enqueued on page load.
406
+	 *            'has_help_popups' => false //defaults(true) //this boolean flag can simply be used to indicate if the
407
+	 *            given route has help popups setup and if it does then we need to make sure thickbox is enqueued.
408
+	 *            'columns' => array(4, 2), //this key triggers the setup of a page that uses columns (metaboxes).  The
409
+	 *            array indicates the max number of columns (4) and the default number of columns on page load (2).
410
+	 *            There is an option in the "screen_options" dropdown that is set up so users can pick what columns they
411
+	 *            want to display.
412
+	 *            'help_tabs' => array( //this is used for adding help tabs to a page
413
+	 *                'tab_id' => array(
414
+	 *                    'title' => 'tab_title',
415
+	 *                    'filename' => 'name_of_file_containing_content', //this is the primary method for setting
416
+	 *                    help tab content.  The fallback if it isn't present is to try the callback.  Filename
417
+	 *                    should match a file in the admin folder's "help_tabs" dir (ie..
418
+	 *                    events/help_tabs/name_of_file_containing_content.help_tab.php)
419
+	 *                    'callback' => 'callback_method_for_content', //if 'filename' isn't present then system will
420
+	 *                    attempt to use the callback which should match the name of a method in the class
421
+	 *                    ),
422
+	 *                'tab2_id' => array(
423
+	 *                    'title' => 'tab2 title',
424
+	 *                    'filename' => 'file_name_2'
425
+	 *                    'callback' => 'callback_method_for_content',
426
+	 *                 ),
427
+	 *            'help_sidebar' => 'callback_for_sidebar_content', //this is used for setting up the sidebar in the
428
+	 *            help tab area on an admin page. @return void
429
+	 *
430
+	 * @abstract
431
+	 */
432
+	abstract protected function _set_page_config();
433
+
434
+
435
+	/**
436
+	 * _add_screen_options
437
+	 * Child classes can add any extra wp_screen_options within this method using built-in WP functions/methods for
438
+	 * doing so. Note child classes can also define _add_screen_options_($this->_current_view) to limit screen options
439
+	 * to a particular view.
440
+	 *
441
+	 * @link   http://chrismarslender.com/wp-tutorials/wordpress-screen-options-tutorial/
442
+	 *         see also WP_Screen object documents...
443
+	 * @link   http://codex.wordpress.org/Class_Reference/WP_Screen
444
+	 * @abstract
445
+	 * @return void
446
+	 */
447
+	abstract protected function _add_screen_options();
448
+
449
+
450
+	/**
451
+	 * _add_feature_pointers
452
+	 * Child classes should use this method for implementing any "feature pointers" (using built-in WP styling js).
453
+	 * Note child classes can also define _add_feature_pointers_($this->_current_view) to limit screen options to a
454
+	 * particular view. Note: this is just a placeholder for now.  Implementation will come down the road See:
455
+	 * WP_Internal_Pointers class in wp-admin/includes/template.php for example (it's a final class so can't be
456
+	 * extended) also see:
457
+	 *
458
+	 * @link   http://eamann.com/tech/wordpress-portland/
459
+	 * @abstract
460
+	 * @return void
461
+	 */
462
+	abstract protected function _add_feature_pointers();
463
+
464
+
465
+	/**
466
+	 * load_scripts_styles
467
+	 * child classes put their wp_enqueue_script and wp_enqueue_style hooks in here for anything they need loaded for
468
+	 * their pages/subpages.  Note this is for all pages/subpages of the system.  You can also load only specific
469
+	 * scripts/styles per view by putting them in a dynamic function in this format
470
+	 * (load_scripts_styles_{$this->_current_view}) which matches your page route (action request arg)
471
+	 *
472
+	 * @abstract
473
+	 * @return void
474
+	 */
475
+	abstract public function load_scripts_styles();
476
+
477
+
478
+	/**
479
+	 * admin_init
480
+	 * Anything that should be set/executed at 'admin_init' WP hook runtime should be put in here.  This will apply to
481
+	 * all pages/views loaded by child class.
482
+	 *
483
+	 * @abstract
484
+	 * @return void
485
+	 */
486
+	abstract public function admin_init();
487
+
488
+
489
+	/**
490
+	 * admin_notices
491
+	 * Anything triggered by the 'admin_notices' WP hook should be put in here.  This particular method will apply to
492
+	 * all pages/views loaded by child class.
493
+	 *
494
+	 * @abstract
495
+	 * @return void
496
+	 */
497
+	abstract public function admin_notices();
498
+
499
+
500
+	/**
501
+	 * admin_footer_scripts
502
+	 * Anything triggered by the 'admin_print_footer_scripts' WP hook should be put in here. This particular method
503
+	 * will apply to all pages/views loaded by child class.
504
+	 *
505
+	 * @return void
506
+	 */
507
+	abstract public function admin_footer_scripts();
508
+
509
+
510
+	/**
511
+	 * admin_footer
512
+	 * anything triggered by the 'admin_footer' WP action hook should be added to here. This particular method will
513
+	 * apply to all pages/views loaded by child class.
514
+	 *
515
+	 * @return void
516
+	 */
517
+	public function admin_footer()
518
+	{
519
+	}
520
+
521
+
522
+	/**
523
+	 * _global_ajax_hooks
524
+	 * all global add_action('wp_ajax_{name_of_hook}') hooks in here.
525
+	 * Note: within the ajax callback methods.
526
+	 *
527
+	 * @abstract
528
+	 * @return void
529
+	 */
530
+	protected function _global_ajax_hooks()
531
+	{
532
+		// for lazy loading of metabox content
533
+		add_action('wp_ajax_espresso-ajax-content', [$this, 'ajax_metabox_content']);
534
+
535
+		add_action(
536
+			'wp_ajax_espresso_hide_status_change_notice',
537
+			[$this, 'hideStatusChangeNotice']
538
+		);
539
+		add_action(
540
+			'wp_ajax_nopriv_espresso_hide_status_change_notice',
541
+			[$this, 'hideStatusChangeNotice']
542
+		);
543
+	}
544
+
545
+
546
+	public function ajax_metabox_content()
547
+	{
548
+		$content_id  = $this->request->getRequestParam('contentid', '');
549
+		$content_url = $this->request->getRequestParam('contenturl', '', DataType::URL);
550
+		EE_Admin_Page::cached_rss_display($content_id, $content_url);
551
+		wp_die();
552
+	}
553
+
554
+
555
+	public function hideStatusChangeNotice()
556
+	{
557
+		$response = [];
558
+		try {
559
+			/** @var StatusChangeNotice $status_change_notice */
560
+			$status_change_notice = $this->loader->getShared(
561
+				'EventEspresso\core\domain\services\admin\notices\status_change\StatusChangeNotice'
562
+			);
563
+			$response['success']  = $status_change_notice->dismiss() > -1;
564
+		} catch (Exception $exception) {
565
+			$response['errors'] = $exception->getMessage();
566
+		}
567
+		wp_send_json($response);
568
+	}
569
+
570
+
571
+	/**
572
+	 * allows extending classes do something specific before the parent constructor runs _page_setup().
573
+	 *
574
+	 * @return void
575
+	 */
576
+	protected function _before_page_setup()
577
+	{
578
+		// default is to do nothing
579
+	}
580
+
581
+
582
+	/**
583
+	 * Makes sure any things that need to be loaded early get handled.
584
+	 * We also escape early here if the page requested doesn't match the object.
585
+	 *
586
+	 * @final
587
+	 * @return void
588
+	 * @throws EE_Error
589
+	 * @throws InvalidArgumentException
590
+	 * @throws ReflectionException
591
+	 * @throws InvalidDataTypeException
592
+	 * @throws InvalidInterfaceException
593
+	 * @throws Throwable
594
+	 */
595
+	final protected function _page_setup()
596
+	{
597
+		// requires?
598
+		// admin_init stuff - global - we're setting this REALLY early
599
+		// so if EE_Admin pages have to hook into other WP pages they can.
600
+		// But keep in mind, not everything is available from the EE_Admin Page object at this point.
601
+		add_action('admin_init', [$this, 'admin_init_global'], 5);
602
+		// next verify if we need to load anything...
603
+		$this->_current_page = $this->request->getRequestParam('page', '', DataType::KEY);
604
+		$this->_current_page = $this->request->getRequestParam('current_page', $this->_current_page, DataType::KEY);
605
+		$this->page_folder   = strtolower(
606
+			str_replace(['_Admin_Page', 'Extend_'], '', $this->class_name)
607
+		);
608
+		global $ee_menu_slugs;
609
+		$ee_menu_slugs = (array) $ee_menu_slugs;
610
+		if (
611
+			! $this->request->isAjax()
612
+			&& (! $this->_current_page || ! isset($ee_menu_slugs[ $this->_current_page ]))
613
+		) {
614
+			return;
615
+		}
616
+		// because WP List tables have two duplicate select inputs for choosing bulk actions,
617
+		// we need to copy the action from the second to the first
618
+		$action     = $this->request->getRequestParam('action', '-1', DataType::KEY);
619
+		$action2    = $this->request->getRequestParam('action2', '-1', DataType::KEY);
620
+		$action     = $action !== '-1' ? $action : $action2;
621
+		$req_action = $action !== '-1' ? $action : 'default';
622
+
623
+		// if a specific 'route' has been set, and the action is 'default' OR we are doing_ajax
624
+		// then let's use the route as the action.
625
+		// This covers cases where we're coming in from a list table that isn't on the default route.
626
+		$route = $this->request->getRequestParam('route');
627
+		$route = $route !== '-1' ? $route : 'default';
628
+		$this->_req_action = $route && ($req_action === 'default' || $this->request->isAjax())
629
+			? $route
630
+			: $req_action;
631
+		$this->_current_view = $this->_req_action;
632
+		$this->_req_nonce    = $this->_req_action . '_nonce';
633
+		$this->_define_page_props();
634
+		$this->_current_page_view_url = add_query_arg(
635
+			['page' => $this->_current_page, 'action' => $this->_current_view],
636
+			$this->_admin_base_url
637
+		);
638
+		// set page configs
639
+		$this->_set_page_routes();
640
+		$this->_set_page_config();
641
+		// let's include any referrer data in our default_query_args for this route for "stickiness".
642
+		if ($this->request->requestParamIsSet('wp_referer')) {
643
+			$wp_referer = $this->request->getRequestParam('wp_referer');
644
+			if ($wp_referer) {
645
+				$this->_default_route_query_args['wp_referer'] = $wp_referer;
646
+			}
647
+		}
648
+		// for CPT and other extended functionality.
649
+		// If there is an _extend_page_config_for_cpt
650
+		// then let's run that to modify all the various page configuration arrays.
651
+		if (method_exists($this, '_extend_page_config_for_cpt')) {
652
+			$this->_extend_page_config_for_cpt();
653
+		}
654
+		// filter routes and page_config so addons can add their stuff. Filtering done per class
655
+		$this->_page_routes = apply_filters(
656
+			'FHEE__' . $this->class_name . '__page_setup__page_routes',
657
+			$this->_page_routes,
658
+			$this
659
+		);
660
+		$this->_page_config = apply_filters(
661
+			'FHEE__' . $this->class_name . '__page_setup__page_config',
662
+			$this->_page_config,
663
+			$this
664
+		);
665
+		if ($this->base_class_name !== '') {
666
+			$this->_page_routes = apply_filters(
667
+				'FHEE__' . $this->base_class_name . '__page_setup__page_routes',
668
+				$this->_page_routes,
669
+				$this
670
+			);
671
+			$this->_page_config = apply_filters(
672
+				'FHEE__' . $this->base_class_name . '__page_setup__page_config',
673
+				$this->_page_config,
674
+				$this
675
+			);
676
+		}
677
+		// if AHEE__EE_Admin_Page__route_admin_request_$this->_current_view method is present
678
+		// then we call it hooked into the AHEE__EE_Admin_Page__route_admin_request action
679
+		if (method_exists($this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view)) {
680
+			add_action(
681
+				'AHEE__EE_Admin_Page__route_admin_request',
682
+				[$this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view],
683
+				10,
684
+				2
685
+			);
686
+		}
687
+		// next route only if routing enabled
688
+		if ($this->_routing && ! $this->request->isAjax()) {
689
+			$this->_verify_routes();
690
+			// next let's just check user_access and kill if no access
691
+			$this->check_user_access();
692
+			if ($this->_is_UI_request) {
693
+				// admin_init stuff - global, all views for this page class, specific view
694
+				add_action('admin_init', [$this, 'admin_init']);
695
+				if (method_exists($this, 'admin_init_' . $this->_current_view)) {
696
+					add_action('admin_init', [$this, 'admin_init_' . $this->_current_view], 15);
697
+				}
698
+			} else {
699
+				// hijack regular WP loading and route admin request immediately
700
+				@ini_set('memory_limit', apply_filters('admin_memory_limit', WP_MAX_MEMORY_LIMIT));
701
+				$this->route_admin_request();
702
+			}
703
+		}
704
+	}
705
+
706
+
707
+	/**
708
+	 * Provides a way for related child admin pages to load stuff on the loaded admin page.
709
+	 *
710
+	 * @return void
711
+	 * @throws EE_Error
712
+	 */
713
+	private function _do_other_page_hooks()
714
+	{
715
+		$registered_pages = apply_filters('FHEE_do_other_page_hooks_' . $this->page_slug, []);
716
+		foreach ($registered_pages as $page) {
717
+			// now let's set up the file name and class that should be present
718
+			$classname = str_replace('.class.php', '', $page);
719
+			// autoloaders should take care of loading file
720
+			if (! class_exists($classname)) {
721
+				$error_msg[] = sprintf(
722
+					esc_html__(
723
+						'Something went wrong with loading the %s admin hooks page.',
724
+						'event_espresso'
725
+					),
726
+					$page
727
+				);
728
+				$error_msg[] = $error_msg[0]
729
+							   . "\r\n"
730
+							   . sprintf(
731
+								   esc_html__(
732
+									   'There is no class in place for the %1$s admin hooks page.%2$sMake sure you have %3$s defined. If this is a non-EE-core admin page then you also must have an autoloader in place for your class',
733
+									   'event_espresso'
734
+								   ),
735
+								   $page,
736
+								   '<br />',
737
+								   '<strong>' . $classname . '</strong>'
738
+							   );
739
+				throw new EE_Error(implode('||', $error_msg));
740
+			}
741
+			// don't load the same class twice
742
+			static $loaded = [];
743
+			if (in_array($classname, $loaded, true)) {
744
+				continue;
745
+			}
746
+			$loaded[] = $classname;
747
+			// notice we are passing the instance of this class to the hook object.
748
+			$this->loader->getShared($classname, [$this]);
749
+		}
750
+	}
751
+
752
+
753
+	/**
754
+	 * @throws ReflectionException
755
+	 * @throws EE_Error
756
+	 */
757
+	public function load_page_dependencies()
758
+	{
759
+		try {
760
+			$this->_load_page_dependencies();
761
+		} catch (EE_Error $e) {
762
+			$e->get_error();
763
+		}
764
+	}
765
+
766
+
767
+	/**
768
+	 * load_page_dependencies
769
+	 * loads things specific to this page class when it's loaded.  Really helps with efficiency.
770
+	 *
771
+	 * @return void
772
+	 * @throws DomainException
773
+	 * @throws EE_Error
774
+	 * @throws InvalidArgumentException
775
+	 * @throws InvalidDataTypeException
776
+	 * @throws InvalidInterfaceException
777
+	 * @throws ReflectionException
778
+	 */
779
+	protected function _load_page_dependencies()
780
+	{
781
+		// let's set the current_screen and screen options to override what WP set
782
+		$this->_current_screen = get_current_screen();
783
+		// load admin_notices - global, page class, and view specific
784
+		add_action('admin_notices', [$this, 'admin_notices_global'], 5);
785
+		add_action('admin_notices', [$this, 'admin_notices']);
786
+		if (method_exists($this, 'admin_notices_' . $this->_current_view)) {
787
+			add_action('admin_notices', [$this, 'admin_notices_' . $this->_current_view], 15);
788
+		}
789
+		// load network admin_notices - global, page class, and view specific
790
+		add_action('network_admin_notices', [$this, 'network_admin_notices_global'], 5);
791
+		if (method_exists($this, 'network_admin_notices_' . $this->_current_view)) {
792
+			add_action('network_admin_notices', [$this, 'network_admin_notices_' . $this->_current_view]);
793
+		}
794
+		// this will save any per_page screen options if they are present
795
+		$this->_set_per_page_screen_options();
796
+		// setup list table properties
797
+		$this->_set_list_table();
798
+		// child classes can "register" a metabox to be automatically handled via the _page_config array property.
799
+		// However in some cases the metaboxes will need to be added within a route handling callback.
800
+		add_action('add_meta_boxes', [$this, 'addRegisteredMetaBoxes'], 99);
801
+		// hack because promos admin was loading the edited promotion object in the metaboxes callback
802
+		// which should NOT be generated on non-UI requests like POST updates/inserts
803
+		if (
804
+			$this->class_name === 'Promotions_Admin_Page'
805
+			&& ($this->_req_action === 'edit' || $this->_req_action === 'create_new')
806
+		) {
807
+			$this->addRegisteredMetaBoxes();
808
+		}
809
+		$this->_add_screen_columns();
810
+		// add screen options - global, page child class, and view specific
811
+		$this->_add_global_screen_options();
812
+		$this->_add_screen_options();
813
+		$add_screen_options = "_add_screen_options_$this->_current_view";
814
+		if (method_exists($this, $add_screen_options)) {
815
+			$this->{$add_screen_options}();
816
+		}
817
+		// add help tab(s) - set via page_config and qtips.
818
+		$this->_add_help_tabs();
819
+		$this->_add_qtips();
820
+		// add feature_pointers - global, page child class, and view specific
821
+		$this->_add_feature_pointers();
822
+		$this->_add_global_feature_pointers();
823
+		$add_feature_pointer = "_add_feature_pointer_$this->_current_view";
824
+		if (method_exists($this, $add_feature_pointer)) {
825
+			$this->{$add_feature_pointer}();
826
+		}
827
+		// enqueue scripts/styles - global, page class, and view specific
828
+		add_action('admin_enqueue_scripts', [$this, 'admin_footer_scripts_eei18n_js_strings'], 1);
829
+		add_action('admin_enqueue_scripts', [$this, 'load_global_scripts_styles'], 5);
830
+		add_action('admin_enqueue_scripts', [$this, 'load_scripts_styles']);
831
+		if (method_exists($this, "load_scripts_styles_$this->_current_view")) {
832
+			add_action('admin_enqueue_scripts', [$this, "load_scripts_styles_$this->_current_view"], 15);
833
+		}
834
+		// admin_print_footer_scripts - global, page child class, and view specific.
835
+		// NOTE, despite the name, whenever possible, scripts should NOT be loaded using this.
836
+		// In most cases that's doing_it_wrong().  But adding hidden container elements etc.
837
+		// is a good use case. Notice the late priority we're giving these
838
+		add_action('admin_print_footer_scripts', [$this, 'admin_footer_scripts_global'], 99);
839
+		add_action('admin_print_footer_scripts', [$this, 'admin_footer_scripts'], 100);
840
+		if (method_exists($this, "admin_footer_scripts_$this->_current_view")) {
841
+			add_action('admin_print_footer_scripts', [$this, "admin_footer_scripts_$this->_current_view"], 101);
842
+		}
843
+		// admin footer scripts
844
+		add_action('admin_footer', [$this, 'admin_footer_global'], 99);
845
+		add_action('admin_footer', [$this, 'admin_footer'], 100);
846
+		if (method_exists($this, "admin_footer_$this->_current_view")) {
847
+			add_action('admin_footer', [$this, "admin_footer_$this->_current_view"], 101);
848
+		}
849
+		do_action('FHEE__EE_Admin_Page___load_page_dependencies__after_load', $this->page_slug);
850
+		// targeted hook
851
+		do_action(
852
+			"FHEE__EE_Admin_Page___load_page_dependencies__after_load__{$this->page_slug}__$this->_req_action"
853
+		);
854
+	}
855
+
856
+
857
+	/**
858
+	 * _set_defaults
859
+	 * This sets some global defaults for class properties.
860
+	 */
861
+	private function _set_defaults()
862
+	{
863
+		// init template args
864
+		$this->set_template_args(
865
+			[
866
+				'admin_page_header'  => '',
867
+				'admin_page_content' => '',
868
+				'post_body_content'  => '',
869
+				'before_list_table'  => '',
870
+				'after_list_table'   => '',
871
+			]
872
+		);
873
+	}
874
+
875
+
876
+	/**
877
+	 * route_admin_request
878
+	 *
879
+	 * @return void
880
+	 * @throws InvalidArgumentException
881
+	 * @throws InvalidInterfaceException
882
+	 * @throws InvalidDataTypeException
883
+	 * @throws EE_Error
884
+	 * @throws ReflectionException
885
+	 * @throws Throwable
886
+	 * @see    _route_admin_request()
887
+	 */
888
+	public function route_admin_request()
889
+	{
890
+		try {
891
+			$this->_route_admin_request();
892
+		} catch (EE_Error $e) {
893
+			$e->get_error();
894
+		}
895
+	}
896
+
897
+
898
+	public function set_wp_page_slug($wp_page_slug)
899
+	{
900
+		$this->_wp_page_slug = $wp_page_slug;
901
+		// if in network admin then we need to append "-network" to the page slug. Why? Because that's how WP rolls...
902
+		if (is_network_admin()) {
903
+			$this->_wp_page_slug .= '-network';
904
+		}
905
+	}
906
+
907
+
908
+	/**
909
+	 * _verify_routes
910
+	 * All this method does is verify the incoming request and make sure that routes exist for it.  We do this early so
911
+	 * we know if we need to drop out.
912
+	 *
913
+	 * @return bool
914
+	 * @throws EE_Error
915
+	 */
916
+	protected function _verify_routes(): bool
917
+	{
918
+		if (! $this->_current_page && ! $this->request->isAjax()) {
919
+			return false;
920
+		}
921
+		// check that the page_routes array is not empty
922
+		if (empty($this->_page_routes)) {
923
+			// user error msg
924
+			$error_msg = sprintf(
925
+				esc_html__('No page routes have been set for the %s admin page.', 'event_espresso'),
926
+				$this->_admin_page_title
927
+			);
928
+			// developer error msg
929
+			$error_msg .= '||' . $error_msg
930
+						  . esc_html__(
931
+							  ' Make sure the "set_page_routes()" method exists, and is setting the "_page_routes" array properly.',
932
+							  'event_espresso'
933
+						  );
934
+			throw new EE_Error($error_msg);
935
+		}
936
+		// route 'editpost' routes to CPT 'edit' routes
937
+		$alt_edit_route = $this instanceof EE_Admin_Page_CPT ? $this->cpt_editpost_route : 'edit';
938
+		if (
939
+			$this->_req_action === 'editpost'
940
+			&& ! isset($this->_page_routes['editpost'])
941
+			&& isset($this->_page_routes[ $alt_edit_route ])
942
+		) {
943
+			$this->_req_action = $alt_edit_route;
944
+		}
945
+		// and that the requested page route exists
946
+		if (array_key_exists($this->_req_action, $this->_page_routes)) {
947
+			$this->_route        = $this->_page_routes[ $this->_req_action ];
948
+			$this->_route_config = $this->_page_config[ $this->_req_action ] ?? [];
949
+		} else {
950
+			// user error msg
951
+			$error_msg = sprintf(
952
+				esc_html__(
953
+					'The requested page route does not exist for the %s admin page.',
954
+					'event_espresso'
955
+				),
956
+				$this->_admin_page_title
957
+			);
958
+			// developer error msg
959
+			$error_msg .= '||' . $error_msg
960
+						  . sprintf(
961
+							  esc_html__(
962
+								  ' Create a key in the "_page_routes" array named "%s" and set its value to the appropriate method.',
963
+								  'event_espresso'
964
+							  ),
965
+							  $this->_req_action
966
+						  );
967
+			throw new EE_Error($error_msg);
968
+		}
969
+		// and that a default route exists
970
+		if (! array_key_exists('default', $this->_page_routes)) {
971
+			// user error msg
972
+			$error_msg = sprintf(
973
+				esc_html__(
974
+					'A default page route has not been set for the % admin page.',
975
+					'event_espresso'
976
+				),
977
+				$this->_admin_page_title
978
+			);
979
+			// developer error msg
980
+			$error_msg .= '||' . $error_msg
981
+						  . esc_html__(
982
+							  ' Create a key in the "_page_routes" array named "default" and set its value to your default page method.',
983
+							  'event_espresso'
984
+						  );
985
+			throw new EE_Error($error_msg);
986
+		}
987
+
988
+		// first lets' catch if the UI request has EVER been set.
989
+		if ($this->_is_UI_request === null) {
990
+			// let's set if this is a UI request or not.
991
+			$this->_is_UI_request = ! $this->request->getRequestParam('noheader', false, DataType::BOOL);
992
+			// wait a minute... we might have a noheader in the route array
993
+			$this->_is_UI_request = ! (isset($this->_route['noheader']) && $this->_route['noheader'])
994
+				? $this->_is_UI_request
995
+				: false;
996
+		}
997
+		$this->_set_current_labels();
998
+		return true;
999
+	}
1000
+
1001
+
1002
+	/**
1003
+	 * this method simply verifies a given route and makes sure it's an actual route available for the loaded page
1004
+	 *
1005
+	 * @param string $route the route name we're verifying
1006
+	 * @return bool we'll throw an exception if this isn't a valid route.
1007
+	 * @throws EE_Error
1008
+	 */
1009
+	protected function _verify_route(string $route): bool
1010
+	{
1011
+		if (array_key_exists($this->_req_action, $this->_page_routes)) {
1012
+			return true;
1013
+		}
1014
+		// user error msg
1015
+		$error_msg = sprintf(
1016
+			esc_html__('The given page route does not exist for the %s admin page.', 'event_espresso'),
1017
+			$this->_admin_page_title
1018
+		);
1019
+		// developer error msg
1020
+		$error_msg .= '||' . $error_msg
1021
+					  . sprintf(
1022
+						  esc_html__(
1023
+							  ' Check the route you are using in your method (%s) and make sure it matches a route set in your "_page_routes" array property',
1024
+							  'event_espresso'
1025
+						  ),
1026
+						  $route
1027
+					  );
1028
+		throw new EE_Error($error_msg);
1029
+	}
1030
+
1031
+
1032
+	/**
1033
+	 * perform nonce verification
1034
+	 * This method has be encapsulated here so that any ajax requests that bypass normal routes can verify their nonces
1035
+	 * using this method (and save retyping!)
1036
+	 *
1037
+	 * @param string $nonce     The nonce sent
1038
+	 * @param string $nonce_ref The nonce reference string (name0)
1039
+	 * @return void
1040
+	 * @throws EE_Error
1041
+	 * @throws InvalidArgumentException
1042
+	 * @throws InvalidDataTypeException
1043
+	 * @throws InvalidInterfaceException
1044
+	 */
1045
+	protected function _verify_nonce(string $nonce = '', string $nonce_ref = '')
1046
+	{
1047
+		$nonce = $nonce ?: $this->request->getRequestParam($this->_req_nonce, '');
1048
+		$nonce_ref = $nonce_ref ?: $this->_req_action;
1049
+		// verify nonce against expected value
1050
+		if (! wp_verify_nonce($nonce, $nonce_ref)) {
1051
+			// these are not the droids you are looking for !!!
1052
+			$msg = sprintf(
1053
+				esc_html__('%sNonce Fail.%s', 'event_espresso'),
1054
+				'<a href="https://www.youtube.com/watch?v=56_S0WeTkzs">',
1055
+				'</a>'
1056
+			);
1057
+			if (WP_DEBUG) {
1058
+				$msg .= "\n  ";
1059
+				$msg .= sprintf(
1060
+					esc_html__(
1061
+						'In order to dynamically generate nonces for your actions, use the %s::add_query_args_and_nonce() method. May the Nonce be with you!',
1062
+						'event_espresso'
1063
+					),
1064
+					__CLASS__
1065
+				);
1066
+			}
1067
+			if (! $this->request->isAjax()) {
1068
+				wp_die($msg);
1069
+			}
1070
+			EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
1071
+			$this->_return_json();
1072
+		}
1073
+	}
1074
+
1075
+
1076
+	/**
1077
+	 * _route_admin_request()
1078
+	 * Meat and potatoes of the class.  Basically, this dude checks out what's being requested and sees if there are
1079
+	 * some doodads to work the magic and handle the flingjangy. Translation:  Checks if the requested action is listed
1080
+	 * in the page routes and then will try to load the corresponding method.
1081
+	 *
1082
+	 * @return void
1083
+	 * @throws EE_Error
1084
+	 * @throws InvalidArgumentException
1085
+	 * @throws InvalidDataTypeException
1086
+	 * @throws InvalidInterfaceException
1087
+	 * @throws ReflectionException
1088
+	 * @throws Throwable
1089
+	 */
1090
+	protected function _route_admin_request()
1091
+	{
1092
+		if (! $this->_is_UI_request) {
1093
+			$this->_verify_routes();
1094
+		}
1095
+		$nonce_check = ! isset($this->_route_config['require_nonce']) || $this->_route_config['require_nonce'];
1096
+		if ($this->_req_action !== 'default' && $nonce_check) {
1097
+			// set nonce from post data
1098
+			$nonce = $this->request->getRequestParam($this->_req_nonce, '');
1099
+			$this->_verify_nonce($nonce, $this->_req_nonce);
1100
+		}
1101
+		// set the nav_tabs array but ONLY if this is  UI_request
1102
+		if ($this->_is_UI_request) {
1103
+			$this->_set_nav_tabs();
1104
+		}
1105
+		// grab callback function
1106
+		$func = $this->_route['func'] ?? $this->_route;
1107
+		// check if callback has args
1108
+		$args = $this->_route['args'] ?? [];
1109
+
1110
+		// action right before calling route
1111
+		// (hook is something like 'AHEE__Registrations_Admin_Page__route_admin_request')
1112
+		if (! did_action('AHEE__EE_Admin_Page__route_admin_request')) {
1113
+			do_action('AHEE__EE_Admin_Page__route_admin_request', $this->_current_view, $this);
1114
+		}
1115
+		// strip _wp_http_referer from the server REQUEST_URI
1116
+		// else it grows in length on every submission due to recursion,
1117
+		// ultimately causing a "Request-URI Too Large" error
1118
+		$this->request->unSetRequestParam('_wp_http_referer');
1119
+		$this->request->unSetServerParam('_wp_http_referer');
1120
+		$cleaner_request_uri = remove_query_arg(
1121
+			'_wp_http_referer',
1122
+			wp_unslash($this->request->getServerParam('REQUEST_URI'))
1123
+		);
1124
+		$this->request->setRequestParam('_wp_http_referer', $cleaner_request_uri, true);
1125
+		$this->request->setServerParam('REQUEST_URI', $cleaner_request_uri, true);
1126
+		$route_callback = [];
1127
+		if (! empty($func)) {
1128
+			if (is_array($func) && is_callable($func)) {
1129
+				$route_callback = $func;
1130
+			} elseif (is_string($func)) {
1131
+				if (strpos($func, '::') !== false) {
1132
+					$route_callback = explode('::', $func);
1133
+				} elseif (method_exists($this, $func)) {
1134
+					$route_callback = [$this, $func];
1135
+				} else {
1136
+					$route_callback = $func;
1137
+				}
1138
+			}
1139
+			[$class, $method] = $route_callback;
1140
+			// is it neither a class method NOR a standalone function?
1141
+			if (! is_callable($route_callback)) {
1142
+				// user error msg
1143
+				$error_msg = esc_html__(
1144
+					'An error occurred. The  requested page route could not be found.',
1145
+					'event_espresso'
1146
+				);
1147
+				// developer error msg
1148
+				$error_msg .= '||';
1149
+				$error_msg .= sprintf(
1150
+					esc_html__(
1151
+						'Page route "%s" could not be called. Check that the spelling for method names and actions in the "_page_routes" array are all correct.',
1152
+						'event_espresso'
1153
+					),
1154
+					$method
1155
+				);
1156
+				throw new DomainException($error_msg);
1157
+			}
1158
+			if ($class !== $this && ! in_array($this, $args)) {
1159
+				// send along this admin page object for access by addons.
1160
+				$args['admin_page'] = $this;
1161
+			}
1162
+
1163
+			call_user_func_array($route_callback, $args);
1164
+		}
1165
+		// if we've routed and this route has a no headers route AND a sent_headers_route,
1166
+		// then we need to reset the routing properties to the new route.
1167
+		// now if UI request is FALSE and noheader is true AND we have a headers_sent_route in the route array then let's set UI_request to true because the no header route has a second func after headers have been sent.
1168
+		if (
1169
+			$this->_is_UI_request === false
1170
+			&& is_array($this->_route)
1171
+			&& ! empty($this->_route['headers_sent_route'])
1172
+		) {
1173
+			$this->_reset_routing_properties($this->_route['headers_sent_route']);
1174
+		}
1175
+	}
1176
+
1177
+
1178
+	/**
1179
+	 * This method just allows the resetting of page properties in the case where a no headers
1180
+	 * route redirects to a headers route in its route config.
1181
+	 *
1182
+	 * @param string $new_route New (non header) route to redirect to.
1183
+	 * @return void
1184
+	 * @throws ReflectionException
1185
+	 * @throws InvalidArgumentException
1186
+	 * @throws InvalidInterfaceException
1187
+	 * @throws InvalidDataTypeException
1188
+	 * @throws EE_Error
1189
+	 * @throws Throwable
1190
+	 * @since   4.3.0
1191
+	 */
1192
+	protected function _reset_routing_properties(string $new_route)
1193
+	{
1194
+		$this->_is_UI_request = true;
1195
+		// now we set the current route to whatever the headers_sent_route is set at
1196
+		$this->request->setRequestParam('action', $new_route);
1197
+		// rerun page setup
1198
+		$this->_page_setup();
1199
+	}
1200
+
1201
+
1202
+	/**
1203
+	 * _add_query_arg
1204
+	 * adds nonce to array of arguments then calls WP add_query_arg function
1205
+	 *(internally just uses EEH_URL's function with the same name)
1206
+	 *
1207
+	 * @param array  $args
1208
+	 * @param string $url
1209
+	 * @param bool   $sticky                  if true, then the existing Request params will be appended to the
1210
+	 *                                        generated url in an associative array indexed by the key 'wp_referer';
1211
+	 *                                        Example usage: If the current page is:
1212
+	 *                                        http://mydomain.com/wp-admin/admin.php?page=espresso_registrations
1213
+	 *                                        &action=default&event_id=20&month_range=March%202015
1214
+	 *                                        &_wpnonce=5467821
1215
+	 *                                        and you call:
1216
+	 *                                        EE_Admin_Page::add_query_args_and_nonce(
1217
+	 *                                        array(
1218
+	 *                                        'action' => 'resend_something',
1219
+	 *                                        'page=>espresso_registrations'
1220
+	 *                                        ),
1221
+	 *                                        $some_url,
1222
+	 *                                        true
1223
+	 *                                        );
1224
+	 *                                        It will produce a URL in this structure:
1225
+	 *                                        http://{$some_url}/?page=espresso_registrations&action=resend_something
1226
+	 *                                        &wp_referer[action]=default&wp_referer[event_id]=20&wpreferer[
1227
+	 *                                        month_range]=March%202015
1228
+	 * @param bool   $exclude_nonce           If true, the nonce will be excluded from the generated nonce.
1229
+	 * @param int    $context
1230
+	 * @return string
1231
+	 */
1232
+	public static function add_query_args_and_nonce(
1233
+		array $args = [],
1234
+		string $url = '',
1235
+		bool $sticky = false,
1236
+		bool $exclude_nonce = false,
1237
+		int $context = EEH_URL::CONTEXT_NONE
1238
+	): string {
1239
+		// if there is a _wp_http_referer include the values from the request but only if sticky = true
1240
+		if ($sticky) {
1241
+			/** @var RequestInterface $request */
1242
+			$request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
1243
+			$request->unSetRequestParams(['_wp_http_referer', 'wp_referer'], true);
1244
+			$request->unSetServerParam('_wp_http_referer', true);
1245
+			foreach ($request->requestParams() as $key => $value) {
1246
+				// do not add nonces
1247
+				if (strpos($key, 'nonce') !== false) {
1248
+					continue;
1249
+				}
1250
+				$args[ 'wp_referer[' . $key . ']' ] = is_string($value) ? htmlspecialchars($value) : $value;
1251
+			}
1252
+		}
1253
+		return EEH_URL::add_query_args_and_nonce($args, $url, $exclude_nonce, $context);
1254
+	}
1255
+
1256
+
1257
+	/**
1258
+	 * This returns a generated link that will load the related help tab.
1259
+	 *
1260
+	 * @param string $help_tab_id the id for the connected help tab
1261
+	 * @param string $icon_style  (optional) include css class for the style you want to use for the help icon.
1262
+	 * @param string $help_text   (optional) send help text you want to use for the link if default not to be used
1263
+	 * @return string              generated link
1264
+	 * @uses EEH_Template::get_help_tab_link()
1265
+	 */
1266
+	protected function _get_help_tab_link(string $help_tab_id, string $icon_style = '', string $help_text = ''): string
1267
+	{
1268
+		return EEH_Template::get_help_tab_link(
1269
+			$help_tab_id,
1270
+			$this->page_slug,
1271
+			$this->_req_action,
1272
+			$icon_style,
1273
+			$help_text
1274
+		);
1275
+	}
1276
+
1277
+
1278
+	/**
1279
+	 * _add_help_tabs
1280
+	 * Note child classes define their help tabs within the page_config array.
1281
+	 *
1282
+	 * @link   http://codex.wordpress.org/Function_Reference/add_help_tab
1283
+	 * @return void
1284
+	 * @throws DomainException
1285
+	 * @throws EE_Error
1286
+	 * @throws ReflectionException
1287
+	 */
1288
+	protected function _add_help_tabs()
1289
+	{
1290
+		if (isset($this->_page_config[ $this->_req_action ])) {
1291
+			$config = $this->_page_config[ $this->_req_action ];
1292
+			// let's see if there is a help_sidebar set for the current route and we'll set that up for usage as well.
1293
+			if (is_array($config) && isset($config['help_sidebar'])) {
1294
+				// check that the callback given is valid
1295
+				if (! method_exists($this, $config['help_sidebar'])) {
1296
+					throw new EE_Error(
1297
+						sprintf(
1298
+							esc_html__(
1299
+								'The _page_config array has a callback set for the "help_sidebar" option.  However the callback given (%s) is not a valid callback.  Double check the spelling and make sure this method exists for the class %s',
1300
+								'event_espresso'
1301
+							),
1302
+							$config['help_sidebar'],
1303
+							$this->class_name
1304
+						)
1305
+					);
1306
+				}
1307
+				$content = apply_filters(
1308
+					'FHEE__' . $this->class_name . '__add_help_tabs__help_sidebar',
1309
+					$this->{$config['help_sidebar']}()
1310
+				);
1311
+				$this->_current_screen->set_help_sidebar($content);
1312
+			}
1313
+			if (! isset($config['help_tabs'])) {
1314
+				return;
1315
+			} //no help tabs for this route
1316
+			foreach ((array) $config['help_tabs'] as $tab_id => $cfg) {
1317
+				// we're here so there ARE help tabs!
1318
+				// make sure we've got what we need
1319
+				if (! isset($cfg['title'])) {
1320
+					throw new EE_Error(
1321
+						esc_html__(
1322
+							'The _page_config array is not set up properly for help tabs.  It is missing a title',
1323
+							'event_espresso'
1324
+						)
1325
+					);
1326
+				}
1327
+				if (! isset($cfg['filename']) && ! isset($cfg['callback']) && ! isset($cfg['content'])) {
1328
+					throw new EE_Error(
1329
+						esc_html__(
1330
+							'The _page_config array is not setup properly for help tabs. It is missing a either a filename reference, or a callback reference or a content reference so there is no way to know the content for the help tab',
1331
+							'event_espresso'
1332
+						)
1333
+					);
1334
+				}
1335
+				// first priority goes to content.
1336
+				if (! empty($cfg['content'])) {
1337
+					$content = $cfg['content'];
1338
+					// second priority goes to filename
1339
+				} elseif (! empty($cfg['filename'])) {
1340
+					$file_path = $this->_get_dir() . '/help_tabs/' . $cfg['filename'] . '.help_tab.php';
1341
+					// it's possible that the file is located on decaf route (and above sets up for caf route, if this is the case then lets check decaf route too)
1342
+					$file_path = ! is_readable($file_path) ? EE_ADMIN_PAGES
1343
+															 . basename($this->_get_dir())
1344
+															 . '/help_tabs/'
1345
+															 . $cfg['filename']
1346
+															 . '.help_tab.php' : $file_path;
1347
+					// if file is STILL not readable then let's do an EE_Error so its more graceful than a fatal error.
1348
+					if (! isset($cfg['callback']) && ! is_readable($file_path)) {
1349
+						EE_Error::add_error(
1350
+							sprintf(
1351
+								esc_html__(
1352
+									'The filename given for the help tab %s is not a valid file and there is no other configuration for the tab content.  Please check that the string you set for the help tab on this route (%s) is the correct spelling.  The file should be in %s',
1353
+									'event_espresso'
1354
+								),
1355
+								$tab_id,
1356
+								key($config),
1357
+								$file_path
1358
+							),
1359
+							__FILE__,
1360
+							__FUNCTION__,
1361
+							__LINE__
1362
+						);
1363
+						return;
1364
+					}
1365
+					$template_args['admin_page_obj'] = $this;
1366
+					$content                         = EEH_Template::display_template(
1367
+						$file_path,
1368
+						$template_args,
1369
+						true
1370
+					);
1371
+				} else {
1372
+					$content = '';
1373
+				}
1374
+				// check if callback is valid
1375
+				if (
1376
+					empty($content)
1377
+					&& (
1378
+						! isset($cfg['callback']) || ! method_exists($this, $cfg['callback'])
1379
+					)
1380
+				) {
1381
+					EE_Error::add_error(
1382
+						sprintf(
1383
+							esc_html__(
1384
+								'The callback given for a %s help tab on this page does not content OR a corresponding method for generating the content.  Check the spelling or make sure the method is present.',
1385
+								'event_espresso'
1386
+							),
1387
+							$cfg['title']
1388
+						),
1389
+						__FILE__,
1390
+						__FUNCTION__,
1391
+						__LINE__
1392
+					);
1393
+					return;
1394
+				}
1395
+				// setup config array for help tab method
1396
+				$id  = $this->page_slug . '-' . $this->_req_action . '-' . $tab_id;
1397
+				$_ht = [
1398
+					'id'       => $id,
1399
+					'title'    => $cfg['title'],
1400
+					'callback' => isset($cfg['callback']) && empty($content) ? [$this, $cfg['callback']] : null,
1401
+					'content'  => $content,
1402
+				];
1403
+				$this->_current_screen->add_help_tab($_ht);
1404
+			}
1405
+		}
1406
+	}
1407
+
1408
+
1409
+	/**
1410
+	 * This simply sets up any qtips that have been defined in the page config
1411
+	 *
1412
+	 * @return void
1413
+	 * @throws ReflectionException
1414
+	 * @throws EE_Error
1415
+	 */
1416
+	protected function _add_qtips()
1417
+	{
1418
+		if (isset($this->_route_config['qtips'])) {
1419
+			$qtips = (array) $this->_route_config['qtips'];
1420
+			// load qtip loader
1421
+			$path = [
1422
+				$this->_get_dir() . '/qtips/',
1423
+				EE_ADMIN_PAGES . basename($this->_get_dir()) . '/qtips/',
1424
+			];
1425
+			EEH_Qtip_Loader::instance()->register($qtips, $path);
1426
+		}
1427
+	}
1428
+
1429
+
1430
+	/**
1431
+	 * _set_nav_tabs
1432
+	 * This sets up the nav tabs from the page_routes array.  This method can be overwritten by child classes if you
1433
+	 * wish to add additional tabs or modify accordingly.
1434
+	 *
1435
+	 * @return void
1436
+	 * @throws InvalidArgumentException
1437
+	 * @throws InvalidInterfaceException
1438
+	 * @throws InvalidDataTypeException
1439
+	 */
1440
+	protected function _set_nav_tabs()
1441
+	{
1442
+		$i        = 0;
1443
+		$only_tab = count($this->_page_config) < 2;
1444
+		foreach ($this->_page_config as $slug => $config) {
1445
+			if (! is_array($config) || empty($config['nav'])) {
1446
+				continue;
1447
+			}
1448
+			// no nav tab for this config
1449
+			// check for persistent flag
1450
+			if ($slug !== $this->_req_action && isset($config['nav']['persistent']) && ! $config['nav']['persistent']) {
1451
+				// nav tab is only to appear when route requested.
1452
+				continue;
1453
+			}
1454
+			if (! $this->check_user_access($slug, true)) {
1455
+				// no nav tab because current user does not have access.
1456
+				continue;
1457
+			}
1458
+			$css_class = $config['css_class'] ?? '';
1459
+			$css_class .= $only_tab ? ' ee-only-tab' : '';
1460
+			$css_class .= " ee-nav-tab__$slug";
1461
+
1462
+			$this->_nav_tabs[ $slug ] = [
1463
+				'url'       => $config['nav']['url'] ?? EE_Admin_Page::add_query_args_and_nonce(
1464
+					['action' => $slug],
1465
+					$this->_admin_base_url
1466
+				),
1467
+				'link_text' => $this->navTabLabel($config['nav'], $slug),
1468
+				'css_class' => $this->_req_action === $slug ? $css_class . ' nav-tab-active' : $css_class,
1469
+				'order'     => $config['nav']['order'] ?? $i,
1470
+			];
1471
+			$i++;
1472
+		}
1473
+		// if $this->_nav_tabs is empty then lets set the default
1474
+		if (empty($this->_nav_tabs)) {
1475
+			$this->_nav_tabs[ $this->_default_nav_tab_name ] = [
1476
+				'url'       => $this->_admin_base_url,
1477
+				'link_text' => ucwords(str_replace('_', ' ', $this->_default_nav_tab_name)),
1478
+				'css_class' => 'nav-tab-active',
1479
+				'order'     => 10,
1480
+			];
1481
+		}
1482
+		// now let's sort the tabs according to order
1483
+		usort($this->_nav_tabs, [$this, '_sort_nav_tabs']);
1484
+	}
1485
+
1486
+
1487
+	private function navTabLabel(array $nav_tab, string $slug): string
1488
+	{
1489
+		$label = $nav_tab['label'] ?? ucwords(str_replace('_', ' ', $slug));
1490
+		$icon  = $nav_tab['icon'] ?? null;
1491
+		$icon  = $icon ? '<span class="dashicons ' . $icon . '"></span>' : '';
1492
+		return '
1493 1493
             <span class="ee-admin-screen-tab__label">
1494 1494
                 ' . $icon . '
1495 1495
                 <span class="ee-nav-label__text">' . $label . '</span>
1496 1496
             </span>';
1497
-    }
1498
-
1499
-
1500
-    /**
1501
-     * _set_current_labels
1502
-     * This method modifies the _labels property with any optional specific labels indicated in the _page_routes
1503
-     * property array
1504
-     *
1505
-     * @return void
1506
-     */
1507
-    private function _set_current_labels()
1508
-    {
1509
-        if (isset($this->_route_config['labels'])) {
1510
-            foreach ($this->_route_config['labels'] as $label => $text) {
1511
-                if (is_array($text)) {
1512
-                    foreach ($text as $sublabel => $subtext) {
1513
-                        $this->_labels[ $label ][ $sublabel ] = $subtext;
1514
-                    }
1515
-                } else {
1516
-                    $this->_labels[ $label ] = $text;
1517
-                }
1518
-            }
1519
-        }
1520
-    }
1521
-
1522
-
1523
-    /**
1524
-     *        verifies user access for this admin page
1525
-     *
1526
-     * @param string $route_to_check if present then the capability for the route matching this string is checked.
1527
-     * @param bool   $verify_only    Default is FALSE which means if user check fails then wp_die().  Otherwise just
1528
-     *                               return false if verify fail.
1529
-     * @return bool
1530
-     * @throws InvalidArgumentException
1531
-     * @throws InvalidDataTypeException
1532
-     * @throws InvalidInterfaceException
1533
-     */
1534
-    public function check_user_access(string $route_to_check = '', bool $verify_only = false): bool
1535
-    {
1536
-        // if no route_to_check is passed in then use the current route set via _req_action
1537
-        $action = $route_to_check ?: $this->_req_action;
1538
-        $capability = ! empty($this->_page_routes[ $action ]['capability'])
1539
-            ? $this->_page_routes[ $action ]['capability']
1540
-            : null;
1541
-
1542
-        if (empty($capability)) {
1543
-            $capability = empty($route_to_check) && ! empty($this->_route['capability'])
1544
-                ? $this->_route['capability']
1545
-                : 'manage_options';
1546
-        }
1547
-
1548
-        $id = $this->_route['obj_id'] ?? 0;
1549
-
1550
-        if (
1551
-            ! $this->request->isAjax()
1552
-            && (
1553
-                ! function_exists('is_admin')
1554
-                || ! $this->capabilities->current_user_can($capability, "{$this->page_slug}_$route_to_check", $id)
1555
-            )
1556
-        ) {
1557
-            if ($verify_only) {
1558
-                return false;
1559
-            }
1560
-            if (is_user_logged_in()) {
1561
-                wp_die(esc_html__('You do not have access to this route.', 'event_espresso'));
1562
-            }
1563
-            return false;
1564
-        }
1565
-        return true;
1566
-    }
1567
-
1568
-
1569
-    /**
1570
-     * @param string                 $box_id
1571
-     * @param string                 $title
1572
-     * @param callable|string|null   $callback
1573
-     * @param string|array|WP_Screen $screen
1574
-     * @param string                 $context       Post edit screen contexts include 'normal', 'side', and 'advanced'.
1575
-     *                                              Comments screen contexts include 'normal' and 'side'.
1576
-     *                                              Menus meta boxes (accordion sections) all use the 'side' context.
1577
-     * @param string                 $priority      Accepts 'high', 'core', 'default', or 'low'.
1578
-     * @param array|null             $callback_args
1579
-     */
1580
-    protected function addMetaBox(
1581
-        string $box_id,
1582
-        string $title,
1583
-        $callback,
1584
-        $screen,
1585
-        string $context = 'normal',
1586
-        string $priority = 'default',
1587
-        ?array $callback_args = null
1588
-    ) {
1589
-        if (! (is_callable($callback) || ! function_exists($callback))) {
1590
-            return;
1591
-        }
1592
-
1593
-        add_meta_box($box_id, $title, $callback, $screen, $context, $priority, $callback_args);
1594
-        add_filter(
1595
-            "postbox_classes_{$this->_wp_page_slug}_$box_id",
1596
-            function ($classes) {
1597
-                $classes[] = 'ee-admin-container';
1598
-                return $classes;
1599
-            }
1600
-        );
1601
-    }
1602
-
1603
-
1604
-    /**
1605
-     * admin_init_global
1606
-     * This runs all the code that we want executed within the WP admin_init hook.
1607
-     * This method executes for ALL EE Admin pages.
1608
-     *
1609
-     * @return void
1610
-     */
1611
-    public function admin_init_global()
1612
-    {
1613
-    }
1614
-
1615
-
1616
-    /**
1617
-     * wp_loaded_global
1618
-     * This runs all the code that we want executed within the WP wp_loaded hook.  This method is optional for an
1619
-     * EE_Admin page and will execute on every EE Admin Page load
1620
-     *
1621
-     * @return void
1622
-     */
1623
-    public function wp_loaded()
1624
-    {
1625
-    }
1626
-
1627
-
1628
-    /**
1629
-     * admin_notices
1630
-     * Anything triggered by the 'admin_notices' WP hook should be put in here.  This particular method will apply on
1631
-     * ALL EE_Admin pages.
1632
-     *
1633
-     * @return void
1634
-     */
1635
-    public function admin_notices_global()
1636
-    {
1637
-        $this->_display_no_javascript_warning();
1638
-        $this->_display_espresso_notices();
1639
-    }
1640
-
1641
-
1642
-    public function network_admin_notices_global()
1643
-    {
1644
-        $this->_display_no_javascript_warning();
1645
-        $this->_display_espresso_notices();
1646
-    }
1647
-
1648
-
1649
-    /**
1650
-     * admin_footer_scripts_global
1651
-     * Anything triggered by the 'admin_print_footer_scripts' WP hook should be put in here. This particular method
1652
-     * will apply on ALL EE_Admin pages.
1653
-     *
1654
-     * @return void
1655
-     */
1656
-    public function admin_footer_scripts_global()
1657
-    {
1658
-        $this->_add_admin_page_ajax_loading_img();
1659
-        $this->_add_admin_page_overlay();
1660
-        // if metaboxes are present we need to add the nonce field
1661
-        if (
1662
-            isset($this->_route_config['metaboxes'])
1663
-            || isset($this->_route_config['list_table'])
1664
-            || (isset($this->_route_config['has_metaboxes']) && $this->_route_config['has_metaboxes'])
1665
-        ) {
1666
-            wp_nonce_field('closedpostboxes', 'closedpostboxesnonce', false);
1667
-            wp_nonce_field('meta-box-order', 'meta-box-order-nonce', false);
1668
-        }
1669
-    }
1670
-
1671
-
1672
-    /**
1673
-     * admin_footer_global
1674
-     * Anything triggered by the wp 'admin_footer' wp hook should be put in here.
1675
-     * This particular method will apply on ALL EE_Admin Pages.
1676
-     *
1677
-     * @return void
1678
-     */
1679
-    public function admin_footer_global()
1680
-    {
1681
-        // dialog container for dialog helper
1682
-        echo '
1497
+	}
1498
+
1499
+
1500
+	/**
1501
+	 * _set_current_labels
1502
+	 * This method modifies the _labels property with any optional specific labels indicated in the _page_routes
1503
+	 * property array
1504
+	 *
1505
+	 * @return void
1506
+	 */
1507
+	private function _set_current_labels()
1508
+	{
1509
+		if (isset($this->_route_config['labels'])) {
1510
+			foreach ($this->_route_config['labels'] as $label => $text) {
1511
+				if (is_array($text)) {
1512
+					foreach ($text as $sublabel => $subtext) {
1513
+						$this->_labels[ $label ][ $sublabel ] = $subtext;
1514
+					}
1515
+				} else {
1516
+					$this->_labels[ $label ] = $text;
1517
+				}
1518
+			}
1519
+		}
1520
+	}
1521
+
1522
+
1523
+	/**
1524
+	 *        verifies user access for this admin page
1525
+	 *
1526
+	 * @param string $route_to_check if present then the capability for the route matching this string is checked.
1527
+	 * @param bool   $verify_only    Default is FALSE which means if user check fails then wp_die().  Otherwise just
1528
+	 *                               return false if verify fail.
1529
+	 * @return bool
1530
+	 * @throws InvalidArgumentException
1531
+	 * @throws InvalidDataTypeException
1532
+	 * @throws InvalidInterfaceException
1533
+	 */
1534
+	public function check_user_access(string $route_to_check = '', bool $verify_only = false): bool
1535
+	{
1536
+		// if no route_to_check is passed in then use the current route set via _req_action
1537
+		$action = $route_to_check ?: $this->_req_action;
1538
+		$capability = ! empty($this->_page_routes[ $action ]['capability'])
1539
+			? $this->_page_routes[ $action ]['capability']
1540
+			: null;
1541
+
1542
+		if (empty($capability)) {
1543
+			$capability = empty($route_to_check) && ! empty($this->_route['capability'])
1544
+				? $this->_route['capability']
1545
+				: 'manage_options';
1546
+		}
1547
+
1548
+		$id = $this->_route['obj_id'] ?? 0;
1549
+
1550
+		if (
1551
+			! $this->request->isAjax()
1552
+			&& (
1553
+				! function_exists('is_admin')
1554
+				|| ! $this->capabilities->current_user_can($capability, "{$this->page_slug}_$route_to_check", $id)
1555
+			)
1556
+		) {
1557
+			if ($verify_only) {
1558
+				return false;
1559
+			}
1560
+			if (is_user_logged_in()) {
1561
+				wp_die(esc_html__('You do not have access to this route.', 'event_espresso'));
1562
+			}
1563
+			return false;
1564
+		}
1565
+		return true;
1566
+	}
1567
+
1568
+
1569
+	/**
1570
+	 * @param string                 $box_id
1571
+	 * @param string                 $title
1572
+	 * @param callable|string|null   $callback
1573
+	 * @param string|array|WP_Screen $screen
1574
+	 * @param string                 $context       Post edit screen contexts include 'normal', 'side', and 'advanced'.
1575
+	 *                                              Comments screen contexts include 'normal' and 'side'.
1576
+	 *                                              Menus meta boxes (accordion sections) all use the 'side' context.
1577
+	 * @param string                 $priority      Accepts 'high', 'core', 'default', or 'low'.
1578
+	 * @param array|null             $callback_args
1579
+	 */
1580
+	protected function addMetaBox(
1581
+		string $box_id,
1582
+		string $title,
1583
+		$callback,
1584
+		$screen,
1585
+		string $context = 'normal',
1586
+		string $priority = 'default',
1587
+		?array $callback_args = null
1588
+	) {
1589
+		if (! (is_callable($callback) || ! function_exists($callback))) {
1590
+			return;
1591
+		}
1592
+
1593
+		add_meta_box($box_id, $title, $callback, $screen, $context, $priority, $callback_args);
1594
+		add_filter(
1595
+			"postbox_classes_{$this->_wp_page_slug}_$box_id",
1596
+			function ($classes) {
1597
+				$classes[] = 'ee-admin-container';
1598
+				return $classes;
1599
+			}
1600
+		);
1601
+	}
1602
+
1603
+
1604
+	/**
1605
+	 * admin_init_global
1606
+	 * This runs all the code that we want executed within the WP admin_init hook.
1607
+	 * This method executes for ALL EE Admin pages.
1608
+	 *
1609
+	 * @return void
1610
+	 */
1611
+	public function admin_init_global()
1612
+	{
1613
+	}
1614
+
1615
+
1616
+	/**
1617
+	 * wp_loaded_global
1618
+	 * This runs all the code that we want executed within the WP wp_loaded hook.  This method is optional for an
1619
+	 * EE_Admin page and will execute on every EE Admin Page load
1620
+	 *
1621
+	 * @return void
1622
+	 */
1623
+	public function wp_loaded()
1624
+	{
1625
+	}
1626
+
1627
+
1628
+	/**
1629
+	 * admin_notices
1630
+	 * Anything triggered by the 'admin_notices' WP hook should be put in here.  This particular method will apply on
1631
+	 * ALL EE_Admin pages.
1632
+	 *
1633
+	 * @return void
1634
+	 */
1635
+	public function admin_notices_global()
1636
+	{
1637
+		$this->_display_no_javascript_warning();
1638
+		$this->_display_espresso_notices();
1639
+	}
1640
+
1641
+
1642
+	public function network_admin_notices_global()
1643
+	{
1644
+		$this->_display_no_javascript_warning();
1645
+		$this->_display_espresso_notices();
1646
+	}
1647
+
1648
+
1649
+	/**
1650
+	 * admin_footer_scripts_global
1651
+	 * Anything triggered by the 'admin_print_footer_scripts' WP hook should be put in here. This particular method
1652
+	 * will apply on ALL EE_Admin pages.
1653
+	 *
1654
+	 * @return void
1655
+	 */
1656
+	public function admin_footer_scripts_global()
1657
+	{
1658
+		$this->_add_admin_page_ajax_loading_img();
1659
+		$this->_add_admin_page_overlay();
1660
+		// if metaboxes are present we need to add the nonce field
1661
+		if (
1662
+			isset($this->_route_config['metaboxes'])
1663
+			|| isset($this->_route_config['list_table'])
1664
+			|| (isset($this->_route_config['has_metaboxes']) && $this->_route_config['has_metaboxes'])
1665
+		) {
1666
+			wp_nonce_field('closedpostboxes', 'closedpostboxesnonce', false);
1667
+			wp_nonce_field('meta-box-order', 'meta-box-order-nonce', false);
1668
+		}
1669
+	}
1670
+
1671
+
1672
+	/**
1673
+	 * admin_footer_global
1674
+	 * Anything triggered by the wp 'admin_footer' wp hook should be put in here.
1675
+	 * This particular method will apply on ALL EE_Admin Pages.
1676
+	 *
1677
+	 * @return void
1678
+	 */
1679
+	public function admin_footer_global()
1680
+	{
1681
+		// dialog container for dialog helper
1682
+		echo '
1683 1683
         <div class="ee-admin-dialog-container auto-hide hidden">
1684 1684
             <div class="ee-notices"></div>
1685 1685
             <div class="ee-admin-dialog-container-inner-content"></div>
1686 1686
         </div>
1687 1687
         <span id="current_timezone" class="hidden">' . esc_html(EEH_DTT_Helper::get_timezone()) . '</span>
1688 1688
         <input type="hidden" id="espresso_admin_current_page" value="' . esc_attr($this->_current_page) . '"/>';
1689
-    }
1690
-
1691
-
1692
-    /**
1693
-     * This function sees if there is a method for help popup content existing for the given route.  If there is then
1694
-     * we'll use the retrieved array to output the content using the template. For child classes: If you want to have
1695
-     * help popups then in your templates or your content you set "triggers" for the content using the
1696
-     * "_set_help_trigger('help_trigger_id')" where "help_trigger_id" is what you will use later in your custom method
1697
-     * for the help popup content on that page. Then in your Child_Admin_Page class you need to define a help popup
1698
-     * method for the content in the format "_help_popup_content_{route_name}()"  So if you are setting help content
1699
-     * for the
1700
-     * 'edit_event' route you should have a method named "_help_popup_content_edit_route". In your defined
1701
-     * "help_popup_content_..." method.  You must prepare and return an array in the following format array(
1702
-     *    'help_trigger_id' => array(
1703
-     *        'title' => esc_html__('localized title for popup', 'event_espresso'),
1704
-     *        'content' => esc_html__('localized content for popup', 'event_espresso')
1705
-     *    )
1706
-     * );
1707
-     * Then the EE_Admin_Parent will take care of making sure that is set up properly on the correct route.
1708
-     *
1709
-     * @param array $help_array
1710
-     * @param bool  $display
1711
-     * @return string content
1712
-     * @throws DomainException
1713
-     * @throws EE_Error
1714
-     */
1715
-    protected function _set_help_popup_content(array $help_array = [], bool $display = false): string
1716
-    {
1717
-        $content    = '';
1718
-        $help_array = empty($help_array) ? $this->_get_help_content() : $help_array;
1719
-        // loop through the array and setup content
1720
-        foreach ($help_array as $trigger => $help) {
1721
-            // make sure the array is set up properly
1722
-            if (! isset($help['title'], $help['content'])) {
1723
-                throw new EE_Error(
1724
-                    esc_html__(
1725
-                        'Does not look like the popup content array has been setup correctly.  Might want to double check that.  Read the comments for the _get_help_popup_content method found in "EE_Admin_Page" class',
1726
-                        'event_espresso'
1727
-                    )
1728
-                );
1729
-            }
1730
-            // we're good so let's set up the template vars and then assign parsed template content to our content.
1731
-            $template_args = [
1732
-                'help_popup_id'      => $trigger,
1733
-                'help_popup_title'   => $help['title'],
1734
-                'help_popup_content' => $help['content'],
1735
-            ];
1736
-            $content       .= EEH_Template::display_template(
1737
-                EE_ADMIN_TEMPLATE . 'admin_help_popup.template.php',
1738
-                $template_args,
1739
-                true
1740
-            );
1741
-        }
1742
-        if ($display) {
1743
-            echo wp_kses($content, AllowedTags::getWithFormTags());
1744
-            return '';
1745
-        }
1746
-        return $content;
1747
-    }
1748
-
1749
-
1750
-    /**
1751
-     * All this does is retrieve the help content array if set by the EE_Admin_Page child
1752
-     *
1753
-     * @return array properly formatted array for help popup content
1754
-     * @throws EE_Error
1755
-     */
1756
-    private function _get_help_content(): array
1757
-    {
1758
-        // what is the method we're looking for?
1759
-        $method_name = '_help_popup_content_' . $this->_req_action;
1760
-        // if method doesn't exist let's get out.
1761
-        if (! method_exists($this, $method_name)) {
1762
-            return [];
1763
-        }
1764
-        // k we're good to go let's retrieve the help array
1765
-        $help_array = $this->{$method_name}();
1766
-        // make sure we've got an array!
1767
-        if (! is_array($help_array)) {
1768
-            throw new EE_Error(
1769
-                esc_html__(
1770
-                    'Something went wrong with help popup content generation. Expecting an array and well, this ain\'t no array bub.',
1771
-                    'event_espresso'
1772
-                )
1773
-            );
1774
-        }
1775
-        return $help_array;
1776
-    }
1777
-
1778
-
1779
-    /**
1780
-     * EE Admin Pages can use this to set a properly formatted trigger for a help popup.
1781
-     * By default the trigger html is printed.  Otherwise it can be returned if the $display flag is set "false"
1782
-     * See comments made on the _set_help_content method for understanding other parts to the help popup tool.
1783
-     *
1784
-     * @param string $trigger_id reference for retrieving the trigger content for the popup
1785
-     * @param bool   $display    if false then we return the trigger string
1786
-     * @param array  $dimensions an array of dimensions for the box (array(h,w))
1787
-     * @return string
1788
-     * @throws DomainException
1789
-     * @throws EE_Error
1790
-     */
1791
-    protected function _set_help_trigger(string $trigger_id, bool $display = true, array $dimensions = ['400', '640'])
1792
-    {
1793
-        if ($this->request->isAjax()) {
1794
-            return '';
1795
-        }
1796
-        // let's check and see if there is any content set for this popup.  If there isn't then we'll include a default title and content so that developers know something needs to be corrected
1797
-        $help_array   = $this->_get_help_content();
1798
-        $help_content = '';
1799
-        if (empty($help_array) || ! isset($help_array[ $trigger_id ])) {
1800
-            $help_array[ $trigger_id ] = [
1801
-                'title'   => esc_html__('Missing Content', 'event_espresso'),
1802
-                'content' => esc_html__(
1803
-                    'A trigger has been set that doesn\'t have any corresponding content. Make sure you have set the help content. (see the "_set_help_popup_content" method in the EE_Admin_Page for instructions.)',
1804
-                    'event_espresso'
1805
-                ),
1806
-            ];
1807
-            $help_content              = $this->_set_help_popup_content($help_array);
1808
-        }
1809
-        $height   = esc_attr($dimensions[0]) ?? 400;
1810
-        $width    = esc_attr($dimensions[1]) ?? 640;
1811
-        $inlineId = esc_attr($trigger_id);
1812
-        // let's setup the trigger
1813
-        $content = "
1689
+	}
1690
+
1691
+
1692
+	/**
1693
+	 * This function sees if there is a method for help popup content existing for the given route.  If there is then
1694
+	 * we'll use the retrieved array to output the content using the template. For child classes: If you want to have
1695
+	 * help popups then in your templates or your content you set "triggers" for the content using the
1696
+	 * "_set_help_trigger('help_trigger_id')" where "help_trigger_id" is what you will use later in your custom method
1697
+	 * for the help popup content on that page. Then in your Child_Admin_Page class you need to define a help popup
1698
+	 * method for the content in the format "_help_popup_content_{route_name}()"  So if you are setting help content
1699
+	 * for the
1700
+	 * 'edit_event' route you should have a method named "_help_popup_content_edit_route". In your defined
1701
+	 * "help_popup_content_..." method.  You must prepare and return an array in the following format array(
1702
+	 *    'help_trigger_id' => array(
1703
+	 *        'title' => esc_html__('localized title for popup', 'event_espresso'),
1704
+	 *        'content' => esc_html__('localized content for popup', 'event_espresso')
1705
+	 *    )
1706
+	 * );
1707
+	 * Then the EE_Admin_Parent will take care of making sure that is set up properly on the correct route.
1708
+	 *
1709
+	 * @param array $help_array
1710
+	 * @param bool  $display
1711
+	 * @return string content
1712
+	 * @throws DomainException
1713
+	 * @throws EE_Error
1714
+	 */
1715
+	protected function _set_help_popup_content(array $help_array = [], bool $display = false): string
1716
+	{
1717
+		$content    = '';
1718
+		$help_array = empty($help_array) ? $this->_get_help_content() : $help_array;
1719
+		// loop through the array and setup content
1720
+		foreach ($help_array as $trigger => $help) {
1721
+			// make sure the array is set up properly
1722
+			if (! isset($help['title'], $help['content'])) {
1723
+				throw new EE_Error(
1724
+					esc_html__(
1725
+						'Does not look like the popup content array has been setup correctly.  Might want to double check that.  Read the comments for the _get_help_popup_content method found in "EE_Admin_Page" class',
1726
+						'event_espresso'
1727
+					)
1728
+				);
1729
+			}
1730
+			// we're good so let's set up the template vars and then assign parsed template content to our content.
1731
+			$template_args = [
1732
+				'help_popup_id'      => $trigger,
1733
+				'help_popup_title'   => $help['title'],
1734
+				'help_popup_content' => $help['content'],
1735
+			];
1736
+			$content       .= EEH_Template::display_template(
1737
+				EE_ADMIN_TEMPLATE . 'admin_help_popup.template.php',
1738
+				$template_args,
1739
+				true
1740
+			);
1741
+		}
1742
+		if ($display) {
1743
+			echo wp_kses($content, AllowedTags::getWithFormTags());
1744
+			return '';
1745
+		}
1746
+		return $content;
1747
+	}
1748
+
1749
+
1750
+	/**
1751
+	 * All this does is retrieve the help content array if set by the EE_Admin_Page child
1752
+	 *
1753
+	 * @return array properly formatted array for help popup content
1754
+	 * @throws EE_Error
1755
+	 */
1756
+	private function _get_help_content(): array
1757
+	{
1758
+		// what is the method we're looking for?
1759
+		$method_name = '_help_popup_content_' . $this->_req_action;
1760
+		// if method doesn't exist let's get out.
1761
+		if (! method_exists($this, $method_name)) {
1762
+			return [];
1763
+		}
1764
+		// k we're good to go let's retrieve the help array
1765
+		$help_array = $this->{$method_name}();
1766
+		// make sure we've got an array!
1767
+		if (! is_array($help_array)) {
1768
+			throw new EE_Error(
1769
+				esc_html__(
1770
+					'Something went wrong with help popup content generation. Expecting an array and well, this ain\'t no array bub.',
1771
+					'event_espresso'
1772
+				)
1773
+			);
1774
+		}
1775
+		return $help_array;
1776
+	}
1777
+
1778
+
1779
+	/**
1780
+	 * EE Admin Pages can use this to set a properly formatted trigger for a help popup.
1781
+	 * By default the trigger html is printed.  Otherwise it can be returned if the $display flag is set "false"
1782
+	 * See comments made on the _set_help_content method for understanding other parts to the help popup tool.
1783
+	 *
1784
+	 * @param string $trigger_id reference for retrieving the trigger content for the popup
1785
+	 * @param bool   $display    if false then we return the trigger string
1786
+	 * @param array  $dimensions an array of dimensions for the box (array(h,w))
1787
+	 * @return string
1788
+	 * @throws DomainException
1789
+	 * @throws EE_Error
1790
+	 */
1791
+	protected function _set_help_trigger(string $trigger_id, bool $display = true, array $dimensions = ['400', '640'])
1792
+	{
1793
+		if ($this->request->isAjax()) {
1794
+			return '';
1795
+		}
1796
+		// let's check and see if there is any content set for this popup.  If there isn't then we'll include a default title and content so that developers know something needs to be corrected
1797
+		$help_array   = $this->_get_help_content();
1798
+		$help_content = '';
1799
+		if (empty($help_array) || ! isset($help_array[ $trigger_id ])) {
1800
+			$help_array[ $trigger_id ] = [
1801
+				'title'   => esc_html__('Missing Content', 'event_espresso'),
1802
+				'content' => esc_html__(
1803
+					'A trigger has been set that doesn\'t have any corresponding content. Make sure you have set the help content. (see the "_set_help_popup_content" method in the EE_Admin_Page for instructions.)',
1804
+					'event_espresso'
1805
+				),
1806
+			];
1807
+			$help_content              = $this->_set_help_popup_content($help_array);
1808
+		}
1809
+		$height   = esc_attr($dimensions[0]) ?? 400;
1810
+		$width    = esc_attr($dimensions[1]) ?? 640;
1811
+		$inlineId = esc_attr($trigger_id);
1812
+		// let's setup the trigger
1813
+		$content = "
1814 1814
         <a class='ee-dialog' href='?height=$height&width=$width&inlineId=$inlineId' target='_blank'>
1815 1815
             <span class='question ee-help-popup-question'></span>
1816 1816
         </a>";
1817
-        $content .= $help_content;
1818
-        if ($display) {
1819
-            echo wp_kses($content, AllowedTags::getWithFormTags());
1820
-            return '';
1821
-        }
1822
-        return $content;
1823
-    }
1824
-
1825
-
1826
-    /**
1827
-     * _add_global_screen_options
1828
-     * Add any extra wp_screen_options within this method using built-in WP functions/methods for doing so.
1829
-     * This particular method will add_screen_options on ALL EE_Admin Pages
1830
-     *
1831
-     * @link   http://chrismarslender.com/wp-tutorials/wordpress-screen-options-tutorial/
1832
-     *         see also WP_Screen object documents...
1833
-     * @link   http://codex.wordpress.org/Class_Reference/WP_Screen
1834
-     * @abstract
1835
-     * @return void
1836
-     */
1837
-    private function _add_global_screen_options()
1838
-    {
1839
-    }
1840
-
1841
-
1842
-    /**
1843
-     * _add_global_feature_pointers
1844
-     * This method is used for implementing any "feature pointers" (using built-in WP styling js).
1845
-     * This particular method will implement feature pointers for ALL EE_Admin pages.
1846
-     * Note: this is just a placeholder for now.  Implementation will come down the road
1847
-     *
1848
-     * @see    WP_Internal_Pointers class in wp-admin/includes/template.php for example (it's a final class so can't be
1849
-     *         extended) also see:
1850
-     * @link   http://eamann.com/tech/wordpress-portland/
1851
-     * @abstract
1852
-     * @return void
1853
-     */
1854
-    private function _add_global_feature_pointers()
1855
-    {
1856
-    }
1857
-
1858
-
1859
-    /**
1860
-     * load_global_scripts_styles
1861
-     * The scripts and styles enqueued in here will be loaded on every EE Admin page
1862
-     *
1863
-     * @return void
1864
-     */
1865
-    public function load_global_scripts_styles()
1866
-    {
1867
-        // add debugging styles
1868
-        if (WP_DEBUG) {
1869
-            add_action('admin_head', [$this, 'add_xdebug_style']);
1870
-        }
1871
-        // taking care of metaboxes
1872
-        if (
1873
-            empty($this->_cpt_route)
1874
-            && (isset($this->_route_config['metaboxes']) || isset($this->_route_config['has_metaboxes']))
1875
-        ) {
1876
-            wp_enqueue_script('dashboard');
1877
-        }
1878
-
1879
-        wp_enqueue_script(JqueryAssetManager::JS_HANDLE_JQUERY_UI_TOUCH_PUNCH);
1880
-        wp_enqueue_script(EspressoLegacyAdminAssetManager::JS_HANDLE_EE_ADMIN);
1881
-        // LOCALIZED DATA
1882
-        // localize script for ajax lazy loading
1883
-        wp_localize_script(
1884
-            EspressoLegacyAdminAssetManager::JS_HANDLE_EE_ADMIN,
1885
-            'eeLazyLoadingContainers',
1886
-            apply_filters(
1887
-                'FHEE__EE_Admin_Page_Core__load_global_scripts_styles__loader_containers',
1888
-                ['espresso_news_post_box_content']
1889
-            )
1890
-        );
1891
-        StatusChangeNotice::loadAssets();
1892
-
1893
-        add_filter(
1894
-            'admin_body_class',
1895
-            function ($classes) {
1896
-                if (strpos($classes, 'espresso-admin') === false) {
1897
-                    $classes .= ' espresso-admin';
1898
-                }
1899
-                return $classes;
1900
-            }
1901
-        );
1902
-    }
1903
-
1904
-
1905
-    /**
1906
-     *        admin_footer_scripts_eei18n_js_strings
1907
-     *
1908
-     * @return        void
1909
-     */
1910
-    public function admin_footer_scripts_eei18n_js_strings()
1911
-    {
1912
-        EE_Registry::$i18n_js_strings['confirm_delete'] = wp_strip_all_tags(
1913
-            __(
1914
-                'Are you absolutely sure you want to delete this item?\nThis action will delete ALL DATA associated with this item!!!\nThis can NOT be undone!!!',
1915
-                'event_espresso'
1916
-            )
1917
-        );
1918
-        // now add months and days of the week
1919
-        EE_Registry::$i18n_js_strings['January']   = wp_strip_all_tags(__('January', 'event_espresso'));
1920
-        EE_Registry::$i18n_js_strings['February']  = wp_strip_all_tags(__('February', 'event_espresso'));
1921
-        EE_Registry::$i18n_js_strings['March']     = wp_strip_all_tags(__('March', 'event_espresso'));
1922
-        EE_Registry::$i18n_js_strings['April']     = wp_strip_all_tags(__('April', 'event_espresso'));
1923
-        EE_Registry::$i18n_js_strings['May']       = wp_strip_all_tags(__('May', 'event_espresso'));
1924
-        EE_Registry::$i18n_js_strings['June']      = wp_strip_all_tags(__('June', 'event_espresso'));
1925
-        EE_Registry::$i18n_js_strings['July']      = wp_strip_all_tags(__('July', 'event_espresso'));
1926
-        EE_Registry::$i18n_js_strings['August']    = wp_strip_all_tags(__('August', 'event_espresso'));
1927
-        EE_Registry::$i18n_js_strings['September'] = wp_strip_all_tags(__('September', 'event_espresso'));
1928
-        EE_Registry::$i18n_js_strings['October']   = wp_strip_all_tags(__('October', 'event_espresso'));
1929
-        EE_Registry::$i18n_js_strings['November']  = wp_strip_all_tags(__('November', 'event_espresso'));
1930
-        EE_Registry::$i18n_js_strings['December']  = wp_strip_all_tags(__('December', 'event_espresso'));
1931
-        EE_Registry::$i18n_js_strings['Jan']       = wp_strip_all_tags(__('Jan', 'event_espresso'));
1932
-        EE_Registry::$i18n_js_strings['Feb']       = wp_strip_all_tags(__('Feb', 'event_espresso'));
1933
-        EE_Registry::$i18n_js_strings['Mar']       = wp_strip_all_tags(__('Mar', 'event_espresso'));
1934
-        EE_Registry::$i18n_js_strings['Apr']       = wp_strip_all_tags(__('Apr', 'event_espresso'));
1935
-        EE_Registry::$i18n_js_strings['May']       = wp_strip_all_tags(__('May', 'event_espresso'));
1936
-        EE_Registry::$i18n_js_strings['Jun']       = wp_strip_all_tags(__('Jun', 'event_espresso'));
1937
-        EE_Registry::$i18n_js_strings['Jul']       = wp_strip_all_tags(__('Jul', 'event_espresso'));
1938
-        EE_Registry::$i18n_js_strings['Aug']       = wp_strip_all_tags(__('Aug', 'event_espresso'));
1939
-        EE_Registry::$i18n_js_strings['Sep']       = wp_strip_all_tags(__('Sep', 'event_espresso'));
1940
-        EE_Registry::$i18n_js_strings['Oct']       = wp_strip_all_tags(__('Oct', 'event_espresso'));
1941
-        EE_Registry::$i18n_js_strings['Nov']       = wp_strip_all_tags(__('Nov', 'event_espresso'));
1942
-        EE_Registry::$i18n_js_strings['Dec']       = wp_strip_all_tags(__('Dec', 'event_espresso'));
1943
-        EE_Registry::$i18n_js_strings['Sunday']    = wp_strip_all_tags(__('Sunday', 'event_espresso'));
1944
-        EE_Registry::$i18n_js_strings['Monday']    = wp_strip_all_tags(__('Monday', 'event_espresso'));
1945
-        EE_Registry::$i18n_js_strings['Tuesday']   = wp_strip_all_tags(__('Tuesday', 'event_espresso'));
1946
-        EE_Registry::$i18n_js_strings['Wednesday'] = wp_strip_all_tags(__('Wednesday', 'event_espresso'));
1947
-        EE_Registry::$i18n_js_strings['Thursday']  = wp_strip_all_tags(__('Thursday', 'event_espresso'));
1948
-        EE_Registry::$i18n_js_strings['Friday']    = wp_strip_all_tags(__('Friday', 'event_espresso'));
1949
-        EE_Registry::$i18n_js_strings['Saturday']  = wp_strip_all_tags(__('Saturday', 'event_espresso'));
1950
-        EE_Registry::$i18n_js_strings['Sun']       = wp_strip_all_tags(__('Sun', 'event_espresso'));
1951
-        EE_Registry::$i18n_js_strings['Mon']       = wp_strip_all_tags(__('Mon', 'event_espresso'));
1952
-        EE_Registry::$i18n_js_strings['Tue']       = wp_strip_all_tags(__('Tue', 'event_espresso'));
1953
-        EE_Registry::$i18n_js_strings['Wed']       = wp_strip_all_tags(__('Wed', 'event_espresso'));
1954
-        EE_Registry::$i18n_js_strings['Thu']       = wp_strip_all_tags(__('Thu', 'event_espresso'));
1955
-        EE_Registry::$i18n_js_strings['Fri']       = wp_strip_all_tags(__('Fri', 'event_espresso'));
1956
-        EE_Registry::$i18n_js_strings['Sat']       = wp_strip_all_tags(__('Sat', 'event_espresso'));
1957
-    }
1958
-
1959
-
1960
-    /**
1961
-     *        load enhanced xdebug styles for ppl with failing eyesight
1962
-     *
1963
-     * @return        void
1964
-     */
1965
-    public function add_xdebug_style()
1966
-    {
1967
-        echo '<style>.xdebug-error { font-size:1.5em; }</style>';
1968
-    }
1969
-
1970
-
1971
-    /************************/
1972
-    /** LIST TABLE METHODS **/
1973
-    /************************/
1974
-    /**
1975
-     * this sets up the list table if the current view requires it.
1976
-     *
1977
-     * @return void
1978
-     * @throws EE_Error
1979
-     * @throws InvalidArgumentException
1980
-     * @throws InvalidDataTypeException
1981
-     * @throws InvalidInterfaceException
1982
-     */
1983
-    protected function _set_list_table()
1984
-    {
1985
-        // first is this a list_table view?
1986
-        if (! isset($this->_route_config['list_table'])) {
1987
-            return;
1988
-        } //not a list_table view so get out.
1989
-        // list table functions are per view specific (because some admin pages might have more than one list table!)
1990
-        $list_table_view = '_set_list_table_views_' . $this->_req_action;
1991
-        if (! method_exists($this, $list_table_view)) {
1992
-            // user error msg
1993
-            $error_msg = esc_html__(
1994
-                'An error occurred. The requested list table views could not be found.',
1995
-                'event_espresso'
1996
-            );
1997
-            // developer error msg
1998
-            $error_msg .= '||'
1999
-                          . sprintf(
2000
-                              esc_html__(
2001
-                                  'List table views for "%s" route could not be setup. Check that you have the corresponding method, "%s" set up for defining list_table_views for this route.',
2002
-                                  'event_espresso'
2003
-                              ),
2004
-                              $this->_req_action,
2005
-                              $list_table_view
2006
-                          );
2007
-            throw new EE_Error($error_msg);
2008
-        }
2009
-        $this->{$list_table_view}();
2010
-        // let's provide the ability to filter the views per PAGE AND ROUTE, per PAGE, and globally
2011
-        $this->_views = apply_filters(
2012
-            'FHEE_list_table_views_' . $this->page_slug . '_' . $this->_req_action,
2013
-            $this->_views
2014
-        );
2015
-        $this->_views = apply_filters('FHEE_list_table_views_' . $this->page_slug, $this->_views);
2016
-        $this->_views = apply_filters('FHEE_list_table_views', $this->_views);
2017
-        $this->_set_list_table_view();
2018
-        $this->_set_list_table_object();
2019
-    }
2020
-
2021
-
2022
-    /**
2023
-     * set current view for List Table
2024
-     *
2025
-     * @return void
2026
-     */
2027
-    protected function _set_list_table_view()
2028
-    {
2029
-        $this->_view = isset($this->_views['in_use']) ? 'in_use' : 'all';
2030
-        $status      = $this->request->getRequestParam('status', null, DataType::KEY);
2031
-        $this->_view = $status && array_key_exists($status, $this->_views)
2032
-            ? $status
2033
-            : $this->_view;
2034
-    }
2035
-
2036
-
2037
-    /**
2038
-     * _set_list_table_object
2039
-     * WP_List_Table objects need to be loaded fairly early so automatic stuff WP does is taken care of.
2040
-     *
2041
-     * @throws InvalidInterfaceException
2042
-     * @throws InvalidArgumentException
2043
-     * @throws InvalidDataTypeException
2044
-     * @throws EE_Error
2045
-     * @throws InvalidInterfaceException
2046
-     */
2047
-    protected function _set_list_table_object()
2048
-    {
2049
-        if (isset($this->_route_config['list_table'])) {
2050
-            if (! class_exists($this->_route_config['list_table'])) {
2051
-                throw new EE_Error(
2052
-                    sprintf(
2053
-                        esc_html__(
2054
-                            'The %s class defined for the list table does not exist.  Please check the spelling of the class ref in the $_page_config property on %s.',
2055
-                            'event_espresso'
2056
-                        ),
2057
-                        $this->_route_config['list_table'],
2058
-                        $this->class_name
2059
-                    )
2060
-                );
2061
-            }
2062
-            $this->_list_table_object = $this->loader->getShared(
2063
-                $this->_route_config['list_table'],
2064
-                [
2065
-                    $this,
2066
-                    LoaderFactory::getShared(AdminListTableFilters::class),
2067
-                ]
2068
-            );
2069
-        }
2070
-    }
2071
-
2072
-
2073
-    /**
2074
-     * get_list_table_view_RLs - get it? View RL ?? VU-RL???  URL ??
2075
-     *
2076
-     * @param array $extra_query_args                     Optional. An array of extra query args to add to the generated
2077
-     *                                                    urls.  The array should be indexed by the view it is being
2078
-     *                                                    added to.
2079
-     * @return array
2080
-     */
2081
-    public function get_list_table_view_RLs(array $extra_query_args = []): array
2082
-    {
2083
-        $extra_query_args = apply_filters(
2084
-            'FHEE__EE_Admin_Page__get_list_table_view_RLs__extra_query_args',
2085
-            $extra_query_args,
2086
-            $this
2087
-        );
2088
-        $action_nonce = "{$this->_req_action}_nonce";
2089
-        $nonce = wp_create_nonce($action_nonce);
2090
-        // cycle thru views
2091
-        foreach ($this->_views as $key => $view) {
2092
-            $query_args = [];
2093
-            if ( ! isset($this->_views[ $key ]['class'])) {
2094
-                $this->_views[ $key ]['class'] = '';
2095
-            }
2096
-            // check for current view
2097
-            $this->_views[ $key ]['class'] .= $this->_view === $view['slug'] ? ' current' : '';
2098
-            $query_args['action']          = $this->_req_action;
2099
-            $query_args[ $action_nonce ]   = $nonce;
2100
-            $query_args['status']          = $view['slug'];
2101
-            // merge any other arguments sent in.
2102
-            if (isset($extra_query_args[ $view['slug'] ])) {
2103
-                $query_args = array_merge($query_args, $extra_query_args[ $view['slug'] ]);
2104
-            }
2105
-            $this->_views[ $key ]['url'] = EE_Admin_Page::add_query_args_and_nonce($query_args, $this->_admin_base_url);
2106
-        }
2107
-        return $this->_views;
2108
-    }
2109
-
2110
-
2111
-    /**
2112
-     * generates a dropdown box for selecting the number of visible rows in an admin page list table
2113
-     *
2114
-     * @param int $max_entries total number of rows in the table
2115
-     * @return string
2116
-     * @todo   : Note: ideally this should be added to the screen options dropdown as that would be consistent with how
2117
-     *                         WP does it.
2118
-     */
2119
-    protected function _entries_per_page_dropdown(int $max_entries = 0): string
2120
-    {
2121
-        $values   = [10, 25, 50, 100];
2122
-        $per_page = $this->request->getRequestParam('per_page', 10, DataType::INT);
2123
-        if ($max_entries) {
2124
-            $values[] = $max_entries;
2125
-            sort($values);
2126
-        }
2127
-        $entries_per_page_dropdown = '
1817
+		$content .= $help_content;
1818
+		if ($display) {
1819
+			echo wp_kses($content, AllowedTags::getWithFormTags());
1820
+			return '';
1821
+		}
1822
+		return $content;
1823
+	}
1824
+
1825
+
1826
+	/**
1827
+	 * _add_global_screen_options
1828
+	 * Add any extra wp_screen_options within this method using built-in WP functions/methods for doing so.
1829
+	 * This particular method will add_screen_options on ALL EE_Admin Pages
1830
+	 *
1831
+	 * @link   http://chrismarslender.com/wp-tutorials/wordpress-screen-options-tutorial/
1832
+	 *         see also WP_Screen object documents...
1833
+	 * @link   http://codex.wordpress.org/Class_Reference/WP_Screen
1834
+	 * @abstract
1835
+	 * @return void
1836
+	 */
1837
+	private function _add_global_screen_options()
1838
+	{
1839
+	}
1840
+
1841
+
1842
+	/**
1843
+	 * _add_global_feature_pointers
1844
+	 * This method is used for implementing any "feature pointers" (using built-in WP styling js).
1845
+	 * This particular method will implement feature pointers for ALL EE_Admin pages.
1846
+	 * Note: this is just a placeholder for now.  Implementation will come down the road
1847
+	 *
1848
+	 * @see    WP_Internal_Pointers class in wp-admin/includes/template.php for example (it's a final class so can't be
1849
+	 *         extended) also see:
1850
+	 * @link   http://eamann.com/tech/wordpress-portland/
1851
+	 * @abstract
1852
+	 * @return void
1853
+	 */
1854
+	private function _add_global_feature_pointers()
1855
+	{
1856
+	}
1857
+
1858
+
1859
+	/**
1860
+	 * load_global_scripts_styles
1861
+	 * The scripts and styles enqueued in here will be loaded on every EE Admin page
1862
+	 *
1863
+	 * @return void
1864
+	 */
1865
+	public function load_global_scripts_styles()
1866
+	{
1867
+		// add debugging styles
1868
+		if (WP_DEBUG) {
1869
+			add_action('admin_head', [$this, 'add_xdebug_style']);
1870
+		}
1871
+		// taking care of metaboxes
1872
+		if (
1873
+			empty($this->_cpt_route)
1874
+			&& (isset($this->_route_config['metaboxes']) || isset($this->_route_config['has_metaboxes']))
1875
+		) {
1876
+			wp_enqueue_script('dashboard');
1877
+		}
1878
+
1879
+		wp_enqueue_script(JqueryAssetManager::JS_HANDLE_JQUERY_UI_TOUCH_PUNCH);
1880
+		wp_enqueue_script(EspressoLegacyAdminAssetManager::JS_HANDLE_EE_ADMIN);
1881
+		// LOCALIZED DATA
1882
+		// localize script for ajax lazy loading
1883
+		wp_localize_script(
1884
+			EspressoLegacyAdminAssetManager::JS_HANDLE_EE_ADMIN,
1885
+			'eeLazyLoadingContainers',
1886
+			apply_filters(
1887
+				'FHEE__EE_Admin_Page_Core__load_global_scripts_styles__loader_containers',
1888
+				['espresso_news_post_box_content']
1889
+			)
1890
+		);
1891
+		StatusChangeNotice::loadAssets();
1892
+
1893
+		add_filter(
1894
+			'admin_body_class',
1895
+			function ($classes) {
1896
+				if (strpos($classes, 'espresso-admin') === false) {
1897
+					$classes .= ' espresso-admin';
1898
+				}
1899
+				return $classes;
1900
+			}
1901
+		);
1902
+	}
1903
+
1904
+
1905
+	/**
1906
+	 *        admin_footer_scripts_eei18n_js_strings
1907
+	 *
1908
+	 * @return        void
1909
+	 */
1910
+	public function admin_footer_scripts_eei18n_js_strings()
1911
+	{
1912
+		EE_Registry::$i18n_js_strings['confirm_delete'] = wp_strip_all_tags(
1913
+			__(
1914
+				'Are you absolutely sure you want to delete this item?\nThis action will delete ALL DATA associated with this item!!!\nThis can NOT be undone!!!',
1915
+				'event_espresso'
1916
+			)
1917
+		);
1918
+		// now add months and days of the week
1919
+		EE_Registry::$i18n_js_strings['January']   = wp_strip_all_tags(__('January', 'event_espresso'));
1920
+		EE_Registry::$i18n_js_strings['February']  = wp_strip_all_tags(__('February', 'event_espresso'));
1921
+		EE_Registry::$i18n_js_strings['March']     = wp_strip_all_tags(__('March', 'event_espresso'));
1922
+		EE_Registry::$i18n_js_strings['April']     = wp_strip_all_tags(__('April', 'event_espresso'));
1923
+		EE_Registry::$i18n_js_strings['May']       = wp_strip_all_tags(__('May', 'event_espresso'));
1924
+		EE_Registry::$i18n_js_strings['June']      = wp_strip_all_tags(__('June', 'event_espresso'));
1925
+		EE_Registry::$i18n_js_strings['July']      = wp_strip_all_tags(__('July', 'event_espresso'));
1926
+		EE_Registry::$i18n_js_strings['August']    = wp_strip_all_tags(__('August', 'event_espresso'));
1927
+		EE_Registry::$i18n_js_strings['September'] = wp_strip_all_tags(__('September', 'event_espresso'));
1928
+		EE_Registry::$i18n_js_strings['October']   = wp_strip_all_tags(__('October', 'event_espresso'));
1929
+		EE_Registry::$i18n_js_strings['November']  = wp_strip_all_tags(__('November', 'event_espresso'));
1930
+		EE_Registry::$i18n_js_strings['December']  = wp_strip_all_tags(__('December', 'event_espresso'));
1931
+		EE_Registry::$i18n_js_strings['Jan']       = wp_strip_all_tags(__('Jan', 'event_espresso'));
1932
+		EE_Registry::$i18n_js_strings['Feb']       = wp_strip_all_tags(__('Feb', 'event_espresso'));
1933
+		EE_Registry::$i18n_js_strings['Mar']       = wp_strip_all_tags(__('Mar', 'event_espresso'));
1934
+		EE_Registry::$i18n_js_strings['Apr']       = wp_strip_all_tags(__('Apr', 'event_espresso'));
1935
+		EE_Registry::$i18n_js_strings['May']       = wp_strip_all_tags(__('May', 'event_espresso'));
1936
+		EE_Registry::$i18n_js_strings['Jun']       = wp_strip_all_tags(__('Jun', 'event_espresso'));
1937
+		EE_Registry::$i18n_js_strings['Jul']       = wp_strip_all_tags(__('Jul', 'event_espresso'));
1938
+		EE_Registry::$i18n_js_strings['Aug']       = wp_strip_all_tags(__('Aug', 'event_espresso'));
1939
+		EE_Registry::$i18n_js_strings['Sep']       = wp_strip_all_tags(__('Sep', 'event_espresso'));
1940
+		EE_Registry::$i18n_js_strings['Oct']       = wp_strip_all_tags(__('Oct', 'event_espresso'));
1941
+		EE_Registry::$i18n_js_strings['Nov']       = wp_strip_all_tags(__('Nov', 'event_espresso'));
1942
+		EE_Registry::$i18n_js_strings['Dec']       = wp_strip_all_tags(__('Dec', 'event_espresso'));
1943
+		EE_Registry::$i18n_js_strings['Sunday']    = wp_strip_all_tags(__('Sunday', 'event_espresso'));
1944
+		EE_Registry::$i18n_js_strings['Monday']    = wp_strip_all_tags(__('Monday', 'event_espresso'));
1945
+		EE_Registry::$i18n_js_strings['Tuesday']   = wp_strip_all_tags(__('Tuesday', 'event_espresso'));
1946
+		EE_Registry::$i18n_js_strings['Wednesday'] = wp_strip_all_tags(__('Wednesday', 'event_espresso'));
1947
+		EE_Registry::$i18n_js_strings['Thursday']  = wp_strip_all_tags(__('Thursday', 'event_espresso'));
1948
+		EE_Registry::$i18n_js_strings['Friday']    = wp_strip_all_tags(__('Friday', 'event_espresso'));
1949
+		EE_Registry::$i18n_js_strings['Saturday']  = wp_strip_all_tags(__('Saturday', 'event_espresso'));
1950
+		EE_Registry::$i18n_js_strings['Sun']       = wp_strip_all_tags(__('Sun', 'event_espresso'));
1951
+		EE_Registry::$i18n_js_strings['Mon']       = wp_strip_all_tags(__('Mon', 'event_espresso'));
1952
+		EE_Registry::$i18n_js_strings['Tue']       = wp_strip_all_tags(__('Tue', 'event_espresso'));
1953
+		EE_Registry::$i18n_js_strings['Wed']       = wp_strip_all_tags(__('Wed', 'event_espresso'));
1954
+		EE_Registry::$i18n_js_strings['Thu']       = wp_strip_all_tags(__('Thu', 'event_espresso'));
1955
+		EE_Registry::$i18n_js_strings['Fri']       = wp_strip_all_tags(__('Fri', 'event_espresso'));
1956
+		EE_Registry::$i18n_js_strings['Sat']       = wp_strip_all_tags(__('Sat', 'event_espresso'));
1957
+	}
1958
+
1959
+
1960
+	/**
1961
+	 *        load enhanced xdebug styles for ppl with failing eyesight
1962
+	 *
1963
+	 * @return        void
1964
+	 */
1965
+	public function add_xdebug_style()
1966
+	{
1967
+		echo '<style>.xdebug-error { font-size:1.5em; }</style>';
1968
+	}
1969
+
1970
+
1971
+	/************************/
1972
+	/** LIST TABLE METHODS **/
1973
+	/************************/
1974
+	/**
1975
+	 * this sets up the list table if the current view requires it.
1976
+	 *
1977
+	 * @return void
1978
+	 * @throws EE_Error
1979
+	 * @throws InvalidArgumentException
1980
+	 * @throws InvalidDataTypeException
1981
+	 * @throws InvalidInterfaceException
1982
+	 */
1983
+	protected function _set_list_table()
1984
+	{
1985
+		// first is this a list_table view?
1986
+		if (! isset($this->_route_config['list_table'])) {
1987
+			return;
1988
+		} //not a list_table view so get out.
1989
+		// list table functions are per view specific (because some admin pages might have more than one list table!)
1990
+		$list_table_view = '_set_list_table_views_' . $this->_req_action;
1991
+		if (! method_exists($this, $list_table_view)) {
1992
+			// user error msg
1993
+			$error_msg = esc_html__(
1994
+				'An error occurred. The requested list table views could not be found.',
1995
+				'event_espresso'
1996
+			);
1997
+			// developer error msg
1998
+			$error_msg .= '||'
1999
+						  . sprintf(
2000
+							  esc_html__(
2001
+								  'List table views for "%s" route could not be setup. Check that you have the corresponding method, "%s" set up for defining list_table_views for this route.',
2002
+								  'event_espresso'
2003
+							  ),
2004
+							  $this->_req_action,
2005
+							  $list_table_view
2006
+						  );
2007
+			throw new EE_Error($error_msg);
2008
+		}
2009
+		$this->{$list_table_view}();
2010
+		// let's provide the ability to filter the views per PAGE AND ROUTE, per PAGE, and globally
2011
+		$this->_views = apply_filters(
2012
+			'FHEE_list_table_views_' . $this->page_slug . '_' . $this->_req_action,
2013
+			$this->_views
2014
+		);
2015
+		$this->_views = apply_filters('FHEE_list_table_views_' . $this->page_slug, $this->_views);
2016
+		$this->_views = apply_filters('FHEE_list_table_views', $this->_views);
2017
+		$this->_set_list_table_view();
2018
+		$this->_set_list_table_object();
2019
+	}
2020
+
2021
+
2022
+	/**
2023
+	 * set current view for List Table
2024
+	 *
2025
+	 * @return void
2026
+	 */
2027
+	protected function _set_list_table_view()
2028
+	{
2029
+		$this->_view = isset($this->_views['in_use']) ? 'in_use' : 'all';
2030
+		$status      = $this->request->getRequestParam('status', null, DataType::KEY);
2031
+		$this->_view = $status && array_key_exists($status, $this->_views)
2032
+			? $status
2033
+			: $this->_view;
2034
+	}
2035
+
2036
+
2037
+	/**
2038
+	 * _set_list_table_object
2039
+	 * WP_List_Table objects need to be loaded fairly early so automatic stuff WP does is taken care of.
2040
+	 *
2041
+	 * @throws InvalidInterfaceException
2042
+	 * @throws InvalidArgumentException
2043
+	 * @throws InvalidDataTypeException
2044
+	 * @throws EE_Error
2045
+	 * @throws InvalidInterfaceException
2046
+	 */
2047
+	protected function _set_list_table_object()
2048
+	{
2049
+		if (isset($this->_route_config['list_table'])) {
2050
+			if (! class_exists($this->_route_config['list_table'])) {
2051
+				throw new EE_Error(
2052
+					sprintf(
2053
+						esc_html__(
2054
+							'The %s class defined for the list table does not exist.  Please check the spelling of the class ref in the $_page_config property on %s.',
2055
+							'event_espresso'
2056
+						),
2057
+						$this->_route_config['list_table'],
2058
+						$this->class_name
2059
+					)
2060
+				);
2061
+			}
2062
+			$this->_list_table_object = $this->loader->getShared(
2063
+				$this->_route_config['list_table'],
2064
+				[
2065
+					$this,
2066
+					LoaderFactory::getShared(AdminListTableFilters::class),
2067
+				]
2068
+			);
2069
+		}
2070
+	}
2071
+
2072
+
2073
+	/**
2074
+	 * get_list_table_view_RLs - get it? View RL ?? VU-RL???  URL ??
2075
+	 *
2076
+	 * @param array $extra_query_args                     Optional. An array of extra query args to add to the generated
2077
+	 *                                                    urls.  The array should be indexed by the view it is being
2078
+	 *                                                    added to.
2079
+	 * @return array
2080
+	 */
2081
+	public function get_list_table_view_RLs(array $extra_query_args = []): array
2082
+	{
2083
+		$extra_query_args = apply_filters(
2084
+			'FHEE__EE_Admin_Page__get_list_table_view_RLs__extra_query_args',
2085
+			$extra_query_args,
2086
+			$this
2087
+		);
2088
+		$action_nonce = "{$this->_req_action}_nonce";
2089
+		$nonce = wp_create_nonce($action_nonce);
2090
+		// cycle thru views
2091
+		foreach ($this->_views as $key => $view) {
2092
+			$query_args = [];
2093
+			if ( ! isset($this->_views[ $key ]['class'])) {
2094
+				$this->_views[ $key ]['class'] = '';
2095
+			}
2096
+			// check for current view
2097
+			$this->_views[ $key ]['class'] .= $this->_view === $view['slug'] ? ' current' : '';
2098
+			$query_args['action']          = $this->_req_action;
2099
+			$query_args[ $action_nonce ]   = $nonce;
2100
+			$query_args['status']          = $view['slug'];
2101
+			// merge any other arguments sent in.
2102
+			if (isset($extra_query_args[ $view['slug'] ])) {
2103
+				$query_args = array_merge($query_args, $extra_query_args[ $view['slug'] ]);
2104
+			}
2105
+			$this->_views[ $key ]['url'] = EE_Admin_Page::add_query_args_and_nonce($query_args, $this->_admin_base_url);
2106
+		}
2107
+		return $this->_views;
2108
+	}
2109
+
2110
+
2111
+	/**
2112
+	 * generates a dropdown box for selecting the number of visible rows in an admin page list table
2113
+	 *
2114
+	 * @param int $max_entries total number of rows in the table
2115
+	 * @return string
2116
+	 * @todo   : Note: ideally this should be added to the screen options dropdown as that would be consistent with how
2117
+	 *                         WP does it.
2118
+	 */
2119
+	protected function _entries_per_page_dropdown(int $max_entries = 0): string
2120
+	{
2121
+		$values   = [10, 25, 50, 100];
2122
+		$per_page = $this->request->getRequestParam('per_page', 10, DataType::INT);
2123
+		if ($max_entries) {
2124
+			$values[] = $max_entries;
2125
+			sort($values);
2126
+		}
2127
+		$entries_per_page_dropdown = '
2128 2128
 			<div id="entries-per-page-dv" class="alignleft actions">
2129 2129
 				<label class="hide-if-no-js">
2130 2130
 					Show
2131 2131
 					<select id="entries-per-page-slct" name="entries-per-page-slct">';
2132
-        foreach ($values as $value) {
2133
-            if ($value < $max_entries) {
2134
-                $selected                  = $value === $per_page ? ' selected="' . $per_page . '"' : '';
2135
-                $entries_per_page_dropdown .= '
2132
+		foreach ($values as $value) {
2133
+			if ($value < $max_entries) {
2134
+				$selected                  = $value === $per_page ? ' selected="' . $per_page . '"' : '';
2135
+				$entries_per_page_dropdown .= '
2136 2136
 						<option value="' . $value . '"' . $selected . '>' . $value . '&nbsp;&nbsp;</option>';
2137
-            }
2138
-        }
2139
-        $selected                  = $max_entries === $per_page ? ' selected="' . $per_page . '"' : '';
2140
-        $entries_per_page_dropdown .= '
2137
+			}
2138
+		}
2139
+		$selected                  = $max_entries === $per_page ? ' selected="' . $per_page . '"' : '';
2140
+		$entries_per_page_dropdown .= '
2141 2141
 						<option value="' . $max_entries . '"' . $selected . '>All&nbsp;&nbsp;</option>';
2142
-        $entries_per_page_dropdown .= '
2142
+		$entries_per_page_dropdown .= '
2143 2143
 					</select>
2144 2144
 					entries
2145 2145
 				</label>
2146 2146
 				<input id="entries-per-page-btn" class="button button--secondary" type="submit" value="Go" >
2147 2147
 			</div>
2148 2148
 		';
2149
-        return $entries_per_page_dropdown;
2150
-    }
2151
-
2152
-
2153
-    /**
2154
-     *        _set_search_attributes
2155
-     *
2156
-     * @return        void
2157
-     */
2158
-    public function _set_search_attributes()
2159
-    {
2160
-        $this->_template_args['search']['btn_label'] = sprintf(
2161
-            esc_html__('Search %s', 'event_espresso'),
2162
-            empty($this->_search_btn_label) ? $this->page_label
2163
-                : $this->_search_btn_label
2164
-        );
2165
-        $this->_template_args['search']['callback']  = 'search_' . $this->page_slug;
2166
-    }
2167
-
2168
-
2169
-
2170
-    /*** END LIST TABLE METHODS **/
2171
-
2172
-    /**
2173
-     * @return void
2174
-     * @throws EE_Error
2175
-     */
2176
-    public function addRegisteredMetaBoxes()
2177
-    {
2178
-        remove_action('add_meta_boxes', [$this, 'addRegisteredMetaBoxes'], 99);
2179
-        $this->_add_registered_meta_boxes();
2180
-    }
2181
-
2182
-
2183
-    /**
2184
-     * _add_registered_metaboxes
2185
-     *  this loads any registered metaboxes via the 'metaboxes' index in the _page_config property array.
2186
-     *
2187
-     * @link   http://codex.wordpress.org/Function_Reference/add_meta_box
2188
-     * @return void
2189
-     * @throws EE_Error
2190
-     */
2191
-    private function _add_registered_meta_boxes()
2192
-    {
2193
-        // we only add meta boxes if the page_route calls for it
2194
-        if (isset($this->_route_config['metaboxes']) && is_array($this->_route_config['metaboxes'])) {
2195
-            // this simply loops through the callbacks provided
2196
-            // and checks if there is a corresponding callback registered by the child
2197
-            // if there is then we go ahead and process the metabox loader.
2198
-            foreach ($this->_route_config['metaboxes'] as $key => $metabox_callback) {
2199
-                // first check for Closures
2200
-                if ($metabox_callback instanceof Closure) {
2201
-                    $result = $metabox_callback();
2202
-                } elseif (is_callable($metabox_callback)) {
2203
-                    $result = call_user_func($metabox_callback);
2204
-                } elseif (method_exists($this, $metabox_callback)) {
2205
-                    $result = $this->{$metabox_callback}();
2206
-                } else {
2207
-                    $result = false;
2208
-                }
2209
-                if ($result === false) {
2210
-                    // user error msg
2211
-                    $error_msg = esc_html__(
2212
-                        'An error occurred. The  requested metabox could not be found.',
2213
-                        'event_espresso'
2214
-                    );
2215
-                    // developer error msg
2216
-                    $error_msg .= '||'
2217
-                                  . sprintf(
2218
-                                      esc_html__(
2219
-                                          'The metabox with the string "%s" could not be called. Check that the spelling for method names and actions in the "_page_config[\'metaboxes\']" array are all correct.',
2220
-                                          'event_espresso'
2221
-                                      ),
2222
-                                      $metabox_callback
2223
-                                  );
2224
-                    throw new EE_Error($error_msg);
2225
-                }
2226
-                unset($this->_route_config['metaboxes'][ $key ]);
2227
-            }
2228
-        }
2229
-    }
2230
-
2231
-
2232
-    /**
2233
-     * _add_screen_columns
2234
-     * This will check the _page_config array and if there is "columns" key index indicated, we'll set the template as
2235
-     * the dynamic column template and we'll set up the column options for the page.
2236
-     *
2237
-     * @return void
2238
-     */
2239
-    private function _add_screen_columns()
2240
-    {
2241
-        if (
2242
-            isset($this->_route_config['columns'])
2243
-            && is_array($this->_route_config['columns'])
2244
-            && count($this->_route_config['columns']) === 2
2245
-        ) {
2246
-            add_screen_option(
2247
-                'layout_columns',
2248
-                [
2249
-                    'max'     => (int) $this->_route_config['columns'][0],
2250
-                    'default' => (int) $this->_route_config['columns'][1],
2251
-                ]
2252
-            );
2253
-            $this->_template_args['num_columns']                 = $this->_route_config['columns'][0];
2254
-            $screen_id                                           = $this->_current_screen->id;
2255
-            $screen_columns                                      = (int) get_user_option("screen_layout_$screen_id");
2256
-            $total_columns                                       = ! empty($screen_columns)
2257
-                ? $screen_columns
2258
-                : $this->_route_config['columns'][1];
2259
-            $this->_template_args['current_screen_widget_class'] = 'columns-' . $total_columns;
2260
-            $this->_template_args['current_page']                = $this->_wp_page_slug;
2261
-            $this->_template_args['screen']                      = $this->_current_screen;
2262
-            $this->_column_template_path                         = EE_ADMIN_TEMPLATE
2263
-                                                                   . 'admin_details_metabox_column_wrapper.template.php';
2264
-            // finally if we don't have has_metaboxes set in the route config
2265
-            // let's make sure it IS set otherwise the necessary hidden fields for this won't be loaded.
2266
-            $this->_route_config['has_metaboxes'] = true;
2267
-        }
2268
-    }
2269
-
2270
-
2271
-
2272
-    /** GLOBALLY AVAILABLE METABOXES **/
2273
-
2274
-
2275
-    /**
2276
-     * In this section we put any globally available EE metaboxes for all EE Admin pages.  They are called by simply
2277
-     * referencing the callback in the _page_config array property.  This way you can be very specific about what pages
2278
-     * these get loaded on.
2279
-     */
2280
-    private function _espresso_news_post_box()
2281
-    {
2282
-        $news_box_title = apply_filters(
2283
-            'FHEE__EE_Admin_Page___espresso_news_post_box__news_box_title',
2284
-            esc_html__('New @ Event Espresso', 'event_espresso')
2285
-        );
2286
-        $this->addMetaBox(
2287
-            'espresso_news_post_box',
2288
-            $news_box_title,
2289
-            [
2290
-                $this,
2291
-                'espresso_news_post_box',
2292
-            ],
2293
-            $this->_wp_page_slug,
2294
-            'side',
2295
-            'low'
2296
-        );
2297
-    }
2298
-
2299
-
2300
-    /**
2301
-     * Code for setting up espresso ratings request metabox.
2302
-     */
2303
-    protected function _espresso_ratings_request()
2304
-    {
2305
-        if (! apply_filters('FHEE_show_ratings_request_meta_box', true)) {
2306
-            return;
2307
-        }
2308
-        $ratings_box_title = apply_filters(
2309
-            'FHEE__EE_Admin_Page___espresso_news_post_box__news_box_title',
2310
-            esc_html__('Keep Event Espresso Decaf Free', 'event_espresso')
2311
-        );
2312
-        $this->addMetaBox(
2313
-            'espresso_ratings_request',
2314
-            $ratings_box_title,
2315
-            [
2316
-                $this,
2317
-                'espresso_ratings_request',
2318
-            ],
2319
-            $this->_wp_page_slug,
2320
-            'side'
2321
-        );
2322
-    }
2323
-
2324
-
2325
-    /**
2326
-     * Code for setting up espresso ratings request metabox content.
2327
-     *
2328
-     * @throws DomainException
2329
-     */
2330
-    public function espresso_ratings_request()
2331
-    {
2332
-        EEH_Template::display_template(EE_ADMIN_TEMPLATE . 'espresso_ratings_request_content.template.php');
2333
-    }
2334
-
2335
-
2336
-    public static function cached_rss_display(string $rss_id, string $url): bool
2337
-    {
2338
-        $loading   = '<p class="widget-loading hide-if-no-js">'
2339
-                     . esc_html__('Loading&#8230;', 'event_espresso')
2340
-                     . '</p><p class="hide-if-js">'
2341
-                     . esc_html__('This widget requires JavaScript.', 'event_espresso')
2342
-                     . '</p>';
2343
-        $pre       = '<div class="espresso-rss-display">' . "\n\t";
2344
-        $pre       .= '<span id="' . esc_attr($rss_id) . '_url" class="hidden">' . esc_url_raw($url) . '</span>';
2345
-        $post      = '</div>' . "\n";
2346
-        $cache_key = 'ee_rss_' . md5($rss_id);
2347
-        $output    = get_transient($cache_key);
2348
-        if ($output !== false) {
2349
-            echo wp_kses($pre . $output . $post, AllowedTags::getWithFormTags());
2350
-            return true;
2351
-        }
2352
-        if (! (defined('DOING_AJAX') && DOING_AJAX)) {
2353
-            echo wp_kses($pre . $loading . $post, AllowedTags::getWithFormTags());
2354
-            return false;
2355
-        }
2356
-        ob_start();
2357
-        wp_widget_rss_output($url, ['show_date' => 0, 'items' => 5]);
2358
-        set_transient($cache_key, ob_get_flush(), 12 * HOUR_IN_SECONDS);
2359
-        return true;
2360
-    }
2361
-
2362
-
2363
-    public function espresso_news_post_box()
2364
-    {
2365
-        ?>
2149
+		return $entries_per_page_dropdown;
2150
+	}
2151
+
2152
+
2153
+	/**
2154
+	 *        _set_search_attributes
2155
+	 *
2156
+	 * @return        void
2157
+	 */
2158
+	public function _set_search_attributes()
2159
+	{
2160
+		$this->_template_args['search']['btn_label'] = sprintf(
2161
+			esc_html__('Search %s', 'event_espresso'),
2162
+			empty($this->_search_btn_label) ? $this->page_label
2163
+				: $this->_search_btn_label
2164
+		);
2165
+		$this->_template_args['search']['callback']  = 'search_' . $this->page_slug;
2166
+	}
2167
+
2168
+
2169
+
2170
+	/*** END LIST TABLE METHODS **/
2171
+
2172
+	/**
2173
+	 * @return void
2174
+	 * @throws EE_Error
2175
+	 */
2176
+	public function addRegisteredMetaBoxes()
2177
+	{
2178
+		remove_action('add_meta_boxes', [$this, 'addRegisteredMetaBoxes'], 99);
2179
+		$this->_add_registered_meta_boxes();
2180
+	}
2181
+
2182
+
2183
+	/**
2184
+	 * _add_registered_metaboxes
2185
+	 *  this loads any registered metaboxes via the 'metaboxes' index in the _page_config property array.
2186
+	 *
2187
+	 * @link   http://codex.wordpress.org/Function_Reference/add_meta_box
2188
+	 * @return void
2189
+	 * @throws EE_Error
2190
+	 */
2191
+	private function _add_registered_meta_boxes()
2192
+	{
2193
+		// we only add meta boxes if the page_route calls for it
2194
+		if (isset($this->_route_config['metaboxes']) && is_array($this->_route_config['metaboxes'])) {
2195
+			// this simply loops through the callbacks provided
2196
+			// and checks if there is a corresponding callback registered by the child
2197
+			// if there is then we go ahead and process the metabox loader.
2198
+			foreach ($this->_route_config['metaboxes'] as $key => $metabox_callback) {
2199
+				// first check for Closures
2200
+				if ($metabox_callback instanceof Closure) {
2201
+					$result = $metabox_callback();
2202
+				} elseif (is_callable($metabox_callback)) {
2203
+					$result = call_user_func($metabox_callback);
2204
+				} elseif (method_exists($this, $metabox_callback)) {
2205
+					$result = $this->{$metabox_callback}();
2206
+				} else {
2207
+					$result = false;
2208
+				}
2209
+				if ($result === false) {
2210
+					// user error msg
2211
+					$error_msg = esc_html__(
2212
+						'An error occurred. The  requested metabox could not be found.',
2213
+						'event_espresso'
2214
+					);
2215
+					// developer error msg
2216
+					$error_msg .= '||'
2217
+								  . sprintf(
2218
+									  esc_html__(
2219
+										  'The metabox with the string "%s" could not be called. Check that the spelling for method names and actions in the "_page_config[\'metaboxes\']" array are all correct.',
2220
+										  'event_espresso'
2221
+									  ),
2222
+									  $metabox_callback
2223
+								  );
2224
+					throw new EE_Error($error_msg);
2225
+				}
2226
+				unset($this->_route_config['metaboxes'][ $key ]);
2227
+			}
2228
+		}
2229
+	}
2230
+
2231
+
2232
+	/**
2233
+	 * _add_screen_columns
2234
+	 * This will check the _page_config array and if there is "columns" key index indicated, we'll set the template as
2235
+	 * the dynamic column template and we'll set up the column options for the page.
2236
+	 *
2237
+	 * @return void
2238
+	 */
2239
+	private function _add_screen_columns()
2240
+	{
2241
+		if (
2242
+			isset($this->_route_config['columns'])
2243
+			&& is_array($this->_route_config['columns'])
2244
+			&& count($this->_route_config['columns']) === 2
2245
+		) {
2246
+			add_screen_option(
2247
+				'layout_columns',
2248
+				[
2249
+					'max'     => (int) $this->_route_config['columns'][0],
2250
+					'default' => (int) $this->_route_config['columns'][1],
2251
+				]
2252
+			);
2253
+			$this->_template_args['num_columns']                 = $this->_route_config['columns'][0];
2254
+			$screen_id                                           = $this->_current_screen->id;
2255
+			$screen_columns                                      = (int) get_user_option("screen_layout_$screen_id");
2256
+			$total_columns                                       = ! empty($screen_columns)
2257
+				? $screen_columns
2258
+				: $this->_route_config['columns'][1];
2259
+			$this->_template_args['current_screen_widget_class'] = 'columns-' . $total_columns;
2260
+			$this->_template_args['current_page']                = $this->_wp_page_slug;
2261
+			$this->_template_args['screen']                      = $this->_current_screen;
2262
+			$this->_column_template_path                         = EE_ADMIN_TEMPLATE
2263
+																   . 'admin_details_metabox_column_wrapper.template.php';
2264
+			// finally if we don't have has_metaboxes set in the route config
2265
+			// let's make sure it IS set otherwise the necessary hidden fields for this won't be loaded.
2266
+			$this->_route_config['has_metaboxes'] = true;
2267
+		}
2268
+	}
2269
+
2270
+
2271
+
2272
+	/** GLOBALLY AVAILABLE METABOXES **/
2273
+
2274
+
2275
+	/**
2276
+	 * In this section we put any globally available EE metaboxes for all EE Admin pages.  They are called by simply
2277
+	 * referencing the callback in the _page_config array property.  This way you can be very specific about what pages
2278
+	 * these get loaded on.
2279
+	 */
2280
+	private function _espresso_news_post_box()
2281
+	{
2282
+		$news_box_title = apply_filters(
2283
+			'FHEE__EE_Admin_Page___espresso_news_post_box__news_box_title',
2284
+			esc_html__('New @ Event Espresso', 'event_espresso')
2285
+		);
2286
+		$this->addMetaBox(
2287
+			'espresso_news_post_box',
2288
+			$news_box_title,
2289
+			[
2290
+				$this,
2291
+				'espresso_news_post_box',
2292
+			],
2293
+			$this->_wp_page_slug,
2294
+			'side',
2295
+			'low'
2296
+		);
2297
+	}
2298
+
2299
+
2300
+	/**
2301
+	 * Code for setting up espresso ratings request metabox.
2302
+	 */
2303
+	protected function _espresso_ratings_request()
2304
+	{
2305
+		if (! apply_filters('FHEE_show_ratings_request_meta_box', true)) {
2306
+			return;
2307
+		}
2308
+		$ratings_box_title = apply_filters(
2309
+			'FHEE__EE_Admin_Page___espresso_news_post_box__news_box_title',
2310
+			esc_html__('Keep Event Espresso Decaf Free', 'event_espresso')
2311
+		);
2312
+		$this->addMetaBox(
2313
+			'espresso_ratings_request',
2314
+			$ratings_box_title,
2315
+			[
2316
+				$this,
2317
+				'espresso_ratings_request',
2318
+			],
2319
+			$this->_wp_page_slug,
2320
+			'side'
2321
+		);
2322
+	}
2323
+
2324
+
2325
+	/**
2326
+	 * Code for setting up espresso ratings request metabox content.
2327
+	 *
2328
+	 * @throws DomainException
2329
+	 */
2330
+	public function espresso_ratings_request()
2331
+	{
2332
+		EEH_Template::display_template(EE_ADMIN_TEMPLATE . 'espresso_ratings_request_content.template.php');
2333
+	}
2334
+
2335
+
2336
+	public static function cached_rss_display(string $rss_id, string $url): bool
2337
+	{
2338
+		$loading   = '<p class="widget-loading hide-if-no-js">'
2339
+					 . esc_html__('Loading&#8230;', 'event_espresso')
2340
+					 . '</p><p class="hide-if-js">'
2341
+					 . esc_html__('This widget requires JavaScript.', 'event_espresso')
2342
+					 . '</p>';
2343
+		$pre       = '<div class="espresso-rss-display">' . "\n\t";
2344
+		$pre       .= '<span id="' . esc_attr($rss_id) . '_url" class="hidden">' . esc_url_raw($url) . '</span>';
2345
+		$post      = '</div>' . "\n";
2346
+		$cache_key = 'ee_rss_' . md5($rss_id);
2347
+		$output    = get_transient($cache_key);
2348
+		if ($output !== false) {
2349
+			echo wp_kses($pre . $output . $post, AllowedTags::getWithFormTags());
2350
+			return true;
2351
+		}
2352
+		if (! (defined('DOING_AJAX') && DOING_AJAX)) {
2353
+			echo wp_kses($pre . $loading . $post, AllowedTags::getWithFormTags());
2354
+			return false;
2355
+		}
2356
+		ob_start();
2357
+		wp_widget_rss_output($url, ['show_date' => 0, 'items' => 5]);
2358
+		set_transient($cache_key, ob_get_flush(), 12 * HOUR_IN_SECONDS);
2359
+		return true;
2360
+	}
2361
+
2362
+
2363
+	public function espresso_news_post_box()
2364
+	{
2365
+		?>
2366 2366
 <div class="padding">
2367 2367
     <div id="espresso_news_post_box_content" class="infolinks">
2368 2368
         <?php
2369
-                // Get RSS Feed(s)
2370
-                EE_Admin_Page::cached_rss_display(
2371
-                    'espresso_news_post_box_content',
2372
-                    esc_url_raw(
2373
-                        apply_filters(
2374
-                            'FHEE__EE_Admin_Page__espresso_news_post_box__feed_url',
2375
-                            'https://eventespresso.com/api/feed/'
2376
-                        )
2377
-                    )
2378
-                );
2379
-        ?>
2369
+				// Get RSS Feed(s)
2370
+				EE_Admin_Page::cached_rss_display(
2371
+					'espresso_news_post_box_content',
2372
+					esc_url_raw(
2373
+						apply_filters(
2374
+							'FHEE__EE_Admin_Page__espresso_news_post_box__feed_url',
2375
+							'https://eventespresso.com/api/feed/'
2376
+						)
2377
+					)
2378
+				);
2379
+		?>
2380 2380
     </div>
2381 2381
     <?php do_action('AHEE__EE_Admin_Page__espresso_news_post_box__after_content'); ?>
2382 2382
 </div>
2383 2383
 <?php
2384
-    }
2385
-
2386
-
2387
-    private function _espresso_links_post_box()
2388
-    {
2389
-        // Hiding until we actually have content to put in here...
2390
-        // $this->addMetaBox('espresso_links_post_box', esc_html__('Helpful Plugin Links', 'event_espresso'), array( $this, 'espresso_links_post_box'), $this->_wp_page_slug, 'side');
2391
-    }
2392
-
2393
-
2394
-    public function espresso_links_post_box()
2395
-    {
2396
-        // Hiding until we actually have content to put in here...
2397
-        // EEH_Template::display_template(
2398
-        //     EE_ADMIN_TEMPLATE . 'admin_general_metabox_contents_espresso_links.template.php'
2399
-        // );
2400
-    }
2401
-
2402
-
2403
-    protected function _espresso_sponsors_post_box()
2404
-    {
2405
-        if (apply_filters('FHEE_show_sponsors_meta_box', true)) {
2406
-            $this->addMetaBox(
2407
-                'espresso_sponsors_post_box',
2408
-                esc_html__('Event Espresso Highlights', 'event_espresso'),
2409
-                [$this, 'espresso_sponsors_post_box'],
2410
-                $this->_wp_page_slug,
2411
-                'side'
2412
-            );
2413
-        }
2414
-    }
2415
-
2416
-
2417
-    public function espresso_sponsors_post_box()
2418
-    {
2419
-        EEH_Template::display_template(
2420
-            EE_ADMIN_TEMPLATE . 'admin_general_metabox_contents_espresso_sponsors.template.php'
2421
-        );
2422
-    }
2423
-
2424
-
2425
-    /**
2426
-     * if there is [ 'label' => [ 'publishbox' => 'some title' ]]
2427
-     * present in the _page_config array, then we'll use that for the metabox label.
2428
-     * Otherwise we'll just use publish
2429
-     * (publishbox itself could be an array of labels indexed by routes)
2430
-     *
2431
-     * @return string
2432
-     * @since   5.0.0.p
2433
-     */
2434
-    protected function getPublishBoxTitle(): string
2435
-    {
2436
-        $publish_box_title = esc_html__('Publish', 'event_espresso');
2437
-        if (! empty($this->_labels['publishbox'])) {
2438
-            if (is_array($this->_labels['publishbox'])) {
2439
-                $publish_box_title = $this->_labels['publishbox'][ $this->_req_action ] ?? $publish_box_title;
2440
-            } else {
2441
-                $publish_box_title = $this->_labels['publishbox'];
2442
-            }
2443
-        }
2444
-        return apply_filters(
2445
-            'FHEE__EE_Admin_Page___publish_post_box__box_label',
2446
-            $publish_box_title,
2447
-            $this->_req_action,
2448
-            $this
2449
-        );
2450
-    }
2451
-
2452
-
2453
-    /**
2454
-     * @throws EE_Error
2455
-     */
2456
-    private function _publish_post_box()
2457
-    {
2458
-        $title = $this->getPublishBoxTitle();
2459
-        if (empty($this->_template_args['save_buttons'])) {
2460
-            $this->_set_publish_post_box_vars(sanitize_key($title), "espresso_{$this->page_slug}_editor_overview");
2461
-        } else {
2462
-            $this->addPublishPostMetaBoxHiddenFields(
2463
-                sanitize_key($title),
2464
-                ['type' => 'hidden', 'value' => "espresso_{$this->page_slug}_editor_overview"]
2465
-            );
2466
-        }
2467
-        $this->addMetaBox(
2468
-            "espresso_{$this->page_slug}_editor_overview",
2469
-            $title,
2470
-            [$this, 'editor_overview'],
2471
-            $this->_current_screen->id,
2472
-            'side',
2473
-            'high'
2474
-        );
2475
-    }
2476
-
2477
-
2478
-    public function editor_overview()
2479
-    {
2480
-        /**
2481
-         * @var string $publish_box_extra_content
2482
-         * @var string $publish_hidden_fields
2483
-         * @var string $publish_delete_link
2484
-         * @var string $save_buttons
2485
-         */
2486
-        // if we have extra content set let's add it in if not make sure its empty
2487
-        $this->_template_args['publish_box_extra_content'] = $this->_template_args['publish_box_extra_content'] ?? '';
2488
-        echo EEH_Template::display_template(
2489
-            EE_ADMIN_TEMPLATE . 'admin_details_publish_metabox.template.php',
2490
-            $this->_template_args,
2491
-            true
2492
-        );
2493
-    }
2494
-
2495
-
2496
-    /** end of globally available metaboxes section **/
2497
-
2498
-
2499
-    /**
2500
-     * Sets the _template_args arguments used by the _publish_post_box shortcut
2501
-     * Note: currently there is no validation for this.  However, if you want the delete button, the
2502
-     * save, and save and close buttons to work properly, then you will want to include a
2503
-     * values for the name and id arguments.
2504
-     *
2505
-     * @param string|null $name                     key used for the action ID (i.e. event_id)
2506
-     * @param int|string  $id                       id attached to the item published
2507
-     * @param string|null $delete                   page route callback for the delete action
2508
-     * @param string|null $save_close_redirect_URL  custom URL to redirect to after Save & Close has been completed
2509
-     * @param bool        $both_btns                whether to display BOTH the "Save & Close" and "Save" buttons
2510
-     *                                              or just the "Save" button
2511
-     * @throws EE_Error
2512
-     * @throws InvalidArgumentException
2513
-     * @throws InvalidDataTypeException
2514
-     * @throws InvalidInterfaceException
2515
-     * @todo  Add in validation for name/id arguments.
2516
-     */
2517
-    protected function _set_publish_post_box_vars(
2518
-        ?string $name = '',
2519
-        $id = 0,
2520
-        ?string $delete = '',
2521
-        ?string $save_close_redirect_URL = '',
2522
-        bool $both_btns = true
2523
-    ) {
2524
-        // if Save & Close, use a custom redirect URL or default to the main page?
2525
-        $save_close_redirect_URL = ! empty($save_close_redirect_URL)
2526
-            ? $save_close_redirect_URL
2527
-            : $this->_admin_base_url;
2528
-        // create the Save & Close and Save buttons
2529
-        $this->_set_save_buttons($both_btns, [], [], $save_close_redirect_URL);
2530
-        // if we have extra content set let's add it in if not make sure its empty
2531
-        $this->_template_args['publish_box_extra_content'] = $this->_template_args['publish_box_extra_content'] ?? '';
2532
-        if ($delete && ! empty($id) && empty($this->_template_args['publish_delete_link'])) {
2533
-            // make sure we have a default if just true is sent.
2534
-            $delete                                      = ! empty($delete) ? $delete : 'delete';
2535
-            $this->_template_args['publish_delete_link'] = $this->get_action_link_or_button(
2536
-                $delete,
2537
-                $delete,
2538
-                [$name => $id],
2539
-                'submitdelete deletion button button--outline button--caution'
2540
-            );
2541
-        }
2542
-        if (! isset($this->_template_args['publish_delete_link'])) {
2543
-            $this->_template_args['publish_delete_link'] = '';
2544
-        }
2545
-        if (! empty($name) && ! empty($id)) {
2546
-            $this->addPublishPostMetaBoxHiddenFields($name, ['type' => 'hidden', 'value' => $id]);
2547
-        }
2548
-        $hidden_fields = $this->_generate_admin_form_fields($this->publish_post_meta_box_hidden_fields, 'array');
2549
-        // add hidden fields
2550
-        $this->_template_args['publish_hidden_fields'] = $this->_template_args['publish_hidden_fields'] ?? '';
2551
-        foreach ($hidden_fields as $hidden_field) {
2552
-            $this->_template_args['publish_hidden_fields'] .= $hidden_field['field'] ?? '';
2553
-        }
2554
-    }
2555
-
2556
-
2557
-    /**
2558
-     * @param string|null $name
2559
-     * @param int|string  $id
2560
-     * @param string|null $delete
2561
-     * @param string|null $save_close_redirect_URL
2562
-     * @param bool        $both_btns
2563
-     * @throws EE_Error
2564
-     */
2565
-    public function set_publish_post_box_vars(
2566
-        ?string $name = '',
2567
-        $id = 0,
2568
-        ?string $delete = '',
2569
-        ?string $save_close_redirect_URL = '',
2570
-        bool $both_btns = false
2571
-    ) {
2572
-        $this->_set_publish_post_box_vars($name, $id, $delete, $save_close_redirect_URL, $both_btns);
2573
-    }
2574
-
2575
-
2576
-    protected function addPublishPostMetaBoxHiddenFields(string $field_name, array $field_attributes)
2577
-    {
2578
-        $this->publish_post_meta_box_hidden_fields[ $field_name ] = $field_attributes;
2579
-    }
2580
-
2581
-
2582
-    /**
2583
-     * displays an error message to ppl who have javascript disabled
2584
-     *
2585
-     * @return void
2586
-     */
2587
-    private function _display_no_javascript_warning()
2588
-    {
2589
-        ?>
2384
+	}
2385
+
2386
+
2387
+	private function _espresso_links_post_box()
2388
+	{
2389
+		// Hiding until we actually have content to put in here...
2390
+		// $this->addMetaBox('espresso_links_post_box', esc_html__('Helpful Plugin Links', 'event_espresso'), array( $this, 'espresso_links_post_box'), $this->_wp_page_slug, 'side');
2391
+	}
2392
+
2393
+
2394
+	public function espresso_links_post_box()
2395
+	{
2396
+		// Hiding until we actually have content to put in here...
2397
+		// EEH_Template::display_template(
2398
+		//     EE_ADMIN_TEMPLATE . 'admin_general_metabox_contents_espresso_links.template.php'
2399
+		// );
2400
+	}
2401
+
2402
+
2403
+	protected function _espresso_sponsors_post_box()
2404
+	{
2405
+		if (apply_filters('FHEE_show_sponsors_meta_box', true)) {
2406
+			$this->addMetaBox(
2407
+				'espresso_sponsors_post_box',
2408
+				esc_html__('Event Espresso Highlights', 'event_espresso'),
2409
+				[$this, 'espresso_sponsors_post_box'],
2410
+				$this->_wp_page_slug,
2411
+				'side'
2412
+			);
2413
+		}
2414
+	}
2415
+
2416
+
2417
+	public function espresso_sponsors_post_box()
2418
+	{
2419
+		EEH_Template::display_template(
2420
+			EE_ADMIN_TEMPLATE . 'admin_general_metabox_contents_espresso_sponsors.template.php'
2421
+		);
2422
+	}
2423
+
2424
+
2425
+	/**
2426
+	 * if there is [ 'label' => [ 'publishbox' => 'some title' ]]
2427
+	 * present in the _page_config array, then we'll use that for the metabox label.
2428
+	 * Otherwise we'll just use publish
2429
+	 * (publishbox itself could be an array of labels indexed by routes)
2430
+	 *
2431
+	 * @return string
2432
+	 * @since   5.0.0.p
2433
+	 */
2434
+	protected function getPublishBoxTitle(): string
2435
+	{
2436
+		$publish_box_title = esc_html__('Publish', 'event_espresso');
2437
+		if (! empty($this->_labels['publishbox'])) {
2438
+			if (is_array($this->_labels['publishbox'])) {
2439
+				$publish_box_title = $this->_labels['publishbox'][ $this->_req_action ] ?? $publish_box_title;
2440
+			} else {
2441
+				$publish_box_title = $this->_labels['publishbox'];
2442
+			}
2443
+		}
2444
+		return apply_filters(
2445
+			'FHEE__EE_Admin_Page___publish_post_box__box_label',
2446
+			$publish_box_title,
2447
+			$this->_req_action,
2448
+			$this
2449
+		);
2450
+	}
2451
+
2452
+
2453
+	/**
2454
+	 * @throws EE_Error
2455
+	 */
2456
+	private function _publish_post_box()
2457
+	{
2458
+		$title = $this->getPublishBoxTitle();
2459
+		if (empty($this->_template_args['save_buttons'])) {
2460
+			$this->_set_publish_post_box_vars(sanitize_key($title), "espresso_{$this->page_slug}_editor_overview");
2461
+		} else {
2462
+			$this->addPublishPostMetaBoxHiddenFields(
2463
+				sanitize_key($title),
2464
+				['type' => 'hidden', 'value' => "espresso_{$this->page_slug}_editor_overview"]
2465
+			);
2466
+		}
2467
+		$this->addMetaBox(
2468
+			"espresso_{$this->page_slug}_editor_overview",
2469
+			$title,
2470
+			[$this, 'editor_overview'],
2471
+			$this->_current_screen->id,
2472
+			'side',
2473
+			'high'
2474
+		);
2475
+	}
2476
+
2477
+
2478
+	public function editor_overview()
2479
+	{
2480
+		/**
2481
+		 * @var string $publish_box_extra_content
2482
+		 * @var string $publish_hidden_fields
2483
+		 * @var string $publish_delete_link
2484
+		 * @var string $save_buttons
2485
+		 */
2486
+		// if we have extra content set let's add it in if not make sure its empty
2487
+		$this->_template_args['publish_box_extra_content'] = $this->_template_args['publish_box_extra_content'] ?? '';
2488
+		echo EEH_Template::display_template(
2489
+			EE_ADMIN_TEMPLATE . 'admin_details_publish_metabox.template.php',
2490
+			$this->_template_args,
2491
+			true
2492
+		);
2493
+	}
2494
+
2495
+
2496
+	/** end of globally available metaboxes section **/
2497
+
2498
+
2499
+	/**
2500
+	 * Sets the _template_args arguments used by the _publish_post_box shortcut
2501
+	 * Note: currently there is no validation for this.  However, if you want the delete button, the
2502
+	 * save, and save and close buttons to work properly, then you will want to include a
2503
+	 * values for the name and id arguments.
2504
+	 *
2505
+	 * @param string|null $name                     key used for the action ID (i.e. event_id)
2506
+	 * @param int|string  $id                       id attached to the item published
2507
+	 * @param string|null $delete                   page route callback for the delete action
2508
+	 * @param string|null $save_close_redirect_URL  custom URL to redirect to after Save & Close has been completed
2509
+	 * @param bool        $both_btns                whether to display BOTH the "Save & Close" and "Save" buttons
2510
+	 *                                              or just the "Save" button
2511
+	 * @throws EE_Error
2512
+	 * @throws InvalidArgumentException
2513
+	 * @throws InvalidDataTypeException
2514
+	 * @throws InvalidInterfaceException
2515
+	 * @todo  Add in validation for name/id arguments.
2516
+	 */
2517
+	protected function _set_publish_post_box_vars(
2518
+		?string $name = '',
2519
+		$id = 0,
2520
+		?string $delete = '',
2521
+		?string $save_close_redirect_URL = '',
2522
+		bool $both_btns = true
2523
+	) {
2524
+		// if Save & Close, use a custom redirect URL or default to the main page?
2525
+		$save_close_redirect_URL = ! empty($save_close_redirect_URL)
2526
+			? $save_close_redirect_URL
2527
+			: $this->_admin_base_url;
2528
+		// create the Save & Close and Save buttons
2529
+		$this->_set_save_buttons($both_btns, [], [], $save_close_redirect_URL);
2530
+		// if we have extra content set let's add it in if not make sure its empty
2531
+		$this->_template_args['publish_box_extra_content'] = $this->_template_args['publish_box_extra_content'] ?? '';
2532
+		if ($delete && ! empty($id) && empty($this->_template_args['publish_delete_link'])) {
2533
+			// make sure we have a default if just true is sent.
2534
+			$delete                                      = ! empty($delete) ? $delete : 'delete';
2535
+			$this->_template_args['publish_delete_link'] = $this->get_action_link_or_button(
2536
+				$delete,
2537
+				$delete,
2538
+				[$name => $id],
2539
+				'submitdelete deletion button button--outline button--caution'
2540
+			);
2541
+		}
2542
+		if (! isset($this->_template_args['publish_delete_link'])) {
2543
+			$this->_template_args['publish_delete_link'] = '';
2544
+		}
2545
+		if (! empty($name) && ! empty($id)) {
2546
+			$this->addPublishPostMetaBoxHiddenFields($name, ['type' => 'hidden', 'value' => $id]);
2547
+		}
2548
+		$hidden_fields = $this->_generate_admin_form_fields($this->publish_post_meta_box_hidden_fields, 'array');
2549
+		// add hidden fields
2550
+		$this->_template_args['publish_hidden_fields'] = $this->_template_args['publish_hidden_fields'] ?? '';
2551
+		foreach ($hidden_fields as $hidden_field) {
2552
+			$this->_template_args['publish_hidden_fields'] .= $hidden_field['field'] ?? '';
2553
+		}
2554
+	}
2555
+
2556
+
2557
+	/**
2558
+	 * @param string|null $name
2559
+	 * @param int|string  $id
2560
+	 * @param string|null $delete
2561
+	 * @param string|null $save_close_redirect_URL
2562
+	 * @param bool        $both_btns
2563
+	 * @throws EE_Error
2564
+	 */
2565
+	public function set_publish_post_box_vars(
2566
+		?string $name = '',
2567
+		$id = 0,
2568
+		?string $delete = '',
2569
+		?string $save_close_redirect_URL = '',
2570
+		bool $both_btns = false
2571
+	) {
2572
+		$this->_set_publish_post_box_vars($name, $id, $delete, $save_close_redirect_URL, $both_btns);
2573
+	}
2574
+
2575
+
2576
+	protected function addPublishPostMetaBoxHiddenFields(string $field_name, array $field_attributes)
2577
+	{
2578
+		$this->publish_post_meta_box_hidden_fields[ $field_name ] = $field_attributes;
2579
+	}
2580
+
2581
+
2582
+	/**
2583
+	 * displays an error message to ppl who have javascript disabled
2584
+	 *
2585
+	 * @return void
2586
+	 */
2587
+	private function _display_no_javascript_warning()
2588
+	{
2589
+		?>
2590 2590
 <noscript>
2591 2591
     <div id="no-js-message" class="error">
2592 2592
         <p style="font-size:1.3em;">
2593 2593
             <span style="color:red;"><?php esc_html_e('Warning!', 'event_espresso'); ?></span>
2594 2594
             <?php esc_html_e(
2595
-                'Javascript is currently turned off for your browser. Javascript must be enabled in order for all of the features on this page to function properly. Please turn your javascript back on.',
2596
-                'event_espresso'
2597
-            ); ?>
2595
+				'Javascript is currently turned off for your browser. Javascript must be enabled in order for all of the features on this page to function properly. Please turn your javascript back on.',
2596
+				'event_espresso'
2597
+			); ?>
2598 2598
         </p>
2599 2599
     </div>
2600 2600
 </noscript>
2601 2601
 <?php
2602
-    }
2603
-
2604
-
2605
-    /**
2606
-     * displays espresso success and/or error notices
2607
-     *
2608
-     * @return void
2609
-     */
2610
-    protected function _display_espresso_notices()
2611
-    {
2612
-        $notices = (array) $this->_get_transient(true);
2613
-        foreach ($notices as $notice) {
2614
-            echo $notice ? stripslashes($notice) : '';
2615
-        }
2616
-    }
2617
-
2618
-
2619
-    /**
2620
-     * spinny things pacify the masses
2621
-     *
2622
-     * @return void
2623
-     */
2624
-    protected function _add_admin_page_ajax_loading_img()
2625
-    {
2626
-        ?>
2602
+	}
2603
+
2604
+
2605
+	/**
2606
+	 * displays espresso success and/or error notices
2607
+	 *
2608
+	 * @return void
2609
+	 */
2610
+	protected function _display_espresso_notices()
2611
+	{
2612
+		$notices = (array) $this->_get_transient(true);
2613
+		foreach ($notices as $notice) {
2614
+			echo $notice ? stripslashes($notice) : '';
2615
+		}
2616
+	}
2617
+
2618
+
2619
+	/**
2620
+	 * spinny things pacify the masses
2621
+	 *
2622
+	 * @return void
2623
+	 */
2624
+	protected function _add_admin_page_ajax_loading_img()
2625
+	{
2626
+		?>
2627 2627
 <div id="espresso-ajax-loading" class="ajax-loading-grey">
2628 2628
     <span class="ee-spinner ee-spin"></span><span class="hidden"><?php
2629
-                esc_html_e('loading...', 'event_espresso'); ?></span>
2629
+				esc_html_e('loading...', 'event_espresso'); ?></span>
2630 2630
 </div>
2631 2631
 <?php
2632
-    }
2632
+	}
2633 2633
 
2634 2634
 
2635
-    /**
2636
-     * add admin page overlay for modal boxes
2637
-     *
2638
-     * @return void
2639
-     */
2640
-    protected function _add_admin_page_overlay()
2641
-    {
2642
-        ?>
2635
+	/**
2636
+	 * add admin page overlay for modal boxes
2637
+	 *
2638
+	 * @return void
2639
+	 */
2640
+	protected function _add_admin_page_overlay()
2641
+	{
2642
+		?>
2643 2643
 <div id="espresso-admin-page-overlay-dv" class=""></div>
2644 2644
 <?php
2645
-    }
2646
-
2647
-
2648
-    /**
2649
-     * facade for $this->addMetaBox()
2650
-     *
2651
-     * @param string   $action        where the metabox gets displayed
2652
-     * @param string   $title         Title of Metabox (output in metabox header)
2653
-     * @param callable $callback      If not empty and $create_fun is set to false then we'll use a custom callback
2654
-     *                                instead of the one created in here.
2655
-     * @param array    $callback_args an array of args supplied for the metabox
2656
-     * @param string   $column        what metabox column
2657
-     * @param string   $priority      give this metabox a priority (using accepted priorities for wp meta boxes)
2658
-     * @param bool     $create_func   default is true.  Basically we can say we don't WANT to have the runtime function
2659
-     *                                created but just set our own callback for wp's add_meta_box.
2660
-     * @throws DomainException
2661
-     */
2662
-    public function _add_admin_page_meta_box(
2663
-        string $action,
2664
-        string $title,
2665
-        callable $callback,
2666
-        array $callback_args,
2667
-        string $column = 'normal',
2668
-        string $priority = 'high',
2669
-        bool $create_func = true
2670
-    ) {
2671
-        // if we have empty callback args and we want to automatically create the metabox callback then we need to make sure the callback args are generated.
2672
-        if (empty($callback_args) && $create_func) {
2673
-            $callback_args = [
2674
-                'template_path' => $this->_template_path,
2675
-                'template_args' => $this->_template_args,
2676
-            ];
2677
-        }
2678
-        // if $create_func is true (default) then we automatically create the function for displaying the actual meta box.  If false then we take the $callback reference passed through and use it instead (so callers can define their own callback function/method if they wish)
2679
-        $call_back_func = $create_func
2680
-            ? static function ($post, $metabox) {
2681
-                echo EEH_Template::display_template(
2682
-                    $metabox['args']['template_path'],
2683
-                    $metabox['args']['template_args'],
2684
-                    true
2685
-                );
2686
-            }
2687
-            : $callback;
2688
-        $this->addMetaBox(
2689
-            str_replace('_', '-', $action) . '-mbox',
2690
-            $title,
2691
-            $call_back_func,
2692
-            $this->_wp_page_slug,
2693
-            $column,
2694
-            $priority,
2695
-            $callback_args
2696
-        );
2697
-    }
2698
-
2699
-
2700
-    /**
2701
-     * generates HTML wrapper for and admin details page that contains metaboxes in columns
2702
-     *
2703
-     * @throws DomainException
2704
-     * @throws EE_Error
2705
-     * @throws InvalidArgumentException
2706
-     * @throws InvalidDataTypeException
2707
-     * @throws InvalidInterfaceException
2708
-     */
2709
-    public function display_admin_page_with_metabox_columns()
2710
-    {
2711
-        $this->_template_args['post_body_content']  = $this->_template_args['admin_page_content'];
2712
-        $this->_template_args['admin_page_content'] = EEH_Template::display_template(
2713
-            $this->_column_template_path,
2714
-            $this->_template_args,
2715
-            true
2716
-        );
2717
-        // the final wrapper
2718
-        $this->admin_page_wrapper();
2719
-    }
2720
-
2721
-
2722
-    /**
2723
-     * generates  HTML wrapper for an admin details page
2724
-     *
2725
-     * @return void
2726
-     * @throws DomainException
2727
-     * @throws EE_Error
2728
-     * @throws InvalidArgumentException
2729
-     * @throws InvalidDataTypeException
2730
-     * @throws InvalidInterfaceException
2731
-     */
2732
-    public function display_admin_page_with_sidebar()
2733
-    {
2734
-        $this->_display_admin_page(true);
2735
-    }
2736
-
2737
-
2738
-    /**
2739
-     * generates  HTML wrapper for an admin details page (except no sidebar)
2740
-     *
2741
-     * @return void
2742
-     * @throws DomainException
2743
-     * @throws EE_Error
2744
-     * @throws InvalidArgumentException
2745
-     * @throws InvalidDataTypeException
2746
-     * @throws InvalidInterfaceException
2747
-     */
2748
-    public function display_admin_page_with_no_sidebar()
2749
-    {
2750
-        $this->_display_admin_page();
2751
-    }
2752
-
2753
-
2754
-    /**
2755
-     * generates HTML wrapper for an EE about admin page (no sidebar)
2756
-     *
2757
-     * @return void
2758
-     * @throws DomainException
2759
-     * @throws EE_Error
2760
-     * @throws InvalidArgumentException
2761
-     * @throws InvalidDataTypeException
2762
-     * @throws InvalidInterfaceException
2763
-     */
2764
-    public function display_about_admin_page()
2765
-    {
2766
-        $this->_display_admin_page(false, true);
2767
-    }
2768
-
2769
-
2770
-    /**
2771
-     * display_admin_page
2772
-     * contains the code for actually displaying an admin page
2773
-     *
2774
-     * @param bool $sidebar true with sidebar, false without
2775
-     * @param bool $about   use the About admin wrapper instead of the default.
2776
-     * @return void
2777
-     * @throws DomainException
2778
-     * @throws EE_Error
2779
-     * @throws InvalidArgumentException
2780
-     * @throws InvalidDataTypeException
2781
-     * @throws InvalidInterfaceException
2782
-     */
2783
-    private function _display_admin_page(bool $sidebar = false, bool $about = false): void
2784
-    {
2785
-        // custom remove metaboxes hook to add or remove any metaboxes to/from Admin pages.
2786
-        do_action('AHEE__EE_Admin_Page___display_admin_page__modify_metaboxes');
2787
-
2788
-        // set current wp page slug - looks like: event-espresso_page_event_categories
2789
-        // keep in mind "event-espresso" COULD be something else if the top level menu label has been translated.
2790
-        $post_body_content = $this->_template_args['before_admin_page_content'] ?? '';
2791
-
2792
-        $this->_template_args['add_page_frame'] = $this->_req_action !== 'system_status'
2793
-                                                  && $this->_req_action !== 'data_reset'
2794
-                                                  && $this->_wp_page_slug !== 'event-espresso_page_espresso_packages'
2795
-                                                  && strpos($post_body_content, 'wp-list-table') === false;
2796
-
2797
-        $this->_template_args['current_page']                 = $this->_wp_page_slug;
2798
-        $this->_template_args['admin_page_wrapper_div_id']    = $this->_cpt_route
2799
-            ? 'poststuff'
2800
-            : 'espresso-default-admin';
2801
-        $this->_template_args['admin_page_wrapper_div_class'] = str_replace(
2802
-            'event-espresso_page_espresso_',
2803
-            '',
2804
-            $this->_wp_page_slug
2805
-        ) . ' ' . $this->_req_action . '-route';
2806
-
2807
-        $template_path = $sidebar
2808
-            ? EE_ADMIN_TEMPLATE . 'admin_details_wrapper.template.php'
2809
-            : EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar.template.php';
2810
-
2811
-        $this->_template_args['is_ajax'] = $this->request->isAjax();
2812
-        if ($this->request->isAjax()) {
2813
-            $template_path = EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar_ajax.template.php';
2814
-        }
2815
-        $template_path = ! empty($this->_column_template_path) ? $this->_column_template_path : $template_path;
2816
-
2817
-        $this->_template_args['post_body_content']         = $this->_template_args['admin_page_content'] ?? '';
2818
-        $this->_template_args['before_admin_page_content'] = $post_body_content;
2819
-        $this->_template_args['after_admin_page_content']  = $this->_template_args['after_admin_page_content'] ?? '';
2820
-
2821
-        // ensure $post_type and $post are set
2822
-        // to prevent WooCommerce from blowing things up if not using CPT
2823
-        global $post_type, $post;
2824
-        $this->_template_args['post_type'] = $post_type ?? '';
2825
-        $this->_template_args['post']  = $post ?? new WP_Post((object) [ 'ID' => 0, 'filter' => 'raw' ]);
2826
-
2827
-        $this->_template_args['post_body_content'] = EEH_Template::display_template(
2828
-            EE_ADMIN_TEMPLATE . 'admin_details_wrapper_post_body_content.template.php',
2829
-            $this->_template_args,
2830
-            true
2831
-        );
2832
-
2833
-        $this->_template_args['admin_page_content'] = EEH_Template::display_template(
2834
-            $template_path,
2835
-            $this->_template_args,
2836
-            true
2837
-        );
2838
-        // the final template wrapper
2839
-        $this->admin_page_wrapper($about);
2840
-    }
2841
-
2842
-
2843
-    /**
2844
-     * This is used to display caf preview pages.
2845
-     *
2846
-     * @param string $utm_campaign_source what is the key used for Google Analytics link
2847
-     * @param bool   $display_sidebar     whether to use the sidebar template or the full template for the page.  TRUE
2848
-     *                                    = SHOW sidebar, FALSE = no sidebar. Default no sidebar.
2849
-     * @return void
2850
-     * @throws DomainException
2851
-     * @throws EE_Error
2852
-     * @throws InvalidArgumentException
2853
-     * @throws InvalidDataTypeException
2854
-     * @throws InvalidInterfaceException
2855
-     * @since 4.3.2
2856
-     */
2857
-    public function display_admin_caf_preview_page(string $utm_campaign_source = '', bool $display_sidebar = true)
2858
-    {
2859
-        // let's generate a default preview action button if there isn't one already present.
2860
-        $this->_labels['buttons']['buy_now']           = esc_html__(
2861
-            'Upgrade to Event Espresso 4 Right Now',
2862
-            'event_espresso'
2863
-        );
2864
-        $buy_now_url                                   = add_query_arg(
2865
-            [
2866
-                'ee_ver'       => 'ee4',
2867
-                'utm_source'   => 'ee4_plugin_admin',
2868
-                'utm_medium'   => 'link',
2869
-                'utm_campaign' => $utm_campaign_source,
2870
-                'utm_content'  => 'buy_now_button',
2871
-            ],
2872
-            'https://eventespresso.com/pricing/'
2873
-        );
2874
-        $this->_template_args['preview_action_button'] = ! isset($this->_template_args['preview_action_button'])
2875
-            ? $this->get_action_link_or_button(
2876
-                '',
2877
-                'buy_now',
2878
-                [],
2879
-                'button button--primary button--big',
2880
-                esc_url_raw($buy_now_url),
2881
-                true
2882
-            )
2883
-            : $this->_template_args['preview_action_button'];
2884
-        $this->_template_args['admin_page_content']    = EEH_Template::display_template(
2885
-            EE_ADMIN_TEMPLATE . 'admin_caf_full_page_preview.template.php',
2886
-            $this->_template_args,
2887
-            true
2888
-        );
2889
-        $this->_display_admin_page($display_sidebar);
2890
-    }
2891
-
2892
-
2893
-    /**
2894
-     * display_admin_list_table_page_with_sidebar
2895
-     * generates HTML wrapper for an admin_page with list_table
2896
-     *
2897
-     * @return void
2898
-     * @throws DomainException
2899
-     * @throws EE_Error
2900
-     * @throws InvalidArgumentException
2901
-     * @throws InvalidDataTypeException
2902
-     * @throws InvalidInterfaceException
2903
-     */
2904
-    public function display_admin_list_table_page_with_sidebar()
2905
-    {
2906
-        $this->_display_admin_list_table_page(true);
2907
-    }
2908
-
2909
-
2910
-    /**
2911
-     * display_admin_list_table_page_with_no_sidebar
2912
-     * generates HTML wrapper for an admin_page with list_table (but with no sidebar)
2913
-     *
2914
-     * @return void
2915
-     * @throws DomainException
2916
-     * @throws EE_Error
2917
-     * @throws InvalidArgumentException
2918
-     * @throws InvalidDataTypeException
2919
-     * @throws InvalidInterfaceException
2920
-     */
2921
-    public function display_admin_list_table_page_with_no_sidebar()
2922
-    {
2923
-        $this->_display_admin_list_table_page();
2924
-    }
2925
-
2926
-
2927
-    /**
2928
-     * generates html wrapper for an admin_list_table page
2929
-     *
2930
-     * @param bool $sidebar whether to display with sidebar or not.
2931
-     * @return void
2932
-     * @throws DomainException
2933
-     * @throws EE_Error
2934
-     * @throws InvalidArgumentException
2935
-     * @throws InvalidDataTypeException
2936
-     * @throws InvalidInterfaceException
2937
-     */
2938
-    private function _display_admin_list_table_page(bool $sidebar = false)
2939
-    {
2940
-        // setup search attributes
2941
-        $this->_set_search_attributes();
2942
-        $this->_template_args['current_page']     = $this->_wp_page_slug;
2943
-        $template_path                            = EE_ADMIN_TEMPLATE . 'admin_list_wrapper.template.php';
2944
-        $this->_template_args['table_url']        = $this->request->isAjax()
2945
-            ? add_query_arg(['noheader' => 'true', 'route' => $this->_req_action], $this->_admin_base_url)
2946
-            : add_query_arg(['route' => $this->_req_action], $this->_admin_base_url);
2947
-        $this->_template_args['list_table']       = $this->_list_table_object;
2948
-        $this->_template_args['current_route']    = $this->_req_action;
2949
-        $this->_template_args['list_table_class'] = get_class($this->_list_table_object);
2950
-        $ajax_sorting_callback                    = $this->_list_table_object->get_ajax_sorting_callback();
2951
-        if (! empty($ajax_sorting_callback)) {
2952
-            $sortable_list_table_form_fields = wp_nonce_field(
2953
-                $ajax_sorting_callback . '_nonce',
2954
-                $ajax_sorting_callback . '_nonce',
2955
-                false,
2956
-                false
2957
-            );
2958
-            $sortable_list_table_form_fields .= '<input type="hidden" id="ajax_table_sort_page" name="ajax_table_sort_page" value="'
2959
-                                                . $this->page_slug
2960
-                                                . '" />';
2961
-            $sortable_list_table_form_fields .= '<input type="hidden" id="ajax_table_sort_action" name="ajax_table_sort_action" value="'
2962
-                                                . $ajax_sorting_callback
2963
-                                                . '" />';
2964
-        } else {
2965
-            $sortable_list_table_form_fields = '';
2966
-        }
2967
-        $this->_template_args['sortable_list_table_form_fields'] = $sortable_list_table_form_fields;
2968
-
2969
-        $hidden_form_fields = $this->_template_args['list_table_hidden_fields'] ?? '';
2970
-
2971
-        $nonce_ref          = $this->_req_action . '_nonce';
2972
-        $hidden_form_fields .= '
2645
+	}
2646
+
2647
+
2648
+	/**
2649
+	 * facade for $this->addMetaBox()
2650
+	 *
2651
+	 * @param string   $action        where the metabox gets displayed
2652
+	 * @param string   $title         Title of Metabox (output in metabox header)
2653
+	 * @param callable $callback      If not empty and $create_fun is set to false then we'll use a custom callback
2654
+	 *                                instead of the one created in here.
2655
+	 * @param array    $callback_args an array of args supplied for the metabox
2656
+	 * @param string   $column        what metabox column
2657
+	 * @param string   $priority      give this metabox a priority (using accepted priorities for wp meta boxes)
2658
+	 * @param bool     $create_func   default is true.  Basically we can say we don't WANT to have the runtime function
2659
+	 *                                created but just set our own callback for wp's add_meta_box.
2660
+	 * @throws DomainException
2661
+	 */
2662
+	public function _add_admin_page_meta_box(
2663
+		string $action,
2664
+		string $title,
2665
+		callable $callback,
2666
+		array $callback_args,
2667
+		string $column = 'normal',
2668
+		string $priority = 'high',
2669
+		bool $create_func = true
2670
+	) {
2671
+		// if we have empty callback args and we want to automatically create the metabox callback then we need to make sure the callback args are generated.
2672
+		if (empty($callback_args) && $create_func) {
2673
+			$callback_args = [
2674
+				'template_path' => $this->_template_path,
2675
+				'template_args' => $this->_template_args,
2676
+			];
2677
+		}
2678
+		// if $create_func is true (default) then we automatically create the function for displaying the actual meta box.  If false then we take the $callback reference passed through and use it instead (so callers can define their own callback function/method if they wish)
2679
+		$call_back_func = $create_func
2680
+			? static function ($post, $metabox) {
2681
+				echo EEH_Template::display_template(
2682
+					$metabox['args']['template_path'],
2683
+					$metabox['args']['template_args'],
2684
+					true
2685
+				);
2686
+			}
2687
+			: $callback;
2688
+		$this->addMetaBox(
2689
+			str_replace('_', '-', $action) . '-mbox',
2690
+			$title,
2691
+			$call_back_func,
2692
+			$this->_wp_page_slug,
2693
+			$column,
2694
+			$priority,
2695
+			$callback_args
2696
+		);
2697
+	}
2698
+
2699
+
2700
+	/**
2701
+	 * generates HTML wrapper for and admin details page that contains metaboxes in columns
2702
+	 *
2703
+	 * @throws DomainException
2704
+	 * @throws EE_Error
2705
+	 * @throws InvalidArgumentException
2706
+	 * @throws InvalidDataTypeException
2707
+	 * @throws InvalidInterfaceException
2708
+	 */
2709
+	public function display_admin_page_with_metabox_columns()
2710
+	{
2711
+		$this->_template_args['post_body_content']  = $this->_template_args['admin_page_content'];
2712
+		$this->_template_args['admin_page_content'] = EEH_Template::display_template(
2713
+			$this->_column_template_path,
2714
+			$this->_template_args,
2715
+			true
2716
+		);
2717
+		// the final wrapper
2718
+		$this->admin_page_wrapper();
2719
+	}
2720
+
2721
+
2722
+	/**
2723
+	 * generates  HTML wrapper for an admin details page
2724
+	 *
2725
+	 * @return void
2726
+	 * @throws DomainException
2727
+	 * @throws EE_Error
2728
+	 * @throws InvalidArgumentException
2729
+	 * @throws InvalidDataTypeException
2730
+	 * @throws InvalidInterfaceException
2731
+	 */
2732
+	public function display_admin_page_with_sidebar()
2733
+	{
2734
+		$this->_display_admin_page(true);
2735
+	}
2736
+
2737
+
2738
+	/**
2739
+	 * generates  HTML wrapper for an admin details page (except no sidebar)
2740
+	 *
2741
+	 * @return void
2742
+	 * @throws DomainException
2743
+	 * @throws EE_Error
2744
+	 * @throws InvalidArgumentException
2745
+	 * @throws InvalidDataTypeException
2746
+	 * @throws InvalidInterfaceException
2747
+	 */
2748
+	public function display_admin_page_with_no_sidebar()
2749
+	{
2750
+		$this->_display_admin_page();
2751
+	}
2752
+
2753
+
2754
+	/**
2755
+	 * generates HTML wrapper for an EE about admin page (no sidebar)
2756
+	 *
2757
+	 * @return void
2758
+	 * @throws DomainException
2759
+	 * @throws EE_Error
2760
+	 * @throws InvalidArgumentException
2761
+	 * @throws InvalidDataTypeException
2762
+	 * @throws InvalidInterfaceException
2763
+	 */
2764
+	public function display_about_admin_page()
2765
+	{
2766
+		$this->_display_admin_page(false, true);
2767
+	}
2768
+
2769
+
2770
+	/**
2771
+	 * display_admin_page
2772
+	 * contains the code for actually displaying an admin page
2773
+	 *
2774
+	 * @param bool $sidebar true with sidebar, false without
2775
+	 * @param bool $about   use the About admin wrapper instead of the default.
2776
+	 * @return void
2777
+	 * @throws DomainException
2778
+	 * @throws EE_Error
2779
+	 * @throws InvalidArgumentException
2780
+	 * @throws InvalidDataTypeException
2781
+	 * @throws InvalidInterfaceException
2782
+	 */
2783
+	private function _display_admin_page(bool $sidebar = false, bool $about = false): void
2784
+	{
2785
+		// custom remove metaboxes hook to add or remove any metaboxes to/from Admin pages.
2786
+		do_action('AHEE__EE_Admin_Page___display_admin_page__modify_metaboxes');
2787
+
2788
+		// set current wp page slug - looks like: event-espresso_page_event_categories
2789
+		// keep in mind "event-espresso" COULD be something else if the top level menu label has been translated.
2790
+		$post_body_content = $this->_template_args['before_admin_page_content'] ?? '';
2791
+
2792
+		$this->_template_args['add_page_frame'] = $this->_req_action !== 'system_status'
2793
+												  && $this->_req_action !== 'data_reset'
2794
+												  && $this->_wp_page_slug !== 'event-espresso_page_espresso_packages'
2795
+												  && strpos($post_body_content, 'wp-list-table') === false;
2796
+
2797
+		$this->_template_args['current_page']                 = $this->_wp_page_slug;
2798
+		$this->_template_args['admin_page_wrapper_div_id']    = $this->_cpt_route
2799
+			? 'poststuff'
2800
+			: 'espresso-default-admin';
2801
+		$this->_template_args['admin_page_wrapper_div_class'] = str_replace(
2802
+			'event-espresso_page_espresso_',
2803
+			'',
2804
+			$this->_wp_page_slug
2805
+		) . ' ' . $this->_req_action . '-route';
2806
+
2807
+		$template_path = $sidebar
2808
+			? EE_ADMIN_TEMPLATE . 'admin_details_wrapper.template.php'
2809
+			: EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar.template.php';
2810
+
2811
+		$this->_template_args['is_ajax'] = $this->request->isAjax();
2812
+		if ($this->request->isAjax()) {
2813
+			$template_path = EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar_ajax.template.php';
2814
+		}
2815
+		$template_path = ! empty($this->_column_template_path) ? $this->_column_template_path : $template_path;
2816
+
2817
+		$this->_template_args['post_body_content']         = $this->_template_args['admin_page_content'] ?? '';
2818
+		$this->_template_args['before_admin_page_content'] = $post_body_content;
2819
+		$this->_template_args['after_admin_page_content']  = $this->_template_args['after_admin_page_content'] ?? '';
2820
+
2821
+		// ensure $post_type and $post are set
2822
+		// to prevent WooCommerce from blowing things up if not using CPT
2823
+		global $post_type, $post;
2824
+		$this->_template_args['post_type'] = $post_type ?? '';
2825
+		$this->_template_args['post']  = $post ?? new WP_Post((object) [ 'ID' => 0, 'filter' => 'raw' ]);
2826
+
2827
+		$this->_template_args['post_body_content'] = EEH_Template::display_template(
2828
+			EE_ADMIN_TEMPLATE . 'admin_details_wrapper_post_body_content.template.php',
2829
+			$this->_template_args,
2830
+			true
2831
+		);
2832
+
2833
+		$this->_template_args['admin_page_content'] = EEH_Template::display_template(
2834
+			$template_path,
2835
+			$this->_template_args,
2836
+			true
2837
+		);
2838
+		// the final template wrapper
2839
+		$this->admin_page_wrapper($about);
2840
+	}
2841
+
2842
+
2843
+	/**
2844
+	 * This is used to display caf preview pages.
2845
+	 *
2846
+	 * @param string $utm_campaign_source what is the key used for Google Analytics link
2847
+	 * @param bool   $display_sidebar     whether to use the sidebar template or the full template for the page.  TRUE
2848
+	 *                                    = SHOW sidebar, FALSE = no sidebar. Default no sidebar.
2849
+	 * @return void
2850
+	 * @throws DomainException
2851
+	 * @throws EE_Error
2852
+	 * @throws InvalidArgumentException
2853
+	 * @throws InvalidDataTypeException
2854
+	 * @throws InvalidInterfaceException
2855
+	 * @since 4.3.2
2856
+	 */
2857
+	public function display_admin_caf_preview_page(string $utm_campaign_source = '', bool $display_sidebar = true)
2858
+	{
2859
+		// let's generate a default preview action button if there isn't one already present.
2860
+		$this->_labels['buttons']['buy_now']           = esc_html__(
2861
+			'Upgrade to Event Espresso 4 Right Now',
2862
+			'event_espresso'
2863
+		);
2864
+		$buy_now_url                                   = add_query_arg(
2865
+			[
2866
+				'ee_ver'       => 'ee4',
2867
+				'utm_source'   => 'ee4_plugin_admin',
2868
+				'utm_medium'   => 'link',
2869
+				'utm_campaign' => $utm_campaign_source,
2870
+				'utm_content'  => 'buy_now_button',
2871
+			],
2872
+			'https://eventespresso.com/pricing/'
2873
+		);
2874
+		$this->_template_args['preview_action_button'] = ! isset($this->_template_args['preview_action_button'])
2875
+			? $this->get_action_link_or_button(
2876
+				'',
2877
+				'buy_now',
2878
+				[],
2879
+				'button button--primary button--big',
2880
+				esc_url_raw($buy_now_url),
2881
+				true
2882
+			)
2883
+			: $this->_template_args['preview_action_button'];
2884
+		$this->_template_args['admin_page_content']    = EEH_Template::display_template(
2885
+			EE_ADMIN_TEMPLATE . 'admin_caf_full_page_preview.template.php',
2886
+			$this->_template_args,
2887
+			true
2888
+		);
2889
+		$this->_display_admin_page($display_sidebar);
2890
+	}
2891
+
2892
+
2893
+	/**
2894
+	 * display_admin_list_table_page_with_sidebar
2895
+	 * generates HTML wrapper for an admin_page with list_table
2896
+	 *
2897
+	 * @return void
2898
+	 * @throws DomainException
2899
+	 * @throws EE_Error
2900
+	 * @throws InvalidArgumentException
2901
+	 * @throws InvalidDataTypeException
2902
+	 * @throws InvalidInterfaceException
2903
+	 */
2904
+	public function display_admin_list_table_page_with_sidebar()
2905
+	{
2906
+		$this->_display_admin_list_table_page(true);
2907
+	}
2908
+
2909
+
2910
+	/**
2911
+	 * display_admin_list_table_page_with_no_sidebar
2912
+	 * generates HTML wrapper for an admin_page with list_table (but with no sidebar)
2913
+	 *
2914
+	 * @return void
2915
+	 * @throws DomainException
2916
+	 * @throws EE_Error
2917
+	 * @throws InvalidArgumentException
2918
+	 * @throws InvalidDataTypeException
2919
+	 * @throws InvalidInterfaceException
2920
+	 */
2921
+	public function display_admin_list_table_page_with_no_sidebar()
2922
+	{
2923
+		$this->_display_admin_list_table_page();
2924
+	}
2925
+
2926
+
2927
+	/**
2928
+	 * generates html wrapper for an admin_list_table page
2929
+	 *
2930
+	 * @param bool $sidebar whether to display with sidebar or not.
2931
+	 * @return void
2932
+	 * @throws DomainException
2933
+	 * @throws EE_Error
2934
+	 * @throws InvalidArgumentException
2935
+	 * @throws InvalidDataTypeException
2936
+	 * @throws InvalidInterfaceException
2937
+	 */
2938
+	private function _display_admin_list_table_page(bool $sidebar = false)
2939
+	{
2940
+		// setup search attributes
2941
+		$this->_set_search_attributes();
2942
+		$this->_template_args['current_page']     = $this->_wp_page_slug;
2943
+		$template_path                            = EE_ADMIN_TEMPLATE . 'admin_list_wrapper.template.php';
2944
+		$this->_template_args['table_url']        = $this->request->isAjax()
2945
+			? add_query_arg(['noheader' => 'true', 'route' => $this->_req_action], $this->_admin_base_url)
2946
+			: add_query_arg(['route' => $this->_req_action], $this->_admin_base_url);
2947
+		$this->_template_args['list_table']       = $this->_list_table_object;
2948
+		$this->_template_args['current_route']    = $this->_req_action;
2949
+		$this->_template_args['list_table_class'] = get_class($this->_list_table_object);
2950
+		$ajax_sorting_callback                    = $this->_list_table_object->get_ajax_sorting_callback();
2951
+		if (! empty($ajax_sorting_callback)) {
2952
+			$sortable_list_table_form_fields = wp_nonce_field(
2953
+				$ajax_sorting_callback . '_nonce',
2954
+				$ajax_sorting_callback . '_nonce',
2955
+				false,
2956
+				false
2957
+			);
2958
+			$sortable_list_table_form_fields .= '<input type="hidden" id="ajax_table_sort_page" name="ajax_table_sort_page" value="'
2959
+												. $this->page_slug
2960
+												. '" />';
2961
+			$sortable_list_table_form_fields .= '<input type="hidden" id="ajax_table_sort_action" name="ajax_table_sort_action" value="'
2962
+												. $ajax_sorting_callback
2963
+												. '" />';
2964
+		} else {
2965
+			$sortable_list_table_form_fields = '';
2966
+		}
2967
+		$this->_template_args['sortable_list_table_form_fields'] = $sortable_list_table_form_fields;
2968
+
2969
+		$hidden_form_fields = $this->_template_args['list_table_hidden_fields'] ?? '';
2970
+
2971
+		$nonce_ref          = $this->_req_action . '_nonce';
2972
+		$hidden_form_fields .= '
2973 2973
             <input type="hidden" name="' . $nonce_ref . '" value="' . wp_create_nonce($nonce_ref) . '">';
2974 2974
 
2975
-        $this->_template_args['list_table_hidden_fields'] = $hidden_form_fields;
2976
-        // display message about search results?
2977
-        $search                                    = $this->request->getRequestParam('s');
2978
-        $this->_template_args['before_list_table'] .= ! empty($search)
2979
-            ? '<p class="ee-search-results">' . sprintf(
2980
-                esc_html__('Displaying search results for the search string: %1$s', 'event_espresso'),
2981
-                trim($search, '%')
2982
-            ) . '</p>'
2983
-            : '';
2984
-        // filter before_list_table template arg
2985
-        $this->_template_args['before_list_table'] = apply_filters(
2986
-            'FHEE__EE_Admin_Page___display_admin_list_table_page__before_list_table__template_arg',
2987
-            $this->_template_args['before_list_table'],
2988
-            $this->page_slug,
2989
-            $this->request->requestParams(),
2990
-            $this->_req_action
2991
-        );
2992
-        // convert to array and filter again
2993
-        // arrays are easier to inject new items in a specific location,
2994
-        // but would not be backwards compatible, so we have to add a new filter
2995
-        $this->_template_args['before_list_table'] = implode(
2996
-            " \n",
2997
-            (array) apply_filters(
2998
-                'FHEE__EE_Admin_Page___display_admin_list_table_page__before_list_table__template_args_array',
2999
-                (array) $this->_template_args['before_list_table'],
3000
-                $this->page_slug,
3001
-                $this->request->requestParams(),
3002
-                $this->_req_action
3003
-            )
3004
-        );
3005
-        // filter after_list_table template arg
3006
-        $this->_template_args['after_list_table'] = apply_filters(
3007
-            'FHEE__EE_Admin_Page___display_admin_list_table_page__after_list_table__template_arg',
3008
-            $this->_template_args['after_list_table'],
3009
-            $this->page_slug,
3010
-            $this->request->requestParams(),
3011
-            $this->_req_action
3012
-        );
3013
-        // convert to array and filter again
3014
-        // arrays are easier to inject new items in a specific location,
3015
-        // but would not be backwards compatible, so we have to add a new filter
3016
-        $this->_template_args['after_list_table']   = implode(
3017
-            " \n",
3018
-            (array) apply_filters(
3019
-                'FHEE__EE_Admin_Page___display_admin_list_table_page__after_list_table__template_args_array',
3020
-                (array) $this->_template_args['after_list_table'],
3021
-                $this->page_slug,
3022
-                $this->request->requestParams(),
3023
-                $this->_req_action
3024
-            )
3025
-        );
3026
-        $this->_template_args['admin_page_content'] = EEH_Template::display_template(
3027
-            $template_path,
3028
-            $this->_template_args,
3029
-            true
3030
-        );
3031
-        // the final template wrapper
3032
-        if ($sidebar) {
3033
-            $this->display_admin_page_with_sidebar();
3034
-        } else {
3035
-            $this->display_admin_page_with_no_sidebar();
3036
-        }
3037
-    }
3038
-
3039
-
3040
-    /**
3041
-     * This just prepares a legend using the given items and the admin_details_legend.template.php file and returns the
3042
-     * html string for the legend.
3043
-     * $items are expected in an array in the following format:
3044
-     * $legend_items = array(
3045
-     *        'item_id' => array(
3046
-     *            'icon' => 'http://url_to_icon_being_described.png',
3047
-     *            'desc' => esc_html__('localized description of item');
3048
-     *        )
3049
-     * );
3050
-     *
3051
-     * @param array $items see above for format of array
3052
-     * @return string html string of legend
3053
-     * @throws DomainException
3054
-     */
3055
-    protected function _display_legend(array $items): string
3056
-    {
3057
-        $this->_template_args['items'] = (array) apply_filters(
3058
-            'FHEE__EE_Admin_Page___display_legend__items',
3059
-            $items,
3060
-            $this
3061
-        );
3062
-        /** @var StatusChangeNotice $status_change_notice */
3063
-        $status_change_notice                         = $this->loader->getShared(
3064
-            'EventEspresso\core\domain\services\admin\notices\status_change\StatusChangeNotice'
3065
-        );
3066
-        $this->_template_args['status_change_notice'] = $status_change_notice->display(
3067
-            '__admin-legend',
3068
-            $this->page_slug
3069
-        );
3070
-        return EEH_Template::display_template(
3071
-            EE_ADMIN_TEMPLATE . 'admin_details_legend.template.php',
3072
-            $this->_template_args,
3073
-            true
3074
-        );
3075
-    }
3076
-
3077
-
3078
-    /**
3079
-     * This is used whenever we're DOING_AJAX to return a formatted json array that our calling javascript can expect
3080
-     * The returned json object is created from an array in the following format:
3081
-     * array(
3082
-     *  'error' => FALSE, //(default FALSE), contains any errors and/or exceptions (exceptions return json early),
3083
-     *  'success' => FALSE, //(default FALSE) - contains any special success message.
3084
-     *  'notices' => '', // - contains any EE_Error formatted notices
3085
-     *  'content' => 'string can be html', //this is a string of formatted content (can be html)
3086
-     *  'data' => array() //this can be any key/value pairs that a method returns for later json parsing by the js.
3087
-     *  We're also going to include the template args with every package (so js can pick out any specific template args
3088
-     *  that might be included in here)
3089
-     * )
3090
-     * The json object is populated by whatever is set in the $_template_args property.
3091
-     *
3092
-     * @param bool  $sticky_notices    Used to indicate whether you want to ensure notices are added to a transient
3093
-     *                                 instead of displayed.
3094
-     * @param array $notices_arguments Use this to pass any additional args on to the _process_notices.
3095
-     * @return void
3096
-     * @throws EE_Error
3097
-     * @throws InvalidArgumentException
3098
-     * @throws InvalidDataTypeException
3099
-     * @throws InvalidInterfaceException
3100
-     */
3101
-    protected function _return_json(bool $sticky_notices = false, array $notices_arguments = [])
3102
-    {
3103
-        // make sure any EE_Error notices have been handled.
3104
-        $this->_process_notices($notices_arguments, true, $sticky_notices);
3105
-        $data = $this->_template_args['data'] ?? [];
3106
-        unset($this->_template_args['data']);
3107
-        $json = [
3108
-            'error'     => $this->_template_args['error'] ?? false,
3109
-            'success'   => $this->_template_args['success'] ?? false,
3110
-            'errors'    => $this->_template_args['errors'] ?? false,
3111
-            'attention' => $this->_template_args['attention'] ?? false,
3112
-            'notices'   => EE_Error::get_notices(),
3113
-            'content'   => $this->_template_args['admin_page_content'] ?? '',
3114
-            'data'      => array_merge($data, ['template_args' => $this->_template_args]),
3115
-            'isEEajax'  => true,
3116
-            // special flag so any ajax.Success methods in js can identify this return package as a EEajax package.
3117
-        ];
3118
-        // make sure there are no php errors or headers_sent.  Then we can set correct json header.
3119
-        if (null === error_get_last() || ! headers_sent()) {
3120
-            header('Content-Type: application/json; charset=UTF-8');
3121
-        }
3122
-        echo wp_json_encode($json);
3123
-        exit();
3124
-    }
3125
-
3126
-
3127
-    /**
3128
-     * Simply a wrapper for the protected method so we can call this outside the class (ONLY when doing ajax)
3129
-     *
3130
-     * @return void
3131
-     * @throws EE_Error
3132
-     * @throws InvalidArgumentException
3133
-     * @throws InvalidDataTypeException
3134
-     * @throws InvalidInterfaceException
3135
-     */
3136
-    public function return_json()
3137
-    {
3138
-        if ($this->request->isAjax()) {
3139
-            $this->_return_json();
3140
-        } else {
3141
-            throw new EE_Error(
3142
-                sprintf(
3143
-                    esc_html__('The public %s method can only be called when DOING_AJAX = TRUE', 'event_espresso'),
3144
-                    __FUNCTION__
3145
-                )
3146
-            );
3147
-        }
3148
-    }
3149
-
3150
-
3151
-    /**
3152
-     * This provides a way for child hook classes to send along themselves by reference so methods/properties within
3153
-     * them can be accessed by EE_Admin_child pages. This is assigned to the $_hook_obj property.
3154
-     *
3155
-     * @param EE_Admin_Hooks $hook_obj This will be the object for the EE_Admin_Hooks child
3156
-     * @deprecated  5.0.8.p
3157
-     */
3158
-    public function set_hook_object(EE_Admin_Hooks $hook_obj)
3159
-    {
3160
-        $this->_hook_obj = $hook_obj;
3161
-    }
3162
-
3163
-
3164
-    /**
3165
-     *        generates  HTML wrapper with Tabbed nav for an admin page
3166
-     *
3167
-     * @param bool $about whether to use the special about page wrapper or default.
3168
-     * @return void
3169
-     * @throws DomainException
3170
-     * @throws EE_Error
3171
-     * @throws InvalidArgumentException
3172
-     * @throws InvalidDataTypeException
3173
-     * @throws InvalidInterfaceException
3174
-     */
3175
-    public function admin_page_wrapper(bool $about = false)
3176
-    {
3177
-        $this->_template_args['nav_tabs']         = $this->_get_main_nav_tabs();
3178
-        $this->_template_args['admin_page_title'] = $this->_admin_page_title;
3179
-
3180
-        $this->_template_args['before_admin_page_content'] = apply_filters(
3181
-            "FHEE_before_admin_page_content$this->_current_page$this->_current_view",
3182
-            $this->_template_args['before_admin_page_content'] ?? ''
3183
-        );
3184
-
3185
-        $this->_template_args['after_admin_page_content'] = apply_filters(
3186
-            "FHEE_after_admin_page_content$this->_current_page$this->_current_view",
3187
-            $this->_template_args['after_admin_page_content'] ?? ''
3188
-        );
3189
-        $this->_template_args['after_admin_page_content'] .= $this->_set_help_popup_content();
3190
-
3191
-        if ($this->request->isAjax()) {
3192
-            $this->_template_args['admin_page_content'] = EEH_Template::display_template(
3193
-            // $template_path,
3194
-                EE_ADMIN_TEMPLATE . 'admin_wrapper_ajax.template.php',
3195
-                $this->_template_args,
3196
-                true
3197
-            );
3198
-            $this->_return_json();
3199
-        }
3200
-        // load settings page wrapper template
3201
-        $template_path = $about
3202
-            ? EE_ADMIN_TEMPLATE . 'about_admin_wrapper.template.php'
3203
-            : EE_ADMIN_TEMPLATE . 'admin_wrapper.template.php';
3204
-
3205
-        EEH_Template::display_template($template_path, $this->_template_args);
3206
-    }
3207
-
3208
-
3209
-    /**
3210
-     * This returns the admin_nav tabs html using the configuration in the _nav_tabs property
3211
-     *
3212
-     * @return string html
3213
-     * @throws EE_Error
3214
-     */
3215
-    protected function _get_main_nav_tabs(): string
3216
-    {
3217
-        // let's generate the html using the EEH_Tabbed_Content helper.
3218
-        // We do this here so that it's possible for child classes to add in nav tabs dynamically at the last minute
3219
-        // (rather than setting in the page_routes array)
3220
-        return EEH_Tabbed_Content::display_admin_nav_tabs($this->_nav_tabs, $this->page_slug);
3221
-    }
3222
-
3223
-
3224
-    /**
3225
-     *        sort nav tabs
3226
-     *
3227
-     * @param array $a
3228
-     * @param array $b
3229
-     * @return int
3230
-     */
3231
-    private function _sort_nav_tabs(array $a, array $b): int
3232
-    {
3233
-        if ($a['order'] === $b['order']) {
3234
-            return 0;
3235
-        }
3236
-        return ($a['order'] < $b['order']) ? -1 : 1;
3237
-    }
3238
-
3239
-
3240
-    /**
3241
-     * generates HTML for the forms used on admin pages
3242
-     *
3243
-     * @param array  $input_vars - array of input field details
3244
-     * @param string $generator  indicates which generator to use: options are 'string' or 'array'
3245
-     * @param string $id
3246
-     * @return array|string
3247
-     * @uses   EEH_Form_Fields::get_form_fields (/helper/EEH_Form_Fields.helper.php)
3248
-     * @uses   EEH_Form_Fields::get_form_fields_array (/helper/EEH_Form_Fields.helper.php)
3249
-     */
3250
-    protected function _generate_admin_form_fields(
3251
-        array $input_vars = [],
3252
-        string $generator = 'string',
3253
-        string $id = ''
3254
-    ) {
3255
-        return $generator === 'string'
3256
-            ? EEH_Form_Fields::get_form_fields($input_vars, $id)
3257
-            : EEH_Form_Fields::get_form_fields_array($input_vars);
3258
-    }
3259
-
3260
-
3261
-    /**
3262
-     * generates the "Save" and "Save & Close" buttons for edit forms
3263
-     *
3264
-     * @param bool             $both     if true then both buttons will be generated.  If false then just the "Save &
3265
-     *                                   Close" button.
3266
-     * @param array            $text     if included, generator will use the given text for the buttons ( array([0] =>
3267
-     *                                   'Save', [1] => 'save & close')
3268
-     * @param array            $actions  if included allows us to set the actions that each button will carry out (i.e.
3269
-     *                                   via the "name" value in the button).  We can also use this to just dump
3270
-     *                                   default actions by submitting some other value.
3271
-     * @param bool|string|null $referrer if false then we just do the default action on save and close.  Otherwise it
3272
-     *                                   will use the $referrer string. IF null, then we don't do ANYTHING on save and
3273
-     *                                   close (normal form handling).
3274
-     */
3275
-    protected function _set_save_buttons(bool $both = true, array $text = [], array $actions = [], $referrer = null)
3276
-    {
3277
-        $referrer_url  = ! empty($referrer) ? $referrer : $this->request->getServerParam('REQUEST_URI');
3278
-        $button_text   = ! empty($text)
3279
-            ? $text
3280
-            : [
3281
-                esc_html__('Save', 'event_espresso'),
3282
-                esc_html__('Save and Close', 'event_espresso'),
3283
-            ];
3284
-        $default_names = ['save', 'save_and_close'];
3285
-        $buttons       = '';
3286
-        foreach ($button_text as $key => $button) {
3287
-            $ref     = $default_names[ $key ];
3288
-            $name    = ! empty($actions) ? $actions[ $key ] : $ref;
3289
-            $buttons .= '<input type="submit" class="button button--primary ' . $ref . '" '
3290
-                        . 'value="' . $button . '" name="' . $name . '" '
3291
-                        . 'id="' . $this->_current_view . '_' . $ref . '" />';
3292
-            if (! $both) {
3293
-                break;
3294
-            }
3295
-        }
3296
-        // add in a hidden index for the current page (so save and close redirects properly)
3297
-        $buttons .= '<input type="hidden" id="save_and_close_referrer" name="save_and_close_referrer" value="'
3298
-                    . $referrer_url
3299
-                    . '" />';
3300
-
3301
-        $this->_template_args['save_buttons'] = $buttons;
3302
-    }
3303
-
3304
-
3305
-    /**
3306
-     * Wrapper for the protected function.  Allows plugins/addons to call this to set the form tags.
3307
-     *
3308
-     * @param string $route
3309
-     * @param array  $additional_hidden_fields
3310
-     * @see   $this->_set_add_edit_form_tags() for details on params
3311
-     * @since 4.6.0
3312
-     */
3313
-    public function set_add_edit_form_tags(string $route = '', array $additional_hidden_fields = [])
3314
-    {
3315
-        $this->_set_add_edit_form_tags($route, $additional_hidden_fields);
3316
-    }
3317
-
3318
-
3319
-    /**
3320
-     * set form open and close tags on add/edit pages.
3321
-     *
3322
-     * @param string $route                    the route you want the form to direct to
3323
-     * @param array  $additional_hidden_fields any additional hidden fields required in the form header
3324
-     * @return void
3325
-     */
3326
-    protected function _set_add_edit_form_tags(string $route = '', array $additional_hidden_fields = [])
3327
-    {
3328
-        if (empty($route)) {
3329
-            $user_msg = esc_html__(
3330
-                'An error occurred. No action was set for this page\'s form.',
3331
-                'event_espresso'
3332
-            );
3333
-            $dev_msg  = $user_msg . "\n"
3334
-                        . sprintf(
3335
-                            esc_html__('The $route argument is required for the %s->%s method.', 'event_espresso'),
3336
-                            __FUNCTION__,
3337
-                            __CLASS__
3338
-                        );
3339
-            EE_Error::add_error($user_msg . '||' . $dev_msg, __FILE__, __FUNCTION__, __LINE__);
3340
-        }
3341
-        // open form
3342
-        $action                                            = $this->_admin_base_url;
3343
-        $this->_template_args['before_admin_page_content'] = "
2975
+		$this->_template_args['list_table_hidden_fields'] = $hidden_form_fields;
2976
+		// display message about search results?
2977
+		$search                                    = $this->request->getRequestParam('s');
2978
+		$this->_template_args['before_list_table'] .= ! empty($search)
2979
+			? '<p class="ee-search-results">' . sprintf(
2980
+				esc_html__('Displaying search results for the search string: %1$s', 'event_espresso'),
2981
+				trim($search, '%')
2982
+			) . '</p>'
2983
+			: '';
2984
+		// filter before_list_table template arg
2985
+		$this->_template_args['before_list_table'] = apply_filters(
2986
+			'FHEE__EE_Admin_Page___display_admin_list_table_page__before_list_table__template_arg',
2987
+			$this->_template_args['before_list_table'],
2988
+			$this->page_slug,
2989
+			$this->request->requestParams(),
2990
+			$this->_req_action
2991
+		);
2992
+		// convert to array and filter again
2993
+		// arrays are easier to inject new items in a specific location,
2994
+		// but would not be backwards compatible, so we have to add a new filter
2995
+		$this->_template_args['before_list_table'] = implode(
2996
+			" \n",
2997
+			(array) apply_filters(
2998
+				'FHEE__EE_Admin_Page___display_admin_list_table_page__before_list_table__template_args_array',
2999
+				(array) $this->_template_args['before_list_table'],
3000
+				$this->page_slug,
3001
+				$this->request->requestParams(),
3002
+				$this->_req_action
3003
+			)
3004
+		);
3005
+		// filter after_list_table template arg
3006
+		$this->_template_args['after_list_table'] = apply_filters(
3007
+			'FHEE__EE_Admin_Page___display_admin_list_table_page__after_list_table__template_arg',
3008
+			$this->_template_args['after_list_table'],
3009
+			$this->page_slug,
3010
+			$this->request->requestParams(),
3011
+			$this->_req_action
3012
+		);
3013
+		// convert to array and filter again
3014
+		// arrays are easier to inject new items in a specific location,
3015
+		// but would not be backwards compatible, so we have to add a new filter
3016
+		$this->_template_args['after_list_table']   = implode(
3017
+			" \n",
3018
+			(array) apply_filters(
3019
+				'FHEE__EE_Admin_Page___display_admin_list_table_page__after_list_table__template_args_array',
3020
+				(array) $this->_template_args['after_list_table'],
3021
+				$this->page_slug,
3022
+				$this->request->requestParams(),
3023
+				$this->_req_action
3024
+			)
3025
+		);
3026
+		$this->_template_args['admin_page_content'] = EEH_Template::display_template(
3027
+			$template_path,
3028
+			$this->_template_args,
3029
+			true
3030
+		);
3031
+		// the final template wrapper
3032
+		if ($sidebar) {
3033
+			$this->display_admin_page_with_sidebar();
3034
+		} else {
3035
+			$this->display_admin_page_with_no_sidebar();
3036
+		}
3037
+	}
3038
+
3039
+
3040
+	/**
3041
+	 * This just prepares a legend using the given items and the admin_details_legend.template.php file and returns the
3042
+	 * html string for the legend.
3043
+	 * $items are expected in an array in the following format:
3044
+	 * $legend_items = array(
3045
+	 *        'item_id' => array(
3046
+	 *            'icon' => 'http://url_to_icon_being_described.png',
3047
+	 *            'desc' => esc_html__('localized description of item');
3048
+	 *        )
3049
+	 * );
3050
+	 *
3051
+	 * @param array $items see above for format of array
3052
+	 * @return string html string of legend
3053
+	 * @throws DomainException
3054
+	 */
3055
+	protected function _display_legend(array $items): string
3056
+	{
3057
+		$this->_template_args['items'] = (array) apply_filters(
3058
+			'FHEE__EE_Admin_Page___display_legend__items',
3059
+			$items,
3060
+			$this
3061
+		);
3062
+		/** @var StatusChangeNotice $status_change_notice */
3063
+		$status_change_notice                         = $this->loader->getShared(
3064
+			'EventEspresso\core\domain\services\admin\notices\status_change\StatusChangeNotice'
3065
+		);
3066
+		$this->_template_args['status_change_notice'] = $status_change_notice->display(
3067
+			'__admin-legend',
3068
+			$this->page_slug
3069
+		);
3070
+		return EEH_Template::display_template(
3071
+			EE_ADMIN_TEMPLATE . 'admin_details_legend.template.php',
3072
+			$this->_template_args,
3073
+			true
3074
+		);
3075
+	}
3076
+
3077
+
3078
+	/**
3079
+	 * This is used whenever we're DOING_AJAX to return a formatted json array that our calling javascript can expect
3080
+	 * The returned json object is created from an array in the following format:
3081
+	 * array(
3082
+	 *  'error' => FALSE, //(default FALSE), contains any errors and/or exceptions (exceptions return json early),
3083
+	 *  'success' => FALSE, //(default FALSE) - contains any special success message.
3084
+	 *  'notices' => '', // - contains any EE_Error formatted notices
3085
+	 *  'content' => 'string can be html', //this is a string of formatted content (can be html)
3086
+	 *  'data' => array() //this can be any key/value pairs that a method returns for later json parsing by the js.
3087
+	 *  We're also going to include the template args with every package (so js can pick out any specific template args
3088
+	 *  that might be included in here)
3089
+	 * )
3090
+	 * The json object is populated by whatever is set in the $_template_args property.
3091
+	 *
3092
+	 * @param bool  $sticky_notices    Used to indicate whether you want to ensure notices are added to a transient
3093
+	 *                                 instead of displayed.
3094
+	 * @param array $notices_arguments Use this to pass any additional args on to the _process_notices.
3095
+	 * @return void
3096
+	 * @throws EE_Error
3097
+	 * @throws InvalidArgumentException
3098
+	 * @throws InvalidDataTypeException
3099
+	 * @throws InvalidInterfaceException
3100
+	 */
3101
+	protected function _return_json(bool $sticky_notices = false, array $notices_arguments = [])
3102
+	{
3103
+		// make sure any EE_Error notices have been handled.
3104
+		$this->_process_notices($notices_arguments, true, $sticky_notices);
3105
+		$data = $this->_template_args['data'] ?? [];
3106
+		unset($this->_template_args['data']);
3107
+		$json = [
3108
+			'error'     => $this->_template_args['error'] ?? false,
3109
+			'success'   => $this->_template_args['success'] ?? false,
3110
+			'errors'    => $this->_template_args['errors'] ?? false,
3111
+			'attention' => $this->_template_args['attention'] ?? false,
3112
+			'notices'   => EE_Error::get_notices(),
3113
+			'content'   => $this->_template_args['admin_page_content'] ?? '',
3114
+			'data'      => array_merge($data, ['template_args' => $this->_template_args]),
3115
+			'isEEajax'  => true,
3116
+			// special flag so any ajax.Success methods in js can identify this return package as a EEajax package.
3117
+		];
3118
+		// make sure there are no php errors or headers_sent.  Then we can set correct json header.
3119
+		if (null === error_get_last() || ! headers_sent()) {
3120
+			header('Content-Type: application/json; charset=UTF-8');
3121
+		}
3122
+		echo wp_json_encode($json);
3123
+		exit();
3124
+	}
3125
+
3126
+
3127
+	/**
3128
+	 * Simply a wrapper for the protected method so we can call this outside the class (ONLY when doing ajax)
3129
+	 *
3130
+	 * @return void
3131
+	 * @throws EE_Error
3132
+	 * @throws InvalidArgumentException
3133
+	 * @throws InvalidDataTypeException
3134
+	 * @throws InvalidInterfaceException
3135
+	 */
3136
+	public function return_json()
3137
+	{
3138
+		if ($this->request->isAjax()) {
3139
+			$this->_return_json();
3140
+		} else {
3141
+			throw new EE_Error(
3142
+				sprintf(
3143
+					esc_html__('The public %s method can only be called when DOING_AJAX = TRUE', 'event_espresso'),
3144
+					__FUNCTION__
3145
+				)
3146
+			);
3147
+		}
3148
+	}
3149
+
3150
+
3151
+	/**
3152
+	 * This provides a way for child hook classes to send along themselves by reference so methods/properties within
3153
+	 * them can be accessed by EE_Admin_child pages. This is assigned to the $_hook_obj property.
3154
+	 *
3155
+	 * @param EE_Admin_Hooks $hook_obj This will be the object for the EE_Admin_Hooks child
3156
+	 * @deprecated  5.0.8.p
3157
+	 */
3158
+	public function set_hook_object(EE_Admin_Hooks $hook_obj)
3159
+	{
3160
+		$this->_hook_obj = $hook_obj;
3161
+	}
3162
+
3163
+
3164
+	/**
3165
+	 *        generates  HTML wrapper with Tabbed nav for an admin page
3166
+	 *
3167
+	 * @param bool $about whether to use the special about page wrapper or default.
3168
+	 * @return void
3169
+	 * @throws DomainException
3170
+	 * @throws EE_Error
3171
+	 * @throws InvalidArgumentException
3172
+	 * @throws InvalidDataTypeException
3173
+	 * @throws InvalidInterfaceException
3174
+	 */
3175
+	public function admin_page_wrapper(bool $about = false)
3176
+	{
3177
+		$this->_template_args['nav_tabs']         = $this->_get_main_nav_tabs();
3178
+		$this->_template_args['admin_page_title'] = $this->_admin_page_title;
3179
+
3180
+		$this->_template_args['before_admin_page_content'] = apply_filters(
3181
+			"FHEE_before_admin_page_content$this->_current_page$this->_current_view",
3182
+			$this->_template_args['before_admin_page_content'] ?? ''
3183
+		);
3184
+
3185
+		$this->_template_args['after_admin_page_content'] = apply_filters(
3186
+			"FHEE_after_admin_page_content$this->_current_page$this->_current_view",
3187
+			$this->_template_args['after_admin_page_content'] ?? ''
3188
+		);
3189
+		$this->_template_args['after_admin_page_content'] .= $this->_set_help_popup_content();
3190
+
3191
+		if ($this->request->isAjax()) {
3192
+			$this->_template_args['admin_page_content'] = EEH_Template::display_template(
3193
+			// $template_path,
3194
+				EE_ADMIN_TEMPLATE . 'admin_wrapper_ajax.template.php',
3195
+				$this->_template_args,
3196
+				true
3197
+			);
3198
+			$this->_return_json();
3199
+		}
3200
+		// load settings page wrapper template
3201
+		$template_path = $about
3202
+			? EE_ADMIN_TEMPLATE . 'about_admin_wrapper.template.php'
3203
+			: EE_ADMIN_TEMPLATE . 'admin_wrapper.template.php';
3204
+
3205
+		EEH_Template::display_template($template_path, $this->_template_args);
3206
+	}
3207
+
3208
+
3209
+	/**
3210
+	 * This returns the admin_nav tabs html using the configuration in the _nav_tabs property
3211
+	 *
3212
+	 * @return string html
3213
+	 * @throws EE_Error
3214
+	 */
3215
+	protected function _get_main_nav_tabs(): string
3216
+	{
3217
+		// let's generate the html using the EEH_Tabbed_Content helper.
3218
+		// We do this here so that it's possible for child classes to add in nav tabs dynamically at the last minute
3219
+		// (rather than setting in the page_routes array)
3220
+		return EEH_Tabbed_Content::display_admin_nav_tabs($this->_nav_tabs, $this->page_slug);
3221
+	}
3222
+
3223
+
3224
+	/**
3225
+	 *        sort nav tabs
3226
+	 *
3227
+	 * @param array $a
3228
+	 * @param array $b
3229
+	 * @return int
3230
+	 */
3231
+	private function _sort_nav_tabs(array $a, array $b): int
3232
+	{
3233
+		if ($a['order'] === $b['order']) {
3234
+			return 0;
3235
+		}
3236
+		return ($a['order'] < $b['order']) ? -1 : 1;
3237
+	}
3238
+
3239
+
3240
+	/**
3241
+	 * generates HTML for the forms used on admin pages
3242
+	 *
3243
+	 * @param array  $input_vars - array of input field details
3244
+	 * @param string $generator  indicates which generator to use: options are 'string' or 'array'
3245
+	 * @param string $id
3246
+	 * @return array|string
3247
+	 * @uses   EEH_Form_Fields::get_form_fields (/helper/EEH_Form_Fields.helper.php)
3248
+	 * @uses   EEH_Form_Fields::get_form_fields_array (/helper/EEH_Form_Fields.helper.php)
3249
+	 */
3250
+	protected function _generate_admin_form_fields(
3251
+		array $input_vars = [],
3252
+		string $generator = 'string',
3253
+		string $id = ''
3254
+	) {
3255
+		return $generator === 'string'
3256
+			? EEH_Form_Fields::get_form_fields($input_vars, $id)
3257
+			: EEH_Form_Fields::get_form_fields_array($input_vars);
3258
+	}
3259
+
3260
+
3261
+	/**
3262
+	 * generates the "Save" and "Save & Close" buttons for edit forms
3263
+	 *
3264
+	 * @param bool             $both     if true then both buttons will be generated.  If false then just the "Save &
3265
+	 *                                   Close" button.
3266
+	 * @param array            $text     if included, generator will use the given text for the buttons ( array([0] =>
3267
+	 *                                   'Save', [1] => 'save & close')
3268
+	 * @param array            $actions  if included allows us to set the actions that each button will carry out (i.e.
3269
+	 *                                   via the "name" value in the button).  We can also use this to just dump
3270
+	 *                                   default actions by submitting some other value.
3271
+	 * @param bool|string|null $referrer if false then we just do the default action on save and close.  Otherwise it
3272
+	 *                                   will use the $referrer string. IF null, then we don't do ANYTHING on save and
3273
+	 *                                   close (normal form handling).
3274
+	 */
3275
+	protected function _set_save_buttons(bool $both = true, array $text = [], array $actions = [], $referrer = null)
3276
+	{
3277
+		$referrer_url  = ! empty($referrer) ? $referrer : $this->request->getServerParam('REQUEST_URI');
3278
+		$button_text   = ! empty($text)
3279
+			? $text
3280
+			: [
3281
+				esc_html__('Save', 'event_espresso'),
3282
+				esc_html__('Save and Close', 'event_espresso'),
3283
+			];
3284
+		$default_names = ['save', 'save_and_close'];
3285
+		$buttons       = '';
3286
+		foreach ($button_text as $key => $button) {
3287
+			$ref     = $default_names[ $key ];
3288
+			$name    = ! empty($actions) ? $actions[ $key ] : $ref;
3289
+			$buttons .= '<input type="submit" class="button button--primary ' . $ref . '" '
3290
+						. 'value="' . $button . '" name="' . $name . '" '
3291
+						. 'id="' . $this->_current_view . '_' . $ref . '" />';
3292
+			if (! $both) {
3293
+				break;
3294
+			}
3295
+		}
3296
+		// add in a hidden index for the current page (so save and close redirects properly)
3297
+		$buttons .= '<input type="hidden" id="save_and_close_referrer" name="save_and_close_referrer" value="'
3298
+					. $referrer_url
3299
+					. '" />';
3300
+
3301
+		$this->_template_args['save_buttons'] = $buttons;
3302
+	}
3303
+
3304
+
3305
+	/**
3306
+	 * Wrapper for the protected function.  Allows plugins/addons to call this to set the form tags.
3307
+	 *
3308
+	 * @param string $route
3309
+	 * @param array  $additional_hidden_fields
3310
+	 * @see   $this->_set_add_edit_form_tags() for details on params
3311
+	 * @since 4.6.0
3312
+	 */
3313
+	public function set_add_edit_form_tags(string $route = '', array $additional_hidden_fields = [])
3314
+	{
3315
+		$this->_set_add_edit_form_tags($route, $additional_hidden_fields);
3316
+	}
3317
+
3318
+
3319
+	/**
3320
+	 * set form open and close tags on add/edit pages.
3321
+	 *
3322
+	 * @param string $route                    the route you want the form to direct to
3323
+	 * @param array  $additional_hidden_fields any additional hidden fields required in the form header
3324
+	 * @return void
3325
+	 */
3326
+	protected function _set_add_edit_form_tags(string $route = '', array $additional_hidden_fields = [])
3327
+	{
3328
+		if (empty($route)) {
3329
+			$user_msg = esc_html__(
3330
+				'An error occurred. No action was set for this page\'s form.',
3331
+				'event_espresso'
3332
+			);
3333
+			$dev_msg  = $user_msg . "\n"
3334
+						. sprintf(
3335
+							esc_html__('The $route argument is required for the %s->%s method.', 'event_espresso'),
3336
+							__FUNCTION__,
3337
+							__CLASS__
3338
+						);
3339
+			EE_Error::add_error($user_msg . '||' . $dev_msg, __FILE__, __FUNCTION__, __LINE__);
3340
+		}
3341
+		// open form
3342
+		$action                                            = $this->_admin_base_url;
3343
+		$this->_template_args['before_admin_page_content'] = "
3344 3344
             <form name='form' method='post' action='$action' id='{$route}_event_form' class='ee-admin-page-form' >
3345 3345
             ";
3346
-        // add nonce
3347
-        $nonce                                             =
3348
-            wp_nonce_field($route . '_nonce', $route . '_nonce', false, false);
3349
-        $this->_template_args['before_admin_page_content'] .= "\n\t" . $nonce;
3350
-        // add REQUIRED form action
3351
-        $hidden_fields = [
3352
-            'action' => ['type' => 'hidden', 'value' => $route],
3353
-        ];
3354
-        // merge arrays
3355
-        $hidden_fields = is_array($additional_hidden_fields)
3356
-            ? array_merge($hidden_fields, $additional_hidden_fields)
3357
-            : $hidden_fields;
3358
-        // generate form fields
3359
-        $form_fields = $this->_generate_admin_form_fields($hidden_fields, 'array');
3360
-        // add fields to form
3361
-        foreach ((array) $form_fields as $form_field) {
3362
-            $this->_template_args['before_admin_page_content'] .= "\n\t" . $form_field['field'];
3363
-        }
3364
-        // close form
3365
-        $this->_template_args['after_admin_page_content'] = '</form>';
3366
-    }
3367
-
3368
-
3369
-    /**
3370
-     * Public Wrapper for _redirect_after_action() method since its
3371
-     * discovered it would be useful for external code to have access.
3372
-     *
3373
-     * @param bool|int $success
3374
-     * @param string   $what
3375
-     * @param string   $action_desc
3376
-     * @param array    $query_args
3377
-     * @param bool     $override_overwrite
3378
-     * @throws EE_Error
3379
-     * @see   EE_Admin_Page::_redirect_after_action() for params.
3380
-     * @since 4.5.0
3381
-     */
3382
-    public function redirect_after_action(
3383
-        $success = false,
3384
-        string $what = 'item',
3385
-        string $action_desc = 'processed',
3386
-        array $query_args = [],
3387
-        bool $override_overwrite = false
3388
-    ) {
3389
-        $this->_redirect_after_action(
3390
-            $success,
3391
-            $what,
3392
-            $action_desc,
3393
-            $query_args,
3394
-            $override_overwrite
3395
-        );
3396
-    }
3397
-
3398
-
3399
-    /**
3400
-     * Helper method for merging existing request data with the returned redirect url.
3401
-     * This is typically used for redirects after an action so that if the original view was a filtered view those
3402
-     * filters are still applied.
3403
-     *
3404
-     * @param array $new_route_data
3405
-     * @return array
3406
-     */
3407
-    protected function mergeExistingRequestParamsWithRedirectArgs(array $new_route_data): array
3408
-    {
3409
-        foreach ($this->request->requestParams() as $ref => $value) {
3410
-            // unset nonces
3411
-            if (strpos($ref, 'nonce') !== false) {
3412
-                $this->request->unSetRequestParam($ref);
3413
-                continue;
3414
-            }
3415
-            // urlencode values.
3416
-            $value = is_array($value) ? array_map('urlencode', $value) : urlencode($value);
3417
-            $this->request->setRequestParam($ref, $value);
3418
-        }
3419
-        return array_merge($this->request->requestParams(), $new_route_data);
3420
-    }
3421
-
3422
-
3423
-    /**
3424
-     * @param int|float|string $success            - whether success was for two or more records, or just one, or none
3425
-     * @param string           $what               - what the action was performed on
3426
-     * @param string           $action_desc        - what was done ie: updated, deleted, etc
3427
-     * @param array            $query_args         - an array of query_args to be added to the URL to redirect to
3428
-     * @param BOOL             $override_overwrite - by default all EE_Error::success messages are overwritten,
3429
-     *                                             this allows you to override this so that they show.
3430
-     * @return void
3431
-     * @throws EE_Error
3432
-     * @throws InvalidArgumentException
3433
-     * @throws InvalidDataTypeException
3434
-     * @throws InvalidInterfaceException
3435
-     */
3436
-    protected function _redirect_after_action(
3437
-        $success = 0,
3438
-        string $what = 'item',
3439
-        string $action_desc = 'processed',
3440
-        array $query_args = [],
3441
-        bool $override_overwrite = false
3442
-    ) {
3443
-        $notices = EE_Error::get_notices(false);
3444
-        // overwrite default success messages //BUT ONLY if overwrite not overridden
3445
-        if (! $override_overwrite || ! empty($notices['errors'])) {
3446
-            EE_Error::overwrite_success();
3447
-        }
3448
-        if (! $override_overwrite && ! empty($what) && ! empty($action_desc) && empty($notices['errors'])) {
3449
-            // how many records affected ? more than one record ? or just one ?
3450
-            EE_Error::add_success(
3451
-                sprintf(
3452
-                    esc_html(
3453
-                        _n(
3454
-                            'The "%1$s" has been successfully %2$s.',
3455
-                            'The "%1$s" have been successfully %2$s.',
3456
-                            $success,
3457
-                            'event_espresso'
3458
-                        )
3459
-                    ),
3460
-                    $what,
3461
-                    $action_desc
3462
-                ),
3463
-                __FILE__,
3464
-                __FUNCTION__,
3465
-                __LINE__
3466
-            );
3467
-        }
3468
-        // check that $query_args isn't something crazy
3469
-        $query_args = is_array($query_args) ? $query_args : [];
3470
-        /**
3471
-         * Allow injecting actions before the query_args are modified for possible different
3472
-         * redirections on save and close actions
3473
-         *
3474
-         * @param array $query_args       The original query_args array coming into the
3475
-         *                                method.
3476
-         * @since 4.2.0
3477
-         */
3478
-        do_action(
3479
-            "AHEE__{$this->class_name}___redirect_after_action__before_redirect_modification_$this->_req_action",
3480
-            $query_args
3481
-        );
3482
-        // set redirect url.
3483
-        // Note if there is a "page" index in the $query_args then we go with vanilla admin.php route,
3484
-        // otherwise we go with whatever is set as the _admin_base_url
3485
-        $redirect_url = isset($query_args['page']) ? admin_url('admin.php') : $this->_admin_base_url;
3486
-        // calculate where we're going (if we have a "save and close" button pushed)
3487
-        if (
3488
-            $this->request->requestParamIsSet('save_and_close')
3489
-            && $this->request->requestParamIsSet('save_and_close_referrer')
3490
-        ) {
3491
-            // even though we have the save_and_close referrer, we need to parse the url for the action in order to generate a nonce
3492
-            $parsed_url = parse_url($this->request->getRequestParam('save_and_close_referrer', '', DataType::URL));
3493
-            // regenerate query args array from referrer URL
3494
-            parse_str($parsed_url['query'], $query_args);
3495
-            // correct page and action will be in the query args now
3496
-            $redirect_url = admin_url('admin.php');
3497
-        }
3498
-        // merge any default query_args set in _default_route_query_args property
3499
-        if (! empty($this->_default_route_query_args) && ! $this->_is_UI_request) {
3500
-            $args_to_merge = [];
3501
-            foreach ($this->_default_route_query_args as $query_param => $query_value) {
3502
-                // is there a wp_referer array in our _default_route_query_args property?
3503
-                if ($query_param === 'wp_referer') {
3504
-                    $query_value = (array) $query_value;
3505
-                    foreach ($query_value as $reference => $value) {
3506
-                        if (strpos($reference, 'nonce') !== false) {
3507
-                            continue;
3508
-                        }
3509
-                        // finally we will override any arguments in the referer with
3510
-                        // what might be set on the _default_route_query_args array.
3511
-                        if (isset($this->_default_route_query_args[ $reference ])) {
3512
-                            $args_to_merge[ $reference ] = urlencode($this->_default_route_query_args[ $reference ]);
3513
-                        } else {
3514
-                            $args_to_merge[ $reference ] = urlencode($value);
3515
-                        }
3516
-                    }
3517
-                    continue;
3518
-                }
3519
-                $args_to_merge[ $query_param ] = $query_value;
3520
-            }
3521
-            // now let's merge these arguments but override with what was specifically sent in to the
3522
-            // redirect.
3523
-            $query_args = array_merge($args_to_merge, $query_args);
3524
-        }
3525
-        $this->_process_notices($query_args);
3526
-        // generate redirect url
3527
-        // if redirecting to anything other than the main page, add a nonce
3528
-        if (isset($query_args['action'])) {
3529
-            // manually generate wp_nonce and merge that with the query vars
3530
-            // becuz the wp_nonce_url function wrecks havoc on some vars
3531
-            $query_args['_wpnonce'] = wp_create_nonce($query_args['action'] . '_nonce');
3532
-        }
3533
-        // we're adding some hooks and filters in here for processing any things just before redirects
3534
-        // (example: an admin page has done an insert or update and we want to run something after that).
3535
-        do_action('AHEE_redirect_' . $this->class_name . $this->_req_action, $query_args);
3536
-        $redirect_url = apply_filters(
3537
-            'FHEE_redirect_' . $this->class_name . $this->_req_action,
3538
-            EE_Admin_Page::add_query_args_and_nonce($query_args, $redirect_url),
3539
-            $query_args
3540
-        );
3541
-        // check if we're doing ajax.  If we are then lets just return the results and js can handle how it wants.
3542
-        if ($this->request->isAjax()) {
3543
-            $default_data                    = [
3544
-                'close'        => true,
3545
-                'redirect_url' => $redirect_url,
3546
-                'where'        => 'main',
3547
-                'what'         => 'append',
3548
-            ];
3549
-            $this->_template_args['success'] = $success;
3550
-            $this->_template_args['data']    = ! empty($this->_template_args['data']) ? array_merge(
3551
-                $default_data,
3552
-                $this->_template_args['data']
3553
-            ) : $default_data;
3554
-            $this->_return_json();
3555
-        }
3556
-        wp_safe_redirect($redirect_url);
3557
-        exit();
3558
-    }
3559
-
3560
-
3561
-    /**
3562
-     * process any notices before redirecting (or returning ajax request)
3563
-     * This method sets the $this->_template_args['notices'] attribute;
3564
-     *
3565
-     * @param array $query_args         any query args that need to be used for notice transient ('action')
3566
-     * @param bool  $skip_route_verify  This is typically used when we are processing notices REALLY early and
3567
-     *                                  page_routes haven't been defined yet.
3568
-     * @param bool  $sticky_notices     This is used to flag that regardless of whether this is doing_ajax or not, we
3569
-     *                                  still save a transient for the notice.
3570
-     * @return void
3571
-     * @throws EE_Error
3572
-     * @throws InvalidArgumentException
3573
-     * @throws InvalidDataTypeException
3574
-     * @throws InvalidInterfaceException
3575
-     */
3576
-    protected function _process_notices(
3577
-        array $query_args = [],
3578
-        bool $skip_route_verify = false,
3579
-        bool $sticky_notices = true
3580
-    ) {
3581
-        // first let's set individual error properties if doing_ajax and the properties aren't already set.
3582
-        if ($this->request->isAjax()) {
3583
-            $notices = EE_Error::get_notices(false);
3584
-            if (empty($this->_template_args['success'])) {
3585
-                $this->_template_args['success'] = $notices['success'] ?? false;
3586
-            }
3587
-            if (empty($this->_template_args['errors'])) {
3588
-                $this->_template_args['errors'] = $notices['errors'] ?? false;
3589
-            }
3590
-            if (empty($this->_template_args['attention'])) {
3591
-                $this->_template_args['attention'] = $notices['attention'] ?? false;
3592
-            }
3593
-        }
3594
-        $this->_template_args['notices'] = EE_Error::get_notices();
3595
-        // IF this isn't ajax we need to create a transient for the notices using the route (however, overridden if $sticky_notices == true)
3596
-        if (! $this->request->isAjax() || $sticky_notices) {
3597
-            $route = $query_args['action'] ?? 'default';
3598
-            $this->_add_transient(
3599
-                $route,
3600
-                (array) $this->_template_args['notices'],
3601
-                true,
3602
-                $skip_route_verify
3603
-            );
3604
-        }
3605
-    }
3606
-
3607
-
3608
-    /**
3609
-     * get_action_link_or_button
3610
-     * returns the button html for adding, editing, or deleting an item (depending on given type)
3611
-     *
3612
-     * @param string $action        use this to indicate which action the url is generated with.
3613
-     * @param string $type          accepted strings must be defined in the $_labels['button'] array(as the key)
3614
-     *                              property.
3615
-     * @param array  $extra_request if the button requires extra params you can include them in $key=>$value pairs.
3616
-     * @param string $class         Use this to give the class for the button. Defaults to 'button--primary'
3617
-     * @param string $base_url      If this is not provided
3618
-     *                              the _admin_base_url will be used as the default for the button base_url.
3619
-     *                              Otherwise this value will be used.
3620
-     * @param bool   $exclude_nonce If true then no nonce will be in the generated button link.
3621
-     * @return string
3622
-     * @throws InvalidArgumentException
3623
-     * @throws InvalidInterfaceException
3624
-     * @throws InvalidDataTypeException
3625
-     * @throws EE_Error
3626
-     */
3627
-    public function get_action_link_or_button(
3628
-        string $action,
3629
-        string $type = 'add',
3630
-        array $extra_request = [],
3631
-        string $class = 'button button--primary',
3632
-        string $base_url = '',
3633
-        bool $exclude_nonce = false
3634
-    ): string {
3635
-        // first let's validate the action (if $base_url is FALSE otherwise validation will happen further along)
3636
-        if (empty($base_url) && ! isset($this->_page_routes[ $action ])) {
3637
-            throw new EE_Error(
3638
-                sprintf(
3639
-                    esc_html__(
3640
-                        'There is no page route for given action for the button.  This action was given: %s',
3641
-                        'event_espresso'
3642
-                    ),
3643
-                    $action
3644
-                )
3645
-            );
3646
-        }
3647
-        if (! isset($this->_labels['buttons'][ $type ])) {
3648
-            throw new EE_Error(
3649
-                sprintf(
3650
-                    esc_html__(
3651
-                        'There is no label for the given button type (%s). Labels are set in the <code>_page_config</code> property.',
3652
-                        'event_espresso'
3653
-                    ),
3654
-                    $type
3655
-                )
3656
-            );
3657
-        }
3658
-        // finally check user access for this button.
3659
-        $has_access = $this->check_user_access($action, true);
3660
-        if (! $has_access) {
3661
-            return '';
3662
-        }
3663
-        $_base_url  = ! $base_url ? $this->_admin_base_url : $base_url;
3664
-        $query_args = [
3665
-            'action' => $action,
3666
-        ];
3667
-        // merge extra_request args but make sure our original action takes precedence and doesn't get overwritten.
3668
-        if (! empty($extra_request)) {
3669
-            $query_args = array_merge($extra_request, $query_args);
3670
-        }
3671
-        $url = EE_Admin_Page::add_query_args_and_nonce($query_args, $_base_url, false, $exclude_nonce);
3672
-        return EEH_Template::get_button_or_link($url, $this->_labels['buttons'][ $type ], $class);
3673
-    }
3674
-
3675
-
3676
-    /**
3677
-     * _per_page_screen_option
3678
-     * Utility function for adding in a per_page_option in the screen_options_dropdown.
3679
-     *
3680
-     * @return void
3681
-     * @throws InvalidArgumentException
3682
-     * @throws InvalidInterfaceException
3683
-     * @throws InvalidDataTypeException
3684
-     */
3685
-    protected function _per_page_screen_option()
3686
-    {
3687
-        $option = 'per_page';
3688
-        $args   = [
3689
-            'label'   => apply_filters(
3690
-                'FHEE__EE_Admin_Page___per_page_screen_options___label',
3691
-                $this->_admin_page_title,
3692
-                $this
3693
-            ),
3694
-            'default' => (int) apply_filters(
3695
-                'FHEE__EE_Admin_Page___per_page_screen_options__default',
3696
-                20
3697
-            ),
3698
-            'option'  => $this->_current_page . '_' . $this->_current_view . '_per_page',
3699
-        ];
3700
-        // ONLY add the screen option if the user has access to it.
3701
-        if ($this->check_user_access($this->_current_view, true)) {
3702
-            add_screen_option($option, $args);
3703
-        }
3704
-    }
3705
-
3706
-
3707
-    /**
3708
-     * set_per_page_screen_option
3709
-     * All this does is make sure that WordPress saves any per_page screen options (if set) for the current page.
3710
-     * we have to do this rather than running inside the 'set-screen-options' hook because it runs earlier than
3711
-     * admin_menu.
3712
-     *
3713
-     * @return void
3714
-     */
3715
-    private function _set_per_page_screen_options()
3716
-    {
3717
-        if ($this->request->requestParamIsSet('wp_screen_options')) {
3718
-            check_admin_referer('screen-options-nonce', 'screenoptionnonce');
3719
-            if (! $user = wp_get_current_user()) {
3720
-                return;
3721
-            }
3722
-            $option = $this->request->getRequestParam('wp_screen_options[option]', '', DataType::KEY);
3723
-            if (! $option) {
3724
-                return;
3725
-            }
3726
-            $value      = $this->request->getRequestParam('wp_screen_options[value]', 0, DataType::INT);
3727
-            $map_option = $option;
3728
-            $option     = str_replace('-', '_', $option);
3729
-            switch ($map_option) {
3730
-                case $this->_current_page . '_' . $this->_current_view . '_per_page':
3731
-                    $max_value = apply_filters(
3732
-                        'FHEE__EE_Admin_Page___set_per_page_screen_options__max_value',
3733
-                        999,
3734
-                        $this->_current_page,
3735
-                        $this->_current_view
3736
-                    );
3737
-                    if ($value < 1) {
3738
-                        return;
3739
-                    }
3740
-                    $value = min($value, $max_value);
3741
-                    break;
3742
-                default:
3743
-                    $value = apply_filters(
3744
-                        'FHEE__EE_Admin_Page___set_per_page_screen_options__value',
3745
-                        false,
3746
-                        $option,
3747
-                        $value
3748
-                    );
3749
-                    if (false === $value) {
3750
-                        return;
3751
-                    }
3752
-                    break;
3753
-            }
3754
-            update_user_meta($user->ID, $option, $value);
3755
-            wp_safe_redirect(remove_query_arg(['pagenum', 'apage', 'paged'], wp_get_referer()));
3756
-            exit;
3757
-        }
3758
-    }
3759
-
3760
-
3761
-    /**
3762
-     * This just allows for setting the $_template_args property if it needs to be set outside the object
3763
-     *
3764
-     * @param array $data array that will be assigned to template args.
3765
-     */
3766
-    public function set_template_args(array $data)
3767
-    {
3768
-        $this->_template_args = array_merge($this->_template_args, $data);
3769
-    }
3770
-
3771
-
3772
-    public function setAdminPageTitle(string $title)
3773
-    {
3774
-        $this->_admin_page_title = sanitize_text_field($title);
3775
-    }
3776
-
3777
-
3778
-    /**
3779
-     * This makes available the WP transient system for temporarily moving data between routes
3780
-     *
3781
-     * @param string $route             the route that should receive the transient
3782
-     * @param array  $data              the data that gets sent
3783
-     * @param bool   $notices           If this is for notices then we use this to indicate so, otherwise it's just a
3784
-     *                                  normal route transient.
3785
-     * @param bool   $skip_route_verify Used to indicate we want to skip route verification.  This is usually ONLY used
3786
-     *                                  when we are adding a transient before page_routes have been defined.
3787
-     * @return void
3788
-     * @throws EE_Error
3789
-     */
3790
-    protected function _add_transient(
3791
-        string $route,
3792
-        array $data,
3793
-        bool $notices = false,
3794
-        bool $skip_route_verify = false
3795
-    ) {
3796
-        $user_id = get_current_user_id();
3797
-        if (! $skip_route_verify) {
3798
-            $this->_verify_route($route);
3799
-        }
3800
-        // now let's set the string for what kind of transient we're setting
3801
-        $transient = $notices ? "ee_rte_n_tx_{$route}_$user_id" : "rte_tx_{$route}_$user_id";
3802
-        $data      = $notices ? ['notices' => $data] : $data;
3803
-        // is there already a transient for this route?  If there is then let's ADD to that transient
3804
-        $existing = is_multisite() && is_network_admin()
3805
-            ? get_site_transient($transient)
3806
-            : get_transient($transient);
3807
-        if ($existing) {
3808
-            $data = array_merge($data, (array) $existing);
3809
-        }
3810
-        if (is_multisite() && is_network_admin()) {
3811
-            set_site_transient($transient, $data, 8);
3812
-        } else {
3813
-            set_transient($transient, $data, 8);
3814
-        }
3815
-    }
3816
-
3817
-
3818
-    /**
3819
-     * this retrieves the temporary transient that has been set for moving data between routes.
3820
-     *
3821
-     * @param bool   $notices true we get notices transient. False we just return normal route transient
3822
-     * @param string $route
3823
-     * @return mixed data
3824
-     */
3825
-    protected function _get_transient(bool $notices = false, string $route = '')
3826
-    {
3827
-        $user_id   = get_current_user_id();
3828
-        $route     = ! $route ? $this->_req_action : $route;
3829
-        $transient = $notices
3830
-            ? 'ee_rte_n_tx_' . $route . '_' . $user_id
3831
-            : 'rte_tx_' . $route . '_' . $user_id;
3832
-        $data      = is_multisite() && is_network_admin()
3833
-            ? get_site_transient($transient)
3834
-            : get_transient($transient);
3835
-        // delete transient after retrieval (just in case it hasn't expired);
3836
-        if (is_multisite() && is_network_admin()) {
3837
-            delete_site_transient($transient);
3838
-        } else {
3839
-            delete_transient($transient);
3840
-        }
3841
-        return $notices && isset($data['notices']) ? $data['notices'] : $data;
3842
-    }
3843
-
3844
-
3845
-    /**
3846
-     * The purpose of this method is just to run garbage collection on any EE transients that might have expired but
3847
-     * would not be called later. This will be assigned to run on a specific EE Admin page. (place the method in the
3848
-     * default route callback on the EE_Admin page you want it run.)
3849
-     *
3850
-     * @return void
3851
-     */
3852
-    protected function _transient_garbage_collection()
3853
-    {
3854
-        global $wpdb;
3855
-        // retrieve all existing transients
3856
-        $query =
3857
-            "SELECT option_name FROM $wpdb->options WHERE option_name LIKE '%rte_tx_%' OR option_name LIKE '%rte_n_tx_%'";
3858
-        if ($results = $wpdb->get_results($query)) {
3859
-            foreach ($results as $result) {
3860
-                $transient = str_replace('_transient_', '', $result->option_name);
3861
-                get_transient($transient);
3862
-                if (is_multisite() && is_network_admin()) {
3863
-                    get_site_transient($transient);
3864
-                }
3865
-            }
3866
-        }
3867
-    }
3868
-
3869
-
3870
-    /**
3871
-     * get_view
3872
-     *
3873
-     * @return string content of _view property
3874
-     */
3875
-    public function get_view(): string
3876
-    {
3877
-        return $this->_view;
3878
-    }
3879
-
3880
-
3881
-    /**
3882
-     * getter for the protected $_views property
3883
-     *
3884
-     * @return array
3885
-     */
3886
-    public function get_views(): array
3887
-    {
3888
-        return $this->_views;
3889
-    }
3890
-
3891
-
3892
-    /**
3893
-     * @param array $views
3894
-     * @return void
3895
-     * @since 5.0.13.p
3896
-     */
3897
-    public function updateViews(array $views)
3898
-    {
3899
-        $this->_views = array_merge($this->_views, $views);
3900
-    }
3901
-
3902
-
3903
-    /**
3904
-    /**
3905
-     * get_current_page
3906
-     *
3907
-     * @return string _current_page property value
3908
-     */
3909
-    public function get_current_page(): string
3910
-    {
3911
-        return $this->_current_page;
3912
-    }
3913
-
3914
-
3915
-    /**
3916
-     * get_current_view
3917
-     *
3918
-     * @return string _current_view property value
3919
-     */
3920
-    public function get_current_view(): string
3921
-    {
3922
-        return $this->_current_view;
3923
-    }
3924
-
3925
-
3926
-    /**
3927
-     * get_current_screen
3928
-     *
3929
-     * @return object The current WP_Screen object
3930
-     */
3931
-    public function get_current_screen()
3932
-    {
3933
-        return $this->_current_screen;
3934
-    }
3935
-
3936
-
3937
-    /**
3938
-     * get_current_page_view_url
3939
-     *
3940
-     * @return string This returns the url for the current_page_view.
3941
-     */
3942
-    public function get_current_page_view_url(): string
3943
-    {
3944
-        return $this->_current_page_view_url;
3945
-    }
3946
-
3947
-
3948
-    /**
3949
-     * just returns the Request
3950
-     *
3951
-     * @return RequestInterface
3952
-     */
3953
-    public function get_request(): ?RequestInterface
3954
-    {
3955
-        return $this->request;
3956
-    }
3957
-
3958
-
3959
-    /**
3960
-     * just returns the _req_data property
3961
-     *
3962
-     * @return array
3963
-     */
3964
-    public function get_request_data(): array
3965
-    {
3966
-        return $this->request->requestParams();
3967
-    }
3968
-
3969
-
3970
-    /**
3971
-     * returns the _req_data protected property
3972
-     *
3973
-     * @return string
3974
-     */
3975
-    public function get_req_action(): string
3976
-    {
3977
-        return $this->_req_action;
3978
-    }
3979
-
3980
-
3981
-    /**
3982
-     * @return bool  value of $_is_caf property
3983
-     */
3984
-    public function is_caf(): bool
3985
-    {
3986
-        return $this->_is_caf;
3987
-    }
3988
-
3989
-
3990
-    /**
3991
-     * @return array
3992
-     */
3993
-    public function default_espresso_metaboxes(): array
3994
-    {
3995
-        return $this->_default_espresso_metaboxes;
3996
-    }
3997
-
3998
-
3999
-    /**
4000
-     * @return string
4001
-     */
4002
-    public function admin_base_url(): string
4003
-    {
4004
-        return $this->_admin_base_url;
4005
-    }
4006
-
4007
-
4008
-    /**
4009
-     * @return string
4010
-     */
4011
-    public function wp_page_slug(): string
4012
-    {
4013
-        return $this->_wp_page_slug;
4014
-    }
4015
-
4016
-
4017
-    /**
4018
-     * updates  espresso configuration settings
4019
-     *
4020
-     * @param string                   $tab
4021
-     * @param EE_Config_Base|EE_Config $config
4022
-     * @param string                   $file file where error occurred
4023
-     * @param string                   $func function  where error occurred
4024
-     * @param string                   $line line no where error occurred
4025
-     * @return bool
4026
-     * @throws EE_Error
4027
-     * @throws ReflectionException
4028
-     */
4029
-    protected function _update_espresso_configuration(
4030
-        string $tab,
4031
-        $config,
4032
-        string $file = '',
4033
-        string $func = '',
4034
-        string $line = ''
4035
-    ): bool {
4036
-        // remove any options that are NOT going to be saved with the config settings.
4037
-        if (isset($config->core->ee_ueip_optin)) {
4038
-            // TODO: remove the following two lines and make sure values are migrated from 3.1
4039
-            update_option('ee_ueip_optin', $config->core->ee_ueip_optin);
4040
-            update_option('ee_ueip_has_notified', true);
4041
-        }
4042
-        // and save it (note we're also doing the network save here)
4043
-        $net_saved    = ! is_main_site() || EE_Network_Config::instance()->update_config(false, false);
4044
-        $config_saved = EE_Config::instance()->update_espresso_config(false, false);
4045
-        if ($config_saved && $net_saved) {
4046
-            EE_Error::add_success(sprintf(esc_html__('"%s" have been successfully updated.', 'event_espresso'), $tab));
4047
-            return true;
4048
-        }
4049
-        EE_Error::add_error(
4050
-            sprintf(esc_html__('The "%s" were not updated.', 'event_espresso'), $tab),
4051
-            $file,
4052
-            $func,
4053
-            $line
4054
-        );
4055
-        return false;
4056
-    }
4057
-
4058
-
4059
-    /**
4060
-     * Returns an array to be used for EE_FOrm_Fields.helper.php's select_input as the $values argument.
4061
-     *
4062
-     * @return array
4063
-     */
4064
-    public function get_yes_no_values(): array
4065
-    {
4066
-        return $this->_yes_no_values;
4067
-    }
4068
-
4069
-
4070
-    /**
4071
-     * @return string
4072
-     * @throws ReflectionException
4073
-     * @since 5.0.0.p
4074
-     */
4075
-    protected function _get_dir(): string
4076
-    {
4077
-        $reflector = new ReflectionClass($this->class_name);
4078
-        return dirname($reflector->getFileName());
4079
-    }
4080
-
4081
-
4082
-    /**
4083
-     * A helper for getting a "next link".
4084
-     *
4085
-     * @param string $url   The url to link to
4086
-     * @param string $class The class to use.
4087
-     * @return string
4088
-     */
4089
-    protected function _next_link(string $url, string $class = 'dashicons dashicons-arrow-right'): string
4090
-    {
4091
-        return '<a class="' . $class . '" href="' . $url . '"></a>';
4092
-    }
4093
-
4094
-
4095
-    /**
4096
-     * A helper for getting a "previous link".
4097
-     *
4098
-     * @param string $url   The url to link to
4099
-     * @param string $class The class to use.
4100
-     * @return string
4101
-     */
4102
-    protected function _previous_link(string $url, string $class = 'dashicons dashicons-arrow-left'): string
4103
-    {
4104
-        return '<a class="' . $class . '" href="' . $url . '"></a>';
4105
-    }
4106
-
4107
-
4108
-
4109
-
4110
-
4111
-
4112
-
4113
-    // below are some messages related methods that should be available across the EE_Admin system.  Note, these methods are NOT page specific
4114
-
4115
-
4116
-    /**
4117
-     * This processes a request to resend a registration and assumes we have a _REG_ID for doing so. So if the caller
4118
-     * knows that the _REG_ID isn't in the req_data array but CAN obtain it, the caller should ADD the _REG_ID to the
4119
-     * _req_data array.
4120
-     *
4121
-     * @return bool success/fail
4122
-     * @throws EE_Error
4123
-     * @throws InvalidArgumentException
4124
-     * @throws ReflectionException
4125
-     * @throws InvalidDataTypeException
4126
-     * @throws InvalidInterfaceException
4127
-     */
4128
-    protected function _process_resend_registration(): bool
4129
-    {
4130
-        $this->_template_args['success'] = EED_Messages::process_resend($this->request->requestParams());
4131
-        do_action(
4132
-            'AHEE__EE_Admin_Page___process_resend_registration',
4133
-            $this->_template_args['success'],
4134
-            $this->request->requestParams()
4135
-        );
4136
-        return $this->_template_args['success'];
4137
-    }
4138
-
4139
-
4140
-    /**
4141
-     * This automatically processes any payment message notifications when manual payment has been applied.
4142
-     *
4143
-     * @param EE_Payment $payment
4144
-     * @return bool success/fail
4145
-     */
4146
-    protected function _process_payment_notification(EE_Payment $payment): bool
4147
-    {
4148
-        add_filter('FHEE__EE_Payment_Processor__process_registration_payments__display_notifications', '__return_true');
4149
-        do_action('AHEE__EE_Admin_Page___process_admin_payment_notification', $payment);
4150
-        $this->_template_args['success'] = apply_filters(
4151
-            'FHEE__EE_Admin_Page___process_admin_payment_notification__success',
4152
-            false,
4153
-            $payment
4154
-        );
4155
-        return $this->_template_args['success'];
4156
-    }
4157
-
4158
-
4159
-    /**
4160
-     * @param EEM_Base      $entity_model
4161
-     * @param string        $entity_PK_name name of the primary key field used as a request param, ie: id, ID, etc
4162
-     * @param string        $action         one of the EE_Admin_List_Table::ACTION_* constants: delete, restore, trash
4163
-     * @param string        $delete_column  name of the field that denotes whether entity is trashed
4164
-     * @param callable|null $callback       called after entity is trashed, restored, or deleted
4165
-     * @return int|float
4166
-     * @throws EE_Error
4167
-     */
4168
-    protected function trashRestoreDeleteEntities(
4169
-        EEM_Base $entity_model,
4170
-        string $entity_PK_name,
4171
-        string $action = EE_Admin_List_Table::ACTION_DELETE,
4172
-        string $delete_column = '',
4173
-        ?callable $callback = null
4174
-    ) {
4175
-        $entity_PK      = $entity_model->get_primary_key_field();
4176
-        $entity_PK_name = $entity_PK_name ?: $entity_PK->get_name();
4177
-        $entity_PK_type = $this->resolveEntityFieldDataType($entity_PK);
4178
-        // grab ID if deleting a single entity
4179
-        if ($this->request->requestParamIsSet($entity_PK_name)) {
4180
-            $ID = $this->request->getRequestParam($entity_PK_name, 0, $entity_PK_type);
4181
-            return $this->trashRestoreDeleteEntity($entity_model, $ID, $action, $delete_column, $callback) ? 1 : 0;
4182
-        }
4183
-        // or grab checkbox array if bulk deleting
4184
-        $checkboxes = $this->request->getRequestParam('checkbox', [], $entity_PK_type, true);
4185
-        if (empty($checkboxes)) {
4186
-            return 0;
4187
-        }
4188
-        $success = 0;
4189
-        $IDs     = array_keys($checkboxes);
4190
-        // cycle thru bulk action checkboxes
4191
-        foreach ($IDs as $ID) {
4192
-            // increment $success
4193
-            if ($this->trashRestoreDeleteEntity($entity_model, $ID, $action, $delete_column, $callback)) {
4194
-                $success++;
4195
-            }
4196
-        }
4197
-        $count = (int) count($checkboxes);
4198
-        // if multiple entities were deleted successfully, then $deleted will be full count of deletions,
4199
-        // otherwise it will be a fraction of ( actual deletions / total entities to be deleted )
4200
-        return $success === $count ? $count : $success / $count;
4201
-    }
4202
-
4203
-
4204
-    /**
4205
-     * @param EE_Primary_Key_Field_Base $entity_PK
4206
-     * @return string
4207
-     * @throws EE_Error
4208
-     * @since   4.10.30.p
4209
-     */
4210
-    private function resolveEntityFieldDataType(EE_Primary_Key_Field_Base $entity_PK): string
4211
-    {
4212
-        $entity_PK_type = $entity_PK->dataType();
4213
-        switch ($entity_PK_type) {
4214
-            case DataType::BOOL;
4215
-            case DataType::INT;
4216
-            case DataType::FLOAT;
4217
-            case DataType::STRING;
4218
-                return $entity_PK_type;
4219
-        }
4220
-        throw new RuntimeException(
4221
-            sprintf(
4222
-                esc_html__(
4223
-                    '"%1$s" is an invalid schema type for the %2$s primary key.',
4224
-                    'event_espresso'
4225
-                ),
4226
-                $entity_PK_type,
4227
-                $entity_PK->get_name()
4228
-            )
4229
-        );
4230
-    }
4231
-
4232
-
4233
-    /**
4234
-     * @param EEM_Base      $entity_model
4235
-     * @param int|string    $entity_ID
4236
-     * @param string        $action        one of the EE_Admin_List_Table::ACTION_* constants: delete, restore, trash
4237
-     * @param string        $delete_column name of the field that denotes whether entity is trashed
4238
-     * @param callable|null $callback      called after entity is trashed, restored, or deleted
4239
-     * @return bool
4240
-     */
4241
-    protected function trashRestoreDeleteEntity(
4242
-        EEM_Base $entity_model,
4243
-        $entity_ID,
4244
-        string $action,
4245
-        string $delete_column,
4246
-        ?callable $callback = null
4247
-    ): bool {
4248
-        $entity_ID = absint($entity_ID);
4249
-        if (! $entity_ID) {
4250
-            $this->trashRestoreDeleteError($action, $entity_model);
4251
-        }
4252
-        $result = 0;
4253
-        try {
4254
-            $entity = $entity_model->get_one_by_ID($entity_ID);
4255
-            if (! $entity instanceof EE_Base_Class) {
4256
-                throw new DomainException(
4257
-                    sprintf(
4258
-                        esc_html__(
4259
-                            'Missing or invalid %1$s entity with ID of "%2$s" returned from db.',
4260
-                            'event_espresso'
4261
-                        ),
4262
-                        str_replace('EEM_', '', $entity_model->get_this_model_name()),
4263
-                        $entity_ID
4264
-                    )
4265
-                );
4266
-            }
4267
-            switch ($action) {
4268
-                case EE_Admin_List_Table::ACTION_DELETE:
4269
-                    $result = (bool) $entity->delete_permanently();
4270
-                    break;
4271
-                case EE_Admin_List_Table::ACTION_RESTORE:
4272
-                    $result = $entity->delete_or_restore(false);
4273
-                    break;
4274
-                case EE_Admin_List_Table::ACTION_TRASH:
4275
-                    $result = $entity->delete_or_restore();
4276
-                    break;
4277
-            }
4278
-        } catch (Exception $exception) {
4279
-            $this->trashRestoreDeleteError($action, $entity_model, $exception);
4280
-        }
4281
-        if (is_callable($callback)) {
4282
-            call_user_func_array($callback, [$entity_model, $entity_ID, $action, $result, $delete_column]);
4283
-        }
4284
-        return $result;
4285
-    }
4286
-
4287
-
4288
-    /**
4289
-     * @param EEM_Base $entity_model
4290
-     * @param string   $delete_column
4291
-     * @since 4.10.30.p
4292
-     */
4293
-    private function validateDeleteColumn(EEM_Base $entity_model, string $delete_column)
4294
-    {
4295
-        if (empty($delete_column)) {
4296
-            throw new DomainException(
4297
-                sprintf(
4298
-                    esc_html__(
4299
-                        'You need to specify the name of the "delete column" on the %2$s model, in order to trash or restore an entity.',
4300
-                        'event_espresso'
4301
-                    ),
4302
-                    $entity_model->get_this_model_name()
4303
-                )
4304
-            );
4305
-        }
4306
-        if (! $entity_model->has_field($delete_column)) {
4307
-            throw new DomainException(
4308
-                sprintf(
4309
-                    esc_html__(
4310
-                        'The %1$s field does not exist on the %2$s model.',
4311
-                        'event_espresso'
4312
-                    ),
4313
-                    $delete_column,
4314
-                    $entity_model->get_this_model_name()
4315
-                )
4316
-            );
4317
-        }
4318
-    }
4319
-
4320
-
4321
-    /**
4322
-     * @param EEM_Base       $entity_model
4323
-     * @param Exception|null $exception
4324
-     * @param string         $action
4325
-     * @since 4.10.30.p
4326
-     */
4327
-    private function trashRestoreDeleteError(string $action, EEM_Base $entity_model, Exception $exception = null)
4328
-    {
4329
-        if ($exception instanceof Exception) {
4330
-            throw new RuntimeException(
4331
-                sprintf(
4332
-                    esc_html__(
4333
-                        'Could not %1$s the %2$s because the following error occurred: %3$s',
4334
-                        'event_espresso'
4335
-                    ),
4336
-                    $action,
4337
-                    $entity_model->get_this_model_name(),
4338
-                    $exception->getMessage()
4339
-                )
4340
-            );
4341
-        }
4342
-        throw new RuntimeException(
4343
-            sprintf(
4344
-                esc_html__(
4345
-                    'Could not %1$s the %2$s because an invalid ID was received.',
4346
-                    'event_espresso'
4347
-                ),
4348
-                $action,
4349
-                $entity_model->get_this_model_name()
4350
-            )
4351
-        );
4352
-    }
3346
+		// add nonce
3347
+		$nonce                                             =
3348
+			wp_nonce_field($route . '_nonce', $route . '_nonce', false, false);
3349
+		$this->_template_args['before_admin_page_content'] .= "\n\t" . $nonce;
3350
+		// add REQUIRED form action
3351
+		$hidden_fields = [
3352
+			'action' => ['type' => 'hidden', 'value' => $route],
3353
+		];
3354
+		// merge arrays
3355
+		$hidden_fields = is_array($additional_hidden_fields)
3356
+			? array_merge($hidden_fields, $additional_hidden_fields)
3357
+			: $hidden_fields;
3358
+		// generate form fields
3359
+		$form_fields = $this->_generate_admin_form_fields($hidden_fields, 'array');
3360
+		// add fields to form
3361
+		foreach ((array) $form_fields as $form_field) {
3362
+			$this->_template_args['before_admin_page_content'] .= "\n\t" . $form_field['field'];
3363
+		}
3364
+		// close form
3365
+		$this->_template_args['after_admin_page_content'] = '</form>';
3366
+	}
3367
+
3368
+
3369
+	/**
3370
+	 * Public Wrapper for _redirect_after_action() method since its
3371
+	 * discovered it would be useful for external code to have access.
3372
+	 *
3373
+	 * @param bool|int $success
3374
+	 * @param string   $what
3375
+	 * @param string   $action_desc
3376
+	 * @param array    $query_args
3377
+	 * @param bool     $override_overwrite
3378
+	 * @throws EE_Error
3379
+	 * @see   EE_Admin_Page::_redirect_after_action() for params.
3380
+	 * @since 4.5.0
3381
+	 */
3382
+	public function redirect_after_action(
3383
+		$success = false,
3384
+		string $what = 'item',
3385
+		string $action_desc = 'processed',
3386
+		array $query_args = [],
3387
+		bool $override_overwrite = false
3388
+	) {
3389
+		$this->_redirect_after_action(
3390
+			$success,
3391
+			$what,
3392
+			$action_desc,
3393
+			$query_args,
3394
+			$override_overwrite
3395
+		);
3396
+	}
3397
+
3398
+
3399
+	/**
3400
+	 * Helper method for merging existing request data with the returned redirect url.
3401
+	 * This is typically used for redirects after an action so that if the original view was a filtered view those
3402
+	 * filters are still applied.
3403
+	 *
3404
+	 * @param array $new_route_data
3405
+	 * @return array
3406
+	 */
3407
+	protected function mergeExistingRequestParamsWithRedirectArgs(array $new_route_data): array
3408
+	{
3409
+		foreach ($this->request->requestParams() as $ref => $value) {
3410
+			// unset nonces
3411
+			if (strpos($ref, 'nonce') !== false) {
3412
+				$this->request->unSetRequestParam($ref);
3413
+				continue;
3414
+			}
3415
+			// urlencode values.
3416
+			$value = is_array($value) ? array_map('urlencode', $value) : urlencode($value);
3417
+			$this->request->setRequestParam($ref, $value);
3418
+		}
3419
+		return array_merge($this->request->requestParams(), $new_route_data);
3420
+	}
3421
+
3422
+
3423
+	/**
3424
+	 * @param int|float|string $success            - whether success was for two or more records, or just one, or none
3425
+	 * @param string           $what               - what the action was performed on
3426
+	 * @param string           $action_desc        - what was done ie: updated, deleted, etc
3427
+	 * @param array            $query_args         - an array of query_args to be added to the URL to redirect to
3428
+	 * @param BOOL             $override_overwrite - by default all EE_Error::success messages are overwritten,
3429
+	 *                                             this allows you to override this so that they show.
3430
+	 * @return void
3431
+	 * @throws EE_Error
3432
+	 * @throws InvalidArgumentException
3433
+	 * @throws InvalidDataTypeException
3434
+	 * @throws InvalidInterfaceException
3435
+	 */
3436
+	protected function _redirect_after_action(
3437
+		$success = 0,
3438
+		string $what = 'item',
3439
+		string $action_desc = 'processed',
3440
+		array $query_args = [],
3441
+		bool $override_overwrite = false
3442
+	) {
3443
+		$notices = EE_Error::get_notices(false);
3444
+		// overwrite default success messages //BUT ONLY if overwrite not overridden
3445
+		if (! $override_overwrite || ! empty($notices['errors'])) {
3446
+			EE_Error::overwrite_success();
3447
+		}
3448
+		if (! $override_overwrite && ! empty($what) && ! empty($action_desc) && empty($notices['errors'])) {
3449
+			// how many records affected ? more than one record ? or just one ?
3450
+			EE_Error::add_success(
3451
+				sprintf(
3452
+					esc_html(
3453
+						_n(
3454
+							'The "%1$s" has been successfully %2$s.',
3455
+							'The "%1$s" have been successfully %2$s.',
3456
+							$success,
3457
+							'event_espresso'
3458
+						)
3459
+					),
3460
+					$what,
3461
+					$action_desc
3462
+				),
3463
+				__FILE__,
3464
+				__FUNCTION__,
3465
+				__LINE__
3466
+			);
3467
+		}
3468
+		// check that $query_args isn't something crazy
3469
+		$query_args = is_array($query_args) ? $query_args : [];
3470
+		/**
3471
+		 * Allow injecting actions before the query_args are modified for possible different
3472
+		 * redirections on save and close actions
3473
+		 *
3474
+		 * @param array $query_args       The original query_args array coming into the
3475
+		 *                                method.
3476
+		 * @since 4.2.0
3477
+		 */
3478
+		do_action(
3479
+			"AHEE__{$this->class_name}___redirect_after_action__before_redirect_modification_$this->_req_action",
3480
+			$query_args
3481
+		);
3482
+		// set redirect url.
3483
+		// Note if there is a "page" index in the $query_args then we go with vanilla admin.php route,
3484
+		// otherwise we go with whatever is set as the _admin_base_url
3485
+		$redirect_url = isset($query_args['page']) ? admin_url('admin.php') : $this->_admin_base_url;
3486
+		// calculate where we're going (if we have a "save and close" button pushed)
3487
+		if (
3488
+			$this->request->requestParamIsSet('save_and_close')
3489
+			&& $this->request->requestParamIsSet('save_and_close_referrer')
3490
+		) {
3491
+			// even though we have the save_and_close referrer, we need to parse the url for the action in order to generate a nonce
3492
+			$parsed_url = parse_url($this->request->getRequestParam('save_and_close_referrer', '', DataType::URL));
3493
+			// regenerate query args array from referrer URL
3494
+			parse_str($parsed_url['query'], $query_args);
3495
+			// correct page and action will be in the query args now
3496
+			$redirect_url = admin_url('admin.php');
3497
+		}
3498
+		// merge any default query_args set in _default_route_query_args property
3499
+		if (! empty($this->_default_route_query_args) && ! $this->_is_UI_request) {
3500
+			$args_to_merge = [];
3501
+			foreach ($this->_default_route_query_args as $query_param => $query_value) {
3502
+				// is there a wp_referer array in our _default_route_query_args property?
3503
+				if ($query_param === 'wp_referer') {
3504
+					$query_value = (array) $query_value;
3505
+					foreach ($query_value as $reference => $value) {
3506
+						if (strpos($reference, 'nonce') !== false) {
3507
+							continue;
3508
+						}
3509
+						// finally we will override any arguments in the referer with
3510
+						// what might be set on the _default_route_query_args array.
3511
+						if (isset($this->_default_route_query_args[ $reference ])) {
3512
+							$args_to_merge[ $reference ] = urlencode($this->_default_route_query_args[ $reference ]);
3513
+						} else {
3514
+							$args_to_merge[ $reference ] = urlencode($value);
3515
+						}
3516
+					}
3517
+					continue;
3518
+				}
3519
+				$args_to_merge[ $query_param ] = $query_value;
3520
+			}
3521
+			// now let's merge these arguments but override with what was specifically sent in to the
3522
+			// redirect.
3523
+			$query_args = array_merge($args_to_merge, $query_args);
3524
+		}
3525
+		$this->_process_notices($query_args);
3526
+		// generate redirect url
3527
+		// if redirecting to anything other than the main page, add a nonce
3528
+		if (isset($query_args['action'])) {
3529
+			// manually generate wp_nonce and merge that with the query vars
3530
+			// becuz the wp_nonce_url function wrecks havoc on some vars
3531
+			$query_args['_wpnonce'] = wp_create_nonce($query_args['action'] . '_nonce');
3532
+		}
3533
+		// we're adding some hooks and filters in here for processing any things just before redirects
3534
+		// (example: an admin page has done an insert or update and we want to run something after that).
3535
+		do_action('AHEE_redirect_' . $this->class_name . $this->_req_action, $query_args);
3536
+		$redirect_url = apply_filters(
3537
+			'FHEE_redirect_' . $this->class_name . $this->_req_action,
3538
+			EE_Admin_Page::add_query_args_and_nonce($query_args, $redirect_url),
3539
+			$query_args
3540
+		);
3541
+		// check if we're doing ajax.  If we are then lets just return the results and js can handle how it wants.
3542
+		if ($this->request->isAjax()) {
3543
+			$default_data                    = [
3544
+				'close'        => true,
3545
+				'redirect_url' => $redirect_url,
3546
+				'where'        => 'main',
3547
+				'what'         => 'append',
3548
+			];
3549
+			$this->_template_args['success'] = $success;
3550
+			$this->_template_args['data']    = ! empty($this->_template_args['data']) ? array_merge(
3551
+				$default_data,
3552
+				$this->_template_args['data']
3553
+			) : $default_data;
3554
+			$this->_return_json();
3555
+		}
3556
+		wp_safe_redirect($redirect_url);
3557
+		exit();
3558
+	}
3559
+
3560
+
3561
+	/**
3562
+	 * process any notices before redirecting (or returning ajax request)
3563
+	 * This method sets the $this->_template_args['notices'] attribute;
3564
+	 *
3565
+	 * @param array $query_args         any query args that need to be used for notice transient ('action')
3566
+	 * @param bool  $skip_route_verify  This is typically used when we are processing notices REALLY early and
3567
+	 *                                  page_routes haven't been defined yet.
3568
+	 * @param bool  $sticky_notices     This is used to flag that regardless of whether this is doing_ajax or not, we
3569
+	 *                                  still save a transient for the notice.
3570
+	 * @return void
3571
+	 * @throws EE_Error
3572
+	 * @throws InvalidArgumentException
3573
+	 * @throws InvalidDataTypeException
3574
+	 * @throws InvalidInterfaceException
3575
+	 */
3576
+	protected function _process_notices(
3577
+		array $query_args = [],
3578
+		bool $skip_route_verify = false,
3579
+		bool $sticky_notices = true
3580
+	) {
3581
+		// first let's set individual error properties if doing_ajax and the properties aren't already set.
3582
+		if ($this->request->isAjax()) {
3583
+			$notices = EE_Error::get_notices(false);
3584
+			if (empty($this->_template_args['success'])) {
3585
+				$this->_template_args['success'] = $notices['success'] ?? false;
3586
+			}
3587
+			if (empty($this->_template_args['errors'])) {
3588
+				$this->_template_args['errors'] = $notices['errors'] ?? false;
3589
+			}
3590
+			if (empty($this->_template_args['attention'])) {
3591
+				$this->_template_args['attention'] = $notices['attention'] ?? false;
3592
+			}
3593
+		}
3594
+		$this->_template_args['notices'] = EE_Error::get_notices();
3595
+		// IF this isn't ajax we need to create a transient for the notices using the route (however, overridden if $sticky_notices == true)
3596
+		if (! $this->request->isAjax() || $sticky_notices) {
3597
+			$route = $query_args['action'] ?? 'default';
3598
+			$this->_add_transient(
3599
+				$route,
3600
+				(array) $this->_template_args['notices'],
3601
+				true,
3602
+				$skip_route_verify
3603
+			);
3604
+		}
3605
+	}
3606
+
3607
+
3608
+	/**
3609
+	 * get_action_link_or_button
3610
+	 * returns the button html for adding, editing, or deleting an item (depending on given type)
3611
+	 *
3612
+	 * @param string $action        use this to indicate which action the url is generated with.
3613
+	 * @param string $type          accepted strings must be defined in the $_labels['button'] array(as the key)
3614
+	 *                              property.
3615
+	 * @param array  $extra_request if the button requires extra params you can include them in $key=>$value pairs.
3616
+	 * @param string $class         Use this to give the class for the button. Defaults to 'button--primary'
3617
+	 * @param string $base_url      If this is not provided
3618
+	 *                              the _admin_base_url will be used as the default for the button base_url.
3619
+	 *                              Otherwise this value will be used.
3620
+	 * @param bool   $exclude_nonce If true then no nonce will be in the generated button link.
3621
+	 * @return string
3622
+	 * @throws InvalidArgumentException
3623
+	 * @throws InvalidInterfaceException
3624
+	 * @throws InvalidDataTypeException
3625
+	 * @throws EE_Error
3626
+	 */
3627
+	public function get_action_link_or_button(
3628
+		string $action,
3629
+		string $type = 'add',
3630
+		array $extra_request = [],
3631
+		string $class = 'button button--primary',
3632
+		string $base_url = '',
3633
+		bool $exclude_nonce = false
3634
+	): string {
3635
+		// first let's validate the action (if $base_url is FALSE otherwise validation will happen further along)
3636
+		if (empty($base_url) && ! isset($this->_page_routes[ $action ])) {
3637
+			throw new EE_Error(
3638
+				sprintf(
3639
+					esc_html__(
3640
+						'There is no page route for given action for the button.  This action was given: %s',
3641
+						'event_espresso'
3642
+					),
3643
+					$action
3644
+				)
3645
+			);
3646
+		}
3647
+		if (! isset($this->_labels['buttons'][ $type ])) {
3648
+			throw new EE_Error(
3649
+				sprintf(
3650
+					esc_html__(
3651
+						'There is no label for the given button type (%s). Labels are set in the <code>_page_config</code> property.',
3652
+						'event_espresso'
3653
+					),
3654
+					$type
3655
+				)
3656
+			);
3657
+		}
3658
+		// finally check user access for this button.
3659
+		$has_access = $this->check_user_access($action, true);
3660
+		if (! $has_access) {
3661
+			return '';
3662
+		}
3663
+		$_base_url  = ! $base_url ? $this->_admin_base_url : $base_url;
3664
+		$query_args = [
3665
+			'action' => $action,
3666
+		];
3667
+		// merge extra_request args but make sure our original action takes precedence and doesn't get overwritten.
3668
+		if (! empty($extra_request)) {
3669
+			$query_args = array_merge($extra_request, $query_args);
3670
+		}
3671
+		$url = EE_Admin_Page::add_query_args_and_nonce($query_args, $_base_url, false, $exclude_nonce);
3672
+		return EEH_Template::get_button_or_link($url, $this->_labels['buttons'][ $type ], $class);
3673
+	}
3674
+
3675
+
3676
+	/**
3677
+	 * _per_page_screen_option
3678
+	 * Utility function for adding in a per_page_option in the screen_options_dropdown.
3679
+	 *
3680
+	 * @return void
3681
+	 * @throws InvalidArgumentException
3682
+	 * @throws InvalidInterfaceException
3683
+	 * @throws InvalidDataTypeException
3684
+	 */
3685
+	protected function _per_page_screen_option()
3686
+	{
3687
+		$option = 'per_page';
3688
+		$args   = [
3689
+			'label'   => apply_filters(
3690
+				'FHEE__EE_Admin_Page___per_page_screen_options___label',
3691
+				$this->_admin_page_title,
3692
+				$this
3693
+			),
3694
+			'default' => (int) apply_filters(
3695
+				'FHEE__EE_Admin_Page___per_page_screen_options__default',
3696
+				20
3697
+			),
3698
+			'option'  => $this->_current_page . '_' . $this->_current_view . '_per_page',
3699
+		];
3700
+		// ONLY add the screen option if the user has access to it.
3701
+		if ($this->check_user_access($this->_current_view, true)) {
3702
+			add_screen_option($option, $args);
3703
+		}
3704
+	}
3705
+
3706
+
3707
+	/**
3708
+	 * set_per_page_screen_option
3709
+	 * All this does is make sure that WordPress saves any per_page screen options (if set) for the current page.
3710
+	 * we have to do this rather than running inside the 'set-screen-options' hook because it runs earlier than
3711
+	 * admin_menu.
3712
+	 *
3713
+	 * @return void
3714
+	 */
3715
+	private function _set_per_page_screen_options()
3716
+	{
3717
+		if ($this->request->requestParamIsSet('wp_screen_options')) {
3718
+			check_admin_referer('screen-options-nonce', 'screenoptionnonce');
3719
+			if (! $user = wp_get_current_user()) {
3720
+				return;
3721
+			}
3722
+			$option = $this->request->getRequestParam('wp_screen_options[option]', '', DataType::KEY);
3723
+			if (! $option) {
3724
+				return;
3725
+			}
3726
+			$value      = $this->request->getRequestParam('wp_screen_options[value]', 0, DataType::INT);
3727
+			$map_option = $option;
3728
+			$option     = str_replace('-', '_', $option);
3729
+			switch ($map_option) {
3730
+				case $this->_current_page . '_' . $this->_current_view . '_per_page':
3731
+					$max_value = apply_filters(
3732
+						'FHEE__EE_Admin_Page___set_per_page_screen_options__max_value',
3733
+						999,
3734
+						$this->_current_page,
3735
+						$this->_current_view
3736
+					);
3737
+					if ($value < 1) {
3738
+						return;
3739
+					}
3740
+					$value = min($value, $max_value);
3741
+					break;
3742
+				default:
3743
+					$value = apply_filters(
3744
+						'FHEE__EE_Admin_Page___set_per_page_screen_options__value',
3745
+						false,
3746
+						$option,
3747
+						$value
3748
+					);
3749
+					if (false === $value) {
3750
+						return;
3751
+					}
3752
+					break;
3753
+			}
3754
+			update_user_meta($user->ID, $option, $value);
3755
+			wp_safe_redirect(remove_query_arg(['pagenum', 'apage', 'paged'], wp_get_referer()));
3756
+			exit;
3757
+		}
3758
+	}
3759
+
3760
+
3761
+	/**
3762
+	 * This just allows for setting the $_template_args property if it needs to be set outside the object
3763
+	 *
3764
+	 * @param array $data array that will be assigned to template args.
3765
+	 */
3766
+	public function set_template_args(array $data)
3767
+	{
3768
+		$this->_template_args = array_merge($this->_template_args, $data);
3769
+	}
3770
+
3771
+
3772
+	public function setAdminPageTitle(string $title)
3773
+	{
3774
+		$this->_admin_page_title = sanitize_text_field($title);
3775
+	}
3776
+
3777
+
3778
+	/**
3779
+	 * This makes available the WP transient system for temporarily moving data between routes
3780
+	 *
3781
+	 * @param string $route             the route that should receive the transient
3782
+	 * @param array  $data              the data that gets sent
3783
+	 * @param bool   $notices           If this is for notices then we use this to indicate so, otherwise it's just a
3784
+	 *                                  normal route transient.
3785
+	 * @param bool   $skip_route_verify Used to indicate we want to skip route verification.  This is usually ONLY used
3786
+	 *                                  when we are adding a transient before page_routes have been defined.
3787
+	 * @return void
3788
+	 * @throws EE_Error
3789
+	 */
3790
+	protected function _add_transient(
3791
+		string $route,
3792
+		array $data,
3793
+		bool $notices = false,
3794
+		bool $skip_route_verify = false
3795
+	) {
3796
+		$user_id = get_current_user_id();
3797
+		if (! $skip_route_verify) {
3798
+			$this->_verify_route($route);
3799
+		}
3800
+		// now let's set the string for what kind of transient we're setting
3801
+		$transient = $notices ? "ee_rte_n_tx_{$route}_$user_id" : "rte_tx_{$route}_$user_id";
3802
+		$data      = $notices ? ['notices' => $data] : $data;
3803
+		// is there already a transient for this route?  If there is then let's ADD to that transient
3804
+		$existing = is_multisite() && is_network_admin()
3805
+			? get_site_transient($transient)
3806
+			: get_transient($transient);
3807
+		if ($existing) {
3808
+			$data = array_merge($data, (array) $existing);
3809
+		}
3810
+		if (is_multisite() && is_network_admin()) {
3811
+			set_site_transient($transient, $data, 8);
3812
+		} else {
3813
+			set_transient($transient, $data, 8);
3814
+		}
3815
+	}
3816
+
3817
+
3818
+	/**
3819
+	 * this retrieves the temporary transient that has been set for moving data between routes.
3820
+	 *
3821
+	 * @param bool   $notices true we get notices transient. False we just return normal route transient
3822
+	 * @param string $route
3823
+	 * @return mixed data
3824
+	 */
3825
+	protected function _get_transient(bool $notices = false, string $route = '')
3826
+	{
3827
+		$user_id   = get_current_user_id();
3828
+		$route     = ! $route ? $this->_req_action : $route;
3829
+		$transient = $notices
3830
+			? 'ee_rte_n_tx_' . $route . '_' . $user_id
3831
+			: 'rte_tx_' . $route . '_' . $user_id;
3832
+		$data      = is_multisite() && is_network_admin()
3833
+			? get_site_transient($transient)
3834
+			: get_transient($transient);
3835
+		// delete transient after retrieval (just in case it hasn't expired);
3836
+		if (is_multisite() && is_network_admin()) {
3837
+			delete_site_transient($transient);
3838
+		} else {
3839
+			delete_transient($transient);
3840
+		}
3841
+		return $notices && isset($data['notices']) ? $data['notices'] : $data;
3842
+	}
3843
+
3844
+
3845
+	/**
3846
+	 * The purpose of this method is just to run garbage collection on any EE transients that might have expired but
3847
+	 * would not be called later. This will be assigned to run on a specific EE Admin page. (place the method in the
3848
+	 * default route callback on the EE_Admin page you want it run.)
3849
+	 *
3850
+	 * @return void
3851
+	 */
3852
+	protected function _transient_garbage_collection()
3853
+	{
3854
+		global $wpdb;
3855
+		// retrieve all existing transients
3856
+		$query =
3857
+			"SELECT option_name FROM $wpdb->options WHERE option_name LIKE '%rte_tx_%' OR option_name LIKE '%rte_n_tx_%'";
3858
+		if ($results = $wpdb->get_results($query)) {
3859
+			foreach ($results as $result) {
3860
+				$transient = str_replace('_transient_', '', $result->option_name);
3861
+				get_transient($transient);
3862
+				if (is_multisite() && is_network_admin()) {
3863
+					get_site_transient($transient);
3864
+				}
3865
+			}
3866
+		}
3867
+	}
3868
+
3869
+
3870
+	/**
3871
+	 * get_view
3872
+	 *
3873
+	 * @return string content of _view property
3874
+	 */
3875
+	public function get_view(): string
3876
+	{
3877
+		return $this->_view;
3878
+	}
3879
+
3880
+
3881
+	/**
3882
+	 * getter for the protected $_views property
3883
+	 *
3884
+	 * @return array
3885
+	 */
3886
+	public function get_views(): array
3887
+	{
3888
+		return $this->_views;
3889
+	}
3890
+
3891
+
3892
+	/**
3893
+	 * @param array $views
3894
+	 * @return void
3895
+	 * @since 5.0.13.p
3896
+	 */
3897
+	public function updateViews(array $views)
3898
+	{
3899
+		$this->_views = array_merge($this->_views, $views);
3900
+	}
3901
+
3902
+
3903
+	/**
3904
+    /**
3905
+	 * get_current_page
3906
+	 *
3907
+	 * @return string _current_page property value
3908
+	 */
3909
+	public function get_current_page(): string
3910
+	{
3911
+		return $this->_current_page;
3912
+	}
3913
+
3914
+
3915
+	/**
3916
+	 * get_current_view
3917
+	 *
3918
+	 * @return string _current_view property value
3919
+	 */
3920
+	public function get_current_view(): string
3921
+	{
3922
+		return $this->_current_view;
3923
+	}
3924
+
3925
+
3926
+	/**
3927
+	 * get_current_screen
3928
+	 *
3929
+	 * @return object The current WP_Screen object
3930
+	 */
3931
+	public function get_current_screen()
3932
+	{
3933
+		return $this->_current_screen;
3934
+	}
3935
+
3936
+
3937
+	/**
3938
+	 * get_current_page_view_url
3939
+	 *
3940
+	 * @return string This returns the url for the current_page_view.
3941
+	 */
3942
+	public function get_current_page_view_url(): string
3943
+	{
3944
+		return $this->_current_page_view_url;
3945
+	}
3946
+
3947
+
3948
+	/**
3949
+	 * just returns the Request
3950
+	 *
3951
+	 * @return RequestInterface
3952
+	 */
3953
+	public function get_request(): ?RequestInterface
3954
+	{
3955
+		return $this->request;
3956
+	}
3957
+
3958
+
3959
+	/**
3960
+	 * just returns the _req_data property
3961
+	 *
3962
+	 * @return array
3963
+	 */
3964
+	public function get_request_data(): array
3965
+	{
3966
+		return $this->request->requestParams();
3967
+	}
3968
+
3969
+
3970
+	/**
3971
+	 * returns the _req_data protected property
3972
+	 *
3973
+	 * @return string
3974
+	 */
3975
+	public function get_req_action(): string
3976
+	{
3977
+		return $this->_req_action;
3978
+	}
3979
+
3980
+
3981
+	/**
3982
+	 * @return bool  value of $_is_caf property
3983
+	 */
3984
+	public function is_caf(): bool
3985
+	{
3986
+		return $this->_is_caf;
3987
+	}
3988
+
3989
+
3990
+	/**
3991
+	 * @return array
3992
+	 */
3993
+	public function default_espresso_metaboxes(): array
3994
+	{
3995
+		return $this->_default_espresso_metaboxes;
3996
+	}
3997
+
3998
+
3999
+	/**
4000
+	 * @return string
4001
+	 */
4002
+	public function admin_base_url(): string
4003
+	{
4004
+		return $this->_admin_base_url;
4005
+	}
4006
+
4007
+
4008
+	/**
4009
+	 * @return string
4010
+	 */
4011
+	public function wp_page_slug(): string
4012
+	{
4013
+		return $this->_wp_page_slug;
4014
+	}
4015
+
4016
+
4017
+	/**
4018
+	 * updates  espresso configuration settings
4019
+	 *
4020
+	 * @param string                   $tab
4021
+	 * @param EE_Config_Base|EE_Config $config
4022
+	 * @param string                   $file file where error occurred
4023
+	 * @param string                   $func function  where error occurred
4024
+	 * @param string                   $line line no where error occurred
4025
+	 * @return bool
4026
+	 * @throws EE_Error
4027
+	 * @throws ReflectionException
4028
+	 */
4029
+	protected function _update_espresso_configuration(
4030
+		string $tab,
4031
+		$config,
4032
+		string $file = '',
4033
+		string $func = '',
4034
+		string $line = ''
4035
+	): bool {
4036
+		// remove any options that are NOT going to be saved with the config settings.
4037
+		if (isset($config->core->ee_ueip_optin)) {
4038
+			// TODO: remove the following two lines and make sure values are migrated from 3.1
4039
+			update_option('ee_ueip_optin', $config->core->ee_ueip_optin);
4040
+			update_option('ee_ueip_has_notified', true);
4041
+		}
4042
+		// and save it (note we're also doing the network save here)
4043
+		$net_saved    = ! is_main_site() || EE_Network_Config::instance()->update_config(false, false);
4044
+		$config_saved = EE_Config::instance()->update_espresso_config(false, false);
4045
+		if ($config_saved && $net_saved) {
4046
+			EE_Error::add_success(sprintf(esc_html__('"%s" have been successfully updated.', 'event_espresso'), $tab));
4047
+			return true;
4048
+		}
4049
+		EE_Error::add_error(
4050
+			sprintf(esc_html__('The "%s" were not updated.', 'event_espresso'), $tab),
4051
+			$file,
4052
+			$func,
4053
+			$line
4054
+		);
4055
+		return false;
4056
+	}
4057
+
4058
+
4059
+	/**
4060
+	 * Returns an array to be used for EE_FOrm_Fields.helper.php's select_input as the $values argument.
4061
+	 *
4062
+	 * @return array
4063
+	 */
4064
+	public function get_yes_no_values(): array
4065
+	{
4066
+		return $this->_yes_no_values;
4067
+	}
4068
+
4069
+
4070
+	/**
4071
+	 * @return string
4072
+	 * @throws ReflectionException
4073
+	 * @since 5.0.0.p
4074
+	 */
4075
+	protected function _get_dir(): string
4076
+	{
4077
+		$reflector = new ReflectionClass($this->class_name);
4078
+		return dirname($reflector->getFileName());
4079
+	}
4080
+
4081
+
4082
+	/**
4083
+	 * A helper for getting a "next link".
4084
+	 *
4085
+	 * @param string $url   The url to link to
4086
+	 * @param string $class The class to use.
4087
+	 * @return string
4088
+	 */
4089
+	protected function _next_link(string $url, string $class = 'dashicons dashicons-arrow-right'): string
4090
+	{
4091
+		return '<a class="' . $class . '" href="' . $url . '"></a>';
4092
+	}
4093
+
4094
+
4095
+	/**
4096
+	 * A helper for getting a "previous link".
4097
+	 *
4098
+	 * @param string $url   The url to link to
4099
+	 * @param string $class The class to use.
4100
+	 * @return string
4101
+	 */
4102
+	protected function _previous_link(string $url, string $class = 'dashicons dashicons-arrow-left'): string
4103
+	{
4104
+		return '<a class="' . $class . '" href="' . $url . '"></a>';
4105
+	}
4106
+
4107
+
4108
+
4109
+
4110
+
4111
+
4112
+
4113
+	// below are some messages related methods that should be available across the EE_Admin system.  Note, these methods are NOT page specific
4114
+
4115
+
4116
+	/**
4117
+	 * This processes a request to resend a registration and assumes we have a _REG_ID for doing so. So if the caller
4118
+	 * knows that the _REG_ID isn't in the req_data array but CAN obtain it, the caller should ADD the _REG_ID to the
4119
+	 * _req_data array.
4120
+	 *
4121
+	 * @return bool success/fail
4122
+	 * @throws EE_Error
4123
+	 * @throws InvalidArgumentException
4124
+	 * @throws ReflectionException
4125
+	 * @throws InvalidDataTypeException
4126
+	 * @throws InvalidInterfaceException
4127
+	 */
4128
+	protected function _process_resend_registration(): bool
4129
+	{
4130
+		$this->_template_args['success'] = EED_Messages::process_resend($this->request->requestParams());
4131
+		do_action(
4132
+			'AHEE__EE_Admin_Page___process_resend_registration',
4133
+			$this->_template_args['success'],
4134
+			$this->request->requestParams()
4135
+		);
4136
+		return $this->_template_args['success'];
4137
+	}
4138
+
4139
+
4140
+	/**
4141
+	 * This automatically processes any payment message notifications when manual payment has been applied.
4142
+	 *
4143
+	 * @param EE_Payment $payment
4144
+	 * @return bool success/fail
4145
+	 */
4146
+	protected function _process_payment_notification(EE_Payment $payment): bool
4147
+	{
4148
+		add_filter('FHEE__EE_Payment_Processor__process_registration_payments__display_notifications', '__return_true');
4149
+		do_action('AHEE__EE_Admin_Page___process_admin_payment_notification', $payment);
4150
+		$this->_template_args['success'] = apply_filters(
4151
+			'FHEE__EE_Admin_Page___process_admin_payment_notification__success',
4152
+			false,
4153
+			$payment
4154
+		);
4155
+		return $this->_template_args['success'];
4156
+	}
4157
+
4158
+
4159
+	/**
4160
+	 * @param EEM_Base      $entity_model
4161
+	 * @param string        $entity_PK_name name of the primary key field used as a request param, ie: id, ID, etc
4162
+	 * @param string        $action         one of the EE_Admin_List_Table::ACTION_* constants: delete, restore, trash
4163
+	 * @param string        $delete_column  name of the field that denotes whether entity is trashed
4164
+	 * @param callable|null $callback       called after entity is trashed, restored, or deleted
4165
+	 * @return int|float
4166
+	 * @throws EE_Error
4167
+	 */
4168
+	protected function trashRestoreDeleteEntities(
4169
+		EEM_Base $entity_model,
4170
+		string $entity_PK_name,
4171
+		string $action = EE_Admin_List_Table::ACTION_DELETE,
4172
+		string $delete_column = '',
4173
+		?callable $callback = null
4174
+	) {
4175
+		$entity_PK      = $entity_model->get_primary_key_field();
4176
+		$entity_PK_name = $entity_PK_name ?: $entity_PK->get_name();
4177
+		$entity_PK_type = $this->resolveEntityFieldDataType($entity_PK);
4178
+		// grab ID if deleting a single entity
4179
+		if ($this->request->requestParamIsSet($entity_PK_name)) {
4180
+			$ID = $this->request->getRequestParam($entity_PK_name, 0, $entity_PK_type);
4181
+			return $this->trashRestoreDeleteEntity($entity_model, $ID, $action, $delete_column, $callback) ? 1 : 0;
4182
+		}
4183
+		// or grab checkbox array if bulk deleting
4184
+		$checkboxes = $this->request->getRequestParam('checkbox', [], $entity_PK_type, true);
4185
+		if (empty($checkboxes)) {
4186
+			return 0;
4187
+		}
4188
+		$success = 0;
4189
+		$IDs     = array_keys($checkboxes);
4190
+		// cycle thru bulk action checkboxes
4191
+		foreach ($IDs as $ID) {
4192
+			// increment $success
4193
+			if ($this->trashRestoreDeleteEntity($entity_model, $ID, $action, $delete_column, $callback)) {
4194
+				$success++;
4195
+			}
4196
+		}
4197
+		$count = (int) count($checkboxes);
4198
+		// if multiple entities were deleted successfully, then $deleted will be full count of deletions,
4199
+		// otherwise it will be a fraction of ( actual deletions / total entities to be deleted )
4200
+		return $success === $count ? $count : $success / $count;
4201
+	}
4202
+
4203
+
4204
+	/**
4205
+	 * @param EE_Primary_Key_Field_Base $entity_PK
4206
+	 * @return string
4207
+	 * @throws EE_Error
4208
+	 * @since   4.10.30.p
4209
+	 */
4210
+	private function resolveEntityFieldDataType(EE_Primary_Key_Field_Base $entity_PK): string
4211
+	{
4212
+		$entity_PK_type = $entity_PK->dataType();
4213
+		switch ($entity_PK_type) {
4214
+			case DataType::BOOL;
4215
+			case DataType::INT;
4216
+			case DataType::FLOAT;
4217
+			case DataType::STRING;
4218
+				return $entity_PK_type;
4219
+		}
4220
+		throw new RuntimeException(
4221
+			sprintf(
4222
+				esc_html__(
4223
+					'"%1$s" is an invalid schema type for the %2$s primary key.',
4224
+					'event_espresso'
4225
+				),
4226
+				$entity_PK_type,
4227
+				$entity_PK->get_name()
4228
+			)
4229
+		);
4230
+	}
4231
+
4232
+
4233
+	/**
4234
+	 * @param EEM_Base      $entity_model
4235
+	 * @param int|string    $entity_ID
4236
+	 * @param string        $action        one of the EE_Admin_List_Table::ACTION_* constants: delete, restore, trash
4237
+	 * @param string        $delete_column name of the field that denotes whether entity is trashed
4238
+	 * @param callable|null $callback      called after entity is trashed, restored, or deleted
4239
+	 * @return bool
4240
+	 */
4241
+	protected function trashRestoreDeleteEntity(
4242
+		EEM_Base $entity_model,
4243
+		$entity_ID,
4244
+		string $action,
4245
+		string $delete_column,
4246
+		?callable $callback = null
4247
+	): bool {
4248
+		$entity_ID = absint($entity_ID);
4249
+		if (! $entity_ID) {
4250
+			$this->trashRestoreDeleteError($action, $entity_model);
4251
+		}
4252
+		$result = 0;
4253
+		try {
4254
+			$entity = $entity_model->get_one_by_ID($entity_ID);
4255
+			if (! $entity instanceof EE_Base_Class) {
4256
+				throw new DomainException(
4257
+					sprintf(
4258
+						esc_html__(
4259
+							'Missing or invalid %1$s entity with ID of "%2$s" returned from db.',
4260
+							'event_espresso'
4261
+						),
4262
+						str_replace('EEM_', '', $entity_model->get_this_model_name()),
4263
+						$entity_ID
4264
+					)
4265
+				);
4266
+			}
4267
+			switch ($action) {
4268
+				case EE_Admin_List_Table::ACTION_DELETE:
4269
+					$result = (bool) $entity->delete_permanently();
4270
+					break;
4271
+				case EE_Admin_List_Table::ACTION_RESTORE:
4272
+					$result = $entity->delete_or_restore(false);
4273
+					break;
4274
+				case EE_Admin_List_Table::ACTION_TRASH:
4275
+					$result = $entity->delete_or_restore();
4276
+					break;
4277
+			}
4278
+		} catch (Exception $exception) {
4279
+			$this->trashRestoreDeleteError($action, $entity_model, $exception);
4280
+		}
4281
+		if (is_callable($callback)) {
4282
+			call_user_func_array($callback, [$entity_model, $entity_ID, $action, $result, $delete_column]);
4283
+		}
4284
+		return $result;
4285
+	}
4286
+
4287
+
4288
+	/**
4289
+	 * @param EEM_Base $entity_model
4290
+	 * @param string   $delete_column
4291
+	 * @since 4.10.30.p
4292
+	 */
4293
+	private function validateDeleteColumn(EEM_Base $entity_model, string $delete_column)
4294
+	{
4295
+		if (empty($delete_column)) {
4296
+			throw new DomainException(
4297
+				sprintf(
4298
+					esc_html__(
4299
+						'You need to specify the name of the "delete column" on the %2$s model, in order to trash or restore an entity.',
4300
+						'event_espresso'
4301
+					),
4302
+					$entity_model->get_this_model_name()
4303
+				)
4304
+			);
4305
+		}
4306
+		if (! $entity_model->has_field($delete_column)) {
4307
+			throw new DomainException(
4308
+				sprintf(
4309
+					esc_html__(
4310
+						'The %1$s field does not exist on the %2$s model.',
4311
+						'event_espresso'
4312
+					),
4313
+					$delete_column,
4314
+					$entity_model->get_this_model_name()
4315
+				)
4316
+			);
4317
+		}
4318
+	}
4319
+
4320
+
4321
+	/**
4322
+	 * @param EEM_Base       $entity_model
4323
+	 * @param Exception|null $exception
4324
+	 * @param string         $action
4325
+	 * @since 4.10.30.p
4326
+	 */
4327
+	private function trashRestoreDeleteError(string $action, EEM_Base $entity_model, Exception $exception = null)
4328
+	{
4329
+		if ($exception instanceof Exception) {
4330
+			throw new RuntimeException(
4331
+				sprintf(
4332
+					esc_html__(
4333
+						'Could not %1$s the %2$s because the following error occurred: %3$s',
4334
+						'event_espresso'
4335
+					),
4336
+					$action,
4337
+					$entity_model->get_this_model_name(),
4338
+					$exception->getMessage()
4339
+				)
4340
+			);
4341
+		}
4342
+		throw new RuntimeException(
4343
+			sprintf(
4344
+				esc_html__(
4345
+					'Could not %1$s the %2$s because an invalid ID was received.',
4346
+					'event_espresso'
4347
+				),
4348
+				$action,
4349
+				$entity_model->get_this_model_name()
4350
+			)
4351
+		);
4352
+	}
4353 4353
 }
Please login to merge, or discard this patch.
core/admin/EE_Admin_Page_Init.core.php 2 patches
Indentation   +490 added lines, -490 removed lines patch added patch discarded remove patch
@@ -18,495 +18,495 @@
 block discarded – undo
18 18
  */
19 19
 abstract class EE_Admin_Page_Init extends EE_Base
20 20
 {
21
-    /**
22
-     * This holds the menu map object for this admin page.
23
-     */
24
-    protected ?AdminMenuItem $_menu_map           = null;
25
-
26
-    protected ?EE_Admin_Page $_loaded_page_object = null;
21
+	/**
22
+	 * This holds the menu map object for this admin page.
23
+	 */
24
+	protected ?AdminMenuItem $_menu_map           = null;
25
+
26
+	protected ?EE_Admin_Page $_loaded_page_object = null;
27 27
 
28
-    protected ?LoaderInterface $loader              = null;
29
-
30
-    protected ?RequestInterface $request             = null;
31
-
32
-    private bool $_load_page          = false;
33
-
34
-    protected bool $_routing            = false;
35
-
36
-    /**
37
-     * Menu map has a capability.  However, this allows admin pages to have separate capability requirements for menus
38
-     * and accessing pages.  If capability is NOT set, then it defaults to the menu_map capability.
39
-     *
40
-     * @var string
41
-     */
42
-    public string $capability = '';
43
-
44
-    /**
45
-     * identity properties (set in _set_defaults and _set_init_properties)
46
-     */
47
-    public string $label         = '';
48
-
49
-    protected string $_file_name    = '';
50
-
51
-    protected string $_folder_name  = '';
52
-
53
-    protected string $_folder_path  = '';
54
-
55
-    protected string $_wp_page_slug = '';
56
-
57
-    public string $hook_file     = '';
58
-
59
-    public string $menu_slug     = '';
60
-
61
-    /**
62
-     * @deprecated
63
-     */
64
-    public string $menu_label    = '';
65
-
66
-    protected array $_files_hooked = [];
67
-
68
-    protected array $_hook_paths   = [];
69
-
70
-
71
-    /**
72
-     * @throws InvalidArgumentException
73
-     * @throws InvalidDataTypeException
74
-     * @throws InvalidInterfaceException
75
-     */
76
-    public function __construct(RequestInterface $request = null)
77
-    {
78
-        $this->loader  = LoaderFactory::getLoader();
79
-        $this->request = $request instanceof RequestInterface
80
-            ? $request
81
-            : $this->loader->getShared(RequestInterface::class);
82
-        // set global defaults
83
-        $this->_set_defaults();
84
-        // set properties that are always available with objects.
85
-        $this->_set_init_properties();
86
-        // global styles/scripts across all wp admin pages
87
-        add_action('admin_enqueue_scripts', [$this, 'load_wp_global_scripts_styles'], 5);
88
-        // load initial stuff.
89
-        $this->_set_file_and_folder_name();
90
-    }
91
-
92
-
93
-    /**
94
-     * _set_init_properties
95
-     * Child classes use to set the following properties:
96
-     * $label
97
-     *
98
-     * @abstract
99
-     * @return void
100
-     */
101
-    abstract protected function _set_init_properties();
102
-
103
-
104
-    /**
105
-     * @return AdminMenuItem|null
106
-     * @since       4.4.0
107
-     * @deprecated  5.0.0.p
108
-     */
109
-    public function get_menu_map()
110
-    {
111
-        return $this->adminMenu();
112
-    }
113
-
114
-
115
-    /**
116
-     * _set_menu_map is a function that child classes use to set the menu_map property (which should be an instance of
117
-     * EE_Admin_Page_Menu_Map.  Their menu can either be EE_Admin_Page_Main_Menu or AdminMenuSubItem.
118
-     *
119
-     * @since       4.4.0
120
-     * @deprecated  5.0.0.p
121
-     */
122
-    protected function _set_menu_map()
123
-    {
124
-    }
125
-
126
-
127
-    /**
128
-     * @since   5.0.0.p
129
-     */
130
-    public function setupLegacyAdminMenuItem()
131
-    {
132
-        // will be overridden by child classes not using new system
133
-        $this->_set_menu_map();
134
-    }
135
-
136
-
137
-    /**
138
-     * Child classes should return an array of properties used to construct the AdminMenuItem
139
-     *
140
-     * @return array
141
-     * @since 5.0.0.p
142
-     */
143
-    public function getMenuProperties(): array
144
-    {
145
-        return [];
146
-    }
147
-
148
-
149
-    /**
150
-     * @param AdminMenuItem $menu
151
-     * @return void
152
-     * @since 5.0.0.p
153
-     */
154
-    public function setAdminMenu(AdminMenuItem $menu): void
155
-    {
156
-        $this->_menu_map = $menu;
157
-    }
158
-
159
-
160
-    /**
161
-     * returns the menu map for this admin page
162
-     *
163
-     * @return AdminMenuItem|null
164
-     * @since 5.0.0.p
165
-     */
166
-    public function adminMenu(): ?AdminMenuItem
167
-    {
168
-        return $this->_menu_map;
169
-    }
170
-
171
-
172
-    /**
173
-     * @param string $wp_page_slug
174
-     * @since 5.0.0.p
175
-     */
176
-    public function setWpPageSlug(string $wp_page_slug): void
177
-    {
178
-        $this->_wp_page_slug = $wp_page_slug;
179
-    }
180
-
181
-
182
-    /**
183
-     * This loads scripts and styles for the EE_Admin system
184
-     * that must be available on ALL WP admin pages (i.e. EE_menu items)
185
-     *
186
-     * @return void
187
-     */
188
-    public function load_wp_global_scripts_styles()
189
-    {
190
-        wp_register_style(
191
-            'espresso_admin_base',
192
-            EE_ADMIN_URL . 'assets/ee-admin-base.css',
193
-            ['dashicons'],
194
-            EVENT_ESPRESSO_VERSION
195
-        );
196
-        wp_register_style(
197
-            'espresso_menu',
198
-            EE_ADMIN_URL . 'assets/ee-admin-menu.css',
199
-            ['espresso_admin_base'],
200
-            EVENT_ESPRESSO_VERSION
201
-        );
202
-        wp_enqueue_style('espresso_admin_base');
203
-        wp_enqueue_style('espresso_menu');
204
-    }
205
-
206
-
207
-    /**
208
-     * this sets default properties (might be overridden in _set_init_properties);
209
-     *
210
-     * @return  void
211
-     */
212
-    private function _set_defaults()
213
-    {
214
-        $this->_file_name    = '';
215
-        $this->_folder_name  = '';
216
-        $this->_wp_page_slug = '';
217
-        $this->capability    = '';
218
-        $this->_routing      = true;
219
-        $this->_load_page    = false;
220
-        $this->_files_hooked = [];
221
-        $this->_hook_paths   = [];
222
-    }
223
-
224
-
225
-    public function setCapability($capability, $menu_slug)
226
-    {
227
-        $this->capability = apply_filters("FHEE_{$menu_slug}_capability", $capability);
228
-    }
229
-
230
-
231
-    /**
232
-     * @deprecated 5.0.0.p
233
-     */
234
-    protected function _set_capability()
235
-    {
236
-        if ($this->_menu_map instanceof AdminMenuItem) {
237
-            $this->setCapability($this->_menu_map->capability(), $this->_menu_map->menuSlug());
238
-        }
239
-    }
240
-
241
-
242
-    /**
243
-     * initialize_admin_page
244
-     * This method is what executes the loading of the specific page class for the given dir_name as called by the
245
-     * EE_Admin_Init class.
246
-     *
247
-     * @return void
248
-     * @throws EE_Error
249
-     * @throws ReflectionException
250
-     * @throws Throwable
251
-     */
252
-    public function initialize_admin_page()
253
-    {
254
-        // let's check user access first
255
-        $this->_check_user_access();
256
-        if (! $this->_loaded_page_object instanceof EE_Admin_Page) {
257
-            return;
258
-        }
259
-        $this->_loaded_page_object->route_admin_request();
260
-    }
261
-
262
-
263
-    /**
264
-     * @param string $wp_page_slug
265
-     * @throws EE_Error
266
-     */
267
-    public function set_page_dependencies(string $wp_page_slug)
268
-    {
269
-        if (! $this->_load_page) {
270
-            return;
271
-        }
272
-        if (! $this->_loaded_page_object instanceof EE_Admin_Page) {
273
-            $msg[] = esc_html__(
274
-                'We can\'t load the page because we\'re missing a valid page object that tells us what to load',
275
-                'event_espresso'
276
-            );
277
-            $msg[] = $msg[0] . "\r\n"
278
-                     . sprintf(
279
-                         esc_html__(
280
-                             'The custom slug you have set for this page is %s. This means we\'re looking for the class %s_Admin_Page (found in %s_Admin_Page.core.php) within your %s directory',
281
-                             'event_espresso'
282
-                         ),
283
-                         $this->_file_name,
284
-                         $this->_file_name,
285
-                         $this->_folder_path . $this->_file_name,
286
-                         $this->_menu_map->menuSlug()
287
-                     );
288
-            throw new EE_Error(implode('||', $msg));
289
-        }
290
-        $this->_loaded_page_object->set_wp_page_slug($wp_page_slug);
291
-        $page_hook = "load-$wp_page_slug";
292
-        // hook into page load hook so all page specific stuff gets loaded.
293
-        if (! empty($wp_page_slug)) {
294
-            add_action($page_hook, [$this->_loaded_page_object, 'load_page_dependencies']);
295
-        }
296
-    }
297
-
298
-
299
-    /**
300
-     * This executes the initial page loads for EE_Admin pages to take care of any ajax or other code needing to run
301
-     * before the load-page... hook. Note, the page loads are happening around the wp_init hook.
302
-     *
303
-     * @return void
304
-     * @throws EE_Error
305
-     * @throws ReflectionException
306
-     * @throws Throwable
307
-     */
308
-    public function do_initial_loads()
309
-    {
310
-        // no loading or initializing if menu map is set up incorrectly.
311
-        if (! $this->_menu_map instanceof AdminMenuItem) {
312
-            return;
313
-        }
314
-        $this->_initialize_admin_page();
315
-    }
316
-
317
-
318
-    /**
319
-     * all we're doing here is setting the $_file_name property for later use.
320
-     *
321
-     * @return void
322
-     */
323
-    private function _set_file_and_folder_name()
324
-    {
325
-        $bt = debug_backtrace();
326
-        // for more reliable determination of folder name
327
-        // we're using this to get the actual folder name of the CALLING class (i.e. the child class that extends this).  Why?  Because $this->menu_slug may be different from the folder name (to avoid conflicts with other plugins)
328
-        $class = get_class($this);
329
-        foreach ($bt as $index => $values) {
330
-            if (isset($values['class']) && $values['class'] == $class) {
331
-                $file_index         = $index - 1;
332
-                $this->_folder_name = basename(dirname($bt[ $file_index ]['file']));
333
-                if (! empty($this->_folder_name)) {
334
-                    break;
335
-                }
336
-            }
337
-        }
338
-        $this->_folder_path = EE_ADMIN_PAGES . $this->_folder_name . '/';
339
-        $this->_file_name   = preg_replace('/^ee/', 'EE', $this->_folder_name);
340
-        $this->_file_name   = ucwords(str_replace('_', ' ', $this->_file_name));
341
-        $this->_file_name   = str_replace(' ', '_', $this->_file_name);
342
-    }
343
-
344
-
345
-    /**
346
-     * This automatically checks if we have a hook class in the loaded child directory.  If we DO then we will register
347
-     * it with the appropriate pages.  That way all we have to do is make sure the file is named correctly and
348
-     * "dropped" in. Example: if we wanted to set this up for Messages hooking into Events then we would do:
349
-     * events_Messages_Hooks.class.php
350
-     *
351
-     * @param bool $extend This indicates whether we're checking the "extend" directory for any register_hooks
352
-     *                     files/classes
353
-     * @return array
354
-     */
355
-    public function register_hooks(bool $extend = false): array
356
-    {
357
-        // get a list of files in the directory that have the "Hook" in their name an
358
-        // if this is an extended check (i.e. caf is active) then we will scan the caffeinated/extend directory first
359
-        // and any hook files that are found will have their reference added to the $_files_hook array property.
360
-        // Then, we make sure that when we loop through the core decaf directories to find hook files
361
-        // that we skip over any hooks files that have already been set by caf.
362
-        if ($extend) {
363
-            $hook_files_glob_path = apply_filters(
364
-                'FHEE__EE_Admin_Page_Init__register_hooks__hook_files_glob_path__extend',
365
-                EE_CORE_CAF_ADMIN_EXTEND
366
-                . $this->_folder_name
367
-                . '/*'
368
-                . $this->_file_name
369
-                . '_Hooks_Extend.class.php'
370
-            );
371
-            $this->_hook_paths    = $this->_register_hook_files($hook_files_glob_path, $extend);
372
-        }
373
-        // loop through decaf folders
374
-        $hook_files_glob_path = apply_filters(
375
-            'FHEE__EE_Admin_Page_Init__register_hooks__hook_files_glob_path',
376
-            $this->_folder_path . '*' . $this->_file_name . '_Hooks.class.php'
377
-        );
378
-        $this->_hook_paths    = array_merge(
379
-            $this->_register_hook_files($hook_files_glob_path),
380
-            $this->_hook_paths
381
-        );  // making sure any extended hook paths are later in the array than the core hook paths!
382
-        return $this->_hook_paths;
383
-    }
384
-
385
-
386
-    protected function _register_hook_files($hook_files_glob_path, $extend = false): array
387
-    {
388
-        $hook_paths = glob($hook_files_glob_path);
389
-        if (empty($hook_paths)) {
390
-            return [];
391
-        }
392
-        foreach ($hook_paths as $file) {
393
-            // lets get the linked admin.
394
-            $hook_file = $extend
395
-                ? str_replace(EE_CORE_CAF_ADMIN_EXTEND . $this->_folder_name . '/', '', $file)
396
-                : str_replace($this->_folder_path, '', $file);
397
-            $replace   = $extend
398
-                ? '_' . $this->_file_name . '_Hooks_Extend.class.php'
399
-                : '_' . $this->_file_name . '_Hooks.class.php';
400
-            $rel_admin = str_replace($replace, '', $hook_file);
401
-            $rel_admin = strtolower($rel_admin);
402
-            // make sure we haven't already got a hook setup for this page path
403
-            if (in_array($rel_admin, $this->_files_hooked)) {
404
-                continue;
405
-            }
406
-            require_once $file;
407
-            $this->hook_file = $hook_file;
408
-            $rel_admin_hook  = 'FHEE_do_other_page_hooks_' . $rel_admin;
409
-            add_filter($rel_admin_hook, [$this, 'load_admin_hook']);
410
-            $this->_files_hooked[] = $rel_admin;
411
-        }
412
-        return $hook_paths;
413
-    }
414
-
415
-
416
-    public function load_admin_hook($registered_pages)
417
-    {
418
-        return array_merge((array) $this->hook_file, $registered_pages);
419
-    }
420
-
421
-
422
-    /**
423
-     * @throws EE_Error
424
-     * @throws ReflectionException
425
-     * @throws Throwable
426
-     * @see  initialize_admin_page() for info
427
-     */
428
-    protected function _initialize_admin_page()
429
-    {
430
-        // JUST CHECK WE'RE ON RIGHT PAGE.
431
-        $page      = $this->request->getRequestParam('page');
432
-        $page      = $this->request->getRequestParam('current_page', $page);
433
-        $menu_slug = $this->_menu_map->menuSlug();
434
-
435
-        // leaving the following in for the time being
436
-        // because not sure if preventing multiple admin page loading will result in bad things happening
437
-        $darren_logic                       = false;  // true    false
438
-        $not_the_droids_you_are_looking_for = $darren_logic
439
-            ? $this->_routing && ($page === '' || $page !== $menu_slug)
440
-            : $page === '' || $page !== $menu_slug;
441
-        if ($not_the_droids_you_are_looking_for) {
442
-            // not on the right page so let's get out.
443
-            return;
444
-        }
445
-        $this->_load_page = true;
446
-
447
-        // we don't need to do a page_request check here because it's only called via WP menu system.
448
-        $admin_page  = $this->_file_name . '_Admin_Page';
449
-        $hook_suffix = "{$menu_slug}_$admin_page";
450
-        $admin_page  = apply_filters(
451
-            "FHEE__EE_Admin_Page_Init___initialize_admin_page__admin_page__$hook_suffix",
452
-            $admin_page
453
-        );
454
-        if (empty($admin_page)) {
455
-            return;
456
-        }
457
-        // define requested admin page class name then load the file and instantiate
458
-        $path_to_file = str_replace(['\\', '/'], '/', $this->_folder_path . $admin_page . '.core.php');
459
-        // so if the file would be in EE_ADMIN/attendees/Attendee_Admin_Page.core.php, the filter would be:
460
-        // FHEE__EE_Admin_Page_Init___initialize_admin_page__path_to_file__attendees_Attendee_Admin_Page
461
-        $path_to_file = apply_filters(
462
-            "FHEE__EE_Admin_Page_Init___initialize_admin_page__path_to_file__$hook_suffix",
463
-            $path_to_file
464
-        );
465
-        if (! is_readable($path_to_file)) {
466
-            return;
467
-        }
468
-        // This is a place where EE plugins can hook into
469
-        // to make sure their own files are required in the appropriate place
470
-        do_action('AHEE__EE_Admin_Page___initialize_admin_page__before_initialization');
471
-        do_action("AHEE__EE_Admin_Page___initialize_admin_page__before_initialization_$menu_slug");
472
-        require_once($path_to_file);
473
-        $this->_loaded_page_object = $this->loader->getShared($admin_page, [$this->_routing]);
474
-        $this->_loaded_page_object->initializePage();
475
-
476
-        do_action('AHEE__EE_Admin_Page___initialize_admin_page__after_initialization');
477
-        do_action("AHEE__EE_Admin_Page___initialize_admin_page__after_initialization_$menu_slug");
478
-    }
479
-
480
-
481
-    public function get_admin_page_name(): string
482
-    {
483
-        return $this->_file_name . '_Admin_Page';
484
-    }
485
-
486
-
487
-    /**
488
-     * @return EE_Admin_Page|null
489
-     */
490
-    public function loaded_page_object(): ?EE_Admin_Page
491
-    {
492
-        return $this->_loaded_page_object;
493
-    }
494
-
495
-
496
-    /**
497
-     * verifies user access for this admin page.
498
-     * If no user access is available then let's gracefully exit with a WordPress die message.
499
-     *
500
-     * @return void  wp_die if fail
501
-     */
502
-    private function _check_user_access()
503
-    {
504
-        if (! $this->_menu_map->currentUserHasAccess()) {
505
-            wp_die(
506
-                esc_html__('You don\'t have access to this page.', 'event_espresso'),
507
-                '',
508
-                ['back_link' => true]
509
-            );
510
-        }
511
-    }
28
+	protected ?LoaderInterface $loader              = null;
29
+
30
+	protected ?RequestInterface $request             = null;
31
+
32
+	private bool $_load_page          = false;
33
+
34
+	protected bool $_routing            = false;
35
+
36
+	/**
37
+	 * Menu map has a capability.  However, this allows admin pages to have separate capability requirements for menus
38
+	 * and accessing pages.  If capability is NOT set, then it defaults to the menu_map capability.
39
+	 *
40
+	 * @var string
41
+	 */
42
+	public string $capability = '';
43
+
44
+	/**
45
+	 * identity properties (set in _set_defaults and _set_init_properties)
46
+	 */
47
+	public string $label         = '';
48
+
49
+	protected string $_file_name    = '';
50
+
51
+	protected string $_folder_name  = '';
52
+
53
+	protected string $_folder_path  = '';
54
+
55
+	protected string $_wp_page_slug = '';
56
+
57
+	public string $hook_file     = '';
58
+
59
+	public string $menu_slug     = '';
60
+
61
+	/**
62
+	 * @deprecated
63
+	 */
64
+	public string $menu_label    = '';
65
+
66
+	protected array $_files_hooked = [];
67
+
68
+	protected array $_hook_paths   = [];
69
+
70
+
71
+	/**
72
+	 * @throws InvalidArgumentException
73
+	 * @throws InvalidDataTypeException
74
+	 * @throws InvalidInterfaceException
75
+	 */
76
+	public function __construct(RequestInterface $request = null)
77
+	{
78
+		$this->loader  = LoaderFactory::getLoader();
79
+		$this->request = $request instanceof RequestInterface
80
+			? $request
81
+			: $this->loader->getShared(RequestInterface::class);
82
+		// set global defaults
83
+		$this->_set_defaults();
84
+		// set properties that are always available with objects.
85
+		$this->_set_init_properties();
86
+		// global styles/scripts across all wp admin pages
87
+		add_action('admin_enqueue_scripts', [$this, 'load_wp_global_scripts_styles'], 5);
88
+		// load initial stuff.
89
+		$this->_set_file_and_folder_name();
90
+	}
91
+
92
+
93
+	/**
94
+	 * _set_init_properties
95
+	 * Child classes use to set the following properties:
96
+	 * $label
97
+	 *
98
+	 * @abstract
99
+	 * @return void
100
+	 */
101
+	abstract protected function _set_init_properties();
102
+
103
+
104
+	/**
105
+	 * @return AdminMenuItem|null
106
+	 * @since       4.4.0
107
+	 * @deprecated  5.0.0.p
108
+	 */
109
+	public function get_menu_map()
110
+	{
111
+		return $this->adminMenu();
112
+	}
113
+
114
+
115
+	/**
116
+	 * _set_menu_map is a function that child classes use to set the menu_map property (which should be an instance of
117
+	 * EE_Admin_Page_Menu_Map.  Their menu can either be EE_Admin_Page_Main_Menu or AdminMenuSubItem.
118
+	 *
119
+	 * @since       4.4.0
120
+	 * @deprecated  5.0.0.p
121
+	 */
122
+	protected function _set_menu_map()
123
+	{
124
+	}
125
+
126
+
127
+	/**
128
+	 * @since   5.0.0.p
129
+	 */
130
+	public function setupLegacyAdminMenuItem()
131
+	{
132
+		// will be overridden by child classes not using new system
133
+		$this->_set_menu_map();
134
+	}
135
+
136
+
137
+	/**
138
+	 * Child classes should return an array of properties used to construct the AdminMenuItem
139
+	 *
140
+	 * @return array
141
+	 * @since 5.0.0.p
142
+	 */
143
+	public function getMenuProperties(): array
144
+	{
145
+		return [];
146
+	}
147
+
148
+
149
+	/**
150
+	 * @param AdminMenuItem $menu
151
+	 * @return void
152
+	 * @since 5.0.0.p
153
+	 */
154
+	public function setAdminMenu(AdminMenuItem $menu): void
155
+	{
156
+		$this->_menu_map = $menu;
157
+	}
158
+
159
+
160
+	/**
161
+	 * returns the menu map for this admin page
162
+	 *
163
+	 * @return AdminMenuItem|null
164
+	 * @since 5.0.0.p
165
+	 */
166
+	public function adminMenu(): ?AdminMenuItem
167
+	{
168
+		return $this->_menu_map;
169
+	}
170
+
171
+
172
+	/**
173
+	 * @param string $wp_page_slug
174
+	 * @since 5.0.0.p
175
+	 */
176
+	public function setWpPageSlug(string $wp_page_slug): void
177
+	{
178
+		$this->_wp_page_slug = $wp_page_slug;
179
+	}
180
+
181
+
182
+	/**
183
+	 * This loads scripts and styles for the EE_Admin system
184
+	 * that must be available on ALL WP admin pages (i.e. EE_menu items)
185
+	 *
186
+	 * @return void
187
+	 */
188
+	public function load_wp_global_scripts_styles()
189
+	{
190
+		wp_register_style(
191
+			'espresso_admin_base',
192
+			EE_ADMIN_URL . 'assets/ee-admin-base.css',
193
+			['dashicons'],
194
+			EVENT_ESPRESSO_VERSION
195
+		);
196
+		wp_register_style(
197
+			'espresso_menu',
198
+			EE_ADMIN_URL . 'assets/ee-admin-menu.css',
199
+			['espresso_admin_base'],
200
+			EVENT_ESPRESSO_VERSION
201
+		);
202
+		wp_enqueue_style('espresso_admin_base');
203
+		wp_enqueue_style('espresso_menu');
204
+	}
205
+
206
+
207
+	/**
208
+	 * this sets default properties (might be overridden in _set_init_properties);
209
+	 *
210
+	 * @return  void
211
+	 */
212
+	private function _set_defaults()
213
+	{
214
+		$this->_file_name    = '';
215
+		$this->_folder_name  = '';
216
+		$this->_wp_page_slug = '';
217
+		$this->capability    = '';
218
+		$this->_routing      = true;
219
+		$this->_load_page    = false;
220
+		$this->_files_hooked = [];
221
+		$this->_hook_paths   = [];
222
+	}
223
+
224
+
225
+	public function setCapability($capability, $menu_slug)
226
+	{
227
+		$this->capability = apply_filters("FHEE_{$menu_slug}_capability", $capability);
228
+	}
229
+
230
+
231
+	/**
232
+	 * @deprecated 5.0.0.p
233
+	 */
234
+	protected function _set_capability()
235
+	{
236
+		if ($this->_menu_map instanceof AdminMenuItem) {
237
+			$this->setCapability($this->_menu_map->capability(), $this->_menu_map->menuSlug());
238
+		}
239
+	}
240
+
241
+
242
+	/**
243
+	 * initialize_admin_page
244
+	 * This method is what executes the loading of the specific page class for the given dir_name as called by the
245
+	 * EE_Admin_Init class.
246
+	 *
247
+	 * @return void
248
+	 * @throws EE_Error
249
+	 * @throws ReflectionException
250
+	 * @throws Throwable
251
+	 */
252
+	public function initialize_admin_page()
253
+	{
254
+		// let's check user access first
255
+		$this->_check_user_access();
256
+		if (! $this->_loaded_page_object instanceof EE_Admin_Page) {
257
+			return;
258
+		}
259
+		$this->_loaded_page_object->route_admin_request();
260
+	}
261
+
262
+
263
+	/**
264
+	 * @param string $wp_page_slug
265
+	 * @throws EE_Error
266
+	 */
267
+	public function set_page_dependencies(string $wp_page_slug)
268
+	{
269
+		if (! $this->_load_page) {
270
+			return;
271
+		}
272
+		if (! $this->_loaded_page_object instanceof EE_Admin_Page) {
273
+			$msg[] = esc_html__(
274
+				'We can\'t load the page because we\'re missing a valid page object that tells us what to load',
275
+				'event_espresso'
276
+			);
277
+			$msg[] = $msg[0] . "\r\n"
278
+					 . sprintf(
279
+						 esc_html__(
280
+							 'The custom slug you have set for this page is %s. This means we\'re looking for the class %s_Admin_Page (found in %s_Admin_Page.core.php) within your %s directory',
281
+							 'event_espresso'
282
+						 ),
283
+						 $this->_file_name,
284
+						 $this->_file_name,
285
+						 $this->_folder_path . $this->_file_name,
286
+						 $this->_menu_map->menuSlug()
287
+					 );
288
+			throw new EE_Error(implode('||', $msg));
289
+		}
290
+		$this->_loaded_page_object->set_wp_page_slug($wp_page_slug);
291
+		$page_hook = "load-$wp_page_slug";
292
+		// hook into page load hook so all page specific stuff gets loaded.
293
+		if (! empty($wp_page_slug)) {
294
+			add_action($page_hook, [$this->_loaded_page_object, 'load_page_dependencies']);
295
+		}
296
+	}
297
+
298
+
299
+	/**
300
+	 * This executes the initial page loads for EE_Admin pages to take care of any ajax or other code needing to run
301
+	 * before the load-page... hook. Note, the page loads are happening around the wp_init hook.
302
+	 *
303
+	 * @return void
304
+	 * @throws EE_Error
305
+	 * @throws ReflectionException
306
+	 * @throws Throwable
307
+	 */
308
+	public function do_initial_loads()
309
+	{
310
+		// no loading or initializing if menu map is set up incorrectly.
311
+		if (! $this->_menu_map instanceof AdminMenuItem) {
312
+			return;
313
+		}
314
+		$this->_initialize_admin_page();
315
+	}
316
+
317
+
318
+	/**
319
+	 * all we're doing here is setting the $_file_name property for later use.
320
+	 *
321
+	 * @return void
322
+	 */
323
+	private function _set_file_and_folder_name()
324
+	{
325
+		$bt = debug_backtrace();
326
+		// for more reliable determination of folder name
327
+		// we're using this to get the actual folder name of the CALLING class (i.e. the child class that extends this).  Why?  Because $this->menu_slug may be different from the folder name (to avoid conflicts with other plugins)
328
+		$class = get_class($this);
329
+		foreach ($bt as $index => $values) {
330
+			if (isset($values['class']) && $values['class'] == $class) {
331
+				$file_index         = $index - 1;
332
+				$this->_folder_name = basename(dirname($bt[ $file_index ]['file']));
333
+				if (! empty($this->_folder_name)) {
334
+					break;
335
+				}
336
+			}
337
+		}
338
+		$this->_folder_path = EE_ADMIN_PAGES . $this->_folder_name . '/';
339
+		$this->_file_name   = preg_replace('/^ee/', 'EE', $this->_folder_name);
340
+		$this->_file_name   = ucwords(str_replace('_', ' ', $this->_file_name));
341
+		$this->_file_name   = str_replace(' ', '_', $this->_file_name);
342
+	}
343
+
344
+
345
+	/**
346
+	 * This automatically checks if we have a hook class in the loaded child directory.  If we DO then we will register
347
+	 * it with the appropriate pages.  That way all we have to do is make sure the file is named correctly and
348
+	 * "dropped" in. Example: if we wanted to set this up for Messages hooking into Events then we would do:
349
+	 * events_Messages_Hooks.class.php
350
+	 *
351
+	 * @param bool $extend This indicates whether we're checking the "extend" directory for any register_hooks
352
+	 *                     files/classes
353
+	 * @return array
354
+	 */
355
+	public function register_hooks(bool $extend = false): array
356
+	{
357
+		// get a list of files in the directory that have the "Hook" in their name an
358
+		// if this is an extended check (i.e. caf is active) then we will scan the caffeinated/extend directory first
359
+		// and any hook files that are found will have their reference added to the $_files_hook array property.
360
+		// Then, we make sure that when we loop through the core decaf directories to find hook files
361
+		// that we skip over any hooks files that have already been set by caf.
362
+		if ($extend) {
363
+			$hook_files_glob_path = apply_filters(
364
+				'FHEE__EE_Admin_Page_Init__register_hooks__hook_files_glob_path__extend',
365
+				EE_CORE_CAF_ADMIN_EXTEND
366
+				. $this->_folder_name
367
+				. '/*'
368
+				. $this->_file_name
369
+				. '_Hooks_Extend.class.php'
370
+			);
371
+			$this->_hook_paths    = $this->_register_hook_files($hook_files_glob_path, $extend);
372
+		}
373
+		// loop through decaf folders
374
+		$hook_files_glob_path = apply_filters(
375
+			'FHEE__EE_Admin_Page_Init__register_hooks__hook_files_glob_path',
376
+			$this->_folder_path . '*' . $this->_file_name . '_Hooks.class.php'
377
+		);
378
+		$this->_hook_paths    = array_merge(
379
+			$this->_register_hook_files($hook_files_glob_path),
380
+			$this->_hook_paths
381
+		);  // making sure any extended hook paths are later in the array than the core hook paths!
382
+		return $this->_hook_paths;
383
+	}
384
+
385
+
386
+	protected function _register_hook_files($hook_files_glob_path, $extend = false): array
387
+	{
388
+		$hook_paths = glob($hook_files_glob_path);
389
+		if (empty($hook_paths)) {
390
+			return [];
391
+		}
392
+		foreach ($hook_paths as $file) {
393
+			// lets get the linked admin.
394
+			$hook_file = $extend
395
+				? str_replace(EE_CORE_CAF_ADMIN_EXTEND . $this->_folder_name . '/', '', $file)
396
+				: str_replace($this->_folder_path, '', $file);
397
+			$replace   = $extend
398
+				? '_' . $this->_file_name . '_Hooks_Extend.class.php'
399
+				: '_' . $this->_file_name . '_Hooks.class.php';
400
+			$rel_admin = str_replace($replace, '', $hook_file);
401
+			$rel_admin = strtolower($rel_admin);
402
+			// make sure we haven't already got a hook setup for this page path
403
+			if (in_array($rel_admin, $this->_files_hooked)) {
404
+				continue;
405
+			}
406
+			require_once $file;
407
+			$this->hook_file = $hook_file;
408
+			$rel_admin_hook  = 'FHEE_do_other_page_hooks_' . $rel_admin;
409
+			add_filter($rel_admin_hook, [$this, 'load_admin_hook']);
410
+			$this->_files_hooked[] = $rel_admin;
411
+		}
412
+		return $hook_paths;
413
+	}
414
+
415
+
416
+	public function load_admin_hook($registered_pages)
417
+	{
418
+		return array_merge((array) $this->hook_file, $registered_pages);
419
+	}
420
+
421
+
422
+	/**
423
+	 * @throws EE_Error
424
+	 * @throws ReflectionException
425
+	 * @throws Throwable
426
+	 * @see  initialize_admin_page() for info
427
+	 */
428
+	protected function _initialize_admin_page()
429
+	{
430
+		// JUST CHECK WE'RE ON RIGHT PAGE.
431
+		$page      = $this->request->getRequestParam('page');
432
+		$page      = $this->request->getRequestParam('current_page', $page);
433
+		$menu_slug = $this->_menu_map->menuSlug();
434
+
435
+		// leaving the following in for the time being
436
+		// because not sure if preventing multiple admin page loading will result in bad things happening
437
+		$darren_logic                       = false;  // true    false
438
+		$not_the_droids_you_are_looking_for = $darren_logic
439
+			? $this->_routing && ($page === '' || $page !== $menu_slug)
440
+			: $page === '' || $page !== $menu_slug;
441
+		if ($not_the_droids_you_are_looking_for) {
442
+			// not on the right page so let's get out.
443
+			return;
444
+		}
445
+		$this->_load_page = true;
446
+
447
+		// we don't need to do a page_request check here because it's only called via WP menu system.
448
+		$admin_page  = $this->_file_name . '_Admin_Page';
449
+		$hook_suffix = "{$menu_slug}_$admin_page";
450
+		$admin_page  = apply_filters(
451
+			"FHEE__EE_Admin_Page_Init___initialize_admin_page__admin_page__$hook_suffix",
452
+			$admin_page
453
+		);
454
+		if (empty($admin_page)) {
455
+			return;
456
+		}
457
+		// define requested admin page class name then load the file and instantiate
458
+		$path_to_file = str_replace(['\\', '/'], '/', $this->_folder_path . $admin_page . '.core.php');
459
+		// so if the file would be in EE_ADMIN/attendees/Attendee_Admin_Page.core.php, the filter would be:
460
+		// FHEE__EE_Admin_Page_Init___initialize_admin_page__path_to_file__attendees_Attendee_Admin_Page
461
+		$path_to_file = apply_filters(
462
+			"FHEE__EE_Admin_Page_Init___initialize_admin_page__path_to_file__$hook_suffix",
463
+			$path_to_file
464
+		);
465
+		if (! is_readable($path_to_file)) {
466
+			return;
467
+		}
468
+		// This is a place where EE plugins can hook into
469
+		// to make sure their own files are required in the appropriate place
470
+		do_action('AHEE__EE_Admin_Page___initialize_admin_page__before_initialization');
471
+		do_action("AHEE__EE_Admin_Page___initialize_admin_page__before_initialization_$menu_slug");
472
+		require_once($path_to_file);
473
+		$this->_loaded_page_object = $this->loader->getShared($admin_page, [$this->_routing]);
474
+		$this->_loaded_page_object->initializePage();
475
+
476
+		do_action('AHEE__EE_Admin_Page___initialize_admin_page__after_initialization');
477
+		do_action("AHEE__EE_Admin_Page___initialize_admin_page__after_initialization_$menu_slug");
478
+	}
479
+
480
+
481
+	public function get_admin_page_name(): string
482
+	{
483
+		return $this->_file_name . '_Admin_Page';
484
+	}
485
+
486
+
487
+	/**
488
+	 * @return EE_Admin_Page|null
489
+	 */
490
+	public function loaded_page_object(): ?EE_Admin_Page
491
+	{
492
+		return $this->_loaded_page_object;
493
+	}
494
+
495
+
496
+	/**
497
+	 * verifies user access for this admin page.
498
+	 * If no user access is available then let's gracefully exit with a WordPress die message.
499
+	 *
500
+	 * @return void  wp_die if fail
501
+	 */
502
+	private function _check_user_access()
503
+	{
504
+		if (! $this->_menu_map->currentUserHasAccess()) {
505
+			wp_die(
506
+				esc_html__('You don\'t have access to this page.', 'event_espresso'),
507
+				'',
508
+				['back_link' => true]
509
+			);
510
+		}
511
+	}
512 512
 }
Please login to merge, or discard this patch.
Spacing   +32 added lines, -32 removed lines patch added patch discarded remove patch
@@ -25,13 +25,13 @@  discard block
 block discarded – undo
25 25
 
26 26
     protected ?EE_Admin_Page $_loaded_page_object = null;
27 27
 
28
-    protected ?LoaderInterface $loader              = null;
28
+    protected ?LoaderInterface $loader = null;
29 29
 
30
-    protected ?RequestInterface $request             = null;
30
+    protected ?RequestInterface $request = null;
31 31
 
32
-    private bool $_load_page          = false;
32
+    private bool $_load_page = false;
33 33
 
34
-    protected bool $_routing            = false;
34
+    protected bool $_routing = false;
35 35
 
36 36
     /**
37 37
      * Menu map has a capability.  However, this allows admin pages to have separate capability requirements for menus
@@ -44,7 +44,7 @@  discard block
 block discarded – undo
44 44
     /**
45 45
      * identity properties (set in _set_defaults and _set_init_properties)
46 46
      */
47
-    public string $label         = '';
47
+    public string $label = '';
48 48
 
49 49
     protected string $_file_name    = '';
50 50
 
@@ -61,7 +61,7 @@  discard block
 block discarded – undo
61 61
     /**
62 62
      * @deprecated
63 63
      */
64
-    public string $menu_label    = '';
64
+    public string $menu_label = '';
65 65
 
66 66
     protected array $_files_hooked = [];
67 67
 
@@ -189,13 +189,13 @@  discard block
 block discarded – undo
189 189
     {
190 190
         wp_register_style(
191 191
             'espresso_admin_base',
192
-            EE_ADMIN_URL . 'assets/ee-admin-base.css',
192
+            EE_ADMIN_URL.'assets/ee-admin-base.css',
193 193
             ['dashicons'],
194 194
             EVENT_ESPRESSO_VERSION
195 195
         );
196 196
         wp_register_style(
197 197
             'espresso_menu',
198
-            EE_ADMIN_URL . 'assets/ee-admin-menu.css',
198
+            EE_ADMIN_URL.'assets/ee-admin-menu.css',
199 199
             ['espresso_admin_base'],
200 200
             EVENT_ESPRESSO_VERSION
201 201
         );
@@ -253,7 +253,7 @@  discard block
 block discarded – undo
253 253
     {
254 254
         // let's check user access first
255 255
         $this->_check_user_access();
256
-        if (! $this->_loaded_page_object instanceof EE_Admin_Page) {
256
+        if ( ! $this->_loaded_page_object instanceof EE_Admin_Page) {
257 257
             return;
258 258
         }
259 259
         $this->_loaded_page_object->route_admin_request();
@@ -266,15 +266,15 @@  discard block
 block discarded – undo
266 266
      */
267 267
     public function set_page_dependencies(string $wp_page_slug)
268 268
     {
269
-        if (! $this->_load_page) {
269
+        if ( ! $this->_load_page) {
270 270
             return;
271 271
         }
272
-        if (! $this->_loaded_page_object instanceof EE_Admin_Page) {
272
+        if ( ! $this->_loaded_page_object instanceof EE_Admin_Page) {
273 273
             $msg[] = esc_html__(
274 274
                 'We can\'t load the page because we\'re missing a valid page object that tells us what to load',
275 275
                 'event_espresso'
276 276
             );
277
-            $msg[] = $msg[0] . "\r\n"
277
+            $msg[] = $msg[0]."\r\n"
278 278
                      . sprintf(
279 279
                          esc_html__(
280 280
                              'The custom slug you have set for this page is %s. This means we\'re looking for the class %s_Admin_Page (found in %s_Admin_Page.core.php) within your %s directory',
@@ -282,7 +282,7 @@  discard block
 block discarded – undo
282 282
                          ),
283 283
                          $this->_file_name,
284 284
                          $this->_file_name,
285
-                         $this->_folder_path . $this->_file_name,
285
+                         $this->_folder_path.$this->_file_name,
286 286
                          $this->_menu_map->menuSlug()
287 287
                      );
288 288
             throw new EE_Error(implode('||', $msg));
@@ -290,7 +290,7 @@  discard block
 block discarded – undo
290 290
         $this->_loaded_page_object->set_wp_page_slug($wp_page_slug);
291 291
         $page_hook = "load-$wp_page_slug";
292 292
         // hook into page load hook so all page specific stuff gets loaded.
293
-        if (! empty($wp_page_slug)) {
293
+        if ( ! empty($wp_page_slug)) {
294 294
             add_action($page_hook, [$this->_loaded_page_object, 'load_page_dependencies']);
295 295
         }
296 296
     }
@@ -308,7 +308,7 @@  discard block
 block discarded – undo
308 308
     public function do_initial_loads()
309 309
     {
310 310
         // no loading or initializing if menu map is set up incorrectly.
311
-        if (! $this->_menu_map instanceof AdminMenuItem) {
311
+        if ( ! $this->_menu_map instanceof AdminMenuItem) {
312 312
             return;
313 313
         }
314 314
         $this->_initialize_admin_page();
@@ -329,13 +329,13 @@  discard block
 block discarded – undo
329 329
         foreach ($bt as $index => $values) {
330 330
             if (isset($values['class']) && $values['class'] == $class) {
331 331
                 $file_index         = $index - 1;
332
-                $this->_folder_name = basename(dirname($bt[ $file_index ]['file']));
333
-                if (! empty($this->_folder_name)) {
332
+                $this->_folder_name = basename(dirname($bt[$file_index]['file']));
333
+                if ( ! empty($this->_folder_name)) {
334 334
                     break;
335 335
                 }
336 336
             }
337 337
         }
338
-        $this->_folder_path = EE_ADMIN_PAGES . $this->_folder_name . '/';
338
+        $this->_folder_path = EE_ADMIN_PAGES.$this->_folder_name.'/';
339 339
         $this->_file_name   = preg_replace('/^ee/', 'EE', $this->_folder_name);
340 340
         $this->_file_name   = ucwords(str_replace('_', ' ', $this->_file_name));
341 341
         $this->_file_name   = str_replace(' ', '_', $this->_file_name);
@@ -368,17 +368,17 @@  discard block
 block discarded – undo
368 368
                 . $this->_file_name
369 369
                 . '_Hooks_Extend.class.php'
370 370
             );
371
-            $this->_hook_paths    = $this->_register_hook_files($hook_files_glob_path, $extend);
371
+            $this->_hook_paths = $this->_register_hook_files($hook_files_glob_path, $extend);
372 372
         }
373 373
         // loop through decaf folders
374 374
         $hook_files_glob_path = apply_filters(
375 375
             'FHEE__EE_Admin_Page_Init__register_hooks__hook_files_glob_path',
376
-            $this->_folder_path . '*' . $this->_file_name . '_Hooks.class.php'
376
+            $this->_folder_path.'*'.$this->_file_name.'_Hooks.class.php'
377 377
         );
378
-        $this->_hook_paths    = array_merge(
378
+        $this->_hook_paths = array_merge(
379 379
             $this->_register_hook_files($hook_files_glob_path),
380 380
             $this->_hook_paths
381
-        );  // making sure any extended hook paths are later in the array than the core hook paths!
381
+        ); // making sure any extended hook paths are later in the array than the core hook paths!
382 382
         return $this->_hook_paths;
383 383
     }
384 384
 
@@ -392,11 +392,11 @@  discard block
 block discarded – undo
392 392
         foreach ($hook_paths as $file) {
393 393
             // lets get the linked admin.
394 394
             $hook_file = $extend
395
-                ? str_replace(EE_CORE_CAF_ADMIN_EXTEND . $this->_folder_name . '/', '', $file)
395
+                ? str_replace(EE_CORE_CAF_ADMIN_EXTEND.$this->_folder_name.'/', '', $file)
396 396
                 : str_replace($this->_folder_path, '', $file);
397 397
             $replace   = $extend
398
-                ? '_' . $this->_file_name . '_Hooks_Extend.class.php'
399
-                : '_' . $this->_file_name . '_Hooks.class.php';
398
+                ? '_'.$this->_file_name.'_Hooks_Extend.class.php'
399
+                : '_'.$this->_file_name.'_Hooks.class.php';
400 400
             $rel_admin = str_replace($replace, '', $hook_file);
401 401
             $rel_admin = strtolower($rel_admin);
402 402
             // make sure we haven't already got a hook setup for this page path
@@ -405,7 +405,7 @@  discard block
 block discarded – undo
405 405
             }
406 406
             require_once $file;
407 407
             $this->hook_file = $hook_file;
408
-            $rel_admin_hook  = 'FHEE_do_other_page_hooks_' . $rel_admin;
408
+            $rel_admin_hook  = 'FHEE_do_other_page_hooks_'.$rel_admin;
409 409
             add_filter($rel_admin_hook, [$this, 'load_admin_hook']);
410 410
             $this->_files_hooked[] = $rel_admin;
411 411
         }
@@ -434,7 +434,7 @@  discard block
 block discarded – undo
434 434
 
435 435
         // leaving the following in for the time being
436 436
         // because not sure if preventing multiple admin page loading will result in bad things happening
437
-        $darren_logic                       = false;  // true    false
437
+        $darren_logic                       = false; // true    false
438 438
         $not_the_droids_you_are_looking_for = $darren_logic
439 439
             ? $this->_routing && ($page === '' || $page !== $menu_slug)
440 440
             : $page === '' || $page !== $menu_slug;
@@ -445,7 +445,7 @@  discard block
 block discarded – undo
445 445
         $this->_load_page = true;
446 446
 
447 447
         // we don't need to do a page_request check here because it's only called via WP menu system.
448
-        $admin_page  = $this->_file_name . '_Admin_Page';
448
+        $admin_page  = $this->_file_name.'_Admin_Page';
449 449
         $hook_suffix = "{$menu_slug}_$admin_page";
450 450
         $admin_page  = apply_filters(
451 451
             "FHEE__EE_Admin_Page_Init___initialize_admin_page__admin_page__$hook_suffix",
@@ -455,14 +455,14 @@  discard block
 block discarded – undo
455 455
             return;
456 456
         }
457 457
         // define requested admin page class name then load the file and instantiate
458
-        $path_to_file = str_replace(['\\', '/'], '/', $this->_folder_path . $admin_page . '.core.php');
458
+        $path_to_file = str_replace(['\\', '/'], '/', $this->_folder_path.$admin_page.'.core.php');
459 459
         // so if the file would be in EE_ADMIN/attendees/Attendee_Admin_Page.core.php, the filter would be:
460 460
         // FHEE__EE_Admin_Page_Init___initialize_admin_page__path_to_file__attendees_Attendee_Admin_Page
461 461
         $path_to_file = apply_filters(
462 462
             "FHEE__EE_Admin_Page_Init___initialize_admin_page__path_to_file__$hook_suffix",
463 463
             $path_to_file
464 464
         );
465
-        if (! is_readable($path_to_file)) {
465
+        if ( ! is_readable($path_to_file)) {
466 466
             return;
467 467
         }
468 468
         // This is a place where EE plugins can hook into
@@ -480,7 +480,7 @@  discard block
 block discarded – undo
480 480
 
481 481
     public function get_admin_page_name(): string
482 482
     {
483
-        return $this->_file_name . '_Admin_Page';
483
+        return $this->_file_name.'_Admin_Page';
484 484
     }
485 485
 
486 486
 
@@ -501,7 +501,7 @@  discard block
 block discarded – undo
501 501
      */
502 502
     private function _check_user_access()
503 503
     {
504
-        if (! $this->_menu_map->currentUserHasAccess()) {
504
+        if ( ! $this->_menu_map->currentUserHasAccess()) {
505 505
             wp_die(
506 506
                 esc_html__('You don\'t have access to this page.', 'event_espresso'),
507 507
                 '',
Please login to merge, or discard this patch.
core/exceptions/ExceptionStackTraceDisplay.php 2 patches
Indentation   +273 added lines, -273 removed lines patch added patch discarded remove patch
@@ -21,161 +21,161 @@  discard block
 block discarded – undo
21 21
  */
22 22
 class ExceptionStackTraceDisplay
23 23
 {
24
-    /**
25
-     * @var   string
26
-     * @since 4.10.24.p
27
-     */
28
-    private $class_name = '';
24
+	/**
25
+	 * @var   string
26
+	 * @since 4.10.24.p
27
+	 */
28
+	private $class_name = '';
29 29
 
30
-    /**
31
-     * @var   string
32
-     * @since 4.10.24.p
33
-     */
34
-    private $error_code = '';
30
+	/**
31
+	 * @var   string
32
+	 * @since 4.10.24.p
33
+	 */
34
+	private $error_code = '';
35 35
 
36 36
 
37
-    /**
38
-     * @param Throwable $exception
39
-     * @throws ReflectionException
40
-     * @throws Throwable
41
-     */
42
-    public function __construct(Throwable $exception)
43
-    {
44
-        if (WP_DEBUG && ! defined('EE_TESTS_DIR')) {
45
-            $this->displayException($exception);
46
-        } else {
47
-            throw $exception;
48
-        }
49
-    }
37
+	/**
38
+	 * @param Throwable $exception
39
+	 * @throws ReflectionException
40
+	 * @throws Throwable
41
+	 */
42
+	public function __construct(Throwable $exception)
43
+	{
44
+		if (WP_DEBUG && ! defined('EE_TESTS_DIR')) {
45
+			$this->displayException($exception);
46
+		} else {
47
+			throw $exception;
48
+		}
49
+	}
50 50
 
51 51
 
52
-    /**
53
-     * @access protected
54
-     * @param Throwable $exception
55
-     * @throws ReflectionException
56
-     */
57
-    protected function displayException(Throwable $exception)
58
-    {
59
-        // get separate user and developer messages if they exist
60
-        $msg = explode('||', $exception->getMessage());
61
-        $user_msg = $msg[0];
62
-        $dev_msg = isset($msg[1]) ? $msg[1] : $msg[0];
63
-        $msg = WP_DEBUG ? $dev_msg : $user_msg;
64
-        // process trace info
65
-        $trace_details = $this->traceDetails($exception);
66
-        $code          = $exception->getCode() ?: $this->error_code;
67
-        // add helpful developer messages if debugging is on
68
-        // or generic non-identifying messages for non-privileged users
69
-        $error_message = WP_DEBUG
70
-            ? $this->developerError($exception, $msg, $code, $trace_details)
71
-            : $this->genericError($msg, $code);
72
-        // start gathering output
73
-        $output = '
52
+	/**
53
+	 * @access protected
54
+	 * @param Throwable $exception
55
+	 * @throws ReflectionException
56
+	 */
57
+	protected function displayException(Throwable $exception)
58
+	{
59
+		// get separate user and developer messages if they exist
60
+		$msg = explode('||', $exception->getMessage());
61
+		$user_msg = $msg[0];
62
+		$dev_msg = isset($msg[1]) ? $msg[1] : $msg[0];
63
+		$msg = WP_DEBUG ? $dev_msg : $user_msg;
64
+		// process trace info
65
+		$trace_details = $this->traceDetails($exception);
66
+		$code          = $exception->getCode() ?: $this->error_code;
67
+		// add helpful developer messages if debugging is on
68
+		// or generic non-identifying messages for non-privileged users
69
+		$error_message = WP_DEBUG
70
+			? $this->developerError($exception, $msg, $code, $trace_details)
71
+			: $this->genericError($msg, $code);
72
+		// start gathering output
73
+		$output = '
74 74
         <div id="ee-error-message" class="ee-exception-stack-trace-display" >
75 75
             ' . $error_message . '
76 76
         </div>';
77
-        $scripts = $this->printScripts(true);
78
-        if (defined('DOING_AJAX')) {
79
-            echo wp_json_encode(array('error' => $output . $scripts));
80
-            exit();
81
-        }
82
-        echo wp_kses($output . $scripts, AllowedTags::getWithScriptAndStyleTags());
83
-    }
77
+		$scripts = $this->printScripts(true);
78
+		if (defined('DOING_AJAX')) {
79
+			echo wp_json_encode(array('error' => $output . $scripts));
80
+			exit();
81
+		}
82
+		echo wp_kses($output . $scripts, AllowedTags::getWithScriptAndStyleTags());
83
+	}
84 84
 
85 85
 
86
-    private function genericError($msg, $code)
87
-    {
88
-        return '
86
+	private function genericError($msg, $code)
87
+	{
88
+		return '
89 89
         <p>
90 90
             <span class="ee-error-user-msg-spn">' . trim($msg) . '</span> &nbsp; <sup>' . $code . '</sup>
91 91
         </p>';
92
-    }
92
+	}
93 93
 
94 94
 
95
-    /**
96
-     * @param Throwable $exception
97
-     * @param $msg
98
-     * @param $code
99
-     * @param $trace_details
100
-     * @return string
101
-     * @throws ReflectionException
102
-     */
103
-    private function developerError(Throwable $exception, $msg, $code, $trace_details)
104
-    {
105
-        $time = time();
106
-        return '
95
+	/**
96
+	 * @param Throwable $exception
97
+	 * @param $msg
98
+	 * @param $code
99
+	 * @param $trace_details
100
+	 * @return string
101
+	 * @throws ReflectionException
102
+	 */
103
+	private function developerError(Throwable $exception, $msg, $code, $trace_details)
104
+	{
105
+		$time = time();
106
+		return '
107 107
         <div class="ee-error-dev-msg-dv">
108 108
             <p class="ee-error-dev-msg-pg">
109 109
                 '
110
-                . sprintf(
111
-                    '%1$s %2$s was thrown!%3$s code: %4$s',
112
-                    '<strong class="ee-error-dev-msg-str">',
113
-                    ucwords(EEH_Inflector::add_indefinite_article(get_class($exception))),
114
-                    '</strong>  &nbsp; <span>',
115
-                    $code . '</span>'
116
-                )
117
-                . '
110
+				. sprintf(
111
+					'%1$s %2$s was thrown!%3$s code: %4$s',
112
+					'<strong class="ee-error-dev-msg-str">',
113
+					ucwords(EEH_Inflector::add_indefinite_article(get_class($exception))),
114
+					'</strong>  &nbsp; <span>',
115
+					$code . '</span>'
116
+				)
117
+				. '
118 118
                 <span class="big-text">' . trim($msg) . '</span>
119 119
                 <a id="display-ee-error-trace-1'
120
-                   . $time
121
-                   . '" class="display-ee-error-trace-lnk small-text" rel="ee-error-trace-1'
122
-                   . $time
123
-                   . '">
120
+				   . $time
121
+				   . '" class="display-ee-error-trace-lnk small-text" rel="ee-error-trace-1'
122
+				   . $time
123
+				   . '">
124 124
                     ' . 'click to view backtrace and class/method details' . '
125 125
                 </a><br />
126 126
                 '
127
-                . $exception->getFile()
128
-                . sprintf(
129
-                    '%1$s( line no: %2$s )%3$s',
130
-                    ' &nbsp; <span class="small-text lt-grey-text">',
131
-                    $exception->getLine(),
132
-                    '</span>'
133
-                )
134
-                . '
127
+				. $exception->getFile()
128
+				. sprintf(
129
+					'%1$s( line no: %2$s )%3$s',
130
+					' &nbsp; <span class="small-text lt-grey-text">',
131
+					$exception->getLine(),
132
+					'</span>'
133
+				)
134
+				. '
135 135
             </p>
136 136
             <div id="ee-error-trace-1'
137
-                   . $time
138
-                   . '-dv" class="ee-error-trace-dv ee-error-trace-dv--hidden">
137
+				   . $time
138
+				   . '-dv" class="ee-error-trace-dv ee-error-trace-dv--hidden">
139 139
                 '
140
-                   . $trace_details
141
-                   . $this->classDetails() . '
140
+				   . $trace_details
141
+				   . $this->classDetails() . '
142 142
             </div>
143 143
         </div>';
144
-    }
144
+	}
145 145
 
146 146
 
147
-    /**
148
-     * @throws ReflectionException
149
-     */
150
-    private function classDetails()
151
-    {
152
-        if (empty($this->class_name)) {
153
-            return '';
154
-        }
155
-        $a = new ReflectionClass($this->class_name);
156
-        return '
147
+	/**
148
+	 * @throws ReflectionException
149
+	 */
150
+	private function classDetails()
151
+	{
152
+		if (empty($this->class_name)) {
153
+			return '';
154
+		}
155
+		$a = new ReflectionClass($this->class_name);
156
+		return '
157 157
             <div style="padding:3px; margin:0 0 1em; border:1px solid #999; background:#fff; border-radius:3px;">
158 158
                 <div style="padding:1em 2em; border:1px solid #999; background:#fcfcfc;">
159 159
                     <h3>Class Details</h3>
160 160
                     <pre>' . $a . '</pre>
161 161
                 </div>
162 162
             </div>';
163
-    }
163
+	}
164 164
 
165
-    /**
166
-     * @param Throwable $exception
167
-     * @return string
168
-     * @throws ReflectionException
169
-     * @since 4.10.24.p
170
-     */
171
-    private function traceDetails(Throwable $exception)
172
-    {
173
-        $trace = $exception->getTrace();
174
-        if (empty($trace)) {
175
-            return 'Sorry, but no trace information was available for this exception.';
176
-        }
165
+	/**
166
+	 * @param Throwable $exception
167
+	 * @return string
168
+	 * @throws ReflectionException
169
+	 * @since 4.10.24.p
170
+	 */
171
+	private function traceDetails(Throwable $exception)
172
+	{
173
+		$trace = $exception->getTrace();
174
+		if (empty($trace)) {
175
+			return 'Sorry, but no trace information was available for this exception.';
176
+		}
177 177
 
178
-        $trace_details = '
178
+		$trace_details = '
179 179
         <div id="ee-trace-details">
180 180
             <table>
181 181
                 <tr>
@@ -186,176 +186,176 @@  discard block
 block discarded – undo
186 186
                         Class -> Method
187 187
                     </th>
188 188
                 </tr>';
189
-        $last_on_stack = count($trace) - 1;
190
-        // reverse array so that stack is in proper chronological order
191
-        $sorted_trace = array_reverse($trace);
192
-        foreach ($sorted_trace as $nmbr => $trace) {
193
-            $this->class_name = isset($trace['class']) ? $trace['class'] : '';
194
-            $file     = isset($trace['file']) ? $trace['file'] : '';
195
-            $type     = isset($trace['type']) ? $trace['type'] : '';
196
-            $function = isset($trace['function']) ? $trace['function'] : '';
197
-            $args     = isset($trace['args']) ? $this->_convert_args_to_string($trace['args']) : '';
198
-            $args     = isset($trace['args']) && count($trace['args']) > 4 ? ' <br />' . $args . '<br />' : $args;
199
-            $line     = isset($trace['line']) ? $trace['line'] : '';
200
-            if (empty($file) && ! empty($this->class_name)) {
201
-                $a    = new ReflectionClass($this->class_name);
202
-                $file = $a->getFileName();
203
-                if (empty($line) && ! empty($function)) {
204
-                    try {
205
-                        // if $function is a closure, this throws an exception
206
-                        $b    = new ReflectionMethod($this->class_name, $function);
207
-                        $line = $b->getStartLine();
208
-                    } catch (Exception $closure_exception) {
209
-                        $line = 'unknown';
210
-                    }
211
-                }
212
-            }
213
-            if ($nmbr === $last_on_stack) {
214
-                $file       = $exception->getFile() ?: $file;
215
-                $line       = $exception->getLine() ?: $line;
216
-                $this->error_code = $this->generate_error_code($file, $trace['function'], $line);
217
-            }
218
-            $file          = EEH_File::standardise_directory_separators($file);
219
-            $nmbr          = ! empty($nmbr) ? $nmbr : '&nbsp;';
220
-            $line          = ! empty($line) ? $line : '&nbsp;';
221
-            $file          = ! empty($file) ? $file : '&nbsp;';
222
-            $type          = ! empty($type) ? $type : '';
223
-            $function      = ! empty($function) ? $function : '';
224
-            $args          = ! empty($args) ? '( ' . $args . ' )' : '()';
225
-            $trace_details .= '
189
+		$last_on_stack = count($trace) - 1;
190
+		// reverse array so that stack is in proper chronological order
191
+		$sorted_trace = array_reverse($trace);
192
+		foreach ($sorted_trace as $nmbr => $trace) {
193
+			$this->class_name = isset($trace['class']) ? $trace['class'] : '';
194
+			$file     = isset($trace['file']) ? $trace['file'] : '';
195
+			$type     = isset($trace['type']) ? $trace['type'] : '';
196
+			$function = isset($trace['function']) ? $trace['function'] : '';
197
+			$args     = isset($trace['args']) ? $this->_convert_args_to_string($trace['args']) : '';
198
+			$args     = isset($trace['args']) && count($trace['args']) > 4 ? ' <br />' . $args . '<br />' : $args;
199
+			$line     = isset($trace['line']) ? $trace['line'] : '';
200
+			if (empty($file) && ! empty($this->class_name)) {
201
+				$a    = new ReflectionClass($this->class_name);
202
+				$file = $a->getFileName();
203
+				if (empty($line) && ! empty($function)) {
204
+					try {
205
+						// if $function is a closure, this throws an exception
206
+						$b    = new ReflectionMethod($this->class_name, $function);
207
+						$line = $b->getStartLine();
208
+					} catch (Exception $closure_exception) {
209
+						$line = 'unknown';
210
+					}
211
+				}
212
+			}
213
+			if ($nmbr === $last_on_stack) {
214
+				$file       = $exception->getFile() ?: $file;
215
+				$line       = $exception->getLine() ?: $line;
216
+				$this->error_code = $this->generate_error_code($file, $trace['function'], $line);
217
+			}
218
+			$file          = EEH_File::standardise_directory_separators($file);
219
+			$nmbr          = ! empty($nmbr) ? $nmbr : '&nbsp;';
220
+			$line          = ! empty($line) ? $line : '&nbsp;';
221
+			$file          = ! empty($file) ? $file : '&nbsp;';
222
+			$type          = ! empty($type) ? $type : '';
223
+			$function      = ! empty($function) ? $function : '';
224
+			$args          = ! empty($args) ? '( ' . $args . ' )' : '()';
225
+			$trace_details .= '
226 226
                 <tr>
227 227
                     <td class="ee-align-right">' . $nmbr . '</td>
228 228
                     <td class="ee-align-right">' . $line . '</td>
229 229
                     <td class="ee-align-left">' . $file . '</td>
230 230
                     <td class="ee-align-left">' . $this->class_name . $type . $function . $args . '</td>
231 231
                 </tr>';
232
-        }
233
-        $trace_details .= '
232
+		}
233
+		$trace_details .= '
234 234
             </table>
235 235
         </div>';
236
-        return $trace_details;
237
-    }
236
+		return $trace_details;
237
+	}
238 238
 
239 239
 
240
-    // phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
241
-    // phpcs:disable PSR2.Methods.MethodDeclaration.Underscore
240
+	// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
241
+	// phpcs:disable PSR2.Methods.MethodDeclaration.Underscore
242 242
 
243
-    /**
244
-     * generate string from exception trace args
245
-     *
246
-     * @param array $arguments
247
-     * @param int   $indent
248
-     * @param bool  $array
249
-     * @return string
250
-     */
251
-    private function _convert_args_to_string($arguments = array(), $indent = 0, $array = false)
252
-    {
253
-        $args = array();
254
-        $args_count = count($arguments);
255
-        if ($args_count > 2) {
256
-            $indent++;
257
-            $args[] = '<br />';
258
-        }
259
-        $x = 0;
260
-        foreach ($arguments as $arg) {
261
-            $x++;
262
-            for ($i = 0; $i < $indent; $i++) {
263
-                $args[] = ' &nbsp;&nbsp; ';
264
-            }
265
-            if (is_string($arg)) {
266
-                if (! $array && strlen($arg) > 75) {
267
-                    $args[] = '<br />';
268
-                    for ($i = 0; $i <= $indent; $i++) {
269
-                        $args[] = ' &nbsp;&nbsp; ';
270
-                    }
271
-                    $args[] = "'" . $arg . "'<br />";
272
-                } else {
273
-                    $args[] = " '" . $arg . "'";
274
-                }
275
-            } elseif (is_array($arg)) {
276
-                $arg_count = count($arg);
277
-                if ($arg_count > 2) {
278
-                    $indent++;
279
-                    $args[] = ' array(' . $this->_convert_args_to_string($arg, $indent, true) . ')';
280
-                    $indent--;
281
-                } elseif ($arg_count === 0) {
282
-                    $args[] = ' array()';
283
-                } else {
284
-                    $args[] = ' array( ' . $this->_convert_args_to_string($arg) . ' )';
285
-                }
286
-            } elseif ($arg === null) {
287
-                $args[] = ' null';
288
-            } elseif (is_bool($arg)) {
289
-                $args[] = $arg ? ' true' : ' false';
290
-            } elseif (is_object($arg)) {
291
-                $args[] = get_class($arg);
292
-            } elseif (is_resource($arg)) {
293
-                $args[] = get_resource_type($arg);
294
-            } else {
295
-                $args[] = $arg;
296
-            }
297
-            if ($x === $args_count) {
298
-                if ($args_count > 2) {
299
-                    $args[] = '<br />';
300
-                    $indent--;
301
-                    for ($i = 1; $i < $indent; $i++) {
302
-                        $args[] = ' &nbsp;&nbsp; ';
303
-                    }
304
-                }
305
-            } else {
306
-                $args[] = $args_count > 2 ? ',<br />' : ', ';
307
-            }
308
-        }
309
-        return implode('', $args);
310
-    }
243
+	/**
244
+	 * generate string from exception trace args
245
+	 *
246
+	 * @param array $arguments
247
+	 * @param int   $indent
248
+	 * @param bool  $array
249
+	 * @return string
250
+	 */
251
+	private function _convert_args_to_string($arguments = array(), $indent = 0, $array = false)
252
+	{
253
+		$args = array();
254
+		$args_count = count($arguments);
255
+		if ($args_count > 2) {
256
+			$indent++;
257
+			$args[] = '<br />';
258
+		}
259
+		$x = 0;
260
+		foreach ($arguments as $arg) {
261
+			$x++;
262
+			for ($i = 0; $i < $indent; $i++) {
263
+				$args[] = ' &nbsp;&nbsp; ';
264
+			}
265
+			if (is_string($arg)) {
266
+				if (! $array && strlen($arg) > 75) {
267
+					$args[] = '<br />';
268
+					for ($i = 0; $i <= $indent; $i++) {
269
+						$args[] = ' &nbsp;&nbsp; ';
270
+					}
271
+					$args[] = "'" . $arg . "'<br />";
272
+				} else {
273
+					$args[] = " '" . $arg . "'";
274
+				}
275
+			} elseif (is_array($arg)) {
276
+				$arg_count = count($arg);
277
+				if ($arg_count > 2) {
278
+					$indent++;
279
+					$args[] = ' array(' . $this->_convert_args_to_string($arg, $indent, true) . ')';
280
+					$indent--;
281
+				} elseif ($arg_count === 0) {
282
+					$args[] = ' array()';
283
+				} else {
284
+					$args[] = ' array( ' . $this->_convert_args_to_string($arg) . ' )';
285
+				}
286
+			} elseif ($arg === null) {
287
+				$args[] = ' null';
288
+			} elseif (is_bool($arg)) {
289
+				$args[] = $arg ? ' true' : ' false';
290
+			} elseif (is_object($arg)) {
291
+				$args[] = get_class($arg);
292
+			} elseif (is_resource($arg)) {
293
+				$args[] = get_resource_type($arg);
294
+			} else {
295
+				$args[] = $arg;
296
+			}
297
+			if ($x === $args_count) {
298
+				if ($args_count > 2) {
299
+					$args[] = '<br />';
300
+					$indent--;
301
+					for ($i = 1; $i < $indent; $i++) {
302
+						$args[] = ' &nbsp;&nbsp; ';
303
+					}
304
+				}
305
+			} else {
306
+				$args[] = $args_count > 2 ? ',<br />' : ', ';
307
+			}
308
+		}
309
+		return implode('', $args);
310
+	}
311 311
 
312 312
 
313
-    /**
314
-     * create error code from filepath, function name,
315
-     * and line number where exception or error was thrown
316
-     *
317
-     * @access protected
318
-     * @param string $file
319
-     * @param string $func
320
-     * @param string $line
321
-     * @return string
322
-     */
323
-    protected function generate_error_code($file = '', $func = '', $line = '')
324
-    {
325
-        $file_bits = explode('.', basename($file));
326
-        $error_code = ! empty($file_bits[0]) ? $file_bits[0] : '';
327
-        $error_code .= ! empty($func) ? ' - ' . $func : '';
328
-        $error_code .= ! empty($line) ? ' - ' . $line : '';
329
-        return $error_code;
330
-    }
313
+	/**
314
+	 * create error code from filepath, function name,
315
+	 * and line number where exception or error was thrown
316
+	 *
317
+	 * @access protected
318
+	 * @param string $file
319
+	 * @param string $func
320
+	 * @param string $line
321
+	 * @return string
322
+	 */
323
+	protected function generate_error_code($file = '', $func = '', $line = '')
324
+	{
325
+		$file_bits = explode('.', basename($file));
326
+		$error_code = ! empty($file_bits[0]) ? $file_bits[0] : '';
327
+		$error_code .= ! empty($func) ? ' - ' . $func : '';
328
+		$error_code .= ! empty($line) ? ' - ' . $line : '';
329
+		return $error_code;
330
+	}
331 331
 
332 332
 
333
-    /**
334
-     * _print_scripts
335
-     *
336
-     * @param bool $force_print
337
-     * @return string
338
-     */
339
-    private function printScripts($force_print = false)
340
-    {
341
-        if (! $force_print && (did_action('admin_enqueue_scripts') || did_action('wp_enqueue_scripts'))) {
342
-            //  if script is already enqueued then we can just get out
343
-            if (wp_script_is('ee_error_js')) {
344
-                return '';
345
-            }
346
-            if (wp_script_is('ee_error_js', 'registered')) {
347
-                wp_enqueue_style('espresso_default');
348
-                wp_enqueue_style('espresso_custom_css');
349
-                wp_enqueue_script('ee_error_js');
350
-                wp_localize_script('ee_error_js', 'ee_settings', array('wp_debug' => WP_DEBUG));
351
-                return '';
352
-            }
353
-        }
354
-        $jquery = includes_url() . 'js/jquery/jquery.js';
355
-        $core = EE_GLOBAL_ASSETS_URL . 'scripts/espresso_core.js?ver=' . espresso_version();
356
-        $ee_error = EE_GLOBAL_ASSETS_URL . 'scripts/EE_Error.js?ver=' . espresso_version();
357
-        $style = EE_GLOBAL_ASSETS_URL . 'css/ee-exception-stack-display.css';
358
-        return '
333
+	/**
334
+	 * _print_scripts
335
+	 *
336
+	 * @param bool $force_print
337
+	 * @return string
338
+	 */
339
+	private function printScripts($force_print = false)
340
+	{
341
+		if (! $force_print && (did_action('admin_enqueue_scripts') || did_action('wp_enqueue_scripts'))) {
342
+			//  if script is already enqueued then we can just get out
343
+			if (wp_script_is('ee_error_js')) {
344
+				return '';
345
+			}
346
+			if (wp_script_is('ee_error_js', 'registered')) {
347
+				wp_enqueue_style('espresso_default');
348
+				wp_enqueue_style('espresso_custom_css');
349
+				wp_enqueue_script('ee_error_js');
350
+				wp_localize_script('ee_error_js', 'ee_settings', array('wp_debug' => WP_DEBUG));
351
+				return '';
352
+			}
353
+		}
354
+		$jquery = includes_url() . 'js/jquery/jquery.js';
355
+		$core = EE_GLOBAL_ASSETS_URL . 'scripts/espresso_core.js?ver=' . espresso_version();
356
+		$ee_error = EE_GLOBAL_ASSETS_URL . 'scripts/EE_Error.js?ver=' . espresso_version();
357
+		$style = EE_GLOBAL_ASSETS_URL . 'css/ee-exception-stack-display.css';
358
+		return '
359 359
             <script>
360 360
             const ee_settings = {"wp_debug":"' . WP_DEBUG . '"};
361 361
             </script>
@@ -364,5 +364,5 @@  discard block
 block discarded – undo
364 364
             <script src="' . esc_url_raw($ee_error) . '" type="text/javascript"></script>
365 365
             <link href="' . esc_url_raw($style) . '" rel="stylesheet" />
366 366
         ';
367
-    }
367
+	}
368 368
 }
Please login to merge, or discard this patch.
Spacing   +32 added lines, -32 removed lines patch added patch discarded remove patch
@@ -72,14 +72,14 @@  discard block
 block discarded – undo
72 72
         // start gathering output
73 73
         $output = '
74 74
         <div id="ee-error-message" class="ee-exception-stack-trace-display" >
75
-            ' . $error_message . '
75
+            ' . $error_message.'
76 76
         </div>';
77 77
         $scripts = $this->printScripts(true);
78 78
         if (defined('DOING_AJAX')) {
79
-            echo wp_json_encode(array('error' => $output . $scripts));
79
+            echo wp_json_encode(array('error' => $output.$scripts));
80 80
             exit();
81 81
         }
82
-        echo wp_kses($output . $scripts, AllowedTags::getWithScriptAndStyleTags());
82
+        echo wp_kses($output.$scripts, AllowedTags::getWithScriptAndStyleTags());
83 83
     }
84 84
 
85 85
 
@@ -87,7 +87,7 @@  discard block
 block discarded – undo
87 87
     {
88 88
         return '
89 89
         <p>
90
-            <span class="ee-error-user-msg-spn">' . trim($msg) . '</span> &nbsp; <sup>' . $code . '</sup>
90
+            <span class="ee-error-user-msg-spn">' . trim($msg).'</span> &nbsp; <sup>'.$code.'</sup>
91 91
         </p>';
92 92
     }
93 93
 
@@ -112,16 +112,16 @@  discard block
 block discarded – undo
112 112
                     '<strong class="ee-error-dev-msg-str">',
113 113
                     ucwords(EEH_Inflector::add_indefinite_article(get_class($exception))),
114 114
                     '</strong>  &nbsp; <span>',
115
-                    $code . '</span>'
115
+                    $code.'</span>'
116 116
                 )
117 117
                 . '
118
-                <span class="big-text">' . trim($msg) . '</span>
118
+                <span class="big-text">' . trim($msg).'</span>
119 119
                 <a id="display-ee-error-trace-1'
120 120
                    . $time
121 121
                    . '" class="display-ee-error-trace-lnk small-text" rel="ee-error-trace-1'
122 122
                    . $time
123 123
                    . '">
124
-                    ' . 'click to view backtrace and class/method details' . '
124
+                    ' . 'click to view backtrace and class/method details'.'
125 125
                 </a><br />
126 126
                 '
127 127
                 . $exception->getFile()
@@ -138,7 +138,7 @@  discard block
 block discarded – undo
138 138
                    . '-dv" class="ee-error-trace-dv ee-error-trace-dv--hidden">
139 139
                 '
140 140
                    . $trace_details
141
-                   . $this->classDetails() . '
141
+                   . $this->classDetails().'
142 142
             </div>
143 143
         </div>';
144 144
     }
@@ -157,7 +157,7 @@  discard block
 block discarded – undo
157 157
             <div style="padding:3px; margin:0 0 1em; border:1px solid #999; background:#fff; border-radius:3px;">
158 158
                 <div style="padding:1em 2em; border:1px solid #999; background:#fcfcfc;">
159 159
                     <h3>Class Details</h3>
160
-                    <pre>' . $a . '</pre>
160
+                    <pre>' . $a.'</pre>
161 161
                 </div>
162 162
             </div>';
163 163
     }
@@ -195,7 +195,7 @@  discard block
 block discarded – undo
195 195
             $type     = isset($trace['type']) ? $trace['type'] : '';
196 196
             $function = isset($trace['function']) ? $trace['function'] : '';
197 197
             $args     = isset($trace['args']) ? $this->_convert_args_to_string($trace['args']) : '';
198
-            $args     = isset($trace['args']) && count($trace['args']) > 4 ? ' <br />' . $args . '<br />' : $args;
198
+            $args     = isset($trace['args']) && count($trace['args']) > 4 ? ' <br />'.$args.'<br />' : $args;
199 199
             $line     = isset($trace['line']) ? $trace['line'] : '';
200 200
             if (empty($file) && ! empty($this->class_name)) {
201 201
                 $a    = new ReflectionClass($this->class_name);
@@ -221,13 +221,13 @@  discard block
 block discarded – undo
221 221
             $file          = ! empty($file) ? $file : '&nbsp;';
222 222
             $type          = ! empty($type) ? $type : '';
223 223
             $function      = ! empty($function) ? $function : '';
224
-            $args          = ! empty($args) ? '( ' . $args . ' )' : '()';
224
+            $args          = ! empty($args) ? '( '.$args.' )' : '()';
225 225
             $trace_details .= '
226 226
                 <tr>
227
-                    <td class="ee-align-right">' . $nmbr . '</td>
228
-                    <td class="ee-align-right">' . $line . '</td>
229
-                    <td class="ee-align-left">' . $file . '</td>
230
-                    <td class="ee-align-left">' . $this->class_name . $type . $function . $args . '</td>
227
+                    <td class="ee-align-right">' . $nmbr.'</td>
228
+                    <td class="ee-align-right">' . $line.'</td>
229
+                    <td class="ee-align-left">' . $file.'</td>
230
+                    <td class="ee-align-left">' . $this->class_name.$type.$function.$args.'</td>
231 231
                 </tr>';
232 232
         }
233 233
         $trace_details .= '
@@ -263,25 +263,25 @@  discard block
 block discarded – undo
263 263
                 $args[] = ' &nbsp;&nbsp; ';
264 264
             }
265 265
             if (is_string($arg)) {
266
-                if (! $array && strlen($arg) > 75) {
266
+                if ( ! $array && strlen($arg) > 75) {
267 267
                     $args[] = '<br />';
268 268
                     for ($i = 0; $i <= $indent; $i++) {
269 269
                         $args[] = ' &nbsp;&nbsp; ';
270 270
                     }
271
-                    $args[] = "'" . $arg . "'<br />";
271
+                    $args[] = "'".$arg."'<br />";
272 272
                 } else {
273
-                    $args[] = " '" . $arg . "'";
273
+                    $args[] = " '".$arg."'";
274 274
                 }
275 275
             } elseif (is_array($arg)) {
276 276
                 $arg_count = count($arg);
277 277
                 if ($arg_count > 2) {
278 278
                     $indent++;
279
-                    $args[] = ' array(' . $this->_convert_args_to_string($arg, $indent, true) . ')';
279
+                    $args[] = ' array('.$this->_convert_args_to_string($arg, $indent, true).')';
280 280
                     $indent--;
281 281
                 } elseif ($arg_count === 0) {
282 282
                     $args[] = ' array()';
283 283
                 } else {
284
-                    $args[] = ' array( ' . $this->_convert_args_to_string($arg) . ' )';
284
+                    $args[] = ' array( '.$this->_convert_args_to_string($arg).' )';
285 285
                 }
286 286
             } elseif ($arg === null) {
287 287
                 $args[] = ' null';
@@ -324,8 +324,8 @@  discard block
 block discarded – undo
324 324
     {
325 325
         $file_bits = explode('.', basename($file));
326 326
         $error_code = ! empty($file_bits[0]) ? $file_bits[0] : '';
327
-        $error_code .= ! empty($func) ? ' - ' . $func : '';
328
-        $error_code .= ! empty($line) ? ' - ' . $line : '';
327
+        $error_code .= ! empty($func) ? ' - '.$func : '';
328
+        $error_code .= ! empty($line) ? ' - '.$line : '';
329 329
         return $error_code;
330 330
     }
331 331
 
@@ -338,7 +338,7 @@  discard block
 block discarded – undo
338 338
      */
339 339
     private function printScripts($force_print = false)
340 340
     {
341
-        if (! $force_print && (did_action('admin_enqueue_scripts') || did_action('wp_enqueue_scripts'))) {
341
+        if ( ! $force_print && (did_action('admin_enqueue_scripts') || did_action('wp_enqueue_scripts'))) {
342 342
             //  if script is already enqueued then we can just get out
343 343
             if (wp_script_is('ee_error_js')) {
344 344
                 return '';
@@ -351,18 +351,18 @@  discard block
 block discarded – undo
351 351
                 return '';
352 352
             }
353 353
         }
354
-        $jquery = includes_url() . 'js/jquery/jquery.js';
355
-        $core = EE_GLOBAL_ASSETS_URL . 'scripts/espresso_core.js?ver=' . espresso_version();
356
-        $ee_error = EE_GLOBAL_ASSETS_URL . 'scripts/EE_Error.js?ver=' . espresso_version();
357
-        $style = EE_GLOBAL_ASSETS_URL . 'css/ee-exception-stack-display.css';
354
+        $jquery = includes_url().'js/jquery/jquery.js';
355
+        $core = EE_GLOBAL_ASSETS_URL.'scripts/espresso_core.js?ver='.espresso_version();
356
+        $ee_error = EE_GLOBAL_ASSETS_URL.'scripts/EE_Error.js?ver='.espresso_version();
357
+        $style = EE_GLOBAL_ASSETS_URL.'css/ee-exception-stack-display.css';
358 358
         return '
359 359
             <script>
360
-            const ee_settings = {"wp_debug":"' . WP_DEBUG . '"};
360
+            const ee_settings = {"wp_debug":"' . WP_DEBUG.'"};
361 361
             </script>
362
-            <script src="' . esc_url_raw($jquery) . '" type="text/javascript"></script>
363
-            <script src="' . esc_url_raw($core) . '" type="text/javascript"></script>
364
-            <script src="' . esc_url_raw($ee_error) . '" type="text/javascript"></script>
365
-            <link href="' . esc_url_raw($style) . '" rel="stylesheet" />
362
+            <script src="' . esc_url_raw($jquery).'" type="text/javascript"></script>
363
+            <script src="' . esc_url_raw($core).'" type="text/javascript"></script>
364
+            <script src="' . esc_url_raw($ee_error).'" type="text/javascript"></script>
365
+            <link href="' . esc_url_raw($style).'" rel="stylesheet" />
366 366
         ';
367 367
     }
368 368
 }
Please login to merge, or discard this patch.