Completed
Branch master (f114e9)
by
unknown
10:01 queued 03:45
created
espresso.php 1 patch
Indentation   +107 added lines, -107 removed lines patch added patch discarded remove patch
@@ -37,138 +37,138 @@
 block discarded – undo
37 37
  * @since       4.0
38 38
  */
39 39
 if (function_exists('espresso_version')) {
40
-    if (! function_exists('espresso_duplicate_plugin_error')) {
41
-        /**
42
-         *    espresso_duplicate_plugin_error
43
-         *    displays if more than one version of EE is activated at the same time.
44
-         */
45
-        function espresso_duplicate_plugin_error()
46
-        {
47
-            ?>
40
+	if (! function_exists('espresso_duplicate_plugin_error')) {
41
+		/**
42
+		 *    espresso_duplicate_plugin_error
43
+		 *    displays if more than one version of EE is activated at the same time.
44
+		 */
45
+		function espresso_duplicate_plugin_error()
46
+		{
47
+			?>
48 48
 <div class="error">
49 49
     <p>
50 50
         <?php
51
-                    echo esc_html__(
52
-                        'Can not run multiple versions of Event Espresso! One version has been automatically deactivated. Please verify that you have the correct version you want still active.',
53
-                        'event_espresso'
54
-                    ); ?>
51
+					echo esc_html__(
52
+						'Can not run multiple versions of Event Espresso! One version has been automatically deactivated. Please verify that you have the correct version you want still active.',
53
+						'event_espresso'
54
+					); ?>
55 55
     </p>
56 56
 </div>
57 57
 <?php
58
-            espresso_deactivate_plugin(plugin_basename(__FILE__));
59
-        }
60
-    }
61
-    add_action('admin_notices', 'espresso_duplicate_plugin_error', 1);
58
+			espresso_deactivate_plugin(plugin_basename(__FILE__));
59
+		}
60
+	}
61
+	add_action('admin_notices', 'espresso_duplicate_plugin_error', 1);
62 62
 } else {
63
-    define('EE_MIN_PHP_VER_REQUIRED', '7.4.0');
64
-    if (! version_compare(PHP_VERSION, EE_MIN_PHP_VER_REQUIRED, '>=')) {
65
-        /**
66
-         * espresso_minimum_php_version_error
67
-         *
68
-         * @return void
69
-         */
70
-        function espresso_minimum_php_version_error()
71
-        {
72
-            ?>
63
+	define('EE_MIN_PHP_VER_REQUIRED', '7.4.0');
64
+	if (! version_compare(PHP_VERSION, EE_MIN_PHP_VER_REQUIRED, '>=')) {
65
+		/**
66
+		 * espresso_minimum_php_version_error
67
+		 *
68
+		 * @return void
69
+		 */
70
+		function espresso_minimum_php_version_error()
71
+		{
72
+			?>
73 73
 <div class="error">
74 74
     <p>
75 75
         <?php
76
-                    printf(
77
-                        esc_html__(
78
-                            'We\'re sorry, but Event Espresso requires PHP version %1$s or greater in order to operate. You are currently running version %2$s.%3$sIn order to update your version of PHP, you will need to contact your current hosting provider.%3$sFor information on stable PHP versions, please go to %4$s.',
79
-                            'event_espresso'
80
-                        ),
81
-                        EE_MIN_PHP_VER_REQUIRED,
82
-                        PHP_VERSION,
83
-                        '<br/>',
84
-                        '<a href="https://www.php.net/downloads.php">https://php.net/downloads.php</a>'
85
-                    );
86
-        ?>
76
+					printf(
77
+						esc_html__(
78
+							'We\'re sorry, but Event Espresso requires PHP version %1$s or greater in order to operate. You are currently running version %2$s.%3$sIn order to update your version of PHP, you will need to contact your current hosting provider.%3$sFor information on stable PHP versions, please go to %4$s.',
79
+							'event_espresso'
80
+						),
81
+						EE_MIN_PHP_VER_REQUIRED,
82
+						PHP_VERSION,
83
+						'<br/>',
84
+						'<a href="https://www.php.net/downloads.php">https://php.net/downloads.php</a>'
85
+					);
86
+		?>
87 87
     </p>
88 88
 </div>
89 89
 <?php
90
-            espresso_deactivate_plugin(plugin_basename(__FILE__));
91
-        }
90
+			espresso_deactivate_plugin(plugin_basename(__FILE__));
91
+		}
92 92
 
93
-        add_action('admin_notices', 'espresso_minimum_php_version_error', 1);
94
-    } else {
95
-        define('EVENT_ESPRESSO_MAIN_FILE', __FILE__);
93
+		add_action('admin_notices', 'espresso_minimum_php_version_error', 1);
94
+	} else {
95
+		define('EVENT_ESPRESSO_MAIN_FILE', __FILE__);
96 96
 
97
-        require_once __DIR__ . '/vendor/autoload.php';
97
+		require_once __DIR__ . '/vendor/autoload.php';
98 98
 
99
-        /**
100
-         * espresso_version
101
-         * Returns the plugin version
102
-         *
103
-         * @return string
104
-         */
105
-        function espresso_version(): string
106
-        {
107
-            return apply_filters('FHEE__espresso__espresso_version', '5.0.46');
108
-        }
99
+		/**
100
+		 * espresso_version
101
+		 * Returns the plugin version
102
+		 *
103
+		 * @return string
104
+		 */
105
+		function espresso_version(): string
106
+		{
107
+			return apply_filters('FHEE__espresso__espresso_version', '5.0.46');
108
+		}
109 109
 
110
-        /**
111
-         * espresso_plugin_activation
112
-         * adds a wp-option to indicate that EE has been activated via the WP admin plugins page
113
-         */
114
-        function espresso_plugin_activation()
115
-        {
116
-            update_option('ee_espresso_activation', true);
117
-            update_option('event-espresso-core_allow_tracking', 'no');
118
-            update_option('event-espresso-core_tracking_notice', 'hide');
119
-            // Run WP GraphQL activation callback
120
-            espressoLoadWpGraphQL();
121
-            graphql_activation_callback();
122
-        }
110
+		/**
111
+		 * espresso_plugin_activation
112
+		 * adds a wp-option to indicate that EE has been activated via the WP admin plugins page
113
+		 */
114
+		function espresso_plugin_activation()
115
+		{
116
+			update_option('ee_espresso_activation', true);
117
+			update_option('event-espresso-core_allow_tracking', 'no');
118
+			update_option('event-espresso-core_tracking_notice', 'hide');
119
+			// Run WP GraphQL activation callback
120
+			espressoLoadWpGraphQL();
121
+			graphql_activation_callback();
122
+		}
123 123
 
124
-        register_activation_hook(EVENT_ESPRESSO_MAIN_FILE, 'espresso_plugin_activation');
124
+		register_activation_hook(EVENT_ESPRESSO_MAIN_FILE, 'espresso_plugin_activation');
125 125
 
126
-        /**
127
-         * espresso_plugin_deactivation
128
-         */
129
-        function espresso_plugin_deactivation()
130
-        {
131
-            // Run WP GraphQL deactivation callback
132
-            espressoLoadWpGraphQL();
133
-            graphql_deactivation_callback();
134
-            delete_option('event-espresso-core_allow_tracking');
135
-            delete_option('event-espresso-core_tracking_notice');
136
-        }
137
-        register_deactivation_hook(EVENT_ESPRESSO_MAIN_FILE, 'espresso_plugin_deactivation');
126
+		/**
127
+		 * espresso_plugin_deactivation
128
+		 */
129
+		function espresso_plugin_deactivation()
130
+		{
131
+			// Run WP GraphQL deactivation callback
132
+			espressoLoadWpGraphQL();
133
+			graphql_deactivation_callback();
134
+			delete_option('event-espresso-core_allow_tracking');
135
+			delete_option('event-espresso-core_tracking_notice');
136
+		}
137
+		register_deactivation_hook(EVENT_ESPRESSO_MAIN_FILE, 'espresso_plugin_deactivation');
138 138
 
139
-        require_once __DIR__ . '/core/bootstrap_espresso.php';
140
-        bootstrap_espresso();
141
-    }
139
+		require_once __DIR__ . '/core/bootstrap_espresso.php';
140
+		bootstrap_espresso();
141
+	}
142 142
 }
143 143
 
144 144
 if (! function_exists('espresso_deactivate_plugin')) {
145
-    /**
146
-     *    deactivate_plugin
147
-     * usage:  espresso_deactivate_plugin( plugin_basename( __FILE__ ));
148
-     *
149
-     * @access public
150
-     * @param string $plugin_basename - the results of plugin_basename( __FILE__ ) for the plugin's main file
151
-     * @return    void
152
-     */
153
-    function espresso_deactivate_plugin(string $plugin_basename = '')
154
-    {
155
-        if (! function_exists('deactivate_plugins')) {
156
-            require_once ABSPATH . 'wp-admin/includes/plugin.php';
157
-        }
158
-        unset($_GET['activate'], $_REQUEST['activate']);
159
-        deactivate_plugins($plugin_basename);
160
-    }
145
+	/**
146
+	 *    deactivate_plugin
147
+	 * usage:  espresso_deactivate_plugin( plugin_basename( __FILE__ ));
148
+	 *
149
+	 * @access public
150
+	 * @param string $plugin_basename - the results of plugin_basename( __FILE__ ) for the plugin's main file
151
+	 * @return    void
152
+	 */
153
+	function espresso_deactivate_plugin(string $plugin_basename = '')
154
+	{
155
+		if (! function_exists('deactivate_plugins')) {
156
+			require_once ABSPATH . 'wp-admin/includes/plugin.php';
157
+		}
158
+		unset($_GET['activate'], $_REQUEST['activate']);
159
+		deactivate_plugins($plugin_basename);
160
+	}
161 161
 }
162 162
 
163 163
 
164 164
 if (! function_exists('espressoLoadWpGraphQL')) {
165
-    function espressoLoadWpGraphQL()
166
-    {
167
-        if (
168
-            ! class_exists('WPGraphQL')
169
-            && is_readable(__DIR__ . '/vendor/wp-graphql/wp-graphql/wp-graphql.php')
170
-        ) {
171
-            require_once __DIR__ . '/vendor/wp-graphql/wp-graphql/wp-graphql.php';
172
-        }
173
-    }
165
+	function espressoLoadWpGraphQL()
166
+	{
167
+		if (
168
+			! class_exists('WPGraphQL')
169
+			&& is_readable(__DIR__ . '/vendor/wp-graphql/wp-graphql/wp-graphql.php')
170
+		) {
171
+			require_once __DIR__ . '/vendor/wp-graphql/wp-graphql/wp-graphql.php';
172
+		}
173
+	}
174 174
 }
Please login to merge, or discard this patch.
core/db_models/EEM_Country.model.php 1 patch
Indentation   +211 added lines, -211 removed lines patch added patch discarded remove patch
@@ -12,215 +12,215 @@
 block discarded – undo
12 12
  */
13 13
 class EEM_Country extends EEM_Base
14 14
 {
15
-    protected static ?EEM_Country $_instance = null;
16
-
17
-    // array of all countries
18
-    private static ?array $_all_countries = null;
19
-
20
-    // array of all active countries
21
-    private static ?array $_active_countries = null;
22
-
23
-
24
-    /**
25
-     * Resets the country
26
-     *
27
-     * @param string|null $timezone
28
-     * @return EEM_Country|null
29
-     * @throws EE_Error
30
-     * @throws ReflectionException
31
-     */
32
-    public static function reset(?string $timezone = ''): ?EEM_Country
33
-    {
34
-        self::$_active_countries = null;
35
-        self::$_all_countries    = null;
36
-        return parent::reset($timezone);
37
-    }
38
-
39
-
40
-    /**
41
-     * @param string|null $timezone
42
-     * @throws EE_Error
43
-     */
44
-    protected function __construct(?string $timezone = '')
45
-    {
46
-        $this->singular_item = esc_html__('Country', 'event_espresso');
47
-        $this->plural_item   = esc_html__('Countries', 'event_espresso');
48
-
49
-        $this->_tables = [
50
-            'Country' => new EE_Primary_Table('esp_country', 'CNT_ISO'),
51
-        ];
52
-
53
-        $this->_fields          = [
54
-            'Country' => [
55
-                'CNT_active'      => new EE_Boolean_Field(
56
-                    'CNT_active',
57
-                    esc_html__('Country Appears in Dropdown Select Lists', 'event_espresso'),
58
-                    false,
59
-                    true
60
-                ),
61
-                'CNT_ISO'         => new EE_Primary_Key_String_Field(
62
-                    'CNT_ISO',
63
-                    esc_html__('Country ISO Code', 'event_espresso')
64
-                ),
65
-                'CNT_ISO3'        => new EE_All_Caps_Text_Field(
66
-                    'CNT_ISO3',
67
-                    esc_html__('Country ISO3 Code', 'event_espresso'),
68
-                    false,
69
-                    ''
70
-                ),
71
-                'RGN_ID'          => new EE_Integer_Field(
72
-                    'RGN_ID',
73
-                    esc_html__('Region ID', 'event_espresso'),
74
-                    false,
75
-                    0
76
-                ),
77
-                // should be a foreign key, but no region table exists yet
78
-                'CNT_name'        => new EE_Plain_Text_Field(
79
-                    'CNT_name',
80
-                    esc_html__('Country Name', 'event_espresso'),
81
-                    false,
82
-                    ''
83
-                ),
84
-                'CNT_cur_code'    => new EE_All_Caps_Text_Field(
85
-                    'CNT_cur_code',
86
-                    esc_html__('Country Currency Code', 'event_espresso'),
87
-                    false
88
-                ),
89
-                'CNT_cur_single'  => new EE_Plain_Text_Field(
90
-                    'CNT_cur_single',
91
-                    esc_html__('Currency Name Singular', 'event_espresso'),
92
-                    false
93
-                ),
94
-                'CNT_cur_plural'  => new EE_Plain_Text_Field(
95
-                    'CNT_cur_plural',
96
-                    esc_html__('Currency Name Plural', 'event_espresso'),
97
-                    false
98
-                ),
99
-                'CNT_cur_sign'    => new EE_Plain_Text_Field(
100
-                    'CNT_cur_sign',
101
-                    esc_html__('Currency Sign', 'event_espresso'),
102
-                    false
103
-                ),
104
-                'CNT_cur_sign_b4' => new EE_Boolean_Field(
105
-                    'CNT_cur_sign_b4',
106
-                    esc_html__('Currency Sign Before Number', 'event_espresso'),
107
-                    false,
108
-                    true
109
-                ),
110
-                'CNT_cur_dec_plc' => new EE_Integer_Field(
111
-                    'CNT_cur_dec_plc',
112
-                    esc_html__('Currency Decimal Places', 'event_espresso'),
113
-                    false,
114
-                    2
115
-                ),
116
-                'CNT_cur_dec_mrk' => new EE_Plain_Text_Field(
117
-                    'CNT_cur_dec_mrk',
118
-                    esc_html__('Currency Decimal Mark', 'event_espresso'),
119
-                    false,
120
-                    '.'
121
-                ),
122
-                'CNT_cur_thsnds'  => new EE_Plain_Text_Field(
123
-                    'CNT_cur_thsnds',
124
-                    esc_html__('Currency Thousands Separator', 'event_espresso'),
125
-                    false,
126
-                    ','
127
-                ),
128
-                'CNT_tel_code'    => new EE_Plain_Text_Field(
129
-                    'CNT_tel_code',
130
-                    esc_html__('Country Telephone Code', 'event_espresso'),
131
-                    false,
132
-                    ''
133
-                ),
134
-                'CNT_is_EU'       => new EE_Boolean_Field(
135
-                    'CNT_is_EU',
136
-                    esc_html__('Country is Member of EU', 'event_espresso'),
137
-                    false,
138
-                    false
139
-                ),
140
-            ],
141
-        ];
142
-        $this->_model_relations = [
143
-            'Attendee' => new EE_Has_Many_Relation(),
144
-            'State'    => new EE_Has_Many_Relation(),
145
-            'Venue'    => new EE_Has_Many_Relation(),
146
-        ];
147
-        // only anyone to view, but only those with the default role can do anything
148
-        $this->_cap_restriction_generators[ EEM_Base::caps_read ] = new EE_Restriction_Generator_Public();
149
-
150
-        parent::__construct($timezone);
151
-    }
152
-
153
-
154
-    /**
155
-     * @return EE_Country[]|null
156
-     * @throws EE_Error
157
-     * @throws ReflectionException
158
-     */
159
-    public function get_all_countries(): ?array
160
-    {
161
-        if (! self::$_all_countries) {
162
-            self::$_all_countries = $this->get_all(['order_by' => ['CNT_name' => 'ASC'], 'limit' => [0, 99999]]);
163
-        }
164
-        return self::$_all_countries;
165
-    }
166
-
167
-
168
-    /**
169
-     * Gets and caches the list of active countries. If you know the list of active countries
170
-     * has changed during this request, first use EEM_Country::reset() to flush the cache
171
-     *
172
-     * @return EE_Country[]|null
173
-     * @throws EE_Error
174
-     * @throws ReflectionException
175
-     */
176
-    public function get_all_active_countries(): ?array
177
-    {
178
-        if (! self::$_active_countries) {
179
-            self::$_active_countries =
180
-                $this->get_all([['CNT_active' => true], 'order_by' => ['CNT_name' => 'ASC'], 'limit' => [0, 99999]]);
181
-        }
182
-        return self::$_active_countries;
183
-    }
184
-
185
-
186
-    /**
187
-     * Gets the country's name by its ISO
188
-     *
189
-     * @param string $country_ISO
190
-     * @return string
191
-     * @throws EE_Error
192
-     * @throws ReflectionException
193
-     */
194
-    public function get_country_name_by_ISO(string $country_ISO): string
195
-    {
196
-        $countries = $this->get_all_countries();
197
-        if (isset($countries[ $country_ISO ]) && $countries[ $country_ISO ] instanceof EE_Country) {
198
-            return $countries[ $country_ISO ]->name();
199
-        }
200
-        $names = $this->get_col([['CNT_ISO' => $country_ISO], 'limit' => 1], 'CNT_name');
201
-        if (is_array($names) && ! empty($names)) {
202
-            return reset($names);
203
-        }
204
-        return '';
205
-    }
206
-
207
-
208
-    /**
209
-     * Gets the country's name by its name
210
-     *
211
-     * @param string $country_name
212
-     * @return EE_Country|null
213
-     * @throws EE_Error
214
-     * @throws ReflectionException
215
-     */
216
-    public function getCountryByName(string $country_name): ?EE_Country
217
-    {
218
-        $countries = $this->get_all_countries();
219
-        foreach ($countries as $country) {
220
-            if ($country instanceof EE_Country && $country->name() === $country_name) {
221
-                return $country;
222
-            }
223
-        }
224
-        return $this->get_one([['CNT_name' => $country_name]]);
225
-    }
15
+	protected static ?EEM_Country $_instance = null;
16
+
17
+	// array of all countries
18
+	private static ?array $_all_countries = null;
19
+
20
+	// array of all active countries
21
+	private static ?array $_active_countries = null;
22
+
23
+
24
+	/**
25
+	 * Resets the country
26
+	 *
27
+	 * @param string|null $timezone
28
+	 * @return EEM_Country|null
29
+	 * @throws EE_Error
30
+	 * @throws ReflectionException
31
+	 */
32
+	public static function reset(?string $timezone = ''): ?EEM_Country
33
+	{
34
+		self::$_active_countries = null;
35
+		self::$_all_countries    = null;
36
+		return parent::reset($timezone);
37
+	}
38
+
39
+
40
+	/**
41
+	 * @param string|null $timezone
42
+	 * @throws EE_Error
43
+	 */
44
+	protected function __construct(?string $timezone = '')
45
+	{
46
+		$this->singular_item = esc_html__('Country', 'event_espresso');
47
+		$this->plural_item   = esc_html__('Countries', 'event_espresso');
48
+
49
+		$this->_tables = [
50
+			'Country' => new EE_Primary_Table('esp_country', 'CNT_ISO'),
51
+		];
52
+
53
+		$this->_fields          = [
54
+			'Country' => [
55
+				'CNT_active'      => new EE_Boolean_Field(
56
+					'CNT_active',
57
+					esc_html__('Country Appears in Dropdown Select Lists', 'event_espresso'),
58
+					false,
59
+					true
60
+				),
61
+				'CNT_ISO'         => new EE_Primary_Key_String_Field(
62
+					'CNT_ISO',
63
+					esc_html__('Country ISO Code', 'event_espresso')
64
+				),
65
+				'CNT_ISO3'        => new EE_All_Caps_Text_Field(
66
+					'CNT_ISO3',
67
+					esc_html__('Country ISO3 Code', 'event_espresso'),
68
+					false,
69
+					''
70
+				),
71
+				'RGN_ID'          => new EE_Integer_Field(
72
+					'RGN_ID',
73
+					esc_html__('Region ID', 'event_espresso'),
74
+					false,
75
+					0
76
+				),
77
+				// should be a foreign key, but no region table exists yet
78
+				'CNT_name'        => new EE_Plain_Text_Field(
79
+					'CNT_name',
80
+					esc_html__('Country Name', 'event_espresso'),
81
+					false,
82
+					''
83
+				),
84
+				'CNT_cur_code'    => new EE_All_Caps_Text_Field(
85
+					'CNT_cur_code',
86
+					esc_html__('Country Currency Code', 'event_espresso'),
87
+					false
88
+				),
89
+				'CNT_cur_single'  => new EE_Plain_Text_Field(
90
+					'CNT_cur_single',
91
+					esc_html__('Currency Name Singular', 'event_espresso'),
92
+					false
93
+				),
94
+				'CNT_cur_plural'  => new EE_Plain_Text_Field(
95
+					'CNT_cur_plural',
96
+					esc_html__('Currency Name Plural', 'event_espresso'),
97
+					false
98
+				),
99
+				'CNT_cur_sign'    => new EE_Plain_Text_Field(
100
+					'CNT_cur_sign',
101
+					esc_html__('Currency Sign', 'event_espresso'),
102
+					false
103
+				),
104
+				'CNT_cur_sign_b4' => new EE_Boolean_Field(
105
+					'CNT_cur_sign_b4',
106
+					esc_html__('Currency Sign Before Number', 'event_espresso'),
107
+					false,
108
+					true
109
+				),
110
+				'CNT_cur_dec_plc' => new EE_Integer_Field(
111
+					'CNT_cur_dec_plc',
112
+					esc_html__('Currency Decimal Places', 'event_espresso'),
113
+					false,
114
+					2
115
+				),
116
+				'CNT_cur_dec_mrk' => new EE_Plain_Text_Field(
117
+					'CNT_cur_dec_mrk',
118
+					esc_html__('Currency Decimal Mark', 'event_espresso'),
119
+					false,
120
+					'.'
121
+				),
122
+				'CNT_cur_thsnds'  => new EE_Plain_Text_Field(
123
+					'CNT_cur_thsnds',
124
+					esc_html__('Currency Thousands Separator', 'event_espresso'),
125
+					false,
126
+					','
127
+				),
128
+				'CNT_tel_code'    => new EE_Plain_Text_Field(
129
+					'CNT_tel_code',
130
+					esc_html__('Country Telephone Code', 'event_espresso'),
131
+					false,
132
+					''
133
+				),
134
+				'CNT_is_EU'       => new EE_Boolean_Field(
135
+					'CNT_is_EU',
136
+					esc_html__('Country is Member of EU', 'event_espresso'),
137
+					false,
138
+					false
139
+				),
140
+			],
141
+		];
142
+		$this->_model_relations = [
143
+			'Attendee' => new EE_Has_Many_Relation(),
144
+			'State'    => new EE_Has_Many_Relation(),
145
+			'Venue'    => new EE_Has_Many_Relation(),
146
+		];
147
+		// only anyone to view, but only those with the default role can do anything
148
+		$this->_cap_restriction_generators[ EEM_Base::caps_read ] = new EE_Restriction_Generator_Public();
149
+
150
+		parent::__construct($timezone);
151
+	}
152
+
153
+
154
+	/**
155
+	 * @return EE_Country[]|null
156
+	 * @throws EE_Error
157
+	 * @throws ReflectionException
158
+	 */
159
+	public function get_all_countries(): ?array
160
+	{
161
+		if (! self::$_all_countries) {
162
+			self::$_all_countries = $this->get_all(['order_by' => ['CNT_name' => 'ASC'], 'limit' => [0, 99999]]);
163
+		}
164
+		return self::$_all_countries;
165
+	}
166
+
167
+
168
+	/**
169
+	 * Gets and caches the list of active countries. If you know the list of active countries
170
+	 * has changed during this request, first use EEM_Country::reset() to flush the cache
171
+	 *
172
+	 * @return EE_Country[]|null
173
+	 * @throws EE_Error
174
+	 * @throws ReflectionException
175
+	 */
176
+	public function get_all_active_countries(): ?array
177
+	{
178
+		if (! self::$_active_countries) {
179
+			self::$_active_countries =
180
+				$this->get_all([['CNT_active' => true], 'order_by' => ['CNT_name' => 'ASC'], 'limit' => [0, 99999]]);
181
+		}
182
+		return self::$_active_countries;
183
+	}
184
+
185
+
186
+	/**
187
+	 * Gets the country's name by its ISO
188
+	 *
189
+	 * @param string $country_ISO
190
+	 * @return string
191
+	 * @throws EE_Error
192
+	 * @throws ReflectionException
193
+	 */
194
+	public function get_country_name_by_ISO(string $country_ISO): string
195
+	{
196
+		$countries = $this->get_all_countries();
197
+		if (isset($countries[ $country_ISO ]) && $countries[ $country_ISO ] instanceof EE_Country) {
198
+			return $countries[ $country_ISO ]->name();
199
+		}
200
+		$names = $this->get_col([['CNT_ISO' => $country_ISO], 'limit' => 1], 'CNT_name');
201
+		if (is_array($names) && ! empty($names)) {
202
+			return reset($names);
203
+		}
204
+		return '';
205
+	}
206
+
207
+
208
+	/**
209
+	 * Gets the country's name by its name
210
+	 *
211
+	 * @param string $country_name
212
+	 * @return EE_Country|null
213
+	 * @throws EE_Error
214
+	 * @throws ReflectionException
215
+	 */
216
+	public function getCountryByName(string $country_name): ?EE_Country
217
+	{
218
+		$countries = $this->get_all_countries();
219
+		foreach ($countries as $country) {
220
+			if ($country instanceof EE_Country && $country->name() === $country_name) {
221
+				return $country;
222
+			}
223
+		}
224
+		return $this->get_one([['CNT_name' => $country_name]]);
225
+	}
226 226
 }
Please login to merge, or discard this patch.
core/db_models/EEM_Base.model.php 2 patches
Indentation   +6787 added lines, -6787 removed lines patch added patch discarded remove patch
@@ -39,6801 +39,6801 @@
 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 array $_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 array $_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 string $_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 ?CustomSelects $_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 array $_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
-        $this->_cap_contexts_to_cap_action_map = apply_filters(
654
-            'FHEE__EEM_Base__cap_contexts_to_cap_action_map',
655
-            $this->_cap_contexts_to_cap_action_map,
656
-            $this
657
-        );
658
-        // initialize the standard cap restriction generators if none were specified by the child constructor
659
-        if (is_array($this->_cap_restriction_generators)) {
660
-            foreach ($this->_cap_contexts_to_cap_action_map as $cap_context => $action) {
661
-                if (! isset($this->_cap_restriction_generators[ $cap_context ])) {
662
-                    $this->_cap_restriction_generators[ $cap_context ] = apply_filters(
663
-                        'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
664
-                        new EE_Restriction_Generator_Protected(),
665
-                        $cap_context,
666
-                        $this
667
-                    );
668
-                }
669
-            }
670
-        }
671
-        // if there are cap restriction generators, use them to make the default cap restrictions
672
-        if (is_array($this->_cap_restriction_generators)) {
673
-            foreach ($this->_cap_restriction_generators as $context => $generator_object) {
674
-                if (! $generator_object) {
675
-                    continue;
676
-                }
677
-                if (! $generator_object instanceof EE_Restriction_Generator_Base) {
678
-                    throw new EE_Error(
679
-                        sprintf(
680
-                            esc_html__(
681
-                                '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.',
682
-                                'event_espresso'
683
-                            ),
684
-                            $context,
685
-                            $this->get_this_model_name()
686
-                        )
687
-                    );
688
-                }
689
-                $action = $this->cap_action_for_context($context);
690
-                if (! $generator_object->construction_finalized()) {
691
-                    $generator_object->_construct_finalize($this, $action);
692
-                }
693
-            }
694
-        }
695
-        do_action("AHEE__{$this->class_name}__construct__end");
696
-    }
697
-
698
-
699
-    /**
700
-     * @return LoaderInterface
701
-     * @throws InvalidArgumentException
702
-     * @throws InvalidDataTypeException
703
-     * @throws InvalidInterfaceException
704
-     */
705
-    protected static function getLoader(): LoaderInterface
706
-    {
707
-        if (! EEM_Base::$loader instanceof LoaderInterface) {
708
-            EEM_Base::$loader = LoaderFactory::getLoader();
709
-        }
710
-        return EEM_Base::$loader;
711
-    }
712
-
713
-
714
-    /**
715
-     * @return Mirror
716
-     * @since   5.0.0.p
717
-     */
718
-    private static function getMirror(): Mirror
719
-    {
720
-        if (! EEM_Base::$mirror instanceof Mirror) {
721
-            EEM_Base::$mirror = EEM_Base::getLoader()->getShared(Mirror::class);
722
-        }
723
-        return EEM_Base::$mirror;
724
-    }
725
-
726
-
727
-    /**
728
-     * @param string $model_class_Name
729
-     * @param string $timezone
730
-     * @return array
731
-     * @throws ReflectionException
732
-     * @since   5.0.0.p
733
-     */
734
-    private static function getModelArguments(string $model_class_Name, string $timezone): array
735
-    {
736
-        $arguments = [$timezone];
737
-        $params    = EEM_Base::getMirror()->getParameters($model_class_Name);
738
-        if (count($params) > 1) {
739
-            if ($params[1]->getName() === 'model_field_factory') {
740
-                $arguments = [
741
-                    $timezone,
742
-                    EEM_Base::getLoader()->getShared(ModelFieldFactory::class),
743
-                ];
744
-            } elseif ($model_class_Name === 'EEM_Form_Section') {
745
-                $arguments = [
746
-                    EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
747
-                    $timezone,
748
-                ];
749
-            } elseif ($model_class_Name === 'EEM_Form_Element') {
750
-                $arguments = [
751
-                    EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
752
-                    EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\InputTypes'),
753
-                    $timezone,
754
-                ];
755
-            }
756
-        }
757
-        return $arguments;
758
-    }
759
-
760
-
761
-    /**
762
-     * This function is a singleton method used to instantiate the Espresso_model object
763
-     *
764
-     * @param string|null $timezone   string representing the timezone we want to set for returned Date Time Strings
765
-     *                                (and any incoming timezone data that gets saved).
766
-     *                                Note this just sends the timezone info to the date time model field objects.
767
-     *                                Default is NULL
768
-     *                                (and will be assumed using the set timezone in the 'timezone_string' wp option)
769
-     * @return static::class (as in the concrete child class)
770
-     * @throws EE_Error
771
-     * @throws ReflectionException
772
-     */
773
-    public static function instance(?string $timezone = '')
774
-    {
775
-        // check if instance of Espresso_model already exists
776
-        if (! static::$_instance instanceof static) {
777
-            /**
778
-             * Set blog id for models to current blog. However we ONLY do this if $_model_query_blog_id is not already set.
779
-             */
780
-            if (empty(EEM_Base::$_model_query_blog_id)) {
781
-                EEM_Base::set_model_query_blog_id();
782
-            }
783
-            $model_class = static::class;
784
-            $arguments = EEM_Base::getModelArguments($model_class, (string) $timezone);
785
-            do_action("AHEE__{$model_class}__instance__before_construct", $model_class, $arguments);
786
-            $model = new static(...$arguments);
787
-            EEM_Base::getLoader()->share($model_class, $model, $arguments);
788
-            static::$_instance = $model;
789
-        }
790
-        // we might have a timezone set, let set_timezone decide what to do with it
791
-        if ($timezone) {
792
-            static::$_instance->set_timezone($timezone);
793
-        }
794
-        // Espresso_model object
795
-        return static::$_instance;
796
-    }
797
-
798
-
799
-    /**
800
-     * resets the model and returns it
801
-     *
802
-     * @param string|null $timezone
803
-     * @return EEM_Base|null (if the model was already instantiated, returns it, with
804
-     * all its properties reset; if it wasn't instantiated, returns null)
805
-     * @throws EE_Error
806
-     * @throws ReflectionException
807
-     * @throws InvalidArgumentException
808
-     * @throws InvalidDataTypeException
809
-     * @throws InvalidInterfaceException
810
-     */
811
-    public static function reset(?string $timezone = ''): ?EEM_Base
812
-    {
813
-        if (! static::$_instance instanceof EEM_Base) {
814
-            return null;
815
-        }
816
-        // Let's NOT swap out the current instance for a new one
817
-        // because if someone has a reference to it, we can't remove their reference.
818
-        // It's best to keep using the same reference but change the original object instead,
819
-        // so reset all its properties to their original values as defined in the class.
820
-        $static_properties = EEM_Base::getMirror()->getStaticProperties(static::class);
821
-        foreach (EEM_Base::getMirror()->getDefaultProperties(static::class) as $property => $value) {
822
-            // don't set instance to null like it was originally,
823
-            // but it's static anyways, and we're ignoring static properties (for now at least)
824
-            if (! isset($static_properties[ $property ])) {
825
-                static::$_instance->{$property} = $value;
826
-            }
827
-        }
828
-        // and then directly call its constructor again, like we would if we were creating a new one
829
-        $arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
830
-        static::$_instance->__construct(...$arguments);
831
-        return self::instance();
832
-    }
833
-
834
-
835
-    /**
836
-     * Used to set the $_model_query_blog_id static property.
837
-     *
838
-     * @param int $blog_id  If provided then will set the blog_id for the models to this id.  If not provided then the
839
-     *                      value for get_current_blog_id() will be used.
840
-     */
841
-    public static function set_model_query_blog_id(int $blog_id = 0)
842
-    {
843
-        EEM_Base::$_model_query_blog_id = $blog_id > 0 ? $blog_id : get_current_blog_id();
844
-    }
845
-
846
-
847
-    /**
848
-     * Returns whatever is set as the internal $model_query_blog_id.
849
-     *
850
-     * @return int
851
-     */
852
-    public static function get_model_query_blog_id()
853
-    {
854
-        return EEM_Base::$_model_query_blog_id;
855
-    }
856
-
857
-
858
-    /**
859
-     * retrieve the status details from esp_status table as an array IF this model has the status table as a relation.
860
-     *
861
-     * @param boolean $translated return localized strings or JUST the array.
862
-     * @return array
863
-     * @throws EE_Error
864
-     * @throws InvalidArgumentException
865
-     * @throws InvalidDataTypeException
866
-     * @throws InvalidInterfaceException
867
-     * @throws ReflectionException
868
-     */
869
-    public function status_array($translated = false)
870
-    {
871
-        if (! array_key_exists('Status', $this->_model_relations)) {
872
-            return [];
873
-        }
874
-        $model_name   = $this->get_this_model_name();
875
-        $status_type  = str_replace(' ', '_', strtolower(str_replace('_', ' ', $model_name)));
876
-        $stati        = EEM_Status::instance()->get_all([['STS_type' => $status_type]]);
877
-        $status_array = [];
878
-        foreach ($stati as $status) {
879
-            $status_array[ $status->ID() ] = $status->get('STS_code');
880
-        }
881
-        return $translated
882
-            ? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
883
-            : $status_array;
884
-    }
885
-
886
-
887
-    /**
888
-     * Gets all the EE_Base_Class objects which match the $query_params, by querying the DB.
889
-     *
890
-     * @param array $query_params             @see
891
-     *                                        https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
892
-     *                                        or if you have the development copy of EE you can view this at the path:
893
-     *                                        /docs/G--Model-System/model-query-params.md
894
-     * @return EE_Base_Class[]  *note that there is NO option to pass the output type. If you want results different
895
-     *                                        from EE_Base_Class[], use get_all_wpdb_results(). Array keys are object
896
-     *                                        IDs (if there is a primary key on the model. if not, numerically indexed)
897
-     *                                        Some full examples: get 10 transactions which have Scottish attendees:
898
-     *                                          EEM_Transaction::instance()->get_all(
899
-     *                                              [
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 array $_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 array $_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 string $_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 ?CustomSelects $_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 array $_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
+		$this->_cap_contexts_to_cap_action_map = apply_filters(
654
+			'FHEE__EEM_Base__cap_contexts_to_cap_action_map',
655
+			$this->_cap_contexts_to_cap_action_map,
656
+			$this
657
+		);
658
+		// initialize the standard cap restriction generators if none were specified by the child constructor
659
+		if (is_array($this->_cap_restriction_generators)) {
660
+			foreach ($this->_cap_contexts_to_cap_action_map as $cap_context => $action) {
661
+				if (! isset($this->_cap_restriction_generators[ $cap_context ])) {
662
+					$this->_cap_restriction_generators[ $cap_context ] = apply_filters(
663
+						'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
664
+						new EE_Restriction_Generator_Protected(),
665
+						$cap_context,
666
+						$this
667
+					);
668
+				}
669
+			}
670
+		}
671
+		// if there are cap restriction generators, use them to make the default cap restrictions
672
+		if (is_array($this->_cap_restriction_generators)) {
673
+			foreach ($this->_cap_restriction_generators as $context => $generator_object) {
674
+				if (! $generator_object) {
675
+					continue;
676
+				}
677
+				if (! $generator_object instanceof EE_Restriction_Generator_Base) {
678
+					throw new EE_Error(
679
+						sprintf(
680
+							esc_html__(
681
+								'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.',
682
+								'event_espresso'
683
+							),
684
+							$context,
685
+							$this->get_this_model_name()
686
+						)
687
+					);
688
+				}
689
+				$action = $this->cap_action_for_context($context);
690
+				if (! $generator_object->construction_finalized()) {
691
+					$generator_object->_construct_finalize($this, $action);
692
+				}
693
+			}
694
+		}
695
+		do_action("AHEE__{$this->class_name}__construct__end");
696
+	}
697
+
698
+
699
+	/**
700
+	 * @return LoaderInterface
701
+	 * @throws InvalidArgumentException
702
+	 * @throws InvalidDataTypeException
703
+	 * @throws InvalidInterfaceException
704
+	 */
705
+	protected static function getLoader(): LoaderInterface
706
+	{
707
+		if (! EEM_Base::$loader instanceof LoaderInterface) {
708
+			EEM_Base::$loader = LoaderFactory::getLoader();
709
+		}
710
+		return EEM_Base::$loader;
711
+	}
712
+
713
+
714
+	/**
715
+	 * @return Mirror
716
+	 * @since   5.0.0.p
717
+	 */
718
+	private static function getMirror(): Mirror
719
+	{
720
+		if (! EEM_Base::$mirror instanceof Mirror) {
721
+			EEM_Base::$mirror = EEM_Base::getLoader()->getShared(Mirror::class);
722
+		}
723
+		return EEM_Base::$mirror;
724
+	}
725
+
726
+
727
+	/**
728
+	 * @param string $model_class_Name
729
+	 * @param string $timezone
730
+	 * @return array
731
+	 * @throws ReflectionException
732
+	 * @since   5.0.0.p
733
+	 */
734
+	private static function getModelArguments(string $model_class_Name, string $timezone): array
735
+	{
736
+		$arguments = [$timezone];
737
+		$params    = EEM_Base::getMirror()->getParameters($model_class_Name);
738
+		if (count($params) > 1) {
739
+			if ($params[1]->getName() === 'model_field_factory') {
740
+				$arguments = [
741
+					$timezone,
742
+					EEM_Base::getLoader()->getShared(ModelFieldFactory::class),
743
+				];
744
+			} elseif ($model_class_Name === 'EEM_Form_Section') {
745
+				$arguments = [
746
+					EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
747
+					$timezone,
748
+				];
749
+			} elseif ($model_class_Name === 'EEM_Form_Element') {
750
+				$arguments = [
751
+					EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
752
+					EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\InputTypes'),
753
+					$timezone,
754
+				];
755
+			}
756
+		}
757
+		return $arguments;
758
+	}
759
+
760
+
761
+	/**
762
+	 * This function is a singleton method used to instantiate the Espresso_model object
763
+	 *
764
+	 * @param string|null $timezone   string representing the timezone we want to set for returned Date Time Strings
765
+	 *                                (and any incoming timezone data that gets saved).
766
+	 *                                Note this just sends the timezone info to the date time model field objects.
767
+	 *                                Default is NULL
768
+	 *                                (and will be assumed using the set timezone in the 'timezone_string' wp option)
769
+	 * @return static::class (as in the concrete child class)
770
+	 * @throws EE_Error
771
+	 * @throws ReflectionException
772
+	 */
773
+	public static function instance(?string $timezone = '')
774
+	{
775
+		// check if instance of Espresso_model already exists
776
+		if (! static::$_instance instanceof static) {
777
+			/**
778
+			 * Set blog id for models to current blog. However we ONLY do this if $_model_query_blog_id is not already set.
779
+			 */
780
+			if (empty(EEM_Base::$_model_query_blog_id)) {
781
+				EEM_Base::set_model_query_blog_id();
782
+			}
783
+			$model_class = static::class;
784
+			$arguments = EEM_Base::getModelArguments($model_class, (string) $timezone);
785
+			do_action("AHEE__{$model_class}__instance__before_construct", $model_class, $arguments);
786
+			$model = new static(...$arguments);
787
+			EEM_Base::getLoader()->share($model_class, $model, $arguments);
788
+			static::$_instance = $model;
789
+		}
790
+		// we might have a timezone set, let set_timezone decide what to do with it
791
+		if ($timezone) {
792
+			static::$_instance->set_timezone($timezone);
793
+		}
794
+		// Espresso_model object
795
+		return static::$_instance;
796
+	}
797
+
798
+
799
+	/**
800
+	 * resets the model and returns it
801
+	 *
802
+	 * @param string|null $timezone
803
+	 * @return EEM_Base|null (if the model was already instantiated, returns it, with
804
+	 * all its properties reset; if it wasn't instantiated, returns null)
805
+	 * @throws EE_Error
806
+	 * @throws ReflectionException
807
+	 * @throws InvalidArgumentException
808
+	 * @throws InvalidDataTypeException
809
+	 * @throws InvalidInterfaceException
810
+	 */
811
+	public static function reset(?string $timezone = ''): ?EEM_Base
812
+	{
813
+		if (! static::$_instance instanceof EEM_Base) {
814
+			return null;
815
+		}
816
+		// Let's NOT swap out the current instance for a new one
817
+		// because if someone has a reference to it, we can't remove their reference.
818
+		// It's best to keep using the same reference but change the original object instead,
819
+		// so reset all its properties to their original values as defined in the class.
820
+		$static_properties = EEM_Base::getMirror()->getStaticProperties(static::class);
821
+		foreach (EEM_Base::getMirror()->getDefaultProperties(static::class) as $property => $value) {
822
+			// don't set instance to null like it was originally,
823
+			// but it's static anyways, and we're ignoring static properties (for now at least)
824
+			if (! isset($static_properties[ $property ])) {
825
+				static::$_instance->{$property} = $value;
826
+			}
827
+		}
828
+		// and then directly call its constructor again, like we would if we were creating a new one
829
+		$arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
830
+		static::$_instance->__construct(...$arguments);
831
+		return self::instance();
832
+	}
833
+
834
+
835
+	/**
836
+	 * Used to set the $_model_query_blog_id static property.
837
+	 *
838
+	 * @param int $blog_id  If provided then will set the blog_id for the models to this id.  If not provided then the
839
+	 *                      value for get_current_blog_id() will be used.
840
+	 */
841
+	public static function set_model_query_blog_id(int $blog_id = 0)
842
+	{
843
+		EEM_Base::$_model_query_blog_id = $blog_id > 0 ? $blog_id : get_current_blog_id();
844
+	}
845
+
846
+
847
+	/**
848
+	 * Returns whatever is set as the internal $model_query_blog_id.
849
+	 *
850
+	 * @return int
851
+	 */
852
+	public static function get_model_query_blog_id()
853
+	{
854
+		return EEM_Base::$_model_query_blog_id;
855
+	}
856
+
857
+
858
+	/**
859
+	 * retrieve the status details from esp_status table as an array IF this model has the status table as a relation.
860
+	 *
861
+	 * @param boolean $translated return localized strings or JUST the array.
862
+	 * @return array
863
+	 * @throws EE_Error
864
+	 * @throws InvalidArgumentException
865
+	 * @throws InvalidDataTypeException
866
+	 * @throws InvalidInterfaceException
867
+	 * @throws ReflectionException
868
+	 */
869
+	public function status_array($translated = false)
870
+	{
871
+		if (! array_key_exists('Status', $this->_model_relations)) {
872
+			return [];
873
+		}
874
+		$model_name   = $this->get_this_model_name();
875
+		$status_type  = str_replace(' ', '_', strtolower(str_replace('_', ' ', $model_name)));
876
+		$stati        = EEM_Status::instance()->get_all([['STS_type' => $status_type]]);
877
+		$status_array = [];
878
+		foreach ($stati as $status) {
879
+			$status_array[ $status->ID() ] = $status->get('STS_code');
880
+		}
881
+		return $translated
882
+			? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
883
+			: $status_array;
884
+	}
885
+
886
+
887
+	/**
888
+	 * Gets all the EE_Base_Class objects which match the $query_params, by querying the DB.
889
+	 *
890
+	 * @param array $query_params             @see
891
+	 *                                        https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
892
+	 *                                        or if you have the development copy of EE you can view this at the path:
893
+	 *                                        /docs/G--Model-System/model-query-params.md
894
+	 * @return EE_Base_Class[]  *note that there is NO option to pass the output type. If you want results different
895
+	 *                                        from EE_Base_Class[], use get_all_wpdb_results(). Array keys are object
896
+	 *                                        IDs (if there is a primary key on the model. if not, numerically indexed)
897
+	 *                                        Some full examples: get 10 transactions which have Scottish attendees:
898
+	 *                                          EEM_Transaction::instance()->get_all(
899
+	 *                                              [
900 900
 *                                                       [
901
-     *                                                      'OR' => [
902
-     *                                                          'Registration.Attendee.ATT_fname'       => ['like', 'Mc%'],
903
-     *                                                          'Registration.Attendee.ATT_fname*other' => ['like', 'Mac%'],
904
-     *                                                      ],
905
-     *                                                  ],
906
-     *                                                  'limit'    => 10,
907
-     *                                                  'group_by' => 'TXN_ID',
908
-     *                                              ]
909
-     *                                          );
910
-     *                                        get all the answers to the question titled "shirt size" for event with id
911
-     *                                        12, ordered by their answer:
912
-     *                                          EEM_Answer::instance()->get_all(
913
-     *                                              [
914
-     *                                                  [
915
-     *                                                      'Question.QST_display_text' => 'shirt size',
916
-     *                                                      'Registration.Event.EVT_ID' => 12,
917
-     *                                                  ],
918
-     *                                                  'order_by' => ['ANS_value' => 'ASC'],
919
-     *                                              ]
920
-     *                                          );
921
- * @throws EE_Error
922
-     * @throws ReflectionException
923
-     */
924
-    public function get_all($query_params = [])
925
-    {
926
-        if (
927
-            isset($query_params['limit'])
928
-            && ! isset($query_params['group_by'])
929
-        ) {
930
-            $query_params['group_by'] = array_keys($this->get_combined_primary_key_fields());
931
-        }
932
-        return $this->_create_objects($this->_get_all_wpdb_results($query_params));
933
-    }
934
-
935
-
936
-    /**
937
-     * Modifies the query parameters so we only get back model objects
938
-     * that "belong" to the current user
939
-     *
940
-     * @param array $query_params @see
941
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
942
-     * @return array @see
943
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
944
-     * @throws ReflectionException
945
-     * @throws ReflectionException
946
-     */
947
-    public function alter_query_params_to_only_include_mine($query_params = [])
948
-    {
949
-        $wp_user_field_name = $this->wp_user_field_name();
950
-        if ($wp_user_field_name) {
951
-            $query_params[0][ $wp_user_field_name ] = get_current_user_id();
952
-        }
953
-        return $query_params;
954
-    }
955
-
956
-
957
-    /**
958
-     * Returns the name of the field's name that points to the WP_User table
959
-     *  on this model (or follows the _model_chain_to_wp_user and uses that model's
960
-     * foreign key to the WP_User table)
961
-     *
962
-     * @return string|boolean string on success, boolean false when there is no
963
-     * foreign key to the WP_User table
964
-     * @throws ReflectionException
965
-     * @throws ReflectionException
966
-     */
967
-    public function wp_user_field_name()
968
-    {
969
-        try {
970
-            if (! empty($this->_model_chain_to_wp_user)) {
971
-                $models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
972
-                $last_model_name              = end($models_to_follow_to_wp_users);
973
-                $model_with_fk_to_wp_users    = EE_Registry::instance()->load_model($last_model_name);
974
-                $model_chain_to_wp_user       = $this->_model_chain_to_wp_user . '.';
975
-            } else {
976
-                $model_with_fk_to_wp_users = $this;
977
-                $model_chain_to_wp_user    = '';
978
-            }
979
-            $wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
980
-            return $model_chain_to_wp_user . $wp_user_field->get_name();
981
-        } catch (EE_Error $e) {
982
-            return false;
983
-        }
984
-    }
985
-
986
-
987
-    /**
988
-     * Returns the _model_chain_to_wp_user string, which indicates which related model
989
-     * (or transiently-related model) has a foreign key to the wp_users table;
990
-     * useful for finding if model objects of this type are 'owned' by the current user.
991
-     * This is an empty string when the foreign key is on this model and when it isn't,
992
-     * but is only non-empty when this model's ownership is indicated by a RELATED model
993
-     * (or transiently-related model)
994
-     *
995
-     * @return string
996
-     */
997
-    public function model_chain_to_wp_user()
998
-    {
999
-        return $this->_model_chain_to_wp_user;
1000
-    }
1001
-
1002
-
1003
-    /**
1004
-     * Whether this model is 'owned' by a specific wordpress user (even indirectly,
1005
-     * like how registrations don't have a foreign key to wp_users, but the
1006
-     * events they are for are), or is unrelated to wp users.
1007
-     * generally available
1008
-     *
1009
-     * @return boolean
1010
-     */
1011
-    public function is_owned()
1012
-    {
1013
-        if ($this->model_chain_to_wp_user()) {
1014
-            return true;
1015
-        }
1016
-        try {
1017
-            $this->get_foreign_key_to('WP_User');
1018
-            return true;
1019
-        } catch (EE_Error $e) {
1020
-            return false;
1021
-        }
1022
-    }
1023
-
1024
-
1025
-    /**
1026
-     * Used internally to get WPDB results, because other functions, besides get_all, may want to do some queries, but
1027
-     * may want to preserve the WPDB results (eg, update, which first queries to make sure we have all the tables on
1028
-     * the model)
1029
-     *
1030
-     * @param array  $query_params      @see
1031
-     *                                  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1032
-     * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1033
-     * @param mixed  $columns_to_select What columns to select. By default, we select all columns specified by the
1034
-     *                                  fields on the model, and the models we joined to in the query. However, you can
1035
-     *                                  override this and set the select to "*", or a specific column name, like
1036
-     *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1037
-     *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1038
-     *                                  the aliases used to refer to this selection, and values are to be
1039
-     *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1040
-     *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1041
-     * @return array | stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1042
-     * @throws EE_Error
1043
-     * @throws InvalidArgumentException
1044
-     * @throws ReflectionException
1045
-     */
1046
-    protected function _get_all_wpdb_results($query_params = [], $output = ARRAY_A, $columns_to_select = null)
1047
-    {
1048
-        $this->_custom_selections = $this->getCustomSelection($query_params, $columns_to_select);
1049
-        $model_query_info         = $this->_create_model_query_info_carrier($query_params);
1050
-        $select_expressions       = $columns_to_select === null
1051
-            ? $this->_construct_default_select_sql($model_query_info)
1052
-            : '';
1053
-        if ($this->_custom_selections instanceof CustomSelects) {
1054
-            $custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1055
-            $select_expressions .= $select_expressions
1056
-                ? ', ' . $custom_expressions
1057
-                : $custom_expressions;
1058
-        }
1059
-
1060
-        $SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1061
-        return $this->_do_wpdb_query('get_results', [$SQL, $output]);
1062
-    }
1063
-
1064
-
1065
-    /**
1066
-     * Get a CustomSelects object if the $query_params or $columns_to_select allows for it.
1067
-     * Note: $query_params['extra_selects'] will always override any $columns_to_select values. It is the preferred
1068
-     * method of including extra select information.
1069
-     *
1070
-     * @param array             $query_params
1071
-     * @param null|array|string $columns_to_select
1072
-     * @return null|CustomSelects
1073
-     * @throws InvalidArgumentException
1074
-     */
1075
-    protected function getCustomSelection(array $query_params, $columns_to_select = null): ?CustomSelects
1076
-    {
1077
-        if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1078
-            return null;
1079
-        }
1080
-        $selects = $query_params['extra_selects'] ?? $columns_to_select;
1081
-        $selects = is_string($selects)
1082
-            ? explode(',', $selects)
1083
-            : $selects;
1084
-        return new CustomSelects($selects);
1085
-    }
1086
-
1087
-
1088
-    /**
1089
-     * Gets an array of rows from the database just like $wpdb->get_results would,
1090
-     * but you can use the model query params to more easily
1091
-     * take care of joins, field preparation etc.
1092
-     *
1093
-     * @param array  $query_params      @see
1094
-     *                                  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1095
-     * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1096
-     * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1097
-     *                                  fields on the model, and the models we joined to in the query. However, you can
1098
-     *                                  override this and set the select to "*", or a specific column name, like
1099
-     *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1100
-     *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1101
-     *                                  the aliases used to refer to this selection, and values are to be
1102
-     *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1103
-     *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1104
-     * @return array|stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1105
-     * @throws EE_Error
1106
-     * @throws ReflectionException
1107
-     */
1108
-    public function get_all_wpdb_results($query_params = [], $output = ARRAY_A, $columns_to_select = null)
1109
-    {
1110
-        return $this->_get_all_wpdb_results($query_params, $output, $columns_to_select);
1111
-    }
1112
-
1113
-
1114
-    /**
1115
-     * For creating a custom select statement
1116
-     *
1117
-     * @param mixed $columns_to_select either a string to be inserted directly as the select statement,
1118
-     *                                 or an array where keys are aliases, and values are arrays where 0=>the selection
1119
-     *                                 SQL, and 1=>is the datatype
1120
-     * @return string
1121
-     * @throws EE_Error
1122
-     */
1123
-    private function _construct_select_from_input($columns_to_select)
1124
-    {
1125
-        if (is_array($columns_to_select)) {
1126
-            $select_sql_array = [];
1127
-            foreach ($columns_to_select as $alias => $selection_and_datatype) {
1128
-                if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1129
-                    throw new EE_Error(
1130
-                        sprintf(
1131
-                            esc_html__(
1132
-                                "Custom selection %s (alias %s) needs to be an array like array('COUNT(REG_ID)','%%d')",
1133
-                                'event_espresso'
1134
-                            ),
1135
-                            $selection_and_datatype,
1136
-                            $alias
1137
-                        )
1138
-                    );
1139
-                }
1140
-                if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1141
-                    throw new EE_Error(
1142
-                        sprintf(
1143
-                            esc_html__(
1144
-                                "Datatype %s (for selection '%s' and alias '%s') is not a valid wpdb datatype (eg %%s)",
1145
-                                'event_espresso'
1146
-                            ),
1147
-                            $selection_and_datatype[1],
1148
-                            $selection_and_datatype[0],
1149
-                            $alias,
1150
-                            implode(', ', $this->_valid_wpdb_data_types)
1151
-                        )
1152
-                    );
1153
-                }
1154
-                $select_sql_array[] = "{$selection_and_datatype[0]} AS $alias";
1155
-            }
1156
-            $columns_to_select_string = implode(', ', $select_sql_array);
1157
-        } else {
1158
-            $columns_to_select_string = $columns_to_select;
1159
-        }
1160
-        return $columns_to_select_string;
1161
-    }
1162
-
1163
-
1164
-    /**
1165
-     * Convenient wrapper for getting the primary key field's name. Eg, on Registration, this would be 'REG_ID'
1166
-     *
1167
-     * @return string
1168
-     * @throws EE_Error
1169
-     */
1170
-    public function primary_key_name()
1171
-    {
1172
-        return $this->get_primary_key_field()->get_name();
1173
-    }
1174
-
1175
-
1176
-    /**
1177
-     * Gets a single item for this model from the DB, given only its ID (or null if none is found).
1178
-     * If there is no primary key on this model, $id is treated as primary key string
1179
-     *
1180
-     * @param mixed $id int or string, depending on the type of the model's primary key
1181
-     * @return EE_Base_Class|mixed|null
1182
-     * @throws EE_Error
1183
-     * @throws ReflectionException
1184
-     */
1185
-    public function get_one_by_ID($id)
1186
-    {
1187
-        // since entities with no ID can still have properties, we need to check the cache for them
1188
-        $cached_value = $this->get_from_entity_map($id);
1189
-        if ($cached_value) {
1190
-            return $cached_value;
1191
-        }
1192
-        // but if no cached property AND no id is passed, just return null
1193
-        if (empty($id)) {
1194
-            return null;
1195
-        }
1196
-        $model_object = $this->get_one(
1197
-            $this->alter_query_params_to_restrict_by_ID(
1198
-                $id,
1199
-                ['default_where_conditions' => EE_Default_Where_Conditions::MINIMUM_ALL]
1200
-            )
1201
-        );
1202
-        $className    = $this->_get_class_name();
1203
-        if ($model_object instanceof $className) {
1204
-            // make sure valid objects get added to the entity map
1205
-            // so that the next call to this method doesn't trigger another trip to the db
1206
-            $this->add_to_entity_map($model_object);
1207
-        }
1208
-        return $model_object;
1209
-    }
1210
-
1211
-
1212
-    /**
1213
-     * Alters query parameters to only get items with this ID are returned.
1214
-     * Takes into account that the ID might be a string produced by EEM_Base::get_index_primary_key_string(),
1215
-     * or could just be a simple primary key ID
1216
-     *
1217
-     * @param int   $id
1218
-     * @param array $query_params
1219
-     * @return array of normal query params, @see
1220
-     *               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1221
-     * @throws EE_Error
1222
-     */
1223
-    public function alter_query_params_to_restrict_by_ID($id, $query_params = [])
1224
-    {
1225
-        if (! isset($query_params[0])) {
1226
-            $query_params[0] = [];
1227
-        }
1228
-        $conditions_from_id = $this->parse_index_primary_key_string($id);
1229
-        if ($conditions_from_id === null) {
1230
-            $query_params[0][ $this->primary_key_name() ] = $id;
1231
-        } else {
1232
-            // no primary key, so the $id must be from the get_index_primary_key_string()
1233
-            $query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
1234
-        }
1235
-        return $query_params;
1236
-    }
1237
-
1238
-
1239
-    /**
1240
-     * Gets a single item for this model from the DB, given the $query_params. Only returns a single class, not an
1241
-     * array. If no item is found, null is returned.
1242
-     *
1243
-     * @param array $query_params like EEM_Base's $query_params variable.
1244
-     * @return EE_Base_Class|EE_Soft_Delete_Base_Class|NULL
1245
-     * @throws EE_Error
1246
-     * @throws ReflectionException
1247
-     */
1248
-    public function get_one($query_params = [])
1249
-    {
1250
-        if (! is_array($query_params)) {
1251
-            EE_Error::doing_it_wrong(
1252
-                'EEM_Base::get_one',
1253
-                sprintf(
1254
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1255
-                    gettype($query_params)
1256
-                ),
1257
-                '4.6.0'
1258
-            );
1259
-            $query_params = [];
1260
-        }
1261
-        $query_params['limit'] = 1;
1262
-        $items                 = $this->get_all($query_params);
1263
-        if (empty($items)) {
1264
-            return null;
1265
-        }
1266
-        return array_shift($items);
1267
-    }
1268
-
1269
-
1270
-    /**
1271
-     * Returns the next x number of items in sequence from the given value as
1272
-     * found in the database matching the given query conditions.
1273
-     *
1274
-     * @param mixed $current_field_value    Value used for the reference point.
1275
-     * @param null  $field_to_order_by      What field is used for the
1276
-     *                                      reference point.
1277
-     * @param int   $limit                  How many to return.
1278
-     * @param array $query_params           Extra conditions on the query.
1279
-     * @param null  $columns_to_select      If left null, then an array of
1280
-     *                                      EE_Base_Class objects is returned,
1281
-     *                                      otherwise you can indicate just the
1282
-     *                                      columns you want returned.
1283
-     * @return EE_Base_Class[]|array
1284
-     * @throws EE_Error
1285
-     * @throws ReflectionException
1286
-     */
1287
-    public function next_x(
1288
-        $current_field_value,
1289
-        $field_to_order_by = null,
1290
-        $limit = 1,
1291
-        $query_params = [],
1292
-        $columns_to_select = null
1293
-    ) {
1294
-        return $this->_get_consecutive(
1295
-            $current_field_value,
1296
-            '>',
1297
-            $field_to_order_by,
1298
-            $limit,
1299
-            $query_params,
1300
-            $columns_to_select
1301
-        );
1302
-    }
1303
-
1304
-
1305
-    /**
1306
-     * Returns the previous x number of items in sequence from the given value
1307
-     * as found in the database matching the given query conditions.
1308
-     *
1309
-     * @param mixed $current_field_value    Value used for the reference point.
1310
-     * @param null  $field_to_order_by      What field is used for the
1311
-     *                                      reference point.
1312
-     * @param int   $limit                  How many to return.
1313
-     * @param array $query_params           Extra conditions on the query.
1314
-     * @param null  $columns_to_select      If left null, then an array of
1315
-     *                                      EE_Base_Class objects is returned,
1316
-     *                                      otherwise you can indicate just the
1317
-     *                                      columns you want returned.
1318
-     * @return EE_Base_Class[]|array
1319
-     * @throws EE_Error
1320
-     * @throws ReflectionException
1321
-     */
1322
-    public function previous_x(
1323
-        $current_field_value,
1324
-        $field_to_order_by = null,
1325
-        $limit = 1,
1326
-        $query_params = [],
1327
-        $columns_to_select = null
1328
-    ) {
1329
-        return $this->_get_consecutive(
1330
-            $current_field_value,
1331
-            '<',
1332
-            $field_to_order_by,
1333
-            $limit,
1334
-            $query_params,
1335
-            $columns_to_select
1336
-        );
1337
-    }
1338
-
1339
-
1340
-    /**
1341
-     * Returns the next item in sequence from the given value as found in the
1342
-     * database matching the given query conditions.
1343
-     *
1344
-     * @param mixed $current_field_value    Value used for the reference point.
1345
-     * @param null  $field_to_order_by      What field is used for the
1346
-     *                                      reference point.
1347
-     * @param array $query_params           Extra conditions on the query.
1348
-     * @param null  $columns_to_select      If left null, then an EE_Base_Class
1349
-     *                                      object is returned, otherwise you
1350
-     *                                      can indicate just the columns you
1351
-     *                                      want and a single array indexed by
1352
-     *                                      the columns will be returned.
1353
-     * @return EE_Base_Class|null|array()
1354
-     * @throws EE_Error
1355
-     * @throws ReflectionException
1356
-     */
1357
-    public function next(
1358
-        $current_field_value,
1359
-        $field_to_order_by = null,
1360
-        $query_params = [],
1361
-        $columns_to_select = null
1362
-    ) {
1363
-        $results = $this->_get_consecutive(
1364
-            $current_field_value,
1365
-            '>',
1366
-            $field_to_order_by,
1367
-            1,
1368
-            $query_params,
1369
-            $columns_to_select
1370
-        );
1371
-        return empty($results)
1372
-            ? null
1373
-            : reset($results);
1374
-    }
1375
-
1376
-
1377
-    /**
1378
-     * Returns the previous item in sequence from the given value as found in
1379
-     * the database matching the given query conditions.
1380
-     *
1381
-     * @param mixed $current_field_value    Value used for the reference point.
1382
-     * @param null  $field_to_order_by      What field is used for the
1383
-     *                                      reference point.
1384
-     * @param array $query_params           Extra conditions on the query.
1385
-     * @param null  $columns_to_select      If left null, then an EE_Base_Class
1386
-     *                                      object is returned, otherwise you
1387
-     *                                      can indicate just the columns you
1388
-     *                                      want and a single array indexed by
1389
-     *                                      the columns will be returned.
1390
-     * @return EE_Base_Class|null|array()
1391
-     * @throws EE_Error
1392
-     * @throws ReflectionException
1393
-     */
1394
-    public function previous(
1395
-        $current_field_value,
1396
-        $field_to_order_by = null,
1397
-        $query_params = [],
1398
-        $columns_to_select = null
1399
-    ) {
1400
-        $results = $this->_get_consecutive(
1401
-            $current_field_value,
1402
-            '<',
1403
-            $field_to_order_by,
1404
-            1,
1405
-            $query_params,
1406
-            $columns_to_select
1407
-        );
1408
-        return empty($results)
1409
-            ? null
1410
-            : reset($results);
1411
-    }
1412
-
1413
-
1414
-    /**
1415
-     * Returns the a consecutive number of items in sequence from the given
1416
-     * value as found in the database matching the given query conditions.
1417
-     *
1418
-     * @param mixed  $current_field_value   Value used for the reference point.
1419
-     * @param string $operand               What operand is used for the sequence.
1420
-     * @param string $field_to_order_by     What field is used for the reference point.
1421
-     * @param int    $limit                 How many to return.
1422
-     * @param array  $query_params          Extra conditions on the query.
1423
-     * @param null   $columns_to_select     If left null, then an array of EE_Base_Class objects is returned,
1424
-     *                                      otherwise you can indicate just the columns you want returned.
1425
-     * @return EE_Base_Class[]|array
1426
-     * @throws EE_Error
1427
-     * @throws ReflectionException
1428
-     */
1429
-    protected function _get_consecutive(
1430
-        $current_field_value,
1431
-        $operand = '>',
1432
-        $field_to_order_by = null,
1433
-        $limit = 1,
1434
-        $query_params = [],
1435
-        $columns_to_select = null
1436
-    ) {
1437
-        // if $field_to_order_by is empty then let's assume we're ordering by the primary key.
1438
-        if (empty($field_to_order_by)) {
1439
-            if ($this->has_primary_key_field()) {
1440
-                $field_to_order_by = $this->get_primary_key_field()->get_name();
1441
-            } else {
1442
-                if (defined('WP_DEBUG') && WP_DEBUG) {
1443
-                    throw new EE_Error(
1444
-                        esc_html__(
1445
-                            '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).',
1446
-                            'event_espresso'
1447
-                        )
1448
-                    );
1449
-                }
1450
-                EE_Error::add_error(
1451
-                    esc_html__('There was an error with the query.', 'event_espresso'),
1452
-                    __FILE__,
1453
-                    __FUNCTION__,
1454
-                    __LINE__
1455
-                );
1456
-                return [];
1457
-            }
1458
-        }
1459
-        if (! is_array($query_params)) {
1460
-            EE_Error::doing_it_wrong(
1461
-                'EEM_Base::_get_consecutive',
1462
-                sprintf(
1463
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1464
-                    gettype($query_params)
1465
-                ),
1466
-                '4.6.0'
1467
-            );
1468
-            $query_params = [];
1469
-        }
1470
-        // let's add the where query param for consecutive look up.
1471
-        $query_params[0][ $field_to_order_by ] = [$operand, $current_field_value];
1472
-        $query_params['limit']                 = $limit;
1473
-        // set direction
1474
-        $incoming_orderby         = isset($query_params['order_by'])
1475
-            ? (array) $query_params['order_by']
1476
-            : [];
1477
-        $query_params['order_by'] = $operand === '>'
1478
-            ? [$field_to_order_by => 'ASC'] + $incoming_orderby
1479
-            : [$field_to_order_by => 'DESC'] + $incoming_orderby;
1480
-        // if $columns_to_select is empty then that means we're returning EE_Base_Class objects
1481
-        if (empty($columns_to_select)) {
1482
-            return $this->get_all($query_params);
1483
-        }
1484
-        // getting just the fields
1485
-        return $this->_get_all_wpdb_results($query_params, ARRAY_A, $columns_to_select);
1486
-    }
1487
-
1488
-
1489
-    /**
1490
-     * This sets the _timezone property after model object has been instantiated.
1491
-     *
1492
-     * @param string|null $timezone valid PHP DateTimeZone timezone string
1493
-     * @throws Exception
1494
-     */
1495
-    public function set_timezone(?string $timezone = '')
1496
-    {
1497
-        if (! $timezone) {
1498
-            return;
1499
-        }
1500
-        $this->_timezone = $timezone;
1501
-        // note we need to loop through relations and set the timezone on those objects as well.
1502
-        foreach ($this->_model_relations as $relation) {
1503
-            $relation->set_timezone($timezone);
1504
-        }
1505
-        // and finally we do the same for any datetime fields
1506
-        foreach ($this->_fields as $field) {
1507
-            if ($field instanceof EE_Datetime_Field) {
1508
-                $field->set_timezone($timezone);
1509
-            }
1510
-        }
1511
-    }
1512
-
1513
-
1514
-    /**
1515
-     * This just returns whatever is set for the current timezone.
1516
-     *
1517
-     * @access public
1518
-     * @return string
1519
-     * @throws Exception
1520
-     */
1521
-    public function get_timezone()
1522
-    {
1523
-        // first validate if timezone is set.  If not, then let's set it be whatever is set on the model fields.
1524
-        if (empty($this->_timezone)) {
1525
-            foreach ($this->_fields as $field) {
1526
-                if ($field instanceof EE_Datetime_Field) {
1527
-                    $this->set_timezone($field->get_timezone());
1528
-                    break;
1529
-                }
1530
-            }
1531
-        }
1532
-        // if timezone STILL empty then return the default timezone for the site.
1533
-        if (empty($this->_timezone)) {
1534
-            $this->set_timezone(EEH_DTT_Helper::get_timezone());
1535
-        }
1536
-        return $this->_timezone;
1537
-    }
1538
-
1539
-
1540
-    /**
1541
-     * This returns the date formats set for the given field name and also ensures that
1542
-     * $this->_timezone property is set correctly.
1543
-     *
1544
-     * @param string $field_name The name of the field the formats are being retrieved for.
1545
-     * @param bool   $pretty     Whether to return the pretty formats (true) or not (false).
1546
-     * @return array formats in an array with the date format first, and the time format last.
1547
-     * @throws EE_Error   If the given field_name is not of the EE_Datetime_Field type.
1548
-     * @since 4.6.x
1549
-     */
1550
-    public function get_formats_for($field_name, $pretty = false)
1551
-    {
1552
-        $field_settings = $this->field_settings_for($field_name);
1553
-        // if not a valid EE_Datetime_Field then throw error
1554
-        if (! $field_settings instanceof EE_Datetime_Field) {
1555
-            throw new EE_Error(
1556
-                sprintf(
1557
-                    esc_html__(
1558
-                        '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.',
1559
-                        'event_espresso'
1560
-                    ),
1561
-                    $field_name
1562
-                )
1563
-            );
1564
-        }
1565
-        // while we are here, let's make sure the timezone internally in EEM_Base matches what is stored on
1566
-        // the field.
1567
-        $this->_timezone = (string) $field_settings->get_timezone();
1568
-        return [$field_settings->get_date_format($pretty), $field_settings->get_time_format($pretty)];
1569
-    }
1570
-
1571
-
1572
-    /**
1573
-     * This returns the current time in a format setup for a query on this model.
1574
-     * Usage of this method makes it easier to setup queries against EE_Datetime_Field columns because
1575
-     * it will return:
1576
-     *  - a formatted string in the timezone and format currently set on the EE_Datetime_Field for the given field for
1577
-     *  NOW
1578
-     *  - or a unix timestamp (equivalent to time())
1579
-     * Note: When requesting a formatted string, if the date or time format doesn't include seconds, for example,
1580
-     * the time returned, because it uses that format, will also NOT include seconds. For this reason, if you want
1581
-     * the time returned to be the current time down to the exact second, set $timestamp to true.
1582
-     *
1583
-     * @param string $field_name       The field the current time is needed for.
1584
-     * @param bool   $timestamp        True means to return a unix timestamp. Otherwise a
1585
-     *                                 formatted string matching the set format for the field in the set timezone will
1586
-     *                                 be returned.
1587
-     * @param string $what             Whether to return the string in just the time format, the date format, or both.
1588
-     * @return int|string  If the given field_name is not of the EE_Datetime_Field type, then an EE_Error
1589
-     *                                 exception is triggered.
1590
-     * @throws EE_Error    If the given field_name is not of the EE_Datetime_Field type.
1591
-     * @throws Exception
1592
-     * @since 4.6.x
1593
-     */
1594
-    public function current_time_for_query($field_name, $timestamp = false, $what = 'both')
1595
-    {
1596
-        $formats  = $this->get_formats_for($field_name);
1597
-        $DateTime = new DateTime("now", new DateTimeZone($this->_timezone));
1598
-        if ($timestamp) {
1599
-            return $DateTime->format('U');
1600
-        }
1601
-        // not returning timestamp, so return formatted string in timezone.
1602
-        switch ($what) {
1603
-            case 'time':
1604
-                return $DateTime->format($formats[1]);
1605
-            case 'date':
1606
-                return $DateTime->format($formats[0]);
1607
-            default:
1608
-                return $DateTime->format(implode(' ', $formats));
1609
-        }
1610
-    }
1611
-
1612
-
1613
-    /**
1614
-     * This receives a time string for a given field and ensures
1615
-     * that it is set up to match what the internal settings for the model are.
1616
-     * Returns a DateTime object.
1617
-     * Note: a gotcha for when you send in unix timestamp.  Remember a unix timestamp is already timezone agnostic,
1618
-     * (functionally the equivalent of UTC+0).
1619
-     * So when you send it in, whatever timezone string you include is ignored.
1620
-     *
1621
-     * @param string      $field_name      The field being setup.
1622
-     * @param string      $timestring      The date time string being used.
1623
-     * @param string      $incoming_format The format for the time string.
1624
-     * @param string|null $timezone_string By default, it is assumed the incoming time string is in timezone for
1625
-     *                                     the blog.  If this is not the case, then it can be specified here.  If
1626
-     *                                     incoming format is
1627
-     *                                     'U', this is ignored.
1628
-     * @return DbSafeDateTime
1629
-     * @throws EE_Error
1630
-     * @throws Exception
1631
-     */
1632
-    public function convert_datetime_for_query(
1633
-        string $field_name,
1634
-        string $timestring,
1635
-        string $incoming_format,
1636
-        ?string $timezone_string = ''
1637
-    ): DbSafeDateTime {
1638
-        // just using this to ensure the timezone is set correctly internally
1639
-        $this->get_formats_for($field_name);
1640
-        // load EEH_DTT_Helper
1641
-        $timezone_string     = ! empty($timezone_string) ? $timezone_string : EEH_DTT_Helper::get_timezone();
1642
-        $incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($timezone_string));
1643
-        EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1644
-        return DbSafeDateTime::createFromDateTime($incomingDateTime);
1645
-    }
1646
-
1647
-
1648
-    /**
1649
-     * Gets all the tables comprising this model. Array keys are the table aliases, and values are EE_Table objects
1650
-     *
1651
-     * @return EE_Table_Base[]
1652
-     */
1653
-    public function get_tables()
1654
-    {
1655
-        return $this->_tables;
1656
-    }
1657
-
1658
-
1659
-    /**
1660
-     * Updates all the database entries (in each table for this model) according to $fields_n_values and optionally
1661
-     * also updates all the model objects, where the criteria expressed in $query_params are met..
1662
-     * Also note: if this model has multiple tables, this update verifies all the secondary tables have an entry for
1663
-     * each row (in the primary table) we're trying to update; if not, it inserts an entry in the secondary table. Eg:
1664
-     * if our model has 2 tables: wp_posts (primary), and wp_esp_event (secondary). Let's say we are trying to update a
1665
-     * model object with EVT_ID = 1
1666
-     * (which means where wp_posts has ID = 1, because wp_posts.ID is the primary key's column), which exists, but
1667
-     * there is no entry in wp_esp_event for this entry in wp_posts. So, this update script will insert a row into
1668
-     * wp_esp_event, using any available parameters from $fields_n_values (eg, if "EVT_limit" => 40 is in
1669
-     * $fields_n_values, the new entry in wp_esp_event will set EVT_limit = 40, and use default for other columns which
1670
-     * are not specified)
1671
-     *
1672
-     * @param array   $fields_n_values         keys are model fields (exactly like keys in EEM_Base::_fields, NOT db
1673
-     *                                         columns!), values are strings, ints, floats, and maybe arrays if they
1674
-     *                                         are to be serialized. Basically, the values are what you'd expect to be
1675
-     *                                         values on the model, NOT necessarily what's in the DB. For example, if
1676
-     *                                         we wanted to update only the TXN_details on any Transactions where its
1677
-     *                                         ID=34, we'd use this method as follows:
1678
-     *                                         EEM_Transaction::instance()->update(
1679
-     *                                         array('TXN_details'=>array('detail1'=>'monkey','detail2'=>'banana'),
1680
-     *                                         array(array('TXN_ID'=>34)));
1681
-     * @param array   $query_params            @see
1682
-     *                                         https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1683
-     *                                         Eg, consider updating Question's QST_admin_label field is of type
1684
-     *                                         Simple_HTML. If you use this function to update that field to $new_value
1685
-     *                                         = (note replace 8's with appropriate opening and closing tags in the
1686
-     *                                         following example)"8script8alert('I hack all');8/script88b8boom
1687
-     *                                         baby8/b8", then if you set $values_already_prepared_by_model_object to
1688
-     *                                         TRUE, it is assumed that you've already called
1689
-     *                                         EE_Simple_HTML_Field->prepare_for_set($new_value), which removes the
1690
-     *                                         malicious javascript. However, if
1691
-     *                                         $values_already_prepared_by_model_object is left as FALSE, then
1692
-     *                                         EE_Simple_HTML_Field->prepare_for_set($new_value) will be called on it,
1693
-     *                                         and every other field, before insertion. We provide this parameter
1694
-     *                                         because model objects perform their prepare_for_set function on all
1695
-     *                                         their values, and so don't need to be called again (and in many cases,
1696
-     *                                         shouldn't be called again. Eg: if we escape HTML characters in the
1697
-     *                                         prepare_for_set method...)
1698
-     * @param boolean $keep_model_objs_in_sync if TRUE, makes sure we ALSO update model objects
1699
-     *                                         in this model's entity map according to $fields_n_values that match
1700
-     *                                         $query_params. This obviously has some overhead, so you can disable it
1701
-     *                                         by setting this to FALSE, but be aware that model objects being used
1702
-     *                                         could get out-of-sync with the database
1703
-     * @return int how many rows got updated or FALSE if something went wrong with the query (wp returns FALSE or num
1704
-     *                                         rows affected which *could* include 0 which DOES NOT mean the query was
1705
-     *                                         bad)
1706
-     * @throws EE_Error
1707
-     * @throws ReflectionException
1708
-     */
1709
-    public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1710
-    {
1711
-        if (! is_array($query_params)) {
1712
-            EE_Error::doing_it_wrong(
1713
-                'EEM_Base::update',
1714
-                sprintf(
1715
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1716
-                    gettype($query_params)
1717
-                ),
1718
-                '4.6.0'
1719
-            );
1720
-            $query_params = [];
1721
-        }
1722
-        /**
1723
-         * Action called before a model update call has been made.
1724
-         *
1725
-         * @param EEM_Base $model
1726
-         * @param array    $fields_n_values the updated fields and their new values
1727
-         * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1728
-         */
1729
-        do_action('AHEE__EEM_Base__update__begin', $this, $fields_n_values, $query_params);
1730
-        /**
1731
-         * Filters the fields about to be updated given the query parameters. You can provide the
1732
-         * $query_params to $this->get_all() to find exactly which records will be updated
1733
-         *
1734
-         * @param array    $fields_n_values fields and their new values
1735
-         * @param EEM_Base $model           the model being queried
1736
-         * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1737
-         */
1738
-        $fields_n_values = (array) apply_filters(
1739
-            'FHEE__EEM_Base__update__fields_n_values',
1740
-            $fields_n_values,
1741
-            $this,
1742
-            $query_params
1743
-        );
1744
-        // need to verify that, for any entry we want to update, there are entries in each secondary table.
1745
-        // to do that, for each table, verify that it's PK isn't null.
1746
-        $tables = $this->get_tables();
1747
-        // 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
1748
-        // NOTE: we should make this code more efficient by NOT querying twice
1749
-        // before the real update, but that needs to first go through ALPHA testing
1750
-        // as it's dangerous. says Mike August 8 2014
1751
-        // we want to make sure the default_where strategy is ignored
1752
-        $this->_ignore_where_strategy = true;
1753
-        $wpdb_select_results          = $this->_get_all_wpdb_results($query_params);
1754
-        foreach ($wpdb_select_results as $wpdb_result) {
1755
-            // type cast stdClass as array
1756
-            $wpdb_result = (array) $wpdb_result;
1757
-            // get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1758
-            if ($this->has_primary_key_field()) {
1759
-                $main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1760
-            } else {
1761
-                // 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)
1762
-                $main_table_pk_value = null;
1763
-            }
1764
-            // 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
1765
-            // 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
1766
-            if (count($tables) > 1) {
1767
-                // foreach matching row in the DB, ensure that each table's PK isn't null. If so, there must not be an entry
1768
-                // in that table, and so we'll want to insert one
1769
-                foreach ($tables as $table_obj) {
1770
-                    $this_table_pk_column = $table_obj->get_fully_qualified_pk_column();
1771
-                    // if there is no private key for this table on the results, it means there's no entry
1772
-                    // in this table, right? so insert a row in the current table, using any fields available
1773
-                    if (
1774
-                        ! (array_key_exists($this_table_pk_column, $wpdb_result)
1775
-                           && $wpdb_result[ $this_table_pk_column ])
1776
-                    ) {
1777
-                        $success = $this->_insert_into_specific_table(
1778
-                            $table_obj,
1779
-                            $fields_n_values,
1780
-                            $main_table_pk_value
1781
-                        );
1782
-                        // if we died here, report the error
1783
-                        if (! $success) {
1784
-                            return false;
1785
-                        }
1786
-                    }
1787
-                }
1788
-            }
1789
-            //              //and now check that if we have cached any models by that ID on the model, that
1790
-            //              //they also get updated properly
1791
-            //              $model_object = $this->get_from_entity_map( $main_table_pk_value );
1792
-            //              if( $model_object ){
1793
-            //                  foreach( $fields_n_values as $field => $value ){
1794
-            //                      $model_object->set($field, $value);
1795
-            // let's make sure default_where strategy is followed now
1796
-            $this->_ignore_where_strategy = false;
1797
-        }
1798
-        // if we want to keep model objects in sync, AND
1799
-        // if this wasn't called from a model object (to update itself)
1800
-        // then we want to make sure we keep all the existing
1801
-        // model objects in sync with the db
1802
-        if ($keep_model_objs_in_sync && ! $this->_values_already_prepared_by_model_object) {
1803
-            if ($this->has_primary_key_field()) {
1804
-                $model_objs_affected_ids = $this->get_col($query_params);
1805
-            } else {
1806
-                // we need to select a bunch of columns and then combine them into the the "index primary key string"s
1807
-                $models_affected_key_columns = $this->_get_all_wpdb_results($query_params, ARRAY_A);
1808
-                $model_objs_affected_ids     = [];
1809
-                foreach ($models_affected_key_columns as $row) {
1810
-                    $combined_index_key                             = $this->get_index_primary_key_string($row);
1811
-                    $model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1812
-                }
1813
-            }
1814
-            if (! $model_objs_affected_ids) {
1815
-                // wait wait wait- if nothing was affected let's stop here
1816
-                return 0;
1817
-            }
1818
-            foreach ($model_objs_affected_ids as $id) {
1819
-                $model_obj_in_entity_map = $this->get_from_entity_map($id);
1820
-                if ($model_obj_in_entity_map) {
1821
-                    foreach ($fields_n_values as $field => $new_value) {
1822
-                        $model_obj_in_entity_map->set($field, $new_value);
1823
-                    }
1824
-                }
1825
-            }
1826
-            // if there is a primary key on this model, we can now do a slight optimization
1827
-            if ($this->has_primary_key_field()) {
1828
-                // we already know what we want to update. So let's make the query simpler so it's a little more efficient
1829
-                $query_params = [
1830
-                    [$this->primary_key_name() => ['IN', $model_objs_affected_ids]],
1831
-                    'limit'                    => count($model_objs_affected_ids),
1832
-                    'default_where_conditions' => EE_Default_Where_Conditions::NONE,
1833
-                ];
1834
-            }
1835
-        }
1836
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
1837
-
1838
-        // note: the following query doesn't use _construct_2nd_half_of_select_query()
1839
-        // because it doesn't accept LIMIT, ORDER BY, etc.
1840
-        $rows_affected = $this->_do_wpdb_query(
1841
-            'query',
1842
-            [
1843
-                "UPDATE " . $model_query_info->get_full_join_sql()
1844
-                . " SET " . $this->_construct_update_sql($fields_n_values)
1845
-                . $model_query_info->get_where_sql(),
1846
-            ]
1847
-        );
1848
-
1849
-        /**
1850
-         * Action called after a model update call has been made.
1851
-         *
1852
-         * @param EEM_Base $model
1853
-         * @param array    $fields_n_values the updated fields and their new values
1854
-         * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1855
-         * @param int      $rows_affected
1856
-         */
1857
-        do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1858
-        return $rows_affected;// how many supposedly got updated
1859
-    }
1860
-
1861
-
1862
-    /**
1863
-     * Analogous to $wpdb->get_col, returns a 1-dimensional array where the values
1864
-     * are the values of the field specified (or by default the primary key field)
1865
-     * that matched the query params. Note that you should pass the name of the
1866
-     * model FIELD, not the database table's column name.
1867
-     *
1868
-     * @param array  $query_params
1869
-     * @param string $field_to_select
1870
-     * @return array just like $wpdb->get_col()
1871
-     * @throws EE_Error
1872
-     * @throws ReflectionException
1873
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md for $query_params values
1874
-     */
1875
-    public function get_col($query_params = [], $field_to_select = null)
1876
-    {
1877
-        if ($field_to_select) {
1878
-            $field = $this->field_settings_for($field_to_select);
1879
-        } elseif ($this->has_primary_key_field()) {
1880
-            $field = $this->get_primary_key_field();
1881
-        } else {
1882
-            $field_settings = $this->field_settings();
1883
-            // no primary key, just grab the first column
1884
-            $field = reset($field_settings);
1885
-            // don't need this array now
1886
-            unset($field_settings);
1887
-        }
1888
-        $model_query_info   = $this->_create_model_query_info_carrier($query_params);
1889
-        $select_expressions = $field->get_qualified_column();
1890
-        $SQL                =
1891
-            "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1892
-        return $this->_do_wpdb_query('get_col', [$SQL]);
1893
-    }
1894
-
1895
-
1896
-    /**
1897
-     * Returns a single column value for a single row from the database
1898
-     *
1899
-     * @param array  $query_params    @see
1900
-     *                                https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1901
-     * @param string $field_to_select @see EEM_Base::get_col()
1902
-     * @return string
1903
-     * @throws EE_Error
1904
-     * @throws ReflectionException
1905
-     */
1906
-    public function get_var($query_params = [], $field_to_select = null)
1907
-    {
1908
-        $query_params['limit'] = 1;
1909
-        $col                   = $this->get_col($query_params, $field_to_select);
1910
-        if (! empty($col)) {
1911
-            return reset($col);
1912
-        }
1913
-        return null;
1914
-    }
1915
-
1916
-
1917
-    /**
1918
-     * Makes the SQL for after "UPDATE table_X inner join table_Y..." and before "...WHERE". Eg "Question.name='party
1919
-     * time?', Question.desc='what do you think?',..." Values are filtered through wpdb->prepare to avoid against SQL
1920
-     * injection, but currently no further filtering is done
1921
-     *
1922
-     * @param array $fields_n_values array keys are field names on this model, and values are what those fields should
1923
-     *                               be updated to in the DB
1924
-     * @return string of SQL
1925
-     * @throws EE_Error
1926
-     * @global      $wpdb
1927
-     */
1928
-    public function _construct_update_sql($fields_n_values)
1929
-    {
1930
-        /** @type WPDB $wpdb */
1931
-        global $wpdb;
1932
-        $cols_n_values = [];
1933
-        foreach ($fields_n_values as $field_name => $value) {
1934
-            $field_obj = $this->field_settings_for($field_name);
1935
-            // if the value is NULL, we want to assign the value to that.
1936
-            // wpdb->prepare doesn't really handle that properly
1937
-            $prepared_value  = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1938
-            $value_sql       = $prepared_value === null
1939
-                ? 'NULL'
1940
-                : $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1941
-            $cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1942
-        }
1943
-        return implode(",", $cols_n_values);
1944
-    }
1945
-
1946
-
1947
-    /**
1948
-     * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1949
-     * Performs a HARD delete, meaning the database row should always be removed,
1950
-     * not just have a flag field on it switched
1951
-     * Wrapper for EEM_Base::delete_permanently()
1952
-     *
1953
-     * @param mixed $id
1954
-     * @param bool  $block_deletes whether to allow related model objects to block (prevent) this deletion
1955
-     *                             ie: enforce referential integrity
1956
-     *                             It's advisable to always leave this as TRUE, otherwise you could corrupt your DB
1957
-     * @return int the number of rows deleted
1958
-     * @throws EE_Error
1959
-     * @throws ReflectionException
1960
-     */
1961
-    public function delete_permanently_by_ID($id, $block_deletes = true): int
1962
-    {
1963
-        return $this->delete_permanently(
1964
-            [
1965
-                [$this->get_primary_key_field()->get_name() => $id],
1966
-                'limit' => 1,
1967
-            ],
1968
-            $block_deletes
1969
-        );
1970
-    }
1971
-
1972
-
1973
-    /**
1974
-     * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1975
-     * Wrapper for EEM_Base::delete()
1976
-     *
1977
-     * @param mixed $id
1978
-     * @param bool  $block_deletes whether to allow related model objects to block (prevent) this deletion
1979
-     *                             ie: enforce referential integrity
1980
-     *                             It's advisable to always leave this as TRUE, otherwise you could corrupt your DB
1981
-     * @return int the number of rows deleted
1982
-     * @throws EE_Error
1983
-     * @throws ReflectionException
1984
-     */
1985
-    public function delete_by_ID($id, $block_deletes = true)
1986
-    {
1987
-        return $this->delete(
1988
-            [
1989
-                [$this->get_primary_key_field()->get_name() => $id],
1990
-                'limit' => 1,
1991
-            ],
1992
-            $block_deletes
1993
-        );
1994
-    }
1995
-
1996
-
1997
-    /**
1998
-     * Identical to delete_permanently, but does a "soft" delete if possible,
1999
-     * meaning if the model has a field that indicates its been "trashed" or
2000
-     * "soft deleted", we will just set that instead of actually deleting the rows.
2001
-     *
2002
-     * @param array   $query_params
2003
-     * @param boolean $block_deletes whether to allow related model objects to block (prevent) this deletion
2004
-     *                               ie: enforce referential integrity
2005
-     *                               It's advisable to always leave this as TRUE, otherwise you could corrupt your DB
2006
-     * @return int how many rows got deleted
2007
-     * @throws EE_Error
2008
-     * @throws ReflectionException
2009
-     * @see EEM_Base::delete_permanently
2010
-     */
2011
-    public function delete($query_params, $block_deletes = true)
2012
-    {
2013
-        return $this->delete_permanently($query_params, $block_deletes);
2014
-    }
2015
-
2016
-
2017
-    /**
2018
-     * Deletes the model objects that meet the query params. Note: this method is overridden
2019
-     * in EEM_Soft_Delete_Base so that soft-deleted model objects are instead only flagged
2020
-     * as archived, not actually deleted
2021
-     *
2022
-     * @param array   $query_params
2023
-     * @param boolean $block_deletes  whether to allow related model objects to block (prevent) this deletion
2024
-     *                                ie: enforce referential integrity
2025
-     *                                It's advisable to always leave this as TRUE, otherwise you could corrupt your DB
2026
-     * @return int how many rows got deleted
2027
-     * @throws EE_Error
2028
-     * @throws ReflectionException
2029
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2030
-     */
2031
-    public function delete_permanently($query_params, $block_deletes = true): int
2032
-    {
2033
-        /**
2034
-         * Action called just before performing a real deletion query. You can use the
2035
-         * model and its $query_params to find exactly which items will be deleted
2036
-         *
2037
-         * @param EEM_Base $model
2038
-         * @param array    $query_params  The incoming array of query parameters influencing what gets deleted.
2039
-         * @param bool     $block_deletes @see param description in method phpdoc block.
2040
-         */
2041
-        do_action('AHEE__EEM_Base__delete__begin', $this, $query_params, $block_deletes);
2042
-        // some MySQL databases may be running safe mode, which may restrict
2043
-        // deletion if there is no KEY column used in the WHERE statement of a deletion.
2044
-        // to get around this, we first do a SELECT, get all the IDs, and then run another query
2045
-        // to delete them
2046
-        $items_for_deletion           = $this->_get_all_wpdb_results($query_params);
2047
-        $columns_and_ids_for_deleting = $this->_get_ids_for_delete($items_for_deletion, $block_deletes);
2048
-        $deletion_where_query_part    = $this->_build_query_part_for_deleting_from_columns_and_values(
2049
-            $columns_and_ids_for_deleting
2050
-        );
2051
-        /**
2052
-         * Allows client code to act on the items being deleted before the query is actually executed.
2053
-         * see php doc blocks for more details
2054
-         *
2055
-         * @param EEM_Base $this                         The model instance being acted on.
2056
-         * @param array    $query_params                 The incoming array of query parameters influencing what gets deleted.
2057
-         * @param bool     $block_deletes                @see param description in method phpdoc block.
2058
-         * @param array    $columns_and_ids_for_deleting An array indicating what entities will get removed as
2059
-         *                                               derived from the incoming query parameters.
2060
-         * @see details on the structure of this array in the phpdocs for the `_get_ids_for_delete_method`
2061
-         */
2062
-        do_action(
2063
-            'AHEE__EEM_Base__delete__before_query',
2064
-            $this,
2065
-            $query_params,
2066
-            $block_deletes,
2067
-            $columns_and_ids_for_deleting
2068
-        );
2069
-        $rows_deleted = 0;
2070
-        if ($deletion_where_query_part) {
2071
-            $model_query_info = $this->_create_model_query_info_carrier($query_params);
2072
-            $table_aliases    = array_keys($this->_tables);
2073
-            $SQL              = "DELETE "
2074
-                                . implode(", ", $table_aliases)
2075
-                                . " FROM "
2076
-                                . $model_query_info->get_full_join_sql()
2077
-                                . " WHERE "
2078
-                                . $deletion_where_query_part;
2079
-            $rows_deleted     = $this->_do_wpdb_query('query', [$SQL]);
2080
-        }
2081
-
2082
-        // Next, make sure those items are removed from the entity map; if they could be put into it at all; and if
2083
-        // there was no error with the delete query.
2084
-        if (
2085
-            $this->has_primary_key_field()
2086
-            && $rows_deleted !== false
2087
-            && isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2088
-        ) {
2089
-            $ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2090
-            foreach ($ids_for_removal as $id) {
2091
-                if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2092
-                    unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2093
-                }
2094
-            }
2095
-
2096
-            // delete any extra meta attached to the deleted entities but ONLY if this model is not an instance of
2097
-            // `EEM_Extra_Meta`.  In other words we want to prevent recursion on EEM_Extra_Meta::delete_permanently calls
2098
-            // unnecessarily.  It's very unlikely that users will have assigned Extra Meta to Extra Meta
2099
-            // (although it is possible).
2100
-            // Note this can be skipped by using the provided filter and returning false.
2101
-            if (
2102
-                apply_filters(
2103
-                    'FHEE__EEM_Base__delete_permanently__dont_delete_extra_meta_for_extra_meta',
2104
-                    ! $this instanceof EEM_Extra_Meta,
2105
-                    $this
2106
-                )
2107
-            ) {
2108
-                EEM_Extra_Meta::instance()->delete_permanently(
2109
-                    [
2110
-                        0 => [
2111
-                            'EXM_type' => $this->get_this_model_name(),
2112
-                            'OBJ_ID'   => [
2113
-                                'IN',
2114
-                                $ids_for_removal,
2115
-                            ],
2116
-                        ],
2117
-                    ]
2118
-                );
2119
-            }
2120
-        }
2121
-
2122
-        /**
2123
-         * Action called just after performing a real deletion query. Although at this point the
2124
-         * items should have been deleted
2125
-         *
2126
-         * @param EEM_Base $model
2127
-         * @param array $query_params
2128
-         * @param int   $rows_deleted
2129
-         * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2130
-         */
2131
-        do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2132
-        return (int) $rows_deleted;// how many supposedly got deleted
2133
-    }
2134
-
2135
-
2136
-    /**
2137
-     * Checks all the relations that throw error messages when there are blocking related objects
2138
-     * for related model objects. If there are any related model objects on those relations,
2139
-     * adds an EE_Error, and return true
2140
-     *
2141
-     * @param EE_Base_Class|int $this_model_obj_or_id
2142
-     * @param EE_Base_Class     $ignore_this_model_obj a model object like 'EE_Event', or 'EE_Term_Taxonomy', which
2143
-     *                                                 should be ignored when determining whether there are related
2144
-     *                                                 model objects which block this model object's deletion. Useful
2145
-     *                                                 if you know A is related to B and are considering deleting A,
2146
-     *                                                 but want to see if A has any other objects blocking its deletion
2147
-     *                                                 before removing the relation between A and B
2148
-     * @return boolean
2149
-     * @throws EE_Error
2150
-     * @throws ReflectionException
2151
-     */
2152
-    public function delete_is_blocked_by_related_models($this_model_obj_or_id, $ignore_this_model_obj = null)
2153
-    {
2154
-        // first, if $ignore_this_model_obj was supplied, get its model
2155
-        $ignored_model = $ignore_this_model_obj instanceof EE_Base_Class
2156
-            ? $ignore_this_model_obj->get_model()
2157
-            : null;
2158
-        // now check all the relations of $this_model_obj_or_id and see if there
2159
-        // are any related model objects blocking it?
2160
-        $is_blocked = false;
2161
-        foreach ($this->_model_relations as $relation_name => $relation_obj) {
2162
-            if ($relation_obj->block_delete_if_related_models_exist()) {
2163
-                // if $ignore_this_model_obj was supplied, then for the query
2164
-                // on that model needs to be told to ignore $ignore_this_model_obj
2165
-                if ($ignored_model && $relation_name === $ignored_model->get_this_model_name()) {
2166
-                    $related_model_objects = $relation_obj->get_all_related(
2167
-                        $this_model_obj_or_id,
2168
-                        [
2169
-                            [
2170
-                                $ignored_model->get_primary_key_field()->get_name() => [
2171
-                                    '!=',
2172
-                                    $ignore_this_model_obj->ID(),
2173
-                                ],
2174
-                            ],
2175
-                        ]
2176
-                    );
2177
-                } else {
2178
-                    $related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id);
2179
-                }
2180
-                if ($related_model_objects) {
2181
-                    EE_Error::add_error($relation_obj->get_deletion_error_message(), __FILE__, __FUNCTION__, __LINE__);
2182
-                    $is_blocked = true;
2183
-                }
2184
-            }
2185
-        }
2186
-        return $is_blocked;
2187
-    }
2188
-
2189
-
2190
-    /**
2191
-     * Builds the columns and values for items to delete from the incoming $row_results_for_deleting array.
2192
-     *
2193
-     * @param array $row_results_for_deleting
2194
-     * @param bool  $block_deletes whether to allow related model objects to block (prevent) this deletion
2195
-     *                             ie: enforce referential integrity
2196
-     *                             It's advisable to always leave this as TRUE, otherwise you could corrupt your DB
2197
-     * @return array               The shape of this array depends on whether the model `has_primary_key_field` or not.
2198
-     *                             If the model DOES have a primary_key_field, then the array will be a simple single
2199
-     *                             dimension array where the key is the fully qualified primary key column and
2200
-     *                             the value is an array of ids that will be deleted.
2201
-     *                             Example:
2202
-     *                              [ 'Event.EVT_ID' => [ 1,2,3 ]]
2203
-     *                             If the model DOES NOT have a primary_key_field, then the array will be a
2204
-     *                             two-dimensional array where each element is a group of columns and values that get deleted.
2205
-     *                             Example:
2206
-     *                              [
2207
-     *                                  0 => [
2208
-     *                                      'Term_Relationship.object_id' => 1
2209
-     *                                      'Term_Relationship.term_taxonomy_id' => 5
2210
-     *                                  ],
2211
-     *                                  1 => [
2212
-     *                                      'Term_Relationship.object_id' => 1
2213
-     *                                      'Term_Relationship.term_taxonomy_id' => 6
2214
-     *                                  ]
2215
-     *                              ]
2216
-     * @throws EE_Error
2217
-     * @throws ReflectionException
2218
-     */
2219
-    protected function _get_ids_for_delete(array $row_results_for_deleting, $block_deletes = true)
2220
-    {
2221
-        $ids_to_delete_indexed_by_column = [];
2222
-        if ($this->has_primary_key_field()) {
2223
-            $primary_table = $this->_get_main_table();
2224
-            // following lines are commented out because the variables were not being used
2225
-            // not deleting because unsure if calls were intentionally causing side effects
2226
-            // $primary_table_pk_field          =
2227
-            //     $this->get_field_by_column($primary_table->get_fully_qualified_pk_column());
2228
-            // $other_tables                    = $this->_get_other_tables();
2229
-            $ids_to_delete_indexed_by_column = $query = [];
2230
-            foreach ($row_results_for_deleting as $item_to_delete) {
2231
-                // before we mark this item for deletion,
2232
-                // make sure there's no related entities blocking its deletion (if we're checking)
2233
-                if (
2234
-                    $block_deletes
2235
-                    && $this->delete_is_blocked_by_related_models(
2236
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2237
-                    )
2238
-                ) {
2239
-                    continue;
2240
-                }
2241
-                // primary table deletes
2242
-                if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2243
-                    $ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2244
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2245
-                }
2246
-            }
2247
-        } elseif (count($this->get_combined_primary_key_fields()) > 1) {
2248
-            $fields = $this->get_combined_primary_key_fields();
2249
-            foreach ($row_results_for_deleting as $item_to_delete) {
2250
-                $ids_to_delete_indexed_by_column_for_row = [];
2251
-                foreach ($fields as $cpk_field) {
2252
-                    if ($cpk_field instanceof EE_Model_Field_Base) {
2253
-                        $ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2254
-                            $item_to_delete[ $cpk_field->get_qualified_column() ];
2255
-                    }
2256
-                }
2257
-                $ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
2258
-            }
2259
-        } else {
2260
-            // so there's no primary key and no combined key...
2261
-            // sorry, can't help you
2262
-            throw new EE_Error(
2263
-                sprintf(
2264
-                    esc_html__(
2265
-                        "Cannot delete objects of type %s because there is no primary key NOR combined key",
2266
-                        "event_espresso"
2267
-                    ),
2268
-                    $this->class_name
2269
-                )
2270
-            );
2271
-        }
2272
-        return $ids_to_delete_indexed_by_column;
2273
-    }
2274
-
2275
-
2276
-    /**
2277
-     * This receives an array of columns and values set to be deleted (as prepared by _get_ids_for_delete) and prepares
2278
-     * the corresponding query_part for the query performing the deletion.
2279
-     *
2280
-     * @param array $ids_to_delete_indexed_by_column @see _get_ids_for_delete for how this array might be shaped.
2281
-     * @return string
2282
-     * @throws EE_Error
2283
-     */
2284
-    protected function _build_query_part_for_deleting_from_columns_and_values(array $ids_to_delete_indexed_by_column)
2285
-    {
2286
-        $query_part = '';
2287
-        if (empty($ids_to_delete_indexed_by_column)) {
2288
-            return $query_part;
2289
-        } elseif ($this->has_primary_key_field()) {
2290
-            $query = [];
2291
-            foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2292
-                $query[] = $column . ' IN' . $this->_construct_in_value($ids, $this->_primary_key_field);
2293
-            }
2294
-            $query_part = ! empty($query)
2295
-                ? implode(' AND ', $query)
2296
-                : $query_part;
2297
-        } elseif (count($this->get_combined_primary_key_fields()) > 1) {
2298
-            $ways_to_identify_a_row = [];
2299
-            foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2300
-                $values_for_each_combined_primary_key_for_a_row = [];
2301
-                foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2302
-                    $values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2303
-                }
2304
-                $ways_to_identify_a_row[] = '('
2305
-                                            . implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
2306
-                                            . ')';
2307
-            }
2308
-            $query_part = implode(' OR ', $ways_to_identify_a_row);
2309
-        }
2310
-        return $query_part;
2311
-    }
2312
-
2313
-
2314
-    /**
2315
-     * Gets the model field by the fully qualified name
2316
-     *
2317
-     * @param string $qualified_column_name eg 'Event_CPT.post_name' or $field_obj->get_qualified_column()
2318
-     * @return EE_Model_Field_Base
2319
-     * @throws EE_Error
2320
-     * @throws EE_Error
2321
-     */
2322
-    public function get_field_by_column($qualified_column_name)
2323
-    {
2324
-        foreach ($this->field_settings(true) as $field_name => $field_obj) {
2325
-            if ($field_obj->get_qualified_column() === $qualified_column_name) {
2326
-                return $field_obj;
2327
-            }
2328
-        }
2329
-        throw new EE_Error(
2330
-            sprintf(
2331
-                esc_html__('Could not find a field on the model "%1$s" for qualified column "%2$s"', 'event_espresso'),
2332
-                $this->get_this_model_name(),
2333
-                $qualified_column_name
2334
-            )
2335
-        );
2336
-    }
2337
-
2338
-
2339
-    /**
2340
-     * Count all the rows that match criteria the model query params.
2341
-     * If $field_to_count isn't provided, the model's primary key is used. Otherwise, we count by field_to_count's
2342
-     * column
2343
-     *
2344
-     * @param array  $query_params   @see
2345
-     *                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2346
-     * @param string $field_to_count field on model to count by (not column name)
2347
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2348
-     *                               that by the setting $distinct to TRUE;
2349
-     * @return int
2350
-     * @throws EE_Error
2351
-     * @throws ReflectionException
2352
-     */
2353
-    public function count($query_params = [], $field_to_count = '', $distinct = false)
2354
-    {
2355
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
2356
-        if ($field_to_count) {
2357
-            $field_obj       = $this->field_settings_for($field_to_count);
2358
-            $column_to_count = $field_obj->get_qualified_column();
2359
-        } elseif ($this->has_primary_key_field()) {
2360
-            $pk_field_obj    = $this->get_primary_key_field();
2361
-            $column_to_count = $pk_field_obj->get_qualified_column();
2362
-        } else {
2363
-            // there's no primary key
2364
-            // if we're counting distinct items, and there's no primary key,
2365
-            // we need to list out the columns for distinction;
2366
-            // otherwise we can just use star
2367
-            if ($distinct) {
2368
-                $columns_to_use = [];
2369
-                foreach ($this->get_combined_primary_key_fields() as $field_obj) {
2370
-                    $columns_to_use[] = $field_obj->get_qualified_column();
2371
-                }
2372
-                $column_to_count = implode(',', $columns_to_use);
2373
-            } else {
2374
-                $column_to_count = '*';
2375
-            }
2376
-        }
2377
-        $column_to_count = $distinct
2378
-            ? "DISTINCT " . $column_to_count
2379
-            : $column_to_count;
2380
-        $SQL             =
2381
-            "SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2382
-        return (int) $this->_do_wpdb_query('get_var', [$SQL]);
2383
-    }
2384
-
2385
-
2386
-    /**
2387
-     * Sums up the value of the $field_to_sum (defaults to the primary key, which isn't terribly useful)
2388
-     *
2389
-     * @param array  $query_params @see
2390
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2391
-     * @param string $field_to_sum name of field (array key in $_fields array)
2392
-     * @return float
2393
-     * @throws EE_Error
2394
-     * @throws ReflectionException
2395
-     */
2396
-    public function sum($query_params, $field_to_sum = null)
2397
-    {
2398
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
2399
-        if ($field_to_sum) {
2400
-            $field_obj = $this->field_settings_for($field_to_sum);
2401
-        } else {
2402
-            $field_obj = $this->get_primary_key_field();
2403
-        }
2404
-        $column_to_count = $field_obj->get_qualified_column();
2405
-        $SQL             =
2406
-            "SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2407
-        $return_value    = $this->_do_wpdb_query('get_var', [$SQL]);
2408
-        $data_type       = $field_obj->get_wpdb_data_type();
2409
-        if ($data_type === '%d' || $data_type === '%s') {
2410
-            return (float) $return_value;
2411
-        }
2412
-        // must be %f
2413
-        return (float) $return_value;
2414
-    }
2415
-
2416
-
2417
-    /**
2418
-     * Just calls the specified method on $wpdb with the given arguments
2419
-     * Consolidates a little extra error handling code
2420
-     *
2421
-     * @param string $wpdb_method
2422
-     * @param array  $arguments_to_provide
2423
-     * @return mixed
2424
-     * @throws EE_Error
2425
-     * @global wpdb  $wpdb
2426
-     */
2427
-    protected function _do_wpdb_query($wpdb_method, $arguments_to_provide)
2428
-    {
2429
-        // if we're in maintenance mode level 2, DON'T run any queries
2430
-        // because level 2 indicates the database needs updating and
2431
-        // is probably out of sync with the code
2432
-        if (DbStatus::isOffline()) {
2433
-            throw new RuntimeException(
2434
-                esc_html__(
2435
-                    "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.",
2436
-                    "event_espresso"
2437
-                )
2438
-            );
2439
-        }
2440
-        /** @type WPDB $wpdb */
2441
-        global $wpdb;
2442
-        if (! method_exists($wpdb, $wpdb_method)) {
2443
-            throw new DomainException(
2444
-                sprintf(
2445
-                    esc_html__(
2446
-                        'There is no method named "%s" on Wordpress\' $wpdb object',
2447
-                        'event_espresso'
2448
-                    ),
2449
-                    $wpdb_method
2450
-                )
2451
-            );
2452
-        }
2453
-        $old_show_errors_value = $wpdb->show_errors;
2454
-        if (defined('WP_DEBUG') && WP_DEBUG) {
2455
-            $wpdb->show_errors(false);
2456
-        }
2457
-        $result = $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2458
-        $this->show_db_query_if_previously_requested($wpdb->last_query);
2459
-        if (defined('WP_DEBUG') && WP_DEBUG) {
2460
-            $wpdb->show_errors($old_show_errors_value);
2461
-            if (! empty($wpdb->last_error)) {
2462
-                throw new EE_Error(sprintf(esc_html__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2463
-            }
2464
-            if ($result === false) {
2465
-                throw new EE_Error(
2466
-                    sprintf(
2467
-                        esc_html__(
2468
-                            'WPDB Error occurred, but no error message was logged by wpdb! The wpdb method called was "%1$s" and the arguments were "%2$s"',
2469
-                            'event_espresso'
2470
-                        ),
2471
-                        $wpdb_method,
2472
-                        var_export($arguments_to_provide, true)
2473
-                    )
2474
-                );
2475
-            }
2476
-        } elseif ($result === false) {
2477
-            EE_Error::add_error(
2478
-                sprintf(
2479
-                    esc_html__(
2480
-                        '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"',
2481
-                        'event_espresso'
2482
-                    ),
2483
-                    $wpdb_method,
2484
-                    var_export($arguments_to_provide, true),
2485
-                    $wpdb->last_error
2486
-                ),
2487
-                __FILE__,
2488
-                __FUNCTION__,
2489
-                __LINE__
2490
-            );
2491
-        }
2492
-        return $result;
2493
-    }
2494
-
2495
-
2496
-    /**
2497
-     * Attempts to run the indicated WPDB method with the provided arguments,
2498
-     * and if there's an error tries to verify the DB is correct. Uses
2499
-     * the static property EEM_Base::$_db_verification_level to determine whether
2500
-     * we should try to fix the EE core db, the addons, or just give up
2501
-     *
2502
-     * @param string $wpdb_method
2503
-     * @param array  $arguments_to_provide
2504
-     * @return mixed
2505
-     */
2506
-    private function _process_wpdb_query($wpdb_method, $arguments_to_provide)
2507
-    {
2508
-        /** @type WPDB $wpdb */
2509
-        global $wpdb;
2510
-        $wpdb->last_error = null;
2511
-        $result           = call_user_func_array([$wpdb, $wpdb_method], $arguments_to_provide);
2512
-        // was there an error running the query? but we don't care on new activations
2513
-        // (we're going to setup the DB anyway on new activations)
2514
-        if (
2515
-            ($result === false || ! empty($wpdb->last_error))
2516
-            && EE_System::instance()->detect_req_type() !== EE_System::req_type_new_activation
2517
-        ) {
2518
-            switch (EEM_Base::$_db_verification_level) {
2519
-                case EEM_Base::db_verified_none:
2520
-                    // let's double-check core's DB
2521
-                    $error_message = $this->_verify_core_db($wpdb_method, $arguments_to_provide);
2522
-                    break;
2523
-                case EEM_Base::db_verified_core:
2524
-                    // STILL NO LOVE?? verify all the addons too. Maybe they need to be fixed
2525
-                    $error_message = $this->_verify_addons_db($wpdb_method, $arguments_to_provide);
2526
-                    break;
2527
-                case EEM_Base::db_verified_addons:
2528
-                    // ummmm... you in trouble
2529
-                    return $result;
2530
-            }
2531
-            if (! empty($error_message)) {
2532
-                EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2533
-                trigger_error($error_message);
2534
-            }
2535
-            return $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2536
-        }
2537
-        return $result;
2538
-    }
2539
-
2540
-
2541
-    /**
2542
-     * Verifies the EE core database is up-to-date and records that we've done it on
2543
-     * EEM_Base::$_db_verification_level
2544
-     *
2545
-     * @param string $wpdb_method
2546
-     * @param array  $arguments_to_provide
2547
-     * @return string
2548
-     * @throws EE_Error
2549
-     * @throws ReflectionException
2550
-     */
2551
-    private function _verify_core_db($wpdb_method, $arguments_to_provide)
2552
-    {
2553
-        /** @type WPDB $wpdb */
2554
-        global $wpdb;
2555
-        // ok remember that we've already attempted fixing the core db, in case the problem persists
2556
-        EEM_Base::$_db_verification_level = EEM_Base::db_verified_core;
2557
-        $error_message                    = sprintf(
2558
-            esc_html__(
2559
-                'WPDB Error "%1$s" while running wpdb method "%2$s" with arguments %3$s. Automatically attempting to fix EE Core DB',
2560
-                'event_espresso'
2561
-            ),
2562
-            $wpdb->last_error,
2563
-            $wpdb_method,
2564
-            wp_json_encode($arguments_to_provide)
2565
-        );
2566
-        EE_System::instance()->initialize_db_if_no_migrations_required(false, true);
2567
-        return $error_message;
2568
-    }
2569
-
2570
-
2571
-    /**
2572
-     * Verifies the EE addons' database is up-to-date and records that we've done it on
2573
-     * EEM_Base::$_db_verification_level
2574
-     *
2575
-     * @param $wpdb_method
2576
-     * @param $arguments_to_provide
2577
-     * @return string
2578
-     * @throws EE_Error
2579
-     * @throws ReflectionException
2580
-     */
2581
-    private function _verify_addons_db($wpdb_method, $arguments_to_provide)
2582
-    {
2583
-        /** @type WPDB $wpdb */
2584
-        global $wpdb;
2585
-        // ok remember that we've already attempted fixing the addons dbs, in case the problem persists
2586
-        EEM_Base::$_db_verification_level = EEM_Base::db_verified_addons;
2587
-        $error_message                    = sprintf(
2588
-            esc_html__(
2589
-                'WPDB AGAIN: Error "%1$s" while running the same method and arguments as before. Automatically attempting to fix EE Addons DB',
2590
-                'event_espresso'
2591
-            ),
2592
-            $wpdb->last_error,
2593
-            $wpdb_method,
2594
-            wp_json_encode($arguments_to_provide)
2595
-        );
2596
-        EE_System::instance()->initialize_addons();
2597
-        return $error_message;
2598
-    }
2599
-
2600
-
2601
-    /**
2602
-     * In order to avoid repeating this code for the get_all, sum, and count functions, put the code parts
2603
-     * that are identical in here. Returns a string of SQL of everything in a SELECT query except the beginning
2604
-     * SELECT clause, eg " FROM wp_posts AS Event INNER JOIN ... WHERE ... ORDER BY ... LIMIT ... GROUP BY ... HAVING
2605
-     * ..."
2606
-     *
2607
-     * @param EE_Model_Query_Info_Carrier $model_query_info
2608
-     * @return string
2609
-     */
2610
-    private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2611
-    {
2612
-        return " FROM " . $model_query_info->get_full_join_sql() .
2613
-               $model_query_info->get_where_sql() .
2614
-               $model_query_info->get_group_by_sql() .
2615
-               $model_query_info->get_having_sql() .
2616
-               $model_query_info->get_order_by_sql() .
2617
-               $model_query_info->get_limit_sql();
2618
-    }
2619
-
2620
-
2621
-    /**
2622
-     * Set to easily debug the next X queries ran from this model.
2623
-     *
2624
-     * @param int $count
2625
-     */
2626
-    public function show_next_x_db_queries($count = 1)
2627
-    {
2628
-        $this->_show_next_x_db_queries = $count;
2629
-    }
2630
-
2631
-
2632
-    /**
2633
-     * @param $sql_query
2634
-     */
2635
-    public function show_db_query_if_previously_requested($sql_query)
2636
-    {
2637
-        if ($this->_show_next_x_db_queries > 0) {
2638
-            $left = is_admin() ? '12rem' : '2rem';
2639
-            echo "
901
+	 *                                                      'OR' => [
902
+	 *                                                          'Registration.Attendee.ATT_fname'       => ['like', 'Mc%'],
903
+	 *                                                          'Registration.Attendee.ATT_fname*other' => ['like', 'Mac%'],
904
+	 *                                                      ],
905
+	 *                                                  ],
906
+	 *                                                  'limit'    => 10,
907
+	 *                                                  'group_by' => 'TXN_ID',
908
+	 *                                              ]
909
+	 *                                          );
910
+	 *                                        get all the answers to the question titled "shirt size" for event with id
911
+	 *                                        12, ordered by their answer:
912
+	 *                                          EEM_Answer::instance()->get_all(
913
+	 *                                              [
914
+	 *                                                  [
915
+	 *                                                      'Question.QST_display_text' => 'shirt size',
916
+	 *                                                      'Registration.Event.EVT_ID' => 12,
917
+	 *                                                  ],
918
+	 *                                                  'order_by' => ['ANS_value' => 'ASC'],
919
+	 *                                              ]
920
+	 *                                          );
921
+	 * @throws EE_Error
922
+	 * @throws ReflectionException
923
+	 */
924
+	public function get_all($query_params = [])
925
+	{
926
+		if (
927
+			isset($query_params['limit'])
928
+			&& ! isset($query_params['group_by'])
929
+		) {
930
+			$query_params['group_by'] = array_keys($this->get_combined_primary_key_fields());
931
+		}
932
+		return $this->_create_objects($this->_get_all_wpdb_results($query_params));
933
+	}
934
+
935
+
936
+	/**
937
+	 * Modifies the query parameters so we only get back model objects
938
+	 * that "belong" to the current user
939
+	 *
940
+	 * @param array $query_params @see
941
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
942
+	 * @return array @see
943
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
944
+	 * @throws ReflectionException
945
+	 * @throws ReflectionException
946
+	 */
947
+	public function alter_query_params_to_only_include_mine($query_params = [])
948
+	{
949
+		$wp_user_field_name = $this->wp_user_field_name();
950
+		if ($wp_user_field_name) {
951
+			$query_params[0][ $wp_user_field_name ] = get_current_user_id();
952
+		}
953
+		return $query_params;
954
+	}
955
+
956
+
957
+	/**
958
+	 * Returns the name of the field's name that points to the WP_User table
959
+	 *  on this model (or follows the _model_chain_to_wp_user and uses that model's
960
+	 * foreign key to the WP_User table)
961
+	 *
962
+	 * @return string|boolean string on success, boolean false when there is no
963
+	 * foreign key to the WP_User table
964
+	 * @throws ReflectionException
965
+	 * @throws ReflectionException
966
+	 */
967
+	public function wp_user_field_name()
968
+	{
969
+		try {
970
+			if (! empty($this->_model_chain_to_wp_user)) {
971
+				$models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
972
+				$last_model_name              = end($models_to_follow_to_wp_users);
973
+				$model_with_fk_to_wp_users    = EE_Registry::instance()->load_model($last_model_name);
974
+				$model_chain_to_wp_user       = $this->_model_chain_to_wp_user . '.';
975
+			} else {
976
+				$model_with_fk_to_wp_users = $this;
977
+				$model_chain_to_wp_user    = '';
978
+			}
979
+			$wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
980
+			return $model_chain_to_wp_user . $wp_user_field->get_name();
981
+		} catch (EE_Error $e) {
982
+			return false;
983
+		}
984
+	}
985
+
986
+
987
+	/**
988
+	 * Returns the _model_chain_to_wp_user string, which indicates which related model
989
+	 * (or transiently-related model) has a foreign key to the wp_users table;
990
+	 * useful for finding if model objects of this type are 'owned' by the current user.
991
+	 * This is an empty string when the foreign key is on this model and when it isn't,
992
+	 * but is only non-empty when this model's ownership is indicated by a RELATED model
993
+	 * (or transiently-related model)
994
+	 *
995
+	 * @return string
996
+	 */
997
+	public function model_chain_to_wp_user()
998
+	{
999
+		return $this->_model_chain_to_wp_user;
1000
+	}
1001
+
1002
+
1003
+	/**
1004
+	 * Whether this model is 'owned' by a specific wordpress user (even indirectly,
1005
+	 * like how registrations don't have a foreign key to wp_users, but the
1006
+	 * events they are for are), or is unrelated to wp users.
1007
+	 * generally available
1008
+	 *
1009
+	 * @return boolean
1010
+	 */
1011
+	public function is_owned()
1012
+	{
1013
+		if ($this->model_chain_to_wp_user()) {
1014
+			return true;
1015
+		}
1016
+		try {
1017
+			$this->get_foreign_key_to('WP_User');
1018
+			return true;
1019
+		} catch (EE_Error $e) {
1020
+			return false;
1021
+		}
1022
+	}
1023
+
1024
+
1025
+	/**
1026
+	 * Used internally to get WPDB results, because other functions, besides get_all, may want to do some queries, but
1027
+	 * may want to preserve the WPDB results (eg, update, which first queries to make sure we have all the tables on
1028
+	 * the model)
1029
+	 *
1030
+	 * @param array  $query_params      @see
1031
+	 *                                  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1032
+	 * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1033
+	 * @param mixed  $columns_to_select What columns to select. By default, we select all columns specified by the
1034
+	 *                                  fields on the model, and the models we joined to in the query. However, you can
1035
+	 *                                  override this and set the select to "*", or a specific column name, like
1036
+	 *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1037
+	 *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1038
+	 *                                  the aliases used to refer to this selection, and values are to be
1039
+	 *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1040
+	 *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1041
+	 * @return array | stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1042
+	 * @throws EE_Error
1043
+	 * @throws InvalidArgumentException
1044
+	 * @throws ReflectionException
1045
+	 */
1046
+	protected function _get_all_wpdb_results($query_params = [], $output = ARRAY_A, $columns_to_select = null)
1047
+	{
1048
+		$this->_custom_selections = $this->getCustomSelection($query_params, $columns_to_select);
1049
+		$model_query_info         = $this->_create_model_query_info_carrier($query_params);
1050
+		$select_expressions       = $columns_to_select === null
1051
+			? $this->_construct_default_select_sql($model_query_info)
1052
+			: '';
1053
+		if ($this->_custom_selections instanceof CustomSelects) {
1054
+			$custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1055
+			$select_expressions .= $select_expressions
1056
+				? ', ' . $custom_expressions
1057
+				: $custom_expressions;
1058
+		}
1059
+
1060
+		$SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1061
+		return $this->_do_wpdb_query('get_results', [$SQL, $output]);
1062
+	}
1063
+
1064
+
1065
+	/**
1066
+	 * Get a CustomSelects object if the $query_params or $columns_to_select allows for it.
1067
+	 * Note: $query_params['extra_selects'] will always override any $columns_to_select values. It is the preferred
1068
+	 * method of including extra select information.
1069
+	 *
1070
+	 * @param array             $query_params
1071
+	 * @param null|array|string $columns_to_select
1072
+	 * @return null|CustomSelects
1073
+	 * @throws InvalidArgumentException
1074
+	 */
1075
+	protected function getCustomSelection(array $query_params, $columns_to_select = null): ?CustomSelects
1076
+	{
1077
+		if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1078
+			return null;
1079
+		}
1080
+		$selects = $query_params['extra_selects'] ?? $columns_to_select;
1081
+		$selects = is_string($selects)
1082
+			? explode(',', $selects)
1083
+			: $selects;
1084
+		return new CustomSelects($selects);
1085
+	}
1086
+
1087
+
1088
+	/**
1089
+	 * Gets an array of rows from the database just like $wpdb->get_results would,
1090
+	 * but you can use the model query params to more easily
1091
+	 * take care of joins, field preparation etc.
1092
+	 *
1093
+	 * @param array  $query_params      @see
1094
+	 *                                  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1095
+	 * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1096
+	 * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1097
+	 *                                  fields on the model, and the models we joined to in the query. However, you can
1098
+	 *                                  override this and set the select to "*", or a specific column name, like
1099
+	 *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1100
+	 *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1101
+	 *                                  the aliases used to refer to this selection, and values are to be
1102
+	 *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1103
+	 *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1104
+	 * @return array|stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1105
+	 * @throws EE_Error
1106
+	 * @throws ReflectionException
1107
+	 */
1108
+	public function get_all_wpdb_results($query_params = [], $output = ARRAY_A, $columns_to_select = null)
1109
+	{
1110
+		return $this->_get_all_wpdb_results($query_params, $output, $columns_to_select);
1111
+	}
1112
+
1113
+
1114
+	/**
1115
+	 * For creating a custom select statement
1116
+	 *
1117
+	 * @param mixed $columns_to_select either a string to be inserted directly as the select statement,
1118
+	 *                                 or an array where keys are aliases, and values are arrays where 0=>the selection
1119
+	 *                                 SQL, and 1=>is the datatype
1120
+	 * @return string
1121
+	 * @throws EE_Error
1122
+	 */
1123
+	private function _construct_select_from_input($columns_to_select)
1124
+	{
1125
+		if (is_array($columns_to_select)) {
1126
+			$select_sql_array = [];
1127
+			foreach ($columns_to_select as $alias => $selection_and_datatype) {
1128
+				if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1129
+					throw new EE_Error(
1130
+						sprintf(
1131
+							esc_html__(
1132
+								"Custom selection %s (alias %s) needs to be an array like array('COUNT(REG_ID)','%%d')",
1133
+								'event_espresso'
1134
+							),
1135
+							$selection_and_datatype,
1136
+							$alias
1137
+						)
1138
+					);
1139
+				}
1140
+				if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1141
+					throw new EE_Error(
1142
+						sprintf(
1143
+							esc_html__(
1144
+								"Datatype %s (for selection '%s' and alias '%s') is not a valid wpdb datatype (eg %%s)",
1145
+								'event_espresso'
1146
+							),
1147
+							$selection_and_datatype[1],
1148
+							$selection_and_datatype[0],
1149
+							$alias,
1150
+							implode(', ', $this->_valid_wpdb_data_types)
1151
+						)
1152
+					);
1153
+				}
1154
+				$select_sql_array[] = "{$selection_and_datatype[0]} AS $alias";
1155
+			}
1156
+			$columns_to_select_string = implode(', ', $select_sql_array);
1157
+		} else {
1158
+			$columns_to_select_string = $columns_to_select;
1159
+		}
1160
+		return $columns_to_select_string;
1161
+	}
1162
+
1163
+
1164
+	/**
1165
+	 * Convenient wrapper for getting the primary key field's name. Eg, on Registration, this would be 'REG_ID'
1166
+	 *
1167
+	 * @return string
1168
+	 * @throws EE_Error
1169
+	 */
1170
+	public function primary_key_name()
1171
+	{
1172
+		return $this->get_primary_key_field()->get_name();
1173
+	}
1174
+
1175
+
1176
+	/**
1177
+	 * Gets a single item for this model from the DB, given only its ID (or null if none is found).
1178
+	 * If there is no primary key on this model, $id is treated as primary key string
1179
+	 *
1180
+	 * @param mixed $id int or string, depending on the type of the model's primary key
1181
+	 * @return EE_Base_Class|mixed|null
1182
+	 * @throws EE_Error
1183
+	 * @throws ReflectionException
1184
+	 */
1185
+	public function get_one_by_ID($id)
1186
+	{
1187
+		// since entities with no ID can still have properties, we need to check the cache for them
1188
+		$cached_value = $this->get_from_entity_map($id);
1189
+		if ($cached_value) {
1190
+			return $cached_value;
1191
+		}
1192
+		// but if no cached property AND no id is passed, just return null
1193
+		if (empty($id)) {
1194
+			return null;
1195
+		}
1196
+		$model_object = $this->get_one(
1197
+			$this->alter_query_params_to_restrict_by_ID(
1198
+				$id,
1199
+				['default_where_conditions' => EE_Default_Where_Conditions::MINIMUM_ALL]
1200
+			)
1201
+		);
1202
+		$className    = $this->_get_class_name();
1203
+		if ($model_object instanceof $className) {
1204
+			// make sure valid objects get added to the entity map
1205
+			// so that the next call to this method doesn't trigger another trip to the db
1206
+			$this->add_to_entity_map($model_object);
1207
+		}
1208
+		return $model_object;
1209
+	}
1210
+
1211
+
1212
+	/**
1213
+	 * Alters query parameters to only get items with this ID are returned.
1214
+	 * Takes into account that the ID might be a string produced by EEM_Base::get_index_primary_key_string(),
1215
+	 * or could just be a simple primary key ID
1216
+	 *
1217
+	 * @param int   $id
1218
+	 * @param array $query_params
1219
+	 * @return array of normal query params, @see
1220
+	 *               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1221
+	 * @throws EE_Error
1222
+	 */
1223
+	public function alter_query_params_to_restrict_by_ID($id, $query_params = [])
1224
+	{
1225
+		if (! isset($query_params[0])) {
1226
+			$query_params[0] = [];
1227
+		}
1228
+		$conditions_from_id = $this->parse_index_primary_key_string($id);
1229
+		if ($conditions_from_id === null) {
1230
+			$query_params[0][ $this->primary_key_name() ] = $id;
1231
+		} else {
1232
+			// no primary key, so the $id must be from the get_index_primary_key_string()
1233
+			$query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
1234
+		}
1235
+		return $query_params;
1236
+	}
1237
+
1238
+
1239
+	/**
1240
+	 * Gets a single item for this model from the DB, given the $query_params. Only returns a single class, not an
1241
+	 * array. If no item is found, null is returned.
1242
+	 *
1243
+	 * @param array $query_params like EEM_Base's $query_params variable.
1244
+	 * @return EE_Base_Class|EE_Soft_Delete_Base_Class|NULL
1245
+	 * @throws EE_Error
1246
+	 * @throws ReflectionException
1247
+	 */
1248
+	public function get_one($query_params = [])
1249
+	{
1250
+		if (! is_array($query_params)) {
1251
+			EE_Error::doing_it_wrong(
1252
+				'EEM_Base::get_one',
1253
+				sprintf(
1254
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1255
+					gettype($query_params)
1256
+				),
1257
+				'4.6.0'
1258
+			);
1259
+			$query_params = [];
1260
+		}
1261
+		$query_params['limit'] = 1;
1262
+		$items                 = $this->get_all($query_params);
1263
+		if (empty($items)) {
1264
+			return null;
1265
+		}
1266
+		return array_shift($items);
1267
+	}
1268
+
1269
+
1270
+	/**
1271
+	 * Returns the next x number of items in sequence from the given value as
1272
+	 * found in the database matching the given query conditions.
1273
+	 *
1274
+	 * @param mixed $current_field_value    Value used for the reference point.
1275
+	 * @param null  $field_to_order_by      What field is used for the
1276
+	 *                                      reference point.
1277
+	 * @param int   $limit                  How many to return.
1278
+	 * @param array $query_params           Extra conditions on the query.
1279
+	 * @param null  $columns_to_select      If left null, then an array of
1280
+	 *                                      EE_Base_Class objects is returned,
1281
+	 *                                      otherwise you can indicate just the
1282
+	 *                                      columns you want returned.
1283
+	 * @return EE_Base_Class[]|array
1284
+	 * @throws EE_Error
1285
+	 * @throws ReflectionException
1286
+	 */
1287
+	public function next_x(
1288
+		$current_field_value,
1289
+		$field_to_order_by = null,
1290
+		$limit = 1,
1291
+		$query_params = [],
1292
+		$columns_to_select = null
1293
+	) {
1294
+		return $this->_get_consecutive(
1295
+			$current_field_value,
1296
+			'>',
1297
+			$field_to_order_by,
1298
+			$limit,
1299
+			$query_params,
1300
+			$columns_to_select
1301
+		);
1302
+	}
1303
+
1304
+
1305
+	/**
1306
+	 * Returns the previous x number of items in sequence from the given value
1307
+	 * as found in the database matching the given query conditions.
1308
+	 *
1309
+	 * @param mixed $current_field_value    Value used for the reference point.
1310
+	 * @param null  $field_to_order_by      What field is used for the
1311
+	 *                                      reference point.
1312
+	 * @param int   $limit                  How many to return.
1313
+	 * @param array $query_params           Extra conditions on the query.
1314
+	 * @param null  $columns_to_select      If left null, then an array of
1315
+	 *                                      EE_Base_Class objects is returned,
1316
+	 *                                      otherwise you can indicate just the
1317
+	 *                                      columns you want returned.
1318
+	 * @return EE_Base_Class[]|array
1319
+	 * @throws EE_Error
1320
+	 * @throws ReflectionException
1321
+	 */
1322
+	public function previous_x(
1323
+		$current_field_value,
1324
+		$field_to_order_by = null,
1325
+		$limit = 1,
1326
+		$query_params = [],
1327
+		$columns_to_select = null
1328
+	) {
1329
+		return $this->_get_consecutive(
1330
+			$current_field_value,
1331
+			'<',
1332
+			$field_to_order_by,
1333
+			$limit,
1334
+			$query_params,
1335
+			$columns_to_select
1336
+		);
1337
+	}
1338
+
1339
+
1340
+	/**
1341
+	 * Returns the next item in sequence from the given value as found in the
1342
+	 * database matching the given query conditions.
1343
+	 *
1344
+	 * @param mixed $current_field_value    Value used for the reference point.
1345
+	 * @param null  $field_to_order_by      What field is used for the
1346
+	 *                                      reference point.
1347
+	 * @param array $query_params           Extra conditions on the query.
1348
+	 * @param null  $columns_to_select      If left null, then an EE_Base_Class
1349
+	 *                                      object is returned, otherwise you
1350
+	 *                                      can indicate just the columns you
1351
+	 *                                      want and a single array indexed by
1352
+	 *                                      the columns will be returned.
1353
+	 * @return EE_Base_Class|null|array()
1354
+	 * @throws EE_Error
1355
+	 * @throws ReflectionException
1356
+	 */
1357
+	public function next(
1358
+		$current_field_value,
1359
+		$field_to_order_by = null,
1360
+		$query_params = [],
1361
+		$columns_to_select = null
1362
+	) {
1363
+		$results = $this->_get_consecutive(
1364
+			$current_field_value,
1365
+			'>',
1366
+			$field_to_order_by,
1367
+			1,
1368
+			$query_params,
1369
+			$columns_to_select
1370
+		);
1371
+		return empty($results)
1372
+			? null
1373
+			: reset($results);
1374
+	}
1375
+
1376
+
1377
+	/**
1378
+	 * Returns the previous item in sequence from the given value as found in
1379
+	 * the database matching the given query conditions.
1380
+	 *
1381
+	 * @param mixed $current_field_value    Value used for the reference point.
1382
+	 * @param null  $field_to_order_by      What field is used for the
1383
+	 *                                      reference point.
1384
+	 * @param array $query_params           Extra conditions on the query.
1385
+	 * @param null  $columns_to_select      If left null, then an EE_Base_Class
1386
+	 *                                      object is returned, otherwise you
1387
+	 *                                      can indicate just the columns you
1388
+	 *                                      want and a single array indexed by
1389
+	 *                                      the columns will be returned.
1390
+	 * @return EE_Base_Class|null|array()
1391
+	 * @throws EE_Error
1392
+	 * @throws ReflectionException
1393
+	 */
1394
+	public function previous(
1395
+		$current_field_value,
1396
+		$field_to_order_by = null,
1397
+		$query_params = [],
1398
+		$columns_to_select = null
1399
+	) {
1400
+		$results = $this->_get_consecutive(
1401
+			$current_field_value,
1402
+			'<',
1403
+			$field_to_order_by,
1404
+			1,
1405
+			$query_params,
1406
+			$columns_to_select
1407
+		);
1408
+		return empty($results)
1409
+			? null
1410
+			: reset($results);
1411
+	}
1412
+
1413
+
1414
+	/**
1415
+	 * Returns the a consecutive number of items in sequence from the given
1416
+	 * value as found in the database matching the given query conditions.
1417
+	 *
1418
+	 * @param mixed  $current_field_value   Value used for the reference point.
1419
+	 * @param string $operand               What operand is used for the sequence.
1420
+	 * @param string $field_to_order_by     What field is used for the reference point.
1421
+	 * @param int    $limit                 How many to return.
1422
+	 * @param array  $query_params          Extra conditions on the query.
1423
+	 * @param null   $columns_to_select     If left null, then an array of EE_Base_Class objects is returned,
1424
+	 *                                      otherwise you can indicate just the columns you want returned.
1425
+	 * @return EE_Base_Class[]|array
1426
+	 * @throws EE_Error
1427
+	 * @throws ReflectionException
1428
+	 */
1429
+	protected function _get_consecutive(
1430
+		$current_field_value,
1431
+		$operand = '>',
1432
+		$field_to_order_by = null,
1433
+		$limit = 1,
1434
+		$query_params = [],
1435
+		$columns_to_select = null
1436
+	) {
1437
+		// if $field_to_order_by is empty then let's assume we're ordering by the primary key.
1438
+		if (empty($field_to_order_by)) {
1439
+			if ($this->has_primary_key_field()) {
1440
+				$field_to_order_by = $this->get_primary_key_field()->get_name();
1441
+			} else {
1442
+				if (defined('WP_DEBUG') && WP_DEBUG) {
1443
+					throw new EE_Error(
1444
+						esc_html__(
1445
+							'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).',
1446
+							'event_espresso'
1447
+						)
1448
+					);
1449
+				}
1450
+				EE_Error::add_error(
1451
+					esc_html__('There was an error with the query.', 'event_espresso'),
1452
+					__FILE__,
1453
+					__FUNCTION__,
1454
+					__LINE__
1455
+				);
1456
+				return [];
1457
+			}
1458
+		}
1459
+		if (! is_array($query_params)) {
1460
+			EE_Error::doing_it_wrong(
1461
+				'EEM_Base::_get_consecutive',
1462
+				sprintf(
1463
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1464
+					gettype($query_params)
1465
+				),
1466
+				'4.6.0'
1467
+			);
1468
+			$query_params = [];
1469
+		}
1470
+		// let's add the where query param for consecutive look up.
1471
+		$query_params[0][ $field_to_order_by ] = [$operand, $current_field_value];
1472
+		$query_params['limit']                 = $limit;
1473
+		// set direction
1474
+		$incoming_orderby         = isset($query_params['order_by'])
1475
+			? (array) $query_params['order_by']
1476
+			: [];
1477
+		$query_params['order_by'] = $operand === '>'
1478
+			? [$field_to_order_by => 'ASC'] + $incoming_orderby
1479
+			: [$field_to_order_by => 'DESC'] + $incoming_orderby;
1480
+		// if $columns_to_select is empty then that means we're returning EE_Base_Class objects
1481
+		if (empty($columns_to_select)) {
1482
+			return $this->get_all($query_params);
1483
+		}
1484
+		// getting just the fields
1485
+		return $this->_get_all_wpdb_results($query_params, ARRAY_A, $columns_to_select);
1486
+	}
1487
+
1488
+
1489
+	/**
1490
+	 * This sets the _timezone property after model object has been instantiated.
1491
+	 *
1492
+	 * @param string|null $timezone valid PHP DateTimeZone timezone string
1493
+	 * @throws Exception
1494
+	 */
1495
+	public function set_timezone(?string $timezone = '')
1496
+	{
1497
+		if (! $timezone) {
1498
+			return;
1499
+		}
1500
+		$this->_timezone = $timezone;
1501
+		// note we need to loop through relations and set the timezone on those objects as well.
1502
+		foreach ($this->_model_relations as $relation) {
1503
+			$relation->set_timezone($timezone);
1504
+		}
1505
+		// and finally we do the same for any datetime fields
1506
+		foreach ($this->_fields as $field) {
1507
+			if ($field instanceof EE_Datetime_Field) {
1508
+				$field->set_timezone($timezone);
1509
+			}
1510
+		}
1511
+	}
1512
+
1513
+
1514
+	/**
1515
+	 * This just returns whatever is set for the current timezone.
1516
+	 *
1517
+	 * @access public
1518
+	 * @return string
1519
+	 * @throws Exception
1520
+	 */
1521
+	public function get_timezone()
1522
+	{
1523
+		// first validate if timezone is set.  If not, then let's set it be whatever is set on the model fields.
1524
+		if (empty($this->_timezone)) {
1525
+			foreach ($this->_fields as $field) {
1526
+				if ($field instanceof EE_Datetime_Field) {
1527
+					$this->set_timezone($field->get_timezone());
1528
+					break;
1529
+				}
1530
+			}
1531
+		}
1532
+		// if timezone STILL empty then return the default timezone for the site.
1533
+		if (empty($this->_timezone)) {
1534
+			$this->set_timezone(EEH_DTT_Helper::get_timezone());
1535
+		}
1536
+		return $this->_timezone;
1537
+	}
1538
+
1539
+
1540
+	/**
1541
+	 * This returns the date formats set for the given field name and also ensures that
1542
+	 * $this->_timezone property is set correctly.
1543
+	 *
1544
+	 * @param string $field_name The name of the field the formats are being retrieved for.
1545
+	 * @param bool   $pretty     Whether to return the pretty formats (true) or not (false).
1546
+	 * @return array formats in an array with the date format first, and the time format last.
1547
+	 * @throws EE_Error   If the given field_name is not of the EE_Datetime_Field type.
1548
+	 * @since 4.6.x
1549
+	 */
1550
+	public function get_formats_for($field_name, $pretty = false)
1551
+	{
1552
+		$field_settings = $this->field_settings_for($field_name);
1553
+		// if not a valid EE_Datetime_Field then throw error
1554
+		if (! $field_settings instanceof EE_Datetime_Field) {
1555
+			throw new EE_Error(
1556
+				sprintf(
1557
+					esc_html__(
1558
+						'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.',
1559
+						'event_espresso'
1560
+					),
1561
+					$field_name
1562
+				)
1563
+			);
1564
+		}
1565
+		// while we are here, let's make sure the timezone internally in EEM_Base matches what is stored on
1566
+		// the field.
1567
+		$this->_timezone = (string) $field_settings->get_timezone();
1568
+		return [$field_settings->get_date_format($pretty), $field_settings->get_time_format($pretty)];
1569
+	}
1570
+
1571
+
1572
+	/**
1573
+	 * This returns the current time in a format setup for a query on this model.
1574
+	 * Usage of this method makes it easier to setup queries against EE_Datetime_Field columns because
1575
+	 * it will return:
1576
+	 *  - a formatted string in the timezone and format currently set on the EE_Datetime_Field for the given field for
1577
+	 *  NOW
1578
+	 *  - or a unix timestamp (equivalent to time())
1579
+	 * Note: When requesting a formatted string, if the date or time format doesn't include seconds, for example,
1580
+	 * the time returned, because it uses that format, will also NOT include seconds. For this reason, if you want
1581
+	 * the time returned to be the current time down to the exact second, set $timestamp to true.
1582
+	 *
1583
+	 * @param string $field_name       The field the current time is needed for.
1584
+	 * @param bool   $timestamp        True means to return a unix timestamp. Otherwise a
1585
+	 *                                 formatted string matching the set format for the field in the set timezone will
1586
+	 *                                 be returned.
1587
+	 * @param string $what             Whether to return the string in just the time format, the date format, or both.
1588
+	 * @return int|string  If the given field_name is not of the EE_Datetime_Field type, then an EE_Error
1589
+	 *                                 exception is triggered.
1590
+	 * @throws EE_Error    If the given field_name is not of the EE_Datetime_Field type.
1591
+	 * @throws Exception
1592
+	 * @since 4.6.x
1593
+	 */
1594
+	public function current_time_for_query($field_name, $timestamp = false, $what = 'both')
1595
+	{
1596
+		$formats  = $this->get_formats_for($field_name);
1597
+		$DateTime = new DateTime("now", new DateTimeZone($this->_timezone));
1598
+		if ($timestamp) {
1599
+			return $DateTime->format('U');
1600
+		}
1601
+		// not returning timestamp, so return formatted string in timezone.
1602
+		switch ($what) {
1603
+			case 'time':
1604
+				return $DateTime->format($formats[1]);
1605
+			case 'date':
1606
+				return $DateTime->format($formats[0]);
1607
+			default:
1608
+				return $DateTime->format(implode(' ', $formats));
1609
+		}
1610
+	}
1611
+
1612
+
1613
+	/**
1614
+	 * This receives a time string for a given field and ensures
1615
+	 * that it is set up to match what the internal settings for the model are.
1616
+	 * Returns a DateTime object.
1617
+	 * Note: a gotcha for when you send in unix timestamp.  Remember a unix timestamp is already timezone agnostic,
1618
+	 * (functionally the equivalent of UTC+0).
1619
+	 * So when you send it in, whatever timezone string you include is ignored.
1620
+	 *
1621
+	 * @param string      $field_name      The field being setup.
1622
+	 * @param string      $timestring      The date time string being used.
1623
+	 * @param string      $incoming_format The format for the time string.
1624
+	 * @param string|null $timezone_string By default, it is assumed the incoming time string is in timezone for
1625
+	 *                                     the blog.  If this is not the case, then it can be specified here.  If
1626
+	 *                                     incoming format is
1627
+	 *                                     'U', this is ignored.
1628
+	 * @return DbSafeDateTime
1629
+	 * @throws EE_Error
1630
+	 * @throws Exception
1631
+	 */
1632
+	public function convert_datetime_for_query(
1633
+		string $field_name,
1634
+		string $timestring,
1635
+		string $incoming_format,
1636
+		?string $timezone_string = ''
1637
+	): DbSafeDateTime {
1638
+		// just using this to ensure the timezone is set correctly internally
1639
+		$this->get_formats_for($field_name);
1640
+		// load EEH_DTT_Helper
1641
+		$timezone_string     = ! empty($timezone_string) ? $timezone_string : EEH_DTT_Helper::get_timezone();
1642
+		$incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($timezone_string));
1643
+		EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1644
+		return DbSafeDateTime::createFromDateTime($incomingDateTime);
1645
+	}
1646
+
1647
+
1648
+	/**
1649
+	 * Gets all the tables comprising this model. Array keys are the table aliases, and values are EE_Table objects
1650
+	 *
1651
+	 * @return EE_Table_Base[]
1652
+	 */
1653
+	public function get_tables()
1654
+	{
1655
+		return $this->_tables;
1656
+	}
1657
+
1658
+
1659
+	/**
1660
+	 * Updates all the database entries (in each table for this model) according to $fields_n_values and optionally
1661
+	 * also updates all the model objects, where the criteria expressed in $query_params are met..
1662
+	 * Also note: if this model has multiple tables, this update verifies all the secondary tables have an entry for
1663
+	 * each row (in the primary table) we're trying to update; if not, it inserts an entry in the secondary table. Eg:
1664
+	 * if our model has 2 tables: wp_posts (primary), and wp_esp_event (secondary). Let's say we are trying to update a
1665
+	 * model object with EVT_ID = 1
1666
+	 * (which means where wp_posts has ID = 1, because wp_posts.ID is the primary key's column), which exists, but
1667
+	 * there is no entry in wp_esp_event for this entry in wp_posts. So, this update script will insert a row into
1668
+	 * wp_esp_event, using any available parameters from $fields_n_values (eg, if "EVT_limit" => 40 is in
1669
+	 * $fields_n_values, the new entry in wp_esp_event will set EVT_limit = 40, and use default for other columns which
1670
+	 * are not specified)
1671
+	 *
1672
+	 * @param array   $fields_n_values         keys are model fields (exactly like keys in EEM_Base::_fields, NOT db
1673
+	 *                                         columns!), values are strings, ints, floats, and maybe arrays if they
1674
+	 *                                         are to be serialized. Basically, the values are what you'd expect to be
1675
+	 *                                         values on the model, NOT necessarily what's in the DB. For example, if
1676
+	 *                                         we wanted to update only the TXN_details on any Transactions where its
1677
+	 *                                         ID=34, we'd use this method as follows:
1678
+	 *                                         EEM_Transaction::instance()->update(
1679
+	 *                                         array('TXN_details'=>array('detail1'=>'monkey','detail2'=>'banana'),
1680
+	 *                                         array(array('TXN_ID'=>34)));
1681
+	 * @param array   $query_params            @see
1682
+	 *                                         https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1683
+	 *                                         Eg, consider updating Question's QST_admin_label field is of type
1684
+	 *                                         Simple_HTML. If you use this function to update that field to $new_value
1685
+	 *                                         = (note replace 8's with appropriate opening and closing tags in the
1686
+	 *                                         following example)"8script8alert('I hack all');8/script88b8boom
1687
+	 *                                         baby8/b8", then if you set $values_already_prepared_by_model_object to
1688
+	 *                                         TRUE, it is assumed that you've already called
1689
+	 *                                         EE_Simple_HTML_Field->prepare_for_set($new_value), which removes the
1690
+	 *                                         malicious javascript. However, if
1691
+	 *                                         $values_already_prepared_by_model_object is left as FALSE, then
1692
+	 *                                         EE_Simple_HTML_Field->prepare_for_set($new_value) will be called on it,
1693
+	 *                                         and every other field, before insertion. We provide this parameter
1694
+	 *                                         because model objects perform their prepare_for_set function on all
1695
+	 *                                         their values, and so don't need to be called again (and in many cases,
1696
+	 *                                         shouldn't be called again. Eg: if we escape HTML characters in the
1697
+	 *                                         prepare_for_set method...)
1698
+	 * @param boolean $keep_model_objs_in_sync if TRUE, makes sure we ALSO update model objects
1699
+	 *                                         in this model's entity map according to $fields_n_values that match
1700
+	 *                                         $query_params. This obviously has some overhead, so you can disable it
1701
+	 *                                         by setting this to FALSE, but be aware that model objects being used
1702
+	 *                                         could get out-of-sync with the database
1703
+	 * @return int how many rows got updated or FALSE if something went wrong with the query (wp returns FALSE or num
1704
+	 *                                         rows affected which *could* include 0 which DOES NOT mean the query was
1705
+	 *                                         bad)
1706
+	 * @throws EE_Error
1707
+	 * @throws ReflectionException
1708
+	 */
1709
+	public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1710
+	{
1711
+		if (! is_array($query_params)) {
1712
+			EE_Error::doing_it_wrong(
1713
+				'EEM_Base::update',
1714
+				sprintf(
1715
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1716
+					gettype($query_params)
1717
+				),
1718
+				'4.6.0'
1719
+			);
1720
+			$query_params = [];
1721
+		}
1722
+		/**
1723
+		 * Action called before a model update call has been made.
1724
+		 *
1725
+		 * @param EEM_Base $model
1726
+		 * @param array    $fields_n_values the updated fields and their new values
1727
+		 * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1728
+		 */
1729
+		do_action('AHEE__EEM_Base__update__begin', $this, $fields_n_values, $query_params);
1730
+		/**
1731
+		 * Filters the fields about to be updated given the query parameters. You can provide the
1732
+		 * $query_params to $this->get_all() to find exactly which records will be updated
1733
+		 *
1734
+		 * @param array    $fields_n_values fields and their new values
1735
+		 * @param EEM_Base $model           the model being queried
1736
+		 * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1737
+		 */
1738
+		$fields_n_values = (array) apply_filters(
1739
+			'FHEE__EEM_Base__update__fields_n_values',
1740
+			$fields_n_values,
1741
+			$this,
1742
+			$query_params
1743
+		);
1744
+		// need to verify that, for any entry we want to update, there are entries in each secondary table.
1745
+		// to do that, for each table, verify that it's PK isn't null.
1746
+		$tables = $this->get_tables();
1747
+		// 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
1748
+		// NOTE: we should make this code more efficient by NOT querying twice
1749
+		// before the real update, but that needs to first go through ALPHA testing
1750
+		// as it's dangerous. says Mike August 8 2014
1751
+		// we want to make sure the default_where strategy is ignored
1752
+		$this->_ignore_where_strategy = true;
1753
+		$wpdb_select_results          = $this->_get_all_wpdb_results($query_params);
1754
+		foreach ($wpdb_select_results as $wpdb_result) {
1755
+			// type cast stdClass as array
1756
+			$wpdb_result = (array) $wpdb_result;
1757
+			// get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1758
+			if ($this->has_primary_key_field()) {
1759
+				$main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1760
+			} else {
1761
+				// 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)
1762
+				$main_table_pk_value = null;
1763
+			}
1764
+			// 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
1765
+			// 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
1766
+			if (count($tables) > 1) {
1767
+				// foreach matching row in the DB, ensure that each table's PK isn't null. If so, there must not be an entry
1768
+				// in that table, and so we'll want to insert one
1769
+				foreach ($tables as $table_obj) {
1770
+					$this_table_pk_column = $table_obj->get_fully_qualified_pk_column();
1771
+					// if there is no private key for this table on the results, it means there's no entry
1772
+					// in this table, right? so insert a row in the current table, using any fields available
1773
+					if (
1774
+						! (array_key_exists($this_table_pk_column, $wpdb_result)
1775
+						   && $wpdb_result[ $this_table_pk_column ])
1776
+					) {
1777
+						$success = $this->_insert_into_specific_table(
1778
+							$table_obj,
1779
+							$fields_n_values,
1780
+							$main_table_pk_value
1781
+						);
1782
+						// if we died here, report the error
1783
+						if (! $success) {
1784
+							return false;
1785
+						}
1786
+					}
1787
+				}
1788
+			}
1789
+			//              //and now check that if we have cached any models by that ID on the model, that
1790
+			//              //they also get updated properly
1791
+			//              $model_object = $this->get_from_entity_map( $main_table_pk_value );
1792
+			//              if( $model_object ){
1793
+			//                  foreach( $fields_n_values as $field => $value ){
1794
+			//                      $model_object->set($field, $value);
1795
+			// let's make sure default_where strategy is followed now
1796
+			$this->_ignore_where_strategy = false;
1797
+		}
1798
+		// if we want to keep model objects in sync, AND
1799
+		// if this wasn't called from a model object (to update itself)
1800
+		// then we want to make sure we keep all the existing
1801
+		// model objects in sync with the db
1802
+		if ($keep_model_objs_in_sync && ! $this->_values_already_prepared_by_model_object) {
1803
+			if ($this->has_primary_key_field()) {
1804
+				$model_objs_affected_ids = $this->get_col($query_params);
1805
+			} else {
1806
+				// we need to select a bunch of columns and then combine them into the the "index primary key string"s
1807
+				$models_affected_key_columns = $this->_get_all_wpdb_results($query_params, ARRAY_A);
1808
+				$model_objs_affected_ids     = [];
1809
+				foreach ($models_affected_key_columns as $row) {
1810
+					$combined_index_key                             = $this->get_index_primary_key_string($row);
1811
+					$model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1812
+				}
1813
+			}
1814
+			if (! $model_objs_affected_ids) {
1815
+				// wait wait wait- if nothing was affected let's stop here
1816
+				return 0;
1817
+			}
1818
+			foreach ($model_objs_affected_ids as $id) {
1819
+				$model_obj_in_entity_map = $this->get_from_entity_map($id);
1820
+				if ($model_obj_in_entity_map) {
1821
+					foreach ($fields_n_values as $field => $new_value) {
1822
+						$model_obj_in_entity_map->set($field, $new_value);
1823
+					}
1824
+				}
1825
+			}
1826
+			// if there is a primary key on this model, we can now do a slight optimization
1827
+			if ($this->has_primary_key_field()) {
1828
+				// we already know what we want to update. So let's make the query simpler so it's a little more efficient
1829
+				$query_params = [
1830
+					[$this->primary_key_name() => ['IN', $model_objs_affected_ids]],
1831
+					'limit'                    => count($model_objs_affected_ids),
1832
+					'default_where_conditions' => EE_Default_Where_Conditions::NONE,
1833
+				];
1834
+			}
1835
+		}
1836
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
1837
+
1838
+		// note: the following query doesn't use _construct_2nd_half_of_select_query()
1839
+		// because it doesn't accept LIMIT, ORDER BY, etc.
1840
+		$rows_affected = $this->_do_wpdb_query(
1841
+			'query',
1842
+			[
1843
+				"UPDATE " . $model_query_info->get_full_join_sql()
1844
+				. " SET " . $this->_construct_update_sql($fields_n_values)
1845
+				. $model_query_info->get_where_sql(),
1846
+			]
1847
+		);
1848
+
1849
+		/**
1850
+		 * Action called after a model update call has been made.
1851
+		 *
1852
+		 * @param EEM_Base $model
1853
+		 * @param array    $fields_n_values the updated fields and their new values
1854
+		 * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1855
+		 * @param int      $rows_affected
1856
+		 */
1857
+		do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1858
+		return $rows_affected;// how many supposedly got updated
1859
+	}
1860
+
1861
+
1862
+	/**
1863
+	 * Analogous to $wpdb->get_col, returns a 1-dimensional array where the values
1864
+	 * are the values of the field specified (or by default the primary key field)
1865
+	 * that matched the query params. Note that you should pass the name of the
1866
+	 * model FIELD, not the database table's column name.
1867
+	 *
1868
+	 * @param array  $query_params
1869
+	 * @param string $field_to_select
1870
+	 * @return array just like $wpdb->get_col()
1871
+	 * @throws EE_Error
1872
+	 * @throws ReflectionException
1873
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md for $query_params values
1874
+	 */
1875
+	public function get_col($query_params = [], $field_to_select = null)
1876
+	{
1877
+		if ($field_to_select) {
1878
+			$field = $this->field_settings_for($field_to_select);
1879
+		} elseif ($this->has_primary_key_field()) {
1880
+			$field = $this->get_primary_key_field();
1881
+		} else {
1882
+			$field_settings = $this->field_settings();
1883
+			// no primary key, just grab the first column
1884
+			$field = reset($field_settings);
1885
+			// don't need this array now
1886
+			unset($field_settings);
1887
+		}
1888
+		$model_query_info   = $this->_create_model_query_info_carrier($query_params);
1889
+		$select_expressions = $field->get_qualified_column();
1890
+		$SQL                =
1891
+			"SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1892
+		return $this->_do_wpdb_query('get_col', [$SQL]);
1893
+	}
1894
+
1895
+
1896
+	/**
1897
+	 * Returns a single column value for a single row from the database
1898
+	 *
1899
+	 * @param array  $query_params    @see
1900
+	 *                                https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1901
+	 * @param string $field_to_select @see EEM_Base::get_col()
1902
+	 * @return string
1903
+	 * @throws EE_Error
1904
+	 * @throws ReflectionException
1905
+	 */
1906
+	public function get_var($query_params = [], $field_to_select = null)
1907
+	{
1908
+		$query_params['limit'] = 1;
1909
+		$col                   = $this->get_col($query_params, $field_to_select);
1910
+		if (! empty($col)) {
1911
+			return reset($col);
1912
+		}
1913
+		return null;
1914
+	}
1915
+
1916
+
1917
+	/**
1918
+	 * Makes the SQL for after "UPDATE table_X inner join table_Y..." and before "...WHERE". Eg "Question.name='party
1919
+	 * time?', Question.desc='what do you think?',..." Values are filtered through wpdb->prepare to avoid against SQL
1920
+	 * injection, but currently no further filtering is done
1921
+	 *
1922
+	 * @param array $fields_n_values array keys are field names on this model, and values are what those fields should
1923
+	 *                               be updated to in the DB
1924
+	 * @return string of SQL
1925
+	 * @throws EE_Error
1926
+	 * @global      $wpdb
1927
+	 */
1928
+	public function _construct_update_sql($fields_n_values)
1929
+	{
1930
+		/** @type WPDB $wpdb */
1931
+		global $wpdb;
1932
+		$cols_n_values = [];
1933
+		foreach ($fields_n_values as $field_name => $value) {
1934
+			$field_obj = $this->field_settings_for($field_name);
1935
+			// if the value is NULL, we want to assign the value to that.
1936
+			// wpdb->prepare doesn't really handle that properly
1937
+			$prepared_value  = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1938
+			$value_sql       = $prepared_value === null
1939
+				? 'NULL'
1940
+				: $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1941
+			$cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1942
+		}
1943
+		return implode(",", $cols_n_values);
1944
+	}
1945
+
1946
+
1947
+	/**
1948
+	 * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1949
+	 * Performs a HARD delete, meaning the database row should always be removed,
1950
+	 * not just have a flag field on it switched
1951
+	 * Wrapper for EEM_Base::delete_permanently()
1952
+	 *
1953
+	 * @param mixed $id
1954
+	 * @param bool  $block_deletes whether to allow related model objects to block (prevent) this deletion
1955
+	 *                             ie: enforce referential integrity
1956
+	 *                             It's advisable to always leave this as TRUE, otherwise you could corrupt your DB
1957
+	 * @return int the number of rows deleted
1958
+	 * @throws EE_Error
1959
+	 * @throws ReflectionException
1960
+	 */
1961
+	public function delete_permanently_by_ID($id, $block_deletes = true): int
1962
+	{
1963
+		return $this->delete_permanently(
1964
+			[
1965
+				[$this->get_primary_key_field()->get_name() => $id],
1966
+				'limit' => 1,
1967
+			],
1968
+			$block_deletes
1969
+		);
1970
+	}
1971
+
1972
+
1973
+	/**
1974
+	 * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1975
+	 * Wrapper for EEM_Base::delete()
1976
+	 *
1977
+	 * @param mixed $id
1978
+	 * @param bool  $block_deletes whether to allow related model objects to block (prevent) this deletion
1979
+	 *                             ie: enforce referential integrity
1980
+	 *                             It's advisable to always leave this as TRUE, otherwise you could corrupt your DB
1981
+	 * @return int the number of rows deleted
1982
+	 * @throws EE_Error
1983
+	 * @throws ReflectionException
1984
+	 */
1985
+	public function delete_by_ID($id, $block_deletes = true)
1986
+	{
1987
+		return $this->delete(
1988
+			[
1989
+				[$this->get_primary_key_field()->get_name() => $id],
1990
+				'limit' => 1,
1991
+			],
1992
+			$block_deletes
1993
+		);
1994
+	}
1995
+
1996
+
1997
+	/**
1998
+	 * Identical to delete_permanently, but does a "soft" delete if possible,
1999
+	 * meaning if the model has a field that indicates its been "trashed" or
2000
+	 * "soft deleted", we will just set that instead of actually deleting the rows.
2001
+	 *
2002
+	 * @param array   $query_params
2003
+	 * @param boolean $block_deletes whether to allow related model objects to block (prevent) this deletion
2004
+	 *                               ie: enforce referential integrity
2005
+	 *                               It's advisable to always leave this as TRUE, otherwise you could corrupt your DB
2006
+	 * @return int how many rows got deleted
2007
+	 * @throws EE_Error
2008
+	 * @throws ReflectionException
2009
+	 * @see EEM_Base::delete_permanently
2010
+	 */
2011
+	public function delete($query_params, $block_deletes = true)
2012
+	{
2013
+		return $this->delete_permanently($query_params, $block_deletes);
2014
+	}
2015
+
2016
+
2017
+	/**
2018
+	 * Deletes the model objects that meet the query params. Note: this method is overridden
2019
+	 * in EEM_Soft_Delete_Base so that soft-deleted model objects are instead only flagged
2020
+	 * as archived, not actually deleted
2021
+	 *
2022
+	 * @param array   $query_params
2023
+	 * @param boolean $block_deletes  whether to allow related model objects to block (prevent) this deletion
2024
+	 *                                ie: enforce referential integrity
2025
+	 *                                It's advisable to always leave this as TRUE, otherwise you could corrupt your DB
2026
+	 * @return int how many rows got deleted
2027
+	 * @throws EE_Error
2028
+	 * @throws ReflectionException
2029
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2030
+	 */
2031
+	public function delete_permanently($query_params, $block_deletes = true): int
2032
+	{
2033
+		/**
2034
+		 * Action called just before performing a real deletion query. You can use the
2035
+		 * model and its $query_params to find exactly which items will be deleted
2036
+		 *
2037
+		 * @param EEM_Base $model
2038
+		 * @param array    $query_params  The incoming array of query parameters influencing what gets deleted.
2039
+		 * @param bool     $block_deletes @see param description in method phpdoc block.
2040
+		 */
2041
+		do_action('AHEE__EEM_Base__delete__begin', $this, $query_params, $block_deletes);
2042
+		// some MySQL databases may be running safe mode, which may restrict
2043
+		// deletion if there is no KEY column used in the WHERE statement of a deletion.
2044
+		// to get around this, we first do a SELECT, get all the IDs, and then run another query
2045
+		// to delete them
2046
+		$items_for_deletion           = $this->_get_all_wpdb_results($query_params);
2047
+		$columns_and_ids_for_deleting = $this->_get_ids_for_delete($items_for_deletion, $block_deletes);
2048
+		$deletion_where_query_part    = $this->_build_query_part_for_deleting_from_columns_and_values(
2049
+			$columns_and_ids_for_deleting
2050
+		);
2051
+		/**
2052
+		 * Allows client code to act on the items being deleted before the query is actually executed.
2053
+		 * see php doc blocks for more details
2054
+		 *
2055
+		 * @param EEM_Base $this                         The model instance being acted on.
2056
+		 * @param array    $query_params                 The incoming array of query parameters influencing what gets deleted.
2057
+		 * @param bool     $block_deletes                @see param description in method phpdoc block.
2058
+		 * @param array    $columns_and_ids_for_deleting An array indicating what entities will get removed as
2059
+		 *                                               derived from the incoming query parameters.
2060
+		 * @see details on the structure of this array in the phpdocs for the `_get_ids_for_delete_method`
2061
+		 */
2062
+		do_action(
2063
+			'AHEE__EEM_Base__delete__before_query',
2064
+			$this,
2065
+			$query_params,
2066
+			$block_deletes,
2067
+			$columns_and_ids_for_deleting
2068
+		);
2069
+		$rows_deleted = 0;
2070
+		if ($deletion_where_query_part) {
2071
+			$model_query_info = $this->_create_model_query_info_carrier($query_params);
2072
+			$table_aliases    = array_keys($this->_tables);
2073
+			$SQL              = "DELETE "
2074
+								. implode(", ", $table_aliases)
2075
+								. " FROM "
2076
+								. $model_query_info->get_full_join_sql()
2077
+								. " WHERE "
2078
+								. $deletion_where_query_part;
2079
+			$rows_deleted     = $this->_do_wpdb_query('query', [$SQL]);
2080
+		}
2081
+
2082
+		// Next, make sure those items are removed from the entity map; if they could be put into it at all; and if
2083
+		// there was no error with the delete query.
2084
+		if (
2085
+			$this->has_primary_key_field()
2086
+			&& $rows_deleted !== false
2087
+			&& isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2088
+		) {
2089
+			$ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2090
+			foreach ($ids_for_removal as $id) {
2091
+				if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2092
+					unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2093
+				}
2094
+			}
2095
+
2096
+			// delete any extra meta attached to the deleted entities but ONLY if this model is not an instance of
2097
+			// `EEM_Extra_Meta`.  In other words we want to prevent recursion on EEM_Extra_Meta::delete_permanently calls
2098
+			// unnecessarily.  It's very unlikely that users will have assigned Extra Meta to Extra Meta
2099
+			// (although it is possible).
2100
+			// Note this can be skipped by using the provided filter and returning false.
2101
+			if (
2102
+				apply_filters(
2103
+					'FHEE__EEM_Base__delete_permanently__dont_delete_extra_meta_for_extra_meta',
2104
+					! $this instanceof EEM_Extra_Meta,
2105
+					$this
2106
+				)
2107
+			) {
2108
+				EEM_Extra_Meta::instance()->delete_permanently(
2109
+					[
2110
+						0 => [
2111
+							'EXM_type' => $this->get_this_model_name(),
2112
+							'OBJ_ID'   => [
2113
+								'IN',
2114
+								$ids_for_removal,
2115
+							],
2116
+						],
2117
+					]
2118
+				);
2119
+			}
2120
+		}
2121
+
2122
+		/**
2123
+		 * Action called just after performing a real deletion query. Although at this point the
2124
+		 * items should have been deleted
2125
+		 *
2126
+		 * @param EEM_Base $model
2127
+		 * @param array $query_params
2128
+		 * @param int   $rows_deleted
2129
+		 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2130
+		 */
2131
+		do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2132
+		return (int) $rows_deleted;// how many supposedly got deleted
2133
+	}
2134
+
2135
+
2136
+	/**
2137
+	 * Checks all the relations that throw error messages when there are blocking related objects
2138
+	 * for related model objects. If there are any related model objects on those relations,
2139
+	 * adds an EE_Error, and return true
2140
+	 *
2141
+	 * @param EE_Base_Class|int $this_model_obj_or_id
2142
+	 * @param EE_Base_Class     $ignore_this_model_obj a model object like 'EE_Event', or 'EE_Term_Taxonomy', which
2143
+	 *                                                 should be ignored when determining whether there are related
2144
+	 *                                                 model objects which block this model object's deletion. Useful
2145
+	 *                                                 if you know A is related to B and are considering deleting A,
2146
+	 *                                                 but want to see if A has any other objects blocking its deletion
2147
+	 *                                                 before removing the relation between A and B
2148
+	 * @return boolean
2149
+	 * @throws EE_Error
2150
+	 * @throws ReflectionException
2151
+	 */
2152
+	public function delete_is_blocked_by_related_models($this_model_obj_or_id, $ignore_this_model_obj = null)
2153
+	{
2154
+		// first, if $ignore_this_model_obj was supplied, get its model
2155
+		$ignored_model = $ignore_this_model_obj instanceof EE_Base_Class
2156
+			? $ignore_this_model_obj->get_model()
2157
+			: null;
2158
+		// now check all the relations of $this_model_obj_or_id and see if there
2159
+		// are any related model objects blocking it?
2160
+		$is_blocked = false;
2161
+		foreach ($this->_model_relations as $relation_name => $relation_obj) {
2162
+			if ($relation_obj->block_delete_if_related_models_exist()) {
2163
+				// if $ignore_this_model_obj was supplied, then for the query
2164
+				// on that model needs to be told to ignore $ignore_this_model_obj
2165
+				if ($ignored_model && $relation_name === $ignored_model->get_this_model_name()) {
2166
+					$related_model_objects = $relation_obj->get_all_related(
2167
+						$this_model_obj_or_id,
2168
+						[
2169
+							[
2170
+								$ignored_model->get_primary_key_field()->get_name() => [
2171
+									'!=',
2172
+									$ignore_this_model_obj->ID(),
2173
+								],
2174
+							],
2175
+						]
2176
+					);
2177
+				} else {
2178
+					$related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id);
2179
+				}
2180
+				if ($related_model_objects) {
2181
+					EE_Error::add_error($relation_obj->get_deletion_error_message(), __FILE__, __FUNCTION__, __LINE__);
2182
+					$is_blocked = true;
2183
+				}
2184
+			}
2185
+		}
2186
+		return $is_blocked;
2187
+	}
2188
+
2189
+
2190
+	/**
2191
+	 * Builds the columns and values for items to delete from the incoming $row_results_for_deleting array.
2192
+	 *
2193
+	 * @param array $row_results_for_deleting
2194
+	 * @param bool  $block_deletes whether to allow related model objects to block (prevent) this deletion
2195
+	 *                             ie: enforce referential integrity
2196
+	 *                             It's advisable to always leave this as TRUE, otherwise you could corrupt your DB
2197
+	 * @return array               The shape of this array depends on whether the model `has_primary_key_field` or not.
2198
+	 *                             If the model DOES have a primary_key_field, then the array will be a simple single
2199
+	 *                             dimension array where the key is the fully qualified primary key column and
2200
+	 *                             the value is an array of ids that will be deleted.
2201
+	 *                             Example:
2202
+	 *                              [ 'Event.EVT_ID' => [ 1,2,3 ]]
2203
+	 *                             If the model DOES NOT have a primary_key_field, then the array will be a
2204
+	 *                             two-dimensional array where each element is a group of columns and values that get deleted.
2205
+	 *                             Example:
2206
+	 *                              [
2207
+	 *                                  0 => [
2208
+	 *                                      'Term_Relationship.object_id' => 1
2209
+	 *                                      'Term_Relationship.term_taxonomy_id' => 5
2210
+	 *                                  ],
2211
+	 *                                  1 => [
2212
+	 *                                      'Term_Relationship.object_id' => 1
2213
+	 *                                      'Term_Relationship.term_taxonomy_id' => 6
2214
+	 *                                  ]
2215
+	 *                              ]
2216
+	 * @throws EE_Error
2217
+	 * @throws ReflectionException
2218
+	 */
2219
+	protected function _get_ids_for_delete(array $row_results_for_deleting, $block_deletes = true)
2220
+	{
2221
+		$ids_to_delete_indexed_by_column = [];
2222
+		if ($this->has_primary_key_field()) {
2223
+			$primary_table = $this->_get_main_table();
2224
+			// following lines are commented out because the variables were not being used
2225
+			// not deleting because unsure if calls were intentionally causing side effects
2226
+			// $primary_table_pk_field          =
2227
+			//     $this->get_field_by_column($primary_table->get_fully_qualified_pk_column());
2228
+			// $other_tables                    = $this->_get_other_tables();
2229
+			$ids_to_delete_indexed_by_column = $query = [];
2230
+			foreach ($row_results_for_deleting as $item_to_delete) {
2231
+				// before we mark this item for deletion,
2232
+				// make sure there's no related entities blocking its deletion (if we're checking)
2233
+				if (
2234
+					$block_deletes
2235
+					&& $this->delete_is_blocked_by_related_models(
2236
+						$item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2237
+					)
2238
+				) {
2239
+					continue;
2240
+				}
2241
+				// primary table deletes
2242
+				if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2243
+					$ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2244
+						$item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2245
+				}
2246
+			}
2247
+		} elseif (count($this->get_combined_primary_key_fields()) > 1) {
2248
+			$fields = $this->get_combined_primary_key_fields();
2249
+			foreach ($row_results_for_deleting as $item_to_delete) {
2250
+				$ids_to_delete_indexed_by_column_for_row = [];
2251
+				foreach ($fields as $cpk_field) {
2252
+					if ($cpk_field instanceof EE_Model_Field_Base) {
2253
+						$ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2254
+							$item_to_delete[ $cpk_field->get_qualified_column() ];
2255
+					}
2256
+				}
2257
+				$ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
2258
+			}
2259
+		} else {
2260
+			// so there's no primary key and no combined key...
2261
+			// sorry, can't help you
2262
+			throw new EE_Error(
2263
+				sprintf(
2264
+					esc_html__(
2265
+						"Cannot delete objects of type %s because there is no primary key NOR combined key",
2266
+						"event_espresso"
2267
+					),
2268
+					$this->class_name
2269
+				)
2270
+			);
2271
+		}
2272
+		return $ids_to_delete_indexed_by_column;
2273
+	}
2274
+
2275
+
2276
+	/**
2277
+	 * This receives an array of columns and values set to be deleted (as prepared by _get_ids_for_delete) and prepares
2278
+	 * the corresponding query_part for the query performing the deletion.
2279
+	 *
2280
+	 * @param array $ids_to_delete_indexed_by_column @see _get_ids_for_delete for how this array might be shaped.
2281
+	 * @return string
2282
+	 * @throws EE_Error
2283
+	 */
2284
+	protected function _build_query_part_for_deleting_from_columns_and_values(array $ids_to_delete_indexed_by_column)
2285
+	{
2286
+		$query_part = '';
2287
+		if (empty($ids_to_delete_indexed_by_column)) {
2288
+			return $query_part;
2289
+		} elseif ($this->has_primary_key_field()) {
2290
+			$query = [];
2291
+			foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2292
+				$query[] = $column . ' IN' . $this->_construct_in_value($ids, $this->_primary_key_field);
2293
+			}
2294
+			$query_part = ! empty($query)
2295
+				? implode(' AND ', $query)
2296
+				: $query_part;
2297
+		} elseif (count($this->get_combined_primary_key_fields()) > 1) {
2298
+			$ways_to_identify_a_row = [];
2299
+			foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2300
+				$values_for_each_combined_primary_key_for_a_row = [];
2301
+				foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2302
+					$values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2303
+				}
2304
+				$ways_to_identify_a_row[] = '('
2305
+											. implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
2306
+											. ')';
2307
+			}
2308
+			$query_part = implode(' OR ', $ways_to_identify_a_row);
2309
+		}
2310
+		return $query_part;
2311
+	}
2312
+
2313
+
2314
+	/**
2315
+	 * Gets the model field by the fully qualified name
2316
+	 *
2317
+	 * @param string $qualified_column_name eg 'Event_CPT.post_name' or $field_obj->get_qualified_column()
2318
+	 * @return EE_Model_Field_Base
2319
+	 * @throws EE_Error
2320
+	 * @throws EE_Error
2321
+	 */
2322
+	public function get_field_by_column($qualified_column_name)
2323
+	{
2324
+		foreach ($this->field_settings(true) as $field_name => $field_obj) {
2325
+			if ($field_obj->get_qualified_column() === $qualified_column_name) {
2326
+				return $field_obj;
2327
+			}
2328
+		}
2329
+		throw new EE_Error(
2330
+			sprintf(
2331
+				esc_html__('Could not find a field on the model "%1$s" for qualified column "%2$s"', 'event_espresso'),
2332
+				$this->get_this_model_name(),
2333
+				$qualified_column_name
2334
+			)
2335
+		);
2336
+	}
2337
+
2338
+
2339
+	/**
2340
+	 * Count all the rows that match criteria the model query params.
2341
+	 * If $field_to_count isn't provided, the model's primary key is used. Otherwise, we count by field_to_count's
2342
+	 * column
2343
+	 *
2344
+	 * @param array  $query_params   @see
2345
+	 *                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2346
+	 * @param string $field_to_count field on model to count by (not column name)
2347
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2348
+	 *                               that by the setting $distinct to TRUE;
2349
+	 * @return int
2350
+	 * @throws EE_Error
2351
+	 * @throws ReflectionException
2352
+	 */
2353
+	public function count($query_params = [], $field_to_count = '', $distinct = false)
2354
+	{
2355
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
2356
+		if ($field_to_count) {
2357
+			$field_obj       = $this->field_settings_for($field_to_count);
2358
+			$column_to_count = $field_obj->get_qualified_column();
2359
+		} elseif ($this->has_primary_key_field()) {
2360
+			$pk_field_obj    = $this->get_primary_key_field();
2361
+			$column_to_count = $pk_field_obj->get_qualified_column();
2362
+		} else {
2363
+			// there's no primary key
2364
+			// if we're counting distinct items, and there's no primary key,
2365
+			// we need to list out the columns for distinction;
2366
+			// otherwise we can just use star
2367
+			if ($distinct) {
2368
+				$columns_to_use = [];
2369
+				foreach ($this->get_combined_primary_key_fields() as $field_obj) {
2370
+					$columns_to_use[] = $field_obj->get_qualified_column();
2371
+				}
2372
+				$column_to_count = implode(',', $columns_to_use);
2373
+			} else {
2374
+				$column_to_count = '*';
2375
+			}
2376
+		}
2377
+		$column_to_count = $distinct
2378
+			? "DISTINCT " . $column_to_count
2379
+			: $column_to_count;
2380
+		$SQL             =
2381
+			"SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2382
+		return (int) $this->_do_wpdb_query('get_var', [$SQL]);
2383
+	}
2384
+
2385
+
2386
+	/**
2387
+	 * Sums up the value of the $field_to_sum (defaults to the primary key, which isn't terribly useful)
2388
+	 *
2389
+	 * @param array  $query_params @see
2390
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2391
+	 * @param string $field_to_sum name of field (array key in $_fields array)
2392
+	 * @return float
2393
+	 * @throws EE_Error
2394
+	 * @throws ReflectionException
2395
+	 */
2396
+	public function sum($query_params, $field_to_sum = null)
2397
+	{
2398
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
2399
+		if ($field_to_sum) {
2400
+			$field_obj = $this->field_settings_for($field_to_sum);
2401
+		} else {
2402
+			$field_obj = $this->get_primary_key_field();
2403
+		}
2404
+		$column_to_count = $field_obj->get_qualified_column();
2405
+		$SQL             =
2406
+			"SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2407
+		$return_value    = $this->_do_wpdb_query('get_var', [$SQL]);
2408
+		$data_type       = $field_obj->get_wpdb_data_type();
2409
+		if ($data_type === '%d' || $data_type === '%s') {
2410
+			return (float) $return_value;
2411
+		}
2412
+		// must be %f
2413
+		return (float) $return_value;
2414
+	}
2415
+
2416
+
2417
+	/**
2418
+	 * Just calls the specified method on $wpdb with the given arguments
2419
+	 * Consolidates a little extra error handling code
2420
+	 *
2421
+	 * @param string $wpdb_method
2422
+	 * @param array  $arguments_to_provide
2423
+	 * @return mixed
2424
+	 * @throws EE_Error
2425
+	 * @global wpdb  $wpdb
2426
+	 */
2427
+	protected function _do_wpdb_query($wpdb_method, $arguments_to_provide)
2428
+	{
2429
+		// if we're in maintenance mode level 2, DON'T run any queries
2430
+		// because level 2 indicates the database needs updating and
2431
+		// is probably out of sync with the code
2432
+		if (DbStatus::isOffline()) {
2433
+			throw new RuntimeException(
2434
+				esc_html__(
2435
+					"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.",
2436
+					"event_espresso"
2437
+				)
2438
+			);
2439
+		}
2440
+		/** @type WPDB $wpdb */
2441
+		global $wpdb;
2442
+		if (! method_exists($wpdb, $wpdb_method)) {
2443
+			throw new DomainException(
2444
+				sprintf(
2445
+					esc_html__(
2446
+						'There is no method named "%s" on Wordpress\' $wpdb object',
2447
+						'event_espresso'
2448
+					),
2449
+					$wpdb_method
2450
+				)
2451
+			);
2452
+		}
2453
+		$old_show_errors_value = $wpdb->show_errors;
2454
+		if (defined('WP_DEBUG') && WP_DEBUG) {
2455
+			$wpdb->show_errors(false);
2456
+		}
2457
+		$result = $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2458
+		$this->show_db_query_if_previously_requested($wpdb->last_query);
2459
+		if (defined('WP_DEBUG') && WP_DEBUG) {
2460
+			$wpdb->show_errors($old_show_errors_value);
2461
+			if (! empty($wpdb->last_error)) {
2462
+				throw new EE_Error(sprintf(esc_html__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2463
+			}
2464
+			if ($result === false) {
2465
+				throw new EE_Error(
2466
+					sprintf(
2467
+						esc_html__(
2468
+							'WPDB Error occurred, but no error message was logged by wpdb! The wpdb method called was "%1$s" and the arguments were "%2$s"',
2469
+							'event_espresso'
2470
+						),
2471
+						$wpdb_method,
2472
+						var_export($arguments_to_provide, true)
2473
+					)
2474
+				);
2475
+			}
2476
+		} elseif ($result === false) {
2477
+			EE_Error::add_error(
2478
+				sprintf(
2479
+					esc_html__(
2480
+						'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"',
2481
+						'event_espresso'
2482
+					),
2483
+					$wpdb_method,
2484
+					var_export($arguments_to_provide, true),
2485
+					$wpdb->last_error
2486
+				),
2487
+				__FILE__,
2488
+				__FUNCTION__,
2489
+				__LINE__
2490
+			);
2491
+		}
2492
+		return $result;
2493
+	}
2494
+
2495
+
2496
+	/**
2497
+	 * Attempts to run the indicated WPDB method with the provided arguments,
2498
+	 * and if there's an error tries to verify the DB is correct. Uses
2499
+	 * the static property EEM_Base::$_db_verification_level to determine whether
2500
+	 * we should try to fix the EE core db, the addons, or just give up
2501
+	 *
2502
+	 * @param string $wpdb_method
2503
+	 * @param array  $arguments_to_provide
2504
+	 * @return mixed
2505
+	 */
2506
+	private function _process_wpdb_query($wpdb_method, $arguments_to_provide)
2507
+	{
2508
+		/** @type WPDB $wpdb */
2509
+		global $wpdb;
2510
+		$wpdb->last_error = null;
2511
+		$result           = call_user_func_array([$wpdb, $wpdb_method], $arguments_to_provide);
2512
+		// was there an error running the query? but we don't care on new activations
2513
+		// (we're going to setup the DB anyway on new activations)
2514
+		if (
2515
+			($result === false || ! empty($wpdb->last_error))
2516
+			&& EE_System::instance()->detect_req_type() !== EE_System::req_type_new_activation
2517
+		) {
2518
+			switch (EEM_Base::$_db_verification_level) {
2519
+				case EEM_Base::db_verified_none:
2520
+					// let's double-check core's DB
2521
+					$error_message = $this->_verify_core_db($wpdb_method, $arguments_to_provide);
2522
+					break;
2523
+				case EEM_Base::db_verified_core:
2524
+					// STILL NO LOVE?? verify all the addons too. Maybe they need to be fixed
2525
+					$error_message = $this->_verify_addons_db($wpdb_method, $arguments_to_provide);
2526
+					break;
2527
+				case EEM_Base::db_verified_addons:
2528
+					// ummmm... you in trouble
2529
+					return $result;
2530
+			}
2531
+			if (! empty($error_message)) {
2532
+				EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2533
+				trigger_error($error_message);
2534
+			}
2535
+			return $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2536
+		}
2537
+		return $result;
2538
+	}
2539
+
2540
+
2541
+	/**
2542
+	 * Verifies the EE core database is up-to-date and records that we've done it on
2543
+	 * EEM_Base::$_db_verification_level
2544
+	 *
2545
+	 * @param string $wpdb_method
2546
+	 * @param array  $arguments_to_provide
2547
+	 * @return string
2548
+	 * @throws EE_Error
2549
+	 * @throws ReflectionException
2550
+	 */
2551
+	private function _verify_core_db($wpdb_method, $arguments_to_provide)
2552
+	{
2553
+		/** @type WPDB $wpdb */
2554
+		global $wpdb;
2555
+		// ok remember that we've already attempted fixing the core db, in case the problem persists
2556
+		EEM_Base::$_db_verification_level = EEM_Base::db_verified_core;
2557
+		$error_message                    = sprintf(
2558
+			esc_html__(
2559
+				'WPDB Error "%1$s" while running wpdb method "%2$s" with arguments %3$s. Automatically attempting to fix EE Core DB',
2560
+				'event_espresso'
2561
+			),
2562
+			$wpdb->last_error,
2563
+			$wpdb_method,
2564
+			wp_json_encode($arguments_to_provide)
2565
+		);
2566
+		EE_System::instance()->initialize_db_if_no_migrations_required(false, true);
2567
+		return $error_message;
2568
+	}
2569
+
2570
+
2571
+	/**
2572
+	 * Verifies the EE addons' database is up-to-date and records that we've done it on
2573
+	 * EEM_Base::$_db_verification_level
2574
+	 *
2575
+	 * @param $wpdb_method
2576
+	 * @param $arguments_to_provide
2577
+	 * @return string
2578
+	 * @throws EE_Error
2579
+	 * @throws ReflectionException
2580
+	 */
2581
+	private function _verify_addons_db($wpdb_method, $arguments_to_provide)
2582
+	{
2583
+		/** @type WPDB $wpdb */
2584
+		global $wpdb;
2585
+		// ok remember that we've already attempted fixing the addons dbs, in case the problem persists
2586
+		EEM_Base::$_db_verification_level = EEM_Base::db_verified_addons;
2587
+		$error_message                    = sprintf(
2588
+			esc_html__(
2589
+				'WPDB AGAIN: Error "%1$s" while running the same method and arguments as before. Automatically attempting to fix EE Addons DB',
2590
+				'event_espresso'
2591
+			),
2592
+			$wpdb->last_error,
2593
+			$wpdb_method,
2594
+			wp_json_encode($arguments_to_provide)
2595
+		);
2596
+		EE_System::instance()->initialize_addons();
2597
+		return $error_message;
2598
+	}
2599
+
2600
+
2601
+	/**
2602
+	 * In order to avoid repeating this code for the get_all, sum, and count functions, put the code parts
2603
+	 * that are identical in here. Returns a string of SQL of everything in a SELECT query except the beginning
2604
+	 * SELECT clause, eg " FROM wp_posts AS Event INNER JOIN ... WHERE ... ORDER BY ... LIMIT ... GROUP BY ... HAVING
2605
+	 * ..."
2606
+	 *
2607
+	 * @param EE_Model_Query_Info_Carrier $model_query_info
2608
+	 * @return string
2609
+	 */
2610
+	private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2611
+	{
2612
+		return " FROM " . $model_query_info->get_full_join_sql() .
2613
+			   $model_query_info->get_where_sql() .
2614
+			   $model_query_info->get_group_by_sql() .
2615
+			   $model_query_info->get_having_sql() .
2616
+			   $model_query_info->get_order_by_sql() .
2617
+			   $model_query_info->get_limit_sql();
2618
+	}
2619
+
2620
+
2621
+	/**
2622
+	 * Set to easily debug the next X queries ran from this model.
2623
+	 *
2624
+	 * @param int $count
2625
+	 */
2626
+	public function show_next_x_db_queries($count = 1)
2627
+	{
2628
+		$this->_show_next_x_db_queries = $count;
2629
+	}
2630
+
2631
+
2632
+	/**
2633
+	 * @param $sql_query
2634
+	 */
2635
+	public function show_db_query_if_previously_requested($sql_query)
2636
+	{
2637
+		if ($this->_show_next_x_db_queries > 0) {
2638
+			$left = is_admin() ? '12rem' : '2rem';
2639
+			echo "
2640 2640
             <div class='ee-status-outline ee-status-bg--attention' style='margin: 2rem 2rem 2rem $left;'>
2641 2641
                 " . esc_html($sql_query) . "
2642 2642
             </div>";
2643
-            $this->_show_next_x_db_queries--;
2644
-        }
2645
-    }
2646
-
2647
-
2648
-    /**
2649
-     * Adds a relationship of the correct type between $modelObject and $otherModelObject.
2650
-     * There are the 3 cases:
2651
-     * 'belongsTo' relationship: sets $id_or_obj's foreign_key to be $other_model_id_or_obj's primary_key. If
2652
-     * $otherModelObject has no ID, it is first saved.
2653
-     * 'hasMany' relationship: sets $other_model_id_or_obj's foreign_key to be $id_or_obj's primary_key. If $id_or_obj
2654
-     * has no ID, it is first saved.
2655
-     * 'hasAndBelongsToMany' relationships: checks that there isn't already an entry in the join table, and adds one.
2656
-     * If one of the model Objects has not yet been saved to the database, it is saved before adding the entry in the
2657
-     * join table
2658
-     *
2659
-     * @param EE_Base_Class|int $id_or_obj                        EE_base_Class or ID of $thisModelObject
2660
-     * @param EE_Base_Class|int $other_model_id_or_obj            EE_base_Class or ID of other Model Object
2661
-     * @param string            $relationName                     , key in EEM_Base::_relations
2662
-     *                                                            an attendee to a group, you also want to specify
2663
-     *                                                            which role they will have in that group. So you would
2664
-     *                                                            use this parameter to specify
2665
-     *                                                            array('role-column-name'=>'role-id')
2666
-     * @param array|null        $extra_join_model_fields_n_values This allows you to enter further query params for the
2667
-     *                                                            relation to for relation to methods that allow you to
2668
-     *                                                            further specify extra columns to join by (such as
2669
-     *                                                            HABTM).  Keep in mind that the only acceptable
2670
-     *                                                            query_params is strict "col" => "value" pairs because
2671
-     *                                                            these will be inserted in any new rows created as
2672
-     *                                                            well.
2673
-     * @return EE_Base_Class which was added as a relation. Object referred to by $other_model_id_or_obj
2674
-     * @throws EE_Error
2675
-     */
2676
-    public function add_relationship_to(
2677
-        $id_or_obj,
2678
-        $other_model_id_or_obj,
2679
-        $relationName,
2680
-        $extra_join_model_fields_n_values = []
2681
-    ) {
2682
-        $relation_obj = $this->related_settings_for($relationName);
2683
-        return $relation_obj->add_relation_to($id_or_obj, $other_model_id_or_obj, $extra_join_model_fields_n_values);
2684
-    }
2685
-
2686
-
2687
-    /**
2688
-     * Removes a relationship of the correct type between $modelObject and $otherModelObject.
2689
-     * There are the 3 cases:
2690
-     * 'belongsTo' relationship: sets $modelObject's foreign_key to null, if that field is nullable.Otherwise throws an
2691
-     * error
2692
-     * 'hasMany' relationship: sets $otherModelObject's foreign_key to null,if that field is nullable.Otherwise throws
2693
-     * an error
2694
-     * 'hasAndBelongsToMany' relationships:removes any existing entry in the join table between the two models.
2695
-     *
2696
-     * @param EE_Base_Class|int $id_or_obj             EE_base_Class or ID of $thisModelObject
2697
-     * @param EE_Base_Class|int $other_model_id_or_obj EE_base_Class or ID of other Model Object
2698
-     * @param string            $relationName          key in EEM_Base::_relations
2699
-     * @param array|null        $where_query           This allows you to enter further query params for the relation
2700
-     *                                                 to for relation to methods that allow you to further specify
2701
-     *                                                 extra columns to join by (such as HABTM). Keep in mind that the
2702
-     *                                                 only acceptable query_params is strict "col" => "value" pairs
2703
-     *                                                 because these will be inserted in any new rows created as well.
2704
-     * @return EE_Base_Class
2705
-     * @throws EE_Error
2706
-     */
2707
-    public function remove_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query = [])
2708
-    {
2709
-        $relation_obj = $this->related_settings_for($relationName);
2710
-        return $relation_obj->remove_relation_to($id_or_obj, $other_model_id_or_obj, $where_query);
2711
-    }
2712
-
2713
-
2714
-    /**
2715
-     * @param mixed       $id_or_obj
2716
-     * @param string      $relationName
2717
-     * @param array|null  $where_query_params
2718
-     * @return EE_Base_Class[]
2719
-     * @throws EE_Error
2720
-     * @throws ReflectionException
2721
-     */
2722
-    public function remove_relations($id_or_obj, $relationName, $where_query_params = [])
2723
-    {
2724
-        $relation_obj = $this->related_settings_for($relationName);
2725
-        return $relation_obj->remove_relations($id_or_obj, $where_query_params);
2726
-    }
2727
-
2728
-
2729
-    /**
2730
-     * Gets all the related items of the specified $model_name, using $query_params.
2731
-     * Note: by default, we remove the "default query params"
2732
-     * because we want to get even deleted items etc.
2733
-     *
2734
-     * @param mixed       $id_or_obj    EE_Base_Class child or its ID
2735
-     * @param string      $model_name   like 'Event', 'Registration', etc. always singular
2736
-     * @param array|null  $query_params @see
2737
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2738
-     * @return EE_Base_Class[]
2739
-     * @throws EE_Error
2740
-     * @throws ReflectionException
2741
-     */
2742
-    public function get_all_related($id_or_obj, $model_name, ?array $query_params = [])
2743
-    {
2744
-        $model_obj         = $this->ensure_is_obj($id_or_obj);
2745
-        $relation_settings = $this->related_settings_for($model_name);
2746
-        return $relation_settings->get_all_related($model_obj, $query_params);
2747
-    }
2748
-
2749
-
2750
-    /**
2751
-     * Deletes all the model objects across the relation indicated by $model_name
2752
-     * which are related to $id_or_obj which meet the criteria set in $query_params.
2753
-     * However, if the model objects can't be deleted because of blocking related model objects, then
2754
-     * they aren't deleted. (Unless the thing that would have been deleted can be soft-deleted, that still happens).
2755
-     *
2756
-     * @param EE_Base_Class|int|string $id_or_obj
2757
-     * @param string                   $model_name
2758
-     * @param array|null               $query_params
2759
-     * @return int how many deleted
2760
-     * @throws EE_Error
2761
-     * @throws ReflectionException
2762
-     */
2763
-    public function delete_related($id_or_obj, $model_name, $query_params = [])
2764
-    {
2765
-        $model_obj         = $this->ensure_is_obj($id_or_obj);
2766
-        $relation_settings = $this->related_settings_for($model_name);
2767
-        return $relation_settings->delete_all_related($model_obj, $query_params);
2768
-    }
2769
-
2770
-
2771
-    /**
2772
-     * Hard deletes all the model objects across the relation indicated by $model_name
2773
-     * which are related to $id_or_obj which meet the criteria set in $query_params. If
2774
-     * the model objects can't be hard deleted because of blocking related model objects,
2775
-     * just does a soft-delete on them instead.
2776
-     *
2777
-     * @param EE_Base_Class|int|string $id_or_obj
2778
-     * @param string                   $model_name
2779
-     * @param array|null               $query_params
2780
-     * @return int how many deleted
2781
-     * @throws EE_Error
2782
-     * @throws ReflectionException
2783
-     */
2784
-    public function delete_related_permanently($id_or_obj, $model_name, $query_params = [])
2785
-    {
2786
-        $model_obj         = $this->ensure_is_obj($id_or_obj);
2787
-        $relation_settings = $this->related_settings_for($model_name);
2788
-        return $relation_settings->delete_related_permanently($model_obj, $query_params);
2789
-    }
2790
-
2791
-
2792
-    /**
2793
-     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2794
-     * unless otherwise specified in the $query_params
2795
-     *
2796
-     * @param EE_Base_Class|int|string $id_or_obj
2797
-     * @param string                   $model_name     like 'Event', or 'Registration'
2798
-     * @param array|null               $query_params   @see
2799
-     *                                                 https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2800
-     * @param string                   $field_to_count name of field to count by. By default, uses primary key
2801
-     * @param bool                     $distinct       if we want to only count the distinct values for the column then
2802
-     *                                                 you can trigger that by the setting $distinct to TRUE;
2803
-     * @return int
2804
-     * @throws EE_Error
2805
-     * @throws ReflectionException
2806
-     */
2807
-    public function count_related(
2808
-        $id_or_obj,
2809
-        $model_name,
2810
-        $query_params = [],
2811
-        $field_to_count = null,
2812
-        $distinct = false
2813
-    ) {
2814
-        $related_model = $this->get_related_model_obj($model_name);
2815
-        // we're just going to use the query params on the related model's normal get_all query,
2816
-        // except add a condition to say to match the current mod
2817
-        if (! isset($query_params['default_where_conditions'])) {
2818
-            $query_params['default_where_conditions'] = EE_Default_Where_Conditions::NONE;
2819
-        }
2820
-        $this_model_name                                                 = $this->get_this_model_name();
2821
-        $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2822
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2823
-        return $related_model->count($query_params, $field_to_count, $distinct);
2824
-    }
2825
-
2826
-
2827
-    /**
2828
-     * Instead of getting the related model objects, simply sums up the values of the specified field.
2829
-     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2830
-     *
2831
-     * @param EE_Base_Class|int|string $id_or_obj
2832
-     * @param string                   $model_name   like 'Event', or 'Registration'
2833
-     * @param array|null               $query_params @see
2834
-     *                                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2835
-     * @param string                   $field_to_sum name of field to count by. By default, uses primary key
2836
-     * @return float
2837
-     * @throws EE_Error
2838
-     * @throws ReflectionException
2839
-     */
2840
-    public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2841
-    {
2842
-        $related_model = $this->get_related_model_obj($model_name);
2843
-        if (! is_array($query_params)) {
2844
-            EE_Error::doing_it_wrong(
2845
-                'EEM_Base::sum_related',
2846
-                sprintf(
2847
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
2848
-                    gettype($query_params)
2849
-                ),
2850
-                '4.6.0'
2851
-            );
2852
-            $query_params = [];
2853
-        }
2854
-        // we're just going to use the query params on the related model's normal get_all query,
2855
-        // except add a condition to say to match the current mod
2856
-        if (! isset($query_params['default_where_conditions'])) {
2857
-            $query_params['default_where_conditions'] = EE_Default_Where_Conditions::NONE;
2858
-        }
2859
-        $this_model_name                                                 = $this->get_this_model_name();
2860
-        $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2861
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2862
-        return $related_model->sum($query_params, $field_to_sum);
2863
-    }
2864
-
2865
-
2866
-    /**
2867
-     * Uses $this->_relatedModels info to find the first related model object of relation $relationName to the given
2868
-     * $modelObject
2869
-     *
2870
-     * @param int | EE_Base_Class $id_or_obj        EE_Base_Class child or its ID
2871
-     * @param string              $other_model_name , key in $this->_relatedModels, eg 'Registration', or 'Events'
2872
-     * @param array|null          $query_params     @see
2873
-     *                                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2874
-     * @return EE_Base_Class
2875
-     * @throws EE_Error
2876
-     * @throws ReflectionException
2877
-     */
2878
-    public function get_first_related(EE_Base_Class $id_or_obj, $other_model_name, $query_params)
2879
-    {
2880
-        $query_params['limit'] = 1;
2881
-        $results               = $this->get_all_related($id_or_obj, $other_model_name, $query_params);
2882
-        if ($results) {
2883
-            return array_shift($results);
2884
-        }
2885
-        return null;
2886
-    }
2887
-
2888
-
2889
-    /**
2890
-     * Gets the model's name as it's expected in queries. For example, if this is EEM_Event model, that would be Event
2891
-     *
2892
-     * @return string
2893
-     */
2894
-    public function get_this_model_name()
2895
-    {
2896
-        return str_replace("EEM_", "", get_class($this));
2897
-    }
2898
-
2899
-
2900
-    /**
2901
-     * Gets the model field on this model which is of type EE_Any_Foreign_Model_Name_Field
2902
-     *
2903
-     * @return EE_Any_Foreign_Model_Name_Field
2904
-     * @throws EE_Error
2905
-     */
2906
-    public function get_field_containing_related_model_name()
2907
-    {
2908
-        foreach ($this->field_settings(true) as $field) {
2909
-            if ($field instanceof EE_Any_Foreign_Model_Name_Field) {
2910
-                $field_with_model_name = $field;
2911
-            }
2912
-        }
2913
-        if (! isset($field_with_model_name) || ! $field_with_model_name) {
2914
-            throw new EE_Error(
2915
-                sprintf(
2916
-                    esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
2917
-                    $this->get_this_model_name()
2918
-                )
2919
-            );
2920
-        }
2921
-        return $field_with_model_name;
2922
-    }
2923
-
2924
-
2925
-    /**
2926
-     * Inserts a new entry into the database, for each table.
2927
-     * Note: does not add the item to the entity map because that is done by EE_Base_Class::save() right after this.
2928
-     * If client code uses EEM_Base::insert() directly, then although the item isn't in the entity map,
2929
-     * we also know there is no model object with the newly inserted item's ID at the moment (because
2930
-     * if there were, then they would already be in the DB and this would fail); and in the future if someone
2931
-     * creates a model object with this ID (or grabs it from the DB) then it will be added to the
2932
-     * entity map at that time anyways. SO, no need for EEM_Base::insert ot add to the entity map
2933
-     *
2934
-     * @param array $field_n_values keys are field names, values are their values (in the client code's domain if
2935
-     *                              $values_already_prepared_by_model_object is false, in the model object's domain if
2936
-     *                              $values_already_prepared_by_model_object is true. See comment about this at the top
2937
-     *                              of EEM_Base)
2938
-     * @return int|string new primary key on main table that got inserted
2939
-     * @throws EE_Error
2940
-     * @throws ReflectionException
2941
-     */
2942
-    public function insert($field_n_values)
2943
-    {
2944
-        /**
2945
-         * Filters the fields and their values before inserting an item using the models
2946
-         *
2947
-         * @param array    $fields_n_values keys are the fields and values are their new values
2948
-         * @param EEM_Base $model           the model used
2949
-         */
2950
-        $field_n_values = (array) apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2951
-        if ($this->_satisfies_unique_indexes($field_n_values)) {
2952
-            $main_table = $this->_get_main_table();
2953
-            $new_id     = $this->_insert_into_specific_table($main_table, $field_n_values, false);
2954
-            if ($new_id !== false) {
2955
-                foreach ($this->_get_other_tables() as $other_table) {
2956
-                    $this->_insert_into_specific_table($other_table, $field_n_values, $new_id);
2957
-                }
2958
-            }
2959
-            /**
2960
-             * Done just after attempting to insert a new model object
2961
-             *
2962
-             * @param EEM_Base $model           used
2963
-             * @param array    $fields_n_values fields and their values
2964
-             * @param int|string the              ID of the newly-inserted model object
2965
-             */
2966
-            do_action('AHEE__EEM_Base__insert__end', $this, $field_n_values, $new_id);
2967
-            return $new_id;
2968
-        }
2969
-        return false;
2970
-    }
2971
-
2972
-
2973
-    /**
2974
-     * Checks that the result would satisfy the unique indexes on this model
2975
-     *
2976
-     * @param array  $field_n_values
2977
-     * @param string $action
2978
-     * @return boolean
2979
-     * @throws EE_Error
2980
-     * @throws ReflectionException
2981
-     */
2982
-    protected function _satisfies_unique_indexes(array $field_n_values, $action = 'insert')
2983
-    {
2984
-        foreach ($this->unique_indexes() as $index_name => $index) {
2985
-            $uniqueness_where_params = array_intersect_key($field_n_values, $index->fields());
2986
-            if ($this->exists([$uniqueness_where_params])) {
2987
-                EE_Error::add_error(
2988
-                    sprintf(
2989
-                        esc_html__(
2990
-                            "Could not %s %s. %s uniqueness index failed. Fields %s must form a unique set, but an entry already exists with values %s.",
2991
-                            "event_espresso"
2992
-                        ),
2993
-                        $action,
2994
-                        $this->_get_class_name(),
2995
-                        $index_name,
2996
-                        implode(",", $index->field_names()),
2997
-                        http_build_query($uniqueness_where_params)
2998
-                    ),
2999
-                    __FILE__,
3000
-                    __FUNCTION__,
3001
-                    __LINE__
3002
-                );
3003
-                return false;
3004
-            }
3005
-        }
3006
-        return true;
3007
-    }
3008
-
3009
-
3010
-    /**
3011
-     * Checks the database for an item that conflicts (ie, if this item were
3012
-     * saved to the DB would break some uniqueness requirement, like a primary key
3013
-     * or an index primary key set) with the item specified. $id_obj_or_fields_array
3014
-     * can be either an EE_Base_Class or an array of fields n values
3015
-     *
3016
-     * @param EE_Base_Class|array $obj_or_fields_array
3017
-     * @param boolean             $include_primary_key whether to use the model object's primary key
3018
-     *                                                 when looking for conflicts
3019
-     *                                                 (ie, if false, we ignore the model object's primary key
3020
-     *                                                 when finding "conflicts". If true, it's also considered).
3021
-     *                                                 Only works for INT primary key,
3022
-     *                                                 STRING primary keys cannot be ignored
3023
-     * @return EE_Base_Class|array
3024
-     * @throws EE_Error
3025
-     * @throws ReflectionException
3026
-     */
3027
-    public function get_one_conflicting($obj_or_fields_array, $include_primary_key = true)
3028
-    {
3029
-        if ($obj_or_fields_array instanceof EE_Base_Class) {
3030
-            $fields_n_values = $obj_or_fields_array->model_field_array();
3031
-        } elseif (is_array($obj_or_fields_array)) {
3032
-            $fields_n_values = $obj_or_fields_array;
3033
-        } else {
3034
-            throw new EE_Error(
3035
-                sprintf(
3036
-                    esc_html__(
3037
-                        "%s get_all_conflicting should be called with a model object or an array of field names and values, you provided %d",
3038
-                        "event_espresso"
3039
-                    ),
3040
-                    $this->class_name,
3041
-                    $obj_or_fields_array
3042
-                )
3043
-            );
3044
-        }
3045
-        $query_params = [];
3046
-        if (
3047
-            $this->has_primary_key_field()
3048
-            && ($include_primary_key
3049
-                || $this->get_primary_key_field()
3050
-                   instanceof
3051
-                   EE_Primary_Key_String_Field)
3052
-            && isset($fields_n_values[ $this->primary_key_name() ])
3053
-        ) {
3054
-            $query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
3055
-        }
3056
-        foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
3057
-            $uniqueness_where_params                              =
3058
-                array_intersect_key($fields_n_values, $unique_index->fields());
3059
-            $query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
3060
-        }
3061
-        // if there is nothing to base this search on, then we shouldn't find anything
3062
-        if (empty($query_params)) {
3063
-            return [];
3064
-        }
3065
-        return $this->get_one($query_params);
3066
-    }
3067
-
3068
-
3069
-    /**
3070
-     * Like count, but is optimized and returns a boolean instead of an int
3071
-     *
3072
-     * @param array $query_params
3073
-     * @return boolean
3074
-     * @throws EE_Error
3075
-     * @throws ReflectionException
3076
-     */
3077
-    public function exists($query_params)
3078
-    {
3079
-        $query_params['limit'] = 1;
3080
-        return $this->count($query_params) > 0;
3081
-    }
3082
-
3083
-
3084
-    /**
3085
-     * Wrapper for exists, except ignores default query parameters so we're only considering ID
3086
-     *
3087
-     * @param int|string $id
3088
-     * @return boolean
3089
-     * @throws EE_Error
3090
-     * @throws ReflectionException
3091
-     */
3092
-    public function exists_by_ID($id)
3093
-    {
3094
-        return $this->exists(
3095
-            [
3096
-                'default_where_conditions' => EE_Default_Where_Conditions::NONE,
3097
-                [
3098
-                    $this->primary_key_name() => $id,
3099
-                ],
3100
-            ]
3101
-        );
3102
-    }
3103
-
3104
-
3105
-    /**
3106
-     * Inserts a new row in $table, using the $cols_n_values which apply to that table.
3107
-     * If a $new_id is supplied and if $table is an EE_Other_Table, we assume
3108
-     * we need to add a foreign key column to point to $new_id (which should be the primary key's value
3109
-     * on the main table)
3110
-     * This is protected rather than private because private is not accessible to any child methods and there MAY be
3111
-     * cases where we want to call it directly rather than via insert().
3112
-     *
3113
-     * @access   protected
3114
-     * @param EE_Table_Base $table
3115
-     * @param array         $fields_n_values each key should be in field's keys, and value should be an int, string or
3116
-     *                                       float
3117
-     * @param int           $new_id          for now we assume only int keys
3118
-     * @return int ID of new row inserted, or FALSE on failure
3119
-     * @throws EE_Error
3120
-     * @global WPDB         $wpdb            only used to get the $wpdb->insert_id after performing an insert
3121
-     */
3122
-    protected function _insert_into_specific_table(EE_Table_Base $table, $fields_n_values, $new_id = 0)
3123
-    {
3124
-        global $wpdb;
3125
-        $insertion_col_n_values = [];
3126
-        $format_for_insertion   = [];
3127
-        $fields_on_table        = $this->_get_fields_for_table($table->get_table_alias());
3128
-        foreach ($fields_on_table as $field_obj) {
3129
-            // check if its an auto-incrementing column, in which case we should just leave it to do its autoincrement thing
3130
-            if ($field_obj->is_auto_increment()) {
3131
-                continue;
3132
-            }
3133
-            $prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3134
-            // if the value we want to assign it to is NULL, just don't mention it for the insertion
3135
-            if ($prepared_value !== null) {
3136
-                $insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3137
-                $format_for_insertion[]                                   = $field_obj->get_wpdb_data_type();
3138
-            }
3139
-        }
3140
-        if ($table instanceof EE_Secondary_Table && $new_id) {
3141
-            // its not the main table, so we should have already saved the main table's PK which we just inserted
3142
-            // so add the fk to the main table as a column
3143
-            $insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3144
-            $format_for_insertion[]                              =
3145
-                '%d';// yes right now we're only allowing these foreign keys to be INTs
3146
-        }
3147
-
3148
-        // insert the new entry
3149
-        $result = $this->_do_wpdb_query(
3150
-            'insert',
3151
-            [$table->get_table_name(), $insertion_col_n_values, $format_for_insertion]
3152
-        );
3153
-        if ($result === false) {
3154
-            return false;
3155
-        }
3156
-        // ok, now what do we return for the ID of the newly-inserted thing?
3157
-        if ($this->has_primary_key_field()) {
3158
-            if ($this->get_primary_key_field()->is_auto_increment()) {
3159
-                return $wpdb->insert_id;
3160
-            }
3161
-            // it's not an auto-increment primary key, so
3162
-            // it must have been supplied
3163
-            return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3164
-        }
3165
-        // we can't return a  primary key because there is none. instead return
3166
-        // a unique string indicating this model
3167
-        return $this->get_index_primary_key_string($fields_n_values);
3168
-    }
3169
-
3170
-
3171
-    /**
3172
-     * Prepare the $field_obj 's value in $fields_n_values for use in the database.
3173
-     * If the field doesn't allow NULL, try to use its default. (If it doesn't allow NULL,
3174
-     * and there is no default, we pass it along. WPDB will take care of it)
3175
-     *
3176
-     * @param EE_Model_Field_Base $field_obj
3177
-     * @param array               $fields_n_values
3178
-     * @return mixed string|int|float depending on what the table column will be expecting
3179
-     * @throws EE_Error
3180
-     */
3181
-    protected function _prepare_value_or_use_default($field_obj, $fields_n_values)
3182
-    {
3183
-        $field_name = $field_obj->get_name();
3184
-        // if this field doesn't allow nullable, don't allow it
3185
-        if (! $field_obj->is_nullable() && ! isset($fields_n_values[ $field_name ])) {
3186
-            $fields_n_values[ $field_name ] = $field_obj->get_default_value();
3187
-        }
3188
-        $unprepared_value = $fields_n_values[ $field_name ] ?? null;
3189
-        return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3190
-    }
3191
-
3192
-
3193
-    /**
3194
-     * Consolidates code for preparing  a value supplied to the model for use int eh db. Calls the field's
3195
-     * prepare_for_use_in_db method on the value, and depending on $value_already_prepare_by_model_obj, may also call
3196
-     * the field's prepare_for_set() method.
3197
-     *
3198
-     * @param mixed               $value value in the client code domain if $value_already_prepared_by_model_object is
3199
-     *                                   false, otherwise a value in the model object's domain (see lengthy comment at
3200
-     *                                   top of file)
3201
-     * @param EE_Model_Field_Base $field field which will be doing the preparing of the value. If null, we assume
3202
-     *                                   $value is a custom selection
3203
-     * @return mixed a value ready for use in the database for insertions, updating, or in a where clause
3204
-     */
3205
-    private function _prepare_value_for_use_in_db($value, $field)
3206
-    {
3207
-        if ($field instanceof EE_Model_Field_Base) {
3208
-            // phpcs:disable PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
3209
-            switch ($this->_values_already_prepared_by_model_object) {
3210
-                /** @noinspection PhpMissingBreakStatementInspection */
3211
-                case self::not_prepared_by_model_object:
3212
-                    $value = $field->prepare_for_set($value);
3213
-                // purposefully left out "return"
3214
-                // no break
3215
-                case self::prepared_by_model_object:
3216
-                    /** @noinspection SuspiciousAssignmentsInspection */
3217
-                    $value = $field->prepare_for_use_in_db($value);
3218
-                // no break
3219
-                case self::prepared_for_use_in_db:
3220
-                    // leave the value alone
3221
-            }
3222
-            // phpcs:enable
3223
-        }
3224
-        return $value;
3225
-    }
3226
-
3227
-
3228
-    /**
3229
-     * Returns the main table on this model
3230
-     *
3231
-     * @return EE_Primary_Table
3232
-     * @throws EE_Error
3233
-     */
3234
-    protected function _get_main_table()
3235
-    {
3236
-        foreach ($this->_tables as $table) {
3237
-            if ($table instanceof EE_Primary_Table) {
3238
-                return $table;
3239
-            }
3240
-        }
3241
-        throw new EE_Error(
3242
-            sprintf(
3243
-                esc_html__(
3244
-                    'There are no main tables on %s. They should be added to _tables array in the constructor',
3245
-                    'event_espresso'
3246
-                ),
3247
-                $this->class_name
3248
-            )
3249
-        );
3250
-    }
3251
-
3252
-
3253
-    /**
3254
-     * table
3255
-     * returns EE_Primary_Table table name
3256
-     *
3257
-     * @return string
3258
-     * @throws EE_Error
3259
-     */
3260
-    public function table()
3261
-    {
3262
-        return $this->_get_main_table()->get_table_name();
3263
-    }
3264
-
3265
-
3266
-    /**
3267
-     * table
3268
-     * returns first EE_Secondary_Table table name
3269
-     *
3270
-     * @return string
3271
-     */
3272
-    public function second_table()
3273
-    {
3274
-        // grab second table from tables array
3275
-        $second_table = end($this->_tables);
3276
-        return $second_table instanceof EE_Secondary_Table
3277
-            ? $second_table->get_table_name()
3278
-            : null;
3279
-    }
3280
-
3281
-
3282
-    /**
3283
-     * get_table_obj_by_alias
3284
-     * returns table name given it's alias
3285
-     *
3286
-     * @param string $table_alias
3287
-     * @return EE_Primary_Table | EE_Secondary_Table
3288
-     */
3289
-    public function get_table_obj_by_alias($table_alias = '')
3290
-    {
3291
-        return $this->_tables[ $table_alias ] ?? null;
3292
-    }
3293
-
3294
-
3295
-    /**
3296
-     * Gets all the tables of type EE_Other_Table from EEM_CPT_Basel_Model::_tables
3297
-     *
3298
-     * @return EE_Secondary_Table[]
3299
-     */
3300
-    protected function _get_other_tables()
3301
-    {
3302
-        $other_tables = [];
3303
-        foreach ($this->_tables as $table_alias => $table) {
3304
-            if ($table instanceof EE_Secondary_Table) {
3305
-                $other_tables[ $table_alias ] = $table;
3306
-            }
3307
-        }
3308
-        return $other_tables;
3309
-    }
3310
-
3311
-
3312
-    /**
3313
-     * Finds all the fields that correspond to the given table
3314
-     *
3315
-     * @param string $table_alias , array key in EEM_Base::_tables
3316
-     * @return EE_Model_Field_Base[]
3317
-     */
3318
-    public function _get_fields_for_table($table_alias)
3319
-    {
3320
-        return $this->_fields[ $table_alias ];
3321
-    }
3322
-
3323
-
3324
-    /**
3325
-     * Recurses through all the where parameters, and finds all the related models we'll need
3326
-     * to complete this query. Eg, given where parameters like array('EVT_ID'=>3) from within Event model, we won't
3327
-     * need any related models. But if the array were array('Registrations.REG_ID'=>3), we'd need the related
3328
-     * Registration model. If it were array('Registrations.Transactions.Payments.PAY_ID'=>3), then we'd need the
3329
-     * related Registration, Transaction, and Payment models.
3330
-     *
3331
-     * @param array $query_params @see
3332
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3333
-     * @return EE_Model_Query_Info_Carrier
3334
-     * @throws EE_Error
3335
-     */
3336
-    public function _extract_related_models_from_query($query_params)
3337
-    {
3338
-        $query_info_carrier = new EE_Model_Query_Info_Carrier();
3339
-        if (array_key_exists(0, $query_params)) {
3340
-            $this->_extract_related_models_from_sub_params_array_keys($query_params[0], $query_info_carrier, 0);
3341
-        }
3342
-        if (array_key_exists('group_by', $query_params)) {
3343
-            if (is_array($query_params['group_by'])) {
3344
-                $this->_extract_related_models_from_sub_params_array_values(
3345
-                    $query_params['group_by'],
3346
-                    $query_info_carrier,
3347
-                    'group_by'
3348
-                );
3349
-            } elseif (! empty($query_params['group_by'])) {
3350
-                $this->_extract_related_model_info_from_query_param(
3351
-                    $query_params['group_by'],
3352
-                    $query_info_carrier,
3353
-                    'group_by'
3354
-                );
3355
-            }
3356
-        }
3357
-        if (array_key_exists('having', $query_params)) {
3358
-            $this->_extract_related_models_from_sub_params_array_keys(
3359
-                $query_params[0],
3360
-                $query_info_carrier,
3361
-                'having'
3362
-            );
3363
-        }
3364
-        if (array_key_exists('order_by', $query_params)) {
3365
-            if (is_array($query_params['order_by'])) {
3366
-                $this->_extract_related_models_from_sub_params_array_keys(
3367
-                    $query_params['order_by'],
3368
-                    $query_info_carrier,
3369
-                    'order_by'
3370
-                );
3371
-            } elseif (! empty($query_params['order_by'])) {
3372
-                $this->_extract_related_model_info_from_query_param(
3373
-                    $query_params['order_by'],
3374
-                    $query_info_carrier,
3375
-                    'order_by'
3376
-                );
3377
-            }
3378
-        }
3379
-        if (array_key_exists('force_join', $query_params)) {
3380
-            $this->_extract_related_models_from_sub_params_array_values(
3381
-                $query_params['force_join'],
3382
-                $query_info_carrier,
3383
-                'force_join'
3384
-            );
3385
-        }
3386
-        $this->extractRelatedModelsFromCustomSelects($query_info_carrier);
3387
-        return $query_info_carrier;
3388
-    }
3389
-
3390
-
3391
-    /**
3392
-     * For extracting related models from WHERE (0), HAVING (having), ORDER BY (order_by) or forced joins (force_join)
3393
-     *
3394
-     * @param array                       $sub_query_params
3395
-     * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3396
-     * @param string                      $query_param_type one of $this->_allowed_query_params
3397
-     * @return EE_Model_Query_Info_Carrier
3398
-     * @throws EE_Error
3399
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#-0-where-conditions
3400
-     */
3401
-    private function _extract_related_models_from_sub_params_array_keys(
3402
-        $sub_query_params,
3403
-        EE_Model_Query_Info_Carrier $model_query_info_carrier,
3404
-        $query_param_type
3405
-    ) {
3406
-        if (! empty($sub_query_params)) {
3407
-            $sub_query_params = (array) $sub_query_params;
3408
-            foreach ($sub_query_params as $param => $possibly_array_of_params) {
3409
-                // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3410
-                $this->_extract_related_model_info_from_query_param(
3411
-                    $param,
3412
-                    $model_query_info_carrier,
3413
-                    $query_param_type
3414
-                );
3415
-                // if $possibly_array_of_params is an array, try recursing into it, searching for keys which
3416
-                // indicate needed joins. Eg, array('NOT'=>array('Registration.TXN_ID'=>23)). In this case, we tried
3417
-                // extracting models out of the 'NOT', which obviously wasn't successful, and then we recurse into the value
3418
-                // of array('Registration.TXN_ID'=>23)
3419
-                $query_param_sans_stars =
3420
-                    $this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3421
-                if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3422
-                    if (! is_array($possibly_array_of_params)) {
3423
-                        throw new EE_Error(
3424
-                            sprintf(
3425
-                                esc_html__(
3426
-                                    "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'))",
3427
-                                    "event_espresso"
3428
-                                ),
3429
-                                $param,
3430
-                                $possibly_array_of_params
3431
-                            )
3432
-                        );
3433
-                    }
3434
-                    $this->_extract_related_models_from_sub_params_array_keys(
3435
-                        $possibly_array_of_params,
3436
-                        $model_query_info_carrier,
3437
-                        $query_param_type
3438
-                    );
3439
-                } elseif (
3440
-                    $query_param_type === 0 // ie WHERE
3441
-                    && is_array($possibly_array_of_params) // need is_array() check so we don't try to explode a string
3442
-                    && isset($possibly_array_of_params[2])
3443
-                    && $possibly_array_of_params[2]
3444
-                ) {
3445
-                    // then $possible_array_of_params looks something like array('<','DTT_sold',true)
3446
-                    // indicating that $possible_array_of_params[1] is actually a field name,
3447
-                    // from which we should extract query parameters!
3448
-                    if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3449
-                        throw new EE_Error(
3450
-                            sprintf(
3451
-                                esc_html__(
3452
-                                    "Improperly formed query parameter %s. It should be numerically indexed like array('<','DTT_sold',true); but you provided %s",
3453
-                                    "event_espresso"
3454
-                                ),
3455
-                                $query_param_type,
3456
-                                implode(",", $possibly_array_of_params)
3457
-                            )
3458
-                        );
3459
-                    }
3460
-                    $this->_extract_related_model_info_from_query_param(
3461
-                        $possibly_array_of_params[1],
3462
-                        $model_query_info_carrier,
3463
-                        $query_param_type
3464
-                    );
3465
-                }
3466
-            }
3467
-        }
3468
-        return $model_query_info_carrier;
3469
-    }
3470
-
3471
-
3472
-    /**
3473
-     * For extracting related models from forced_joins, where the array values contain the info about what
3474
-     * models to join with. Eg an array like array('Attendee','Price.Price_Type');
3475
-     *
3476
-     * @param array                       $sub_query_params @see
3477
-     *                                                      https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3478
-     * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3479
-     * @param string                      $query_param_type one of $this->_allowed_query_params
3480
-     * @return EE_Model_Query_Info_Carrier
3481
-     * @throws EE_Error
3482
-     */
3483
-    private function _extract_related_models_from_sub_params_array_values(
3484
-        $sub_query_params,
3485
-        EE_Model_Query_Info_Carrier $model_query_info_carrier,
3486
-        $query_param_type
3487
-    ) {
3488
-        if (! empty($sub_query_params)) {
3489
-            if (! is_array($sub_query_params)) {
3490
-                throw new EE_Error(
3491
-                    sprintf(
3492
-                        esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
3493
-                        $sub_query_params
3494
-                    )
3495
-                );
3496
-            }
3497
-            foreach ($sub_query_params as $param) {
3498
-                // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3499
-                $this->_extract_related_model_info_from_query_param(
3500
-                    $param,
3501
-                    $model_query_info_carrier,
3502
-                    $query_param_type
3503
-                );
3504
-            }
3505
-        }
3506
-        return $model_query_info_carrier;
3507
-    }
3508
-
3509
-
3510
-    /**
3511
-     * Extract all the query parts from  model query params
3512
-     * and put into a EEM_Related_Model_Info_Carrier for easy extraction into a query. We create this object
3513
-     * instead of directly constructing the SQL because often we need to extract info from the $query_params
3514
-     * but use them in a different order. Eg, we need to know what models we are querying
3515
-     * before we know what joins to perform. However, we need to know what data types correspond to which fields on
3516
-     * other models before we can finalize the where clause SQL.
3517
-     *
3518
-     * @param array $query_params
3519
-     * @return EE_Model_Query_Info_Carrier
3520
-     * @throws EE_Error
3521
-     * @throws ModelConfigurationException
3522
-     * @throws ReflectionException
3523
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3524
-     */
3525
-    public function _create_model_query_info_carrier($query_params)
3526
-    {
3527
-        if (! is_array($query_params)) {
3528
-            EE_Error::doing_it_wrong(
3529
-                'EEM_Base::_create_model_query_info_carrier',
3530
-                sprintf(
3531
-                    esc_html__(
3532
-                        '$query_params should be an array, you passed a variable of type %s',
3533
-                        'event_espresso'
3534
-                    ),
3535
-                    gettype($query_params)
3536
-                ),
3537
-                '4.6.0'
3538
-            );
3539
-            $query_params = [];
3540
-        }
3541
-        $query_params[0] = $query_params[0] ?? [];
3542
-        // first check if we should alter the query to account for caps or not
3543
-        // because the caps might require us to do extra joins
3544
-        if (isset($query_params['caps']) && $query_params['caps'] !== 'none') {
3545
-            $query_params[0] = array_replace_recursive(
3546
-                $query_params[0],
3547
-                $this->caps_where_conditions($query_params['caps'])
3548
-            );
3549
-        }
3550
-
3551
-        // check if we should alter the query to remove data related to protected
3552
-        // custom post types
3553
-        if (isset($query_params['exclude_protected']) && $query_params['exclude_protected'] === true) {
3554
-            $where_param_key_for_password = $this->modelChainAndPassword();
3555
-            // only include if related to a cpt where no password has been set
3556
-            $query_params[0]['OR*nopassword'] = [
3557
-                $where_param_key_for_password       => '',
3558
-                $where_param_key_for_password . '*' => ['IS_NULL'],
3559
-            ];
3560
-        }
3561
-        $query_object = $this->_extract_related_models_from_query($query_params);
3562
-        // verify where_query_params has NO numeric indexes.... that's simply not how you use it!
3563
-        foreach ($query_params[0] as $key => $value) {
3564
-            if (is_int($key)) {
3565
-                throw new EE_Error(
3566
-                    sprintf(
3567
-                        esc_html__(
3568
-                            "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.",
3569
-                            "event_espresso"
3570
-                        ),
3571
-                        $key,
3572
-                        var_export($value, true),
3573
-                        var_export($query_params, true),
3574
-                        $this->class_name
3575
-                    )
3576
-                );
3577
-            }
3578
-        }
3579
-        if (
3580
-            array_key_exists('default_where_conditions', $query_params)
3581
-            && ! empty($query_params['default_where_conditions'])
3582
-        ) {
3583
-            $use_default_where_conditions = $query_params['default_where_conditions'];
3584
-        } else {
3585
-            $use_default_where_conditions = EE_Default_Where_Conditions::ALL;
3586
-        }
3587
-        $query_params[0] = array_merge(
3588
-            $this->_get_default_where_conditions_for_models_in_query(
3589
-                $query_object,
3590
-                $use_default_where_conditions,
3591
-                $query_params[0]
3592
-            ),
3593
-            $query_params[0]
3594
-        );
3595
-        $query_object->set_where_sql($this->_construct_where_clause($query_params[0]));
3596
-        // if this is a "on_join_limit" then we are limiting on on a specific table in a multi_table join.
3597
-        // So we need to setup a subquery and use that for the main join.
3598
-        // Note for now this only works on the primary table for the model.
3599
-        // So for instance, you could set the limit array like this:
3600
-        // array( 'on_join_limit' => array('Primary_Table_Alias', array(1,10) ) )
3601
-        if (array_key_exists('on_join_limit', $query_params) && ! empty($query_params['on_join_limit'])) {
3602
-            $query_object->set_main_model_join_sql(
3603
-                $this->_construct_limit_join_select(
3604
-                    $query_params['on_join_limit'][0],
3605
-                    $query_params['on_join_limit'][1]
3606
-                )
3607
-            );
3608
-        }
3609
-        // set limit
3610
-        if (array_key_exists('limit', $query_params)) {
3611
-            if (is_array($query_params['limit'])) {
3612
-                if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3613
-                    $e = sprintf(
3614
-                        esc_html__(
3615
-                            "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)",
3616
-                            "event_espresso"
3617
-                        ),
3618
-                        http_build_query($query_params['limit'])
3619
-                    );
3620
-                    throw new EE_Error($e . "|" . $e);
3621
-                }
3622
-                // they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3623
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3624
-            } elseif (! empty($query_params['limit'])) {
3625
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3626
-            }
3627
-        }
3628
-        // set order by
3629
-        if (array_key_exists('order_by', $query_params)) {
3630
-            if (is_array($query_params['order_by'])) {
3631
-                // if they're using 'order_by' as an array, they can't use 'order' (because 'order_by' must
3632
-                // specify whether to ascend or descend on each field. Eg 'order_by'=>array('EVT_ID'=>'ASC'). So
3633
-                // including 'order' wouldn't make any sense if 'order_by' has already specified which way to order!
3634
-                if (array_key_exists('order', $query_params)) {
3635
-                    throw new EE_Error(
3636
-                        sprintf(
3637
-                            esc_html__(
3638
-                                "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 ",
3639
-                                "event_espresso"
3640
-                            ),
3641
-                            $this->class_name,
3642
-                            implode(", ", array_keys($query_params['order_by'])),
3643
-                            implode(", ", $query_params['order_by']),
3644
-                            $query_params['order']
3645
-                        )
3646
-                    );
3647
-                }
3648
-                $this->_extract_related_models_from_sub_params_array_keys(
3649
-                    $query_params['order_by'],
3650
-                    $query_object,
3651
-                    'order_by'
3652
-                );
3653
-                // assume it's an array of fields to order by
3654
-                $order_array = [];
3655
-                foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3656
-                    $order         = $this->_extract_order($order);
3657
-                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3658
-                }
3659
-                $query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3660
-            } elseif (! empty($query_params['order_by'])) {
3661
-                $this->_extract_related_model_info_from_query_param(
3662
-                    $query_params['order_by'],
3663
-                    $query_object,
3664
-                    'order',
3665
-                    $query_params['order_by']
3666
-                );
3667
-                $order = isset($query_params['order'])
3668
-                    ? $this->_extract_order($query_params['order'])
3669
-                    : 'DESC';
3670
-                $query_object->set_order_by_sql(
3671
-                    " ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3672
-                );
3673
-            }
3674
-        }
3675
-        // if 'order_by' wasn't set, maybe they are just using 'order' on its own?
3676
-        if (
3677
-            ! array_key_exists('order_by', $query_params)
3678
-            && array_key_exists('order', $query_params)
3679
-            && ! empty($query_params['order'])
3680
-        ) {
3681
-            $pk_field = $this->get_primary_key_field();
3682
-            $order    = $this->_extract_order($query_params['order']);
3683
-            $query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3684
-        }
3685
-        // set group by
3686
-        if (array_key_exists('group_by', $query_params)) {
3687
-            if (is_array($query_params['group_by'])) {
3688
-                // it's an array, so assume we'll be grouping by a bunch of stuff
3689
-                $group_by_array = [];
3690
-                foreach ($query_params['group_by'] as $field_name_to_group_by) {
3691
-                    $group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3692
-                }
3693
-                $query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3694
-            } elseif (! empty($query_params['group_by'])) {
3695
-                $query_object->set_group_by_sql(
3696
-                    " GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3697
-                );
3698
-            }
3699
-        }
3700
-        // set having
3701
-        if (array_key_exists('having', $query_params) && $query_params['having']) {
3702
-            $query_object->set_having_sql($this->_construct_having_clause($query_params['having']));
3703
-        }
3704
-        // now, just verify they didn't pass anything wack
3705
-        foreach ($query_params as $query_key => $query_value) {
3706
-            if (! in_array($query_key, $this->_allowed_query_params, true)) {
3707
-                throw new EE_Error(
3708
-                    sprintf(
3709
-                        esc_html__(
3710
-                            "You passed %s as a query parameter to %s, which is illegal! The allowed query parameters are %s",
3711
-                            'event_espresso'
3712
-                        ),
3713
-                        $query_key,
3714
-                        $this->class_name,
3715
-                        //                      print_r( $this->_allowed_query_params, TRUE )
3716
-                        implode(',', $this->_allowed_query_params)
3717
-                    )
3718
-                );
3719
-            }
3720
-        }
3721
-        $main_model_join_sql = $query_object->get_main_model_join_sql();
3722
-        if (empty($main_model_join_sql)) {
3723
-            $query_object->set_main_model_join_sql($this->_construct_internal_join());
3724
-        }
3725
-        return $query_object;
3726
-    }
3727
-
3728
-
3729
-    /**
3730
-     * Gets the where conditions that should be imposed on the query based on the
3731
-     * context (eg reading frontend, backend, edit or delete).
3732
-     *
3733
-     * @param string $context one of EEM_Base::valid_cap_contexts()
3734
-     * @return array @see
3735
-     *                        https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3736
-     * @throws EE_Error
3737
-     */
3738
-    public function caps_where_conditions($context = self::caps_read)
3739
-    {
3740
-        EEM_Base::verify_is_valid_cap_context($context);
3741
-        $cap_where_conditions = [];
3742
-        $cap_restrictions     = $this->caps_missing($context);
3743
-        foreach ($cap_restrictions as $restriction_if_no_cap) {
3744
-            $cap_where_conditions = array_replace_recursive(
3745
-                $cap_where_conditions,
3746
-                $restriction_if_no_cap->get_default_where_conditions()
3747
-            );
3748
-        }
3749
-        return apply_filters(
3750
-            'FHEE__EEM_Base__caps_where_conditions__return',
3751
-            $cap_where_conditions,
3752
-            $this,
3753
-            $context,
3754
-            $cap_restrictions
3755
-        );
3756
-    }
3757
-
3758
-
3759
-    /**
3760
-     * Verifies that $should_be_order_string is in $this->_allowed_order_values,
3761
-     * otherwise throws an exception
3762
-     *
3763
-     * @param string $should_be_order_string
3764
-     * @return string either ASC, asc, DESC or desc
3765
-     * @throws EE_Error
3766
-     */
3767
-    private function _extract_order($should_be_order_string)
3768
-    {
3769
-        if (in_array($should_be_order_string, $this->_allowed_order_values)) {
3770
-            return $should_be_order_string;
3771
-        }
3772
-        throw new EE_Error(
3773
-            sprintf(
3774
-                esc_html__(
3775
-                    "While performing a query on '%s', tried to use '%s' as an order parameter. ",
3776
-                    "event_espresso"
3777
-                ),
3778
-                $this->class_name,
3779
-                $should_be_order_string
3780
-            )
3781
-        );
3782
-    }
3783
-
3784
-
3785
-    /**
3786
-     * Looks at all the models which are included in this query, and asks each
3787
-     * for their universal_where_params, and returns them in the same format as $query_params[0] (where),
3788
-     * so they can be merged
3789
-     *
3790
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
3791
-     * @param string                      $use_default_where_conditions can be 'none','other_models_only', or 'all'.
3792
-     *                                                                  'none' means NO default where conditions will
3793
-     *                                                                  be used AT ALL during this query.
3794
-     *                                                                  'other_models_only' means default where
3795
-     *                                                                  conditions from other models will be used, but
3796
-     *                                                                  not for this primary model. 'all', the default,
3797
-     *                                                                  means default where conditions will apply as
3798
-     *                                                                  normal
3799
-     * @param array                       $where_query_params
3800
-     * @return array
3801
-     * @throws EE_Error
3802
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params
3803
-     *                                                                  .md#0-where-conditions
3804
-     */
3805
-    private function _get_default_where_conditions_for_models_in_query(
3806
-        EE_Model_Query_Info_Carrier $query_info_carrier,
3807
-        $use_default_where_conditions = EE_Default_Where_Conditions::ALL,
3808
-        $where_query_params = []
3809
-    ) {
3810
-        $allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3811
-        if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3812
-            throw new EE_Error(
3813
-                sprintf(
3814
-                    esc_html__(
3815
-                        "You passed an invalid value to the query parameter 'default_where_conditions' of '%s'. Allowed values are %s",
3816
-                        "event_espresso"
3817
-                    ),
3818
-                    $use_default_where_conditions,
3819
-                    implode(", ", $allowed_used_default_where_conditions_values)
3820
-                )
3821
-            );
3822
-        }
3823
-        $universal_query_params = [];
3824
-        if ($this->_should_use_default_where_conditions($use_default_where_conditions, true)) {
3825
-            $universal_query_params = $this->_get_default_where_conditions();
3826
-        } elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, true)) {
3827
-            $universal_query_params = $this->_get_minimum_where_conditions();
3828
-        }
3829
-        foreach ($query_info_carrier->get_model_names_included() as $model_relation_path => $model_name) {
3830
-            $related_model = $this->get_related_model_obj($model_name);
3831
-            if ($this->_should_use_default_where_conditions($use_default_where_conditions, false)) {
3832
-                $related_model_universal_where_params =
3833
-                    $related_model->_get_default_where_conditions($model_relation_path);
3834
-            } elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, false)) {
3835
-                $related_model_universal_where_params =
3836
-                    $related_model->_get_minimum_where_conditions($model_relation_path);
3837
-            } else {
3838
-                // we don't want to add full or even minimum default where conditions from this model, so just continue
3839
-                continue;
3840
-            }
3841
-            $overrides              = $this->_override_defaults_or_make_null_friendly(
3842
-                $related_model_universal_where_params,
3843
-                $where_query_params,
3844
-                $related_model,
3845
-                $model_relation_path
3846
-            );
3847
-            $universal_query_params = EEH_Array::merge_arrays_and_overwrite_keys(
3848
-                $universal_query_params,
3849
-                $overrides
3850
-            );
3851
-        }
3852
-        return $universal_query_params;
3853
-    }
3854
-
3855
-
3856
-    /**
3857
-     * Determines whether we should use default where conditions for the model in question
3858
-     * (this model, or other related models).
3859
-     * Basically, we should use default where conditions on this model if they have requested to use them on all models,
3860
-     * this model only, or to use minimum where conditions on all other models and normal where conditions on this one.
3861
-     * We should use default where conditions on related models when they requested to use default where conditions
3862
-     * on all models, or specifically just on other related models
3863
-     *
3864
-     * @param      $default_where_conditions_value
3865
-     * @param bool $for_this_model false means this is for OTHER related models
3866
-     * @return bool
3867
-     */
3868
-    private function _should_use_default_where_conditions($default_where_conditions_value, $for_this_model = true)
3869
-    {
3870
-        return (
3871
-                   $for_this_model
3872
-                   && in_array(
3873
-                       $default_where_conditions_value,
3874
-                       [
3875
-                           EE_Default_Where_Conditions::ALL,
3876
-                           EE_Default_Where_Conditions::THIS_MODEL_ONLY,
3877
-                           EE_Default_Where_Conditions::MINIMUM_OTHERS,
3878
-                       ],
3879
-                       true
3880
-                   )
3881
-               )
3882
-               || (
3883
-                   ! $for_this_model
3884
-                   && in_array(
3885
-                       $default_where_conditions_value,
3886
-                       [
3887
-                           EE_Default_Where_Conditions::ALL,
3888
-                           EE_Default_Where_Conditions::OTHER_MODELS_ONLY,
3889
-                       ],
3890
-                       true
3891
-                   )
3892
-               );
3893
-    }
3894
-
3895
-
3896
-    /**
3897
-     * Determines whether we should use default minimum conditions for the model in question
3898
-     * (this model, or other related models).
3899
-     * Basically, we should use minimum where conditions on this model only if they requested all models to use minimum
3900
-     * where conditions.
3901
-     * We should use minimum where conditions on related models if they requested to use minimum where conditions
3902
-     * on this model or others
3903
-     *
3904
-     * @param      $default_where_conditions_value
3905
-     * @param bool $for_this_model false means this is for OTHER related models
3906
-     * @return bool
3907
-     */
3908
-    private function _should_use_minimum_where_conditions($default_where_conditions_value, $for_this_model = true)
3909
-    {
3910
-        return (
3911
-                   $for_this_model
3912
-                   && $default_where_conditions_value === EE_Default_Where_Conditions::MINIMUM_ALL
3913
-               )
3914
-               || (
3915
-                   ! $for_this_model
3916
-                   && in_array(
3917
-                       $default_where_conditions_value,
3918
-                       [
3919
-                           EE_Default_Where_Conditions::MINIMUM_OTHERS,
3920
-                           EE_Default_Where_Conditions::MINIMUM_ALL,
3921
-                       ],
3922
-                       true
3923
-                   )
3924
-               );
3925
-    }
3926
-
3927
-
3928
-    /**
3929
-     * Checks if any of the defaults have been overridden. If there are any that AREN'T overridden,
3930
-     * then we also add a special where condition which allows for that model's primary key
3931
-     * to be null (which is important for JOINs. Eg, if you want to see all Events ordered by Venue's name,
3932
-     * then Event's with NO Venue won't appear unless you allow VNU_ID to be NULL)
3933
-     *
3934
-     * @param array    $default_where_conditions
3935
-     * @param array    $provided_where_conditions
3936
-     * @param EEM_Base $model
3937
-     * @param string   $model_relation_path like 'Transaction.Payment.'
3938
-     * @return array @see
3939
-     *                                      https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3940
-     * @throws EE_Error
3941
-     */
3942
-    private function _override_defaults_or_make_null_friendly(
3943
-        $default_where_conditions,
3944
-        $provided_where_conditions,
3945
-        $model,
3946
-        $model_relation_path
3947
-    ) {
3948
-        $null_friendly_where_conditions = [];
3949
-        $none_overridden                = true;
3950
-        $or_condition_key_for_defaults  = 'OR*' . get_class($model);
3951
-        foreach ($default_where_conditions as $key => $val) {
3952
-            if (isset($provided_where_conditions[ $key ])) {
3953
-                $none_overridden = false;
3954
-            } else {
3955
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3956
-            }
3957
-        }
3958
-        if ($none_overridden && $default_where_conditions) {
3959
-            if ($model->has_primary_key_field()) {
3960
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3961
-                                                                                   . "."
3962
-                                                                                   . $model->primary_key_name() ] =
3963
-                    ['IS NULL'];
3964
-            }/*else{
2643
+			$this->_show_next_x_db_queries--;
2644
+		}
2645
+	}
2646
+
2647
+
2648
+	/**
2649
+	 * Adds a relationship of the correct type between $modelObject and $otherModelObject.
2650
+	 * There are the 3 cases:
2651
+	 * 'belongsTo' relationship: sets $id_or_obj's foreign_key to be $other_model_id_or_obj's primary_key. If
2652
+	 * $otherModelObject has no ID, it is first saved.
2653
+	 * 'hasMany' relationship: sets $other_model_id_or_obj's foreign_key to be $id_or_obj's primary_key. If $id_or_obj
2654
+	 * has no ID, it is first saved.
2655
+	 * 'hasAndBelongsToMany' relationships: checks that there isn't already an entry in the join table, and adds one.
2656
+	 * If one of the model Objects has not yet been saved to the database, it is saved before adding the entry in the
2657
+	 * join table
2658
+	 *
2659
+	 * @param EE_Base_Class|int $id_or_obj                        EE_base_Class or ID of $thisModelObject
2660
+	 * @param EE_Base_Class|int $other_model_id_or_obj            EE_base_Class or ID of other Model Object
2661
+	 * @param string            $relationName                     , key in EEM_Base::_relations
2662
+	 *                                                            an attendee to a group, you also want to specify
2663
+	 *                                                            which role they will have in that group. So you would
2664
+	 *                                                            use this parameter to specify
2665
+	 *                                                            array('role-column-name'=>'role-id')
2666
+	 * @param array|null        $extra_join_model_fields_n_values This allows you to enter further query params for the
2667
+	 *                                                            relation to for relation to methods that allow you to
2668
+	 *                                                            further specify extra columns to join by (such as
2669
+	 *                                                            HABTM).  Keep in mind that the only acceptable
2670
+	 *                                                            query_params is strict "col" => "value" pairs because
2671
+	 *                                                            these will be inserted in any new rows created as
2672
+	 *                                                            well.
2673
+	 * @return EE_Base_Class which was added as a relation. Object referred to by $other_model_id_or_obj
2674
+	 * @throws EE_Error
2675
+	 */
2676
+	public function add_relationship_to(
2677
+		$id_or_obj,
2678
+		$other_model_id_or_obj,
2679
+		$relationName,
2680
+		$extra_join_model_fields_n_values = []
2681
+	) {
2682
+		$relation_obj = $this->related_settings_for($relationName);
2683
+		return $relation_obj->add_relation_to($id_or_obj, $other_model_id_or_obj, $extra_join_model_fields_n_values);
2684
+	}
2685
+
2686
+
2687
+	/**
2688
+	 * Removes a relationship of the correct type between $modelObject and $otherModelObject.
2689
+	 * There are the 3 cases:
2690
+	 * 'belongsTo' relationship: sets $modelObject's foreign_key to null, if that field is nullable.Otherwise throws an
2691
+	 * error
2692
+	 * 'hasMany' relationship: sets $otherModelObject's foreign_key to null,if that field is nullable.Otherwise throws
2693
+	 * an error
2694
+	 * 'hasAndBelongsToMany' relationships:removes any existing entry in the join table between the two models.
2695
+	 *
2696
+	 * @param EE_Base_Class|int $id_or_obj             EE_base_Class or ID of $thisModelObject
2697
+	 * @param EE_Base_Class|int $other_model_id_or_obj EE_base_Class or ID of other Model Object
2698
+	 * @param string            $relationName          key in EEM_Base::_relations
2699
+	 * @param array|null        $where_query           This allows you to enter further query params for the relation
2700
+	 *                                                 to for relation to methods that allow you to further specify
2701
+	 *                                                 extra columns to join by (such as HABTM). Keep in mind that the
2702
+	 *                                                 only acceptable query_params is strict "col" => "value" pairs
2703
+	 *                                                 because these will be inserted in any new rows created as well.
2704
+	 * @return EE_Base_Class
2705
+	 * @throws EE_Error
2706
+	 */
2707
+	public function remove_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query = [])
2708
+	{
2709
+		$relation_obj = $this->related_settings_for($relationName);
2710
+		return $relation_obj->remove_relation_to($id_or_obj, $other_model_id_or_obj, $where_query);
2711
+	}
2712
+
2713
+
2714
+	/**
2715
+	 * @param mixed       $id_or_obj
2716
+	 * @param string      $relationName
2717
+	 * @param array|null  $where_query_params
2718
+	 * @return EE_Base_Class[]
2719
+	 * @throws EE_Error
2720
+	 * @throws ReflectionException
2721
+	 */
2722
+	public function remove_relations($id_or_obj, $relationName, $where_query_params = [])
2723
+	{
2724
+		$relation_obj = $this->related_settings_for($relationName);
2725
+		return $relation_obj->remove_relations($id_or_obj, $where_query_params);
2726
+	}
2727
+
2728
+
2729
+	/**
2730
+	 * Gets all the related items of the specified $model_name, using $query_params.
2731
+	 * Note: by default, we remove the "default query params"
2732
+	 * because we want to get even deleted items etc.
2733
+	 *
2734
+	 * @param mixed       $id_or_obj    EE_Base_Class child or its ID
2735
+	 * @param string      $model_name   like 'Event', 'Registration', etc. always singular
2736
+	 * @param array|null  $query_params @see
2737
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2738
+	 * @return EE_Base_Class[]
2739
+	 * @throws EE_Error
2740
+	 * @throws ReflectionException
2741
+	 */
2742
+	public function get_all_related($id_or_obj, $model_name, ?array $query_params = [])
2743
+	{
2744
+		$model_obj         = $this->ensure_is_obj($id_or_obj);
2745
+		$relation_settings = $this->related_settings_for($model_name);
2746
+		return $relation_settings->get_all_related($model_obj, $query_params);
2747
+	}
2748
+
2749
+
2750
+	/**
2751
+	 * Deletes all the model objects across the relation indicated by $model_name
2752
+	 * which are related to $id_or_obj which meet the criteria set in $query_params.
2753
+	 * However, if the model objects can't be deleted because of blocking related model objects, then
2754
+	 * they aren't deleted. (Unless the thing that would have been deleted can be soft-deleted, that still happens).
2755
+	 *
2756
+	 * @param EE_Base_Class|int|string $id_or_obj
2757
+	 * @param string                   $model_name
2758
+	 * @param array|null               $query_params
2759
+	 * @return int how many deleted
2760
+	 * @throws EE_Error
2761
+	 * @throws ReflectionException
2762
+	 */
2763
+	public function delete_related($id_or_obj, $model_name, $query_params = [])
2764
+	{
2765
+		$model_obj         = $this->ensure_is_obj($id_or_obj);
2766
+		$relation_settings = $this->related_settings_for($model_name);
2767
+		return $relation_settings->delete_all_related($model_obj, $query_params);
2768
+	}
2769
+
2770
+
2771
+	/**
2772
+	 * Hard deletes all the model objects across the relation indicated by $model_name
2773
+	 * which are related to $id_or_obj which meet the criteria set in $query_params. If
2774
+	 * the model objects can't be hard deleted because of blocking related model objects,
2775
+	 * just does a soft-delete on them instead.
2776
+	 *
2777
+	 * @param EE_Base_Class|int|string $id_or_obj
2778
+	 * @param string                   $model_name
2779
+	 * @param array|null               $query_params
2780
+	 * @return int how many deleted
2781
+	 * @throws EE_Error
2782
+	 * @throws ReflectionException
2783
+	 */
2784
+	public function delete_related_permanently($id_or_obj, $model_name, $query_params = [])
2785
+	{
2786
+		$model_obj         = $this->ensure_is_obj($id_or_obj);
2787
+		$relation_settings = $this->related_settings_for($model_name);
2788
+		return $relation_settings->delete_related_permanently($model_obj, $query_params);
2789
+	}
2790
+
2791
+
2792
+	/**
2793
+	 * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2794
+	 * unless otherwise specified in the $query_params
2795
+	 *
2796
+	 * @param EE_Base_Class|int|string $id_or_obj
2797
+	 * @param string                   $model_name     like 'Event', or 'Registration'
2798
+	 * @param array|null               $query_params   @see
2799
+	 *                                                 https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2800
+	 * @param string                   $field_to_count name of field to count by. By default, uses primary key
2801
+	 * @param bool                     $distinct       if we want to only count the distinct values for the column then
2802
+	 *                                                 you can trigger that by the setting $distinct to TRUE;
2803
+	 * @return int
2804
+	 * @throws EE_Error
2805
+	 * @throws ReflectionException
2806
+	 */
2807
+	public function count_related(
2808
+		$id_or_obj,
2809
+		$model_name,
2810
+		$query_params = [],
2811
+		$field_to_count = null,
2812
+		$distinct = false
2813
+	) {
2814
+		$related_model = $this->get_related_model_obj($model_name);
2815
+		// we're just going to use the query params on the related model's normal get_all query,
2816
+		// except add a condition to say to match the current mod
2817
+		if (! isset($query_params['default_where_conditions'])) {
2818
+			$query_params['default_where_conditions'] = EE_Default_Where_Conditions::NONE;
2819
+		}
2820
+		$this_model_name                                                 = $this->get_this_model_name();
2821
+		$this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2822
+		$query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2823
+		return $related_model->count($query_params, $field_to_count, $distinct);
2824
+	}
2825
+
2826
+
2827
+	/**
2828
+	 * Instead of getting the related model objects, simply sums up the values of the specified field.
2829
+	 * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2830
+	 *
2831
+	 * @param EE_Base_Class|int|string $id_or_obj
2832
+	 * @param string                   $model_name   like 'Event', or 'Registration'
2833
+	 * @param array|null               $query_params @see
2834
+	 *                                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2835
+	 * @param string                   $field_to_sum name of field to count by. By default, uses primary key
2836
+	 * @return float
2837
+	 * @throws EE_Error
2838
+	 * @throws ReflectionException
2839
+	 */
2840
+	public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2841
+	{
2842
+		$related_model = $this->get_related_model_obj($model_name);
2843
+		if (! is_array($query_params)) {
2844
+			EE_Error::doing_it_wrong(
2845
+				'EEM_Base::sum_related',
2846
+				sprintf(
2847
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
2848
+					gettype($query_params)
2849
+				),
2850
+				'4.6.0'
2851
+			);
2852
+			$query_params = [];
2853
+		}
2854
+		// we're just going to use the query params on the related model's normal get_all query,
2855
+		// except add a condition to say to match the current mod
2856
+		if (! isset($query_params['default_where_conditions'])) {
2857
+			$query_params['default_where_conditions'] = EE_Default_Where_Conditions::NONE;
2858
+		}
2859
+		$this_model_name                                                 = $this->get_this_model_name();
2860
+		$this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2861
+		$query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2862
+		return $related_model->sum($query_params, $field_to_sum);
2863
+	}
2864
+
2865
+
2866
+	/**
2867
+	 * Uses $this->_relatedModels info to find the first related model object of relation $relationName to the given
2868
+	 * $modelObject
2869
+	 *
2870
+	 * @param int | EE_Base_Class $id_or_obj        EE_Base_Class child or its ID
2871
+	 * @param string              $other_model_name , key in $this->_relatedModels, eg 'Registration', or 'Events'
2872
+	 * @param array|null          $query_params     @see
2873
+	 *                                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2874
+	 * @return EE_Base_Class
2875
+	 * @throws EE_Error
2876
+	 * @throws ReflectionException
2877
+	 */
2878
+	public function get_first_related(EE_Base_Class $id_or_obj, $other_model_name, $query_params)
2879
+	{
2880
+		$query_params['limit'] = 1;
2881
+		$results               = $this->get_all_related($id_or_obj, $other_model_name, $query_params);
2882
+		if ($results) {
2883
+			return array_shift($results);
2884
+		}
2885
+		return null;
2886
+	}
2887
+
2888
+
2889
+	/**
2890
+	 * Gets the model's name as it's expected in queries. For example, if this is EEM_Event model, that would be Event
2891
+	 *
2892
+	 * @return string
2893
+	 */
2894
+	public function get_this_model_name()
2895
+	{
2896
+		return str_replace("EEM_", "", get_class($this));
2897
+	}
2898
+
2899
+
2900
+	/**
2901
+	 * Gets the model field on this model which is of type EE_Any_Foreign_Model_Name_Field
2902
+	 *
2903
+	 * @return EE_Any_Foreign_Model_Name_Field
2904
+	 * @throws EE_Error
2905
+	 */
2906
+	public function get_field_containing_related_model_name()
2907
+	{
2908
+		foreach ($this->field_settings(true) as $field) {
2909
+			if ($field instanceof EE_Any_Foreign_Model_Name_Field) {
2910
+				$field_with_model_name = $field;
2911
+			}
2912
+		}
2913
+		if (! isset($field_with_model_name) || ! $field_with_model_name) {
2914
+			throw new EE_Error(
2915
+				sprintf(
2916
+					esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
2917
+					$this->get_this_model_name()
2918
+				)
2919
+			);
2920
+		}
2921
+		return $field_with_model_name;
2922
+	}
2923
+
2924
+
2925
+	/**
2926
+	 * Inserts a new entry into the database, for each table.
2927
+	 * Note: does not add the item to the entity map because that is done by EE_Base_Class::save() right after this.
2928
+	 * If client code uses EEM_Base::insert() directly, then although the item isn't in the entity map,
2929
+	 * we also know there is no model object with the newly inserted item's ID at the moment (because
2930
+	 * if there were, then they would already be in the DB and this would fail); and in the future if someone
2931
+	 * creates a model object with this ID (or grabs it from the DB) then it will be added to the
2932
+	 * entity map at that time anyways. SO, no need for EEM_Base::insert ot add to the entity map
2933
+	 *
2934
+	 * @param array $field_n_values keys are field names, values are their values (in the client code's domain if
2935
+	 *                              $values_already_prepared_by_model_object is false, in the model object's domain if
2936
+	 *                              $values_already_prepared_by_model_object is true. See comment about this at the top
2937
+	 *                              of EEM_Base)
2938
+	 * @return int|string new primary key on main table that got inserted
2939
+	 * @throws EE_Error
2940
+	 * @throws ReflectionException
2941
+	 */
2942
+	public function insert($field_n_values)
2943
+	{
2944
+		/**
2945
+		 * Filters the fields and their values before inserting an item using the models
2946
+		 *
2947
+		 * @param array    $fields_n_values keys are the fields and values are their new values
2948
+		 * @param EEM_Base $model           the model used
2949
+		 */
2950
+		$field_n_values = (array) apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2951
+		if ($this->_satisfies_unique_indexes($field_n_values)) {
2952
+			$main_table = $this->_get_main_table();
2953
+			$new_id     = $this->_insert_into_specific_table($main_table, $field_n_values, false);
2954
+			if ($new_id !== false) {
2955
+				foreach ($this->_get_other_tables() as $other_table) {
2956
+					$this->_insert_into_specific_table($other_table, $field_n_values, $new_id);
2957
+				}
2958
+			}
2959
+			/**
2960
+			 * Done just after attempting to insert a new model object
2961
+			 *
2962
+			 * @param EEM_Base $model           used
2963
+			 * @param array    $fields_n_values fields and their values
2964
+			 * @param int|string the              ID of the newly-inserted model object
2965
+			 */
2966
+			do_action('AHEE__EEM_Base__insert__end', $this, $field_n_values, $new_id);
2967
+			return $new_id;
2968
+		}
2969
+		return false;
2970
+	}
2971
+
2972
+
2973
+	/**
2974
+	 * Checks that the result would satisfy the unique indexes on this model
2975
+	 *
2976
+	 * @param array  $field_n_values
2977
+	 * @param string $action
2978
+	 * @return boolean
2979
+	 * @throws EE_Error
2980
+	 * @throws ReflectionException
2981
+	 */
2982
+	protected function _satisfies_unique_indexes(array $field_n_values, $action = 'insert')
2983
+	{
2984
+		foreach ($this->unique_indexes() as $index_name => $index) {
2985
+			$uniqueness_where_params = array_intersect_key($field_n_values, $index->fields());
2986
+			if ($this->exists([$uniqueness_where_params])) {
2987
+				EE_Error::add_error(
2988
+					sprintf(
2989
+						esc_html__(
2990
+							"Could not %s %s. %s uniqueness index failed. Fields %s must form a unique set, but an entry already exists with values %s.",
2991
+							"event_espresso"
2992
+						),
2993
+						$action,
2994
+						$this->_get_class_name(),
2995
+						$index_name,
2996
+						implode(",", $index->field_names()),
2997
+						http_build_query($uniqueness_where_params)
2998
+					),
2999
+					__FILE__,
3000
+					__FUNCTION__,
3001
+					__LINE__
3002
+				);
3003
+				return false;
3004
+			}
3005
+		}
3006
+		return true;
3007
+	}
3008
+
3009
+
3010
+	/**
3011
+	 * Checks the database for an item that conflicts (ie, if this item were
3012
+	 * saved to the DB would break some uniqueness requirement, like a primary key
3013
+	 * or an index primary key set) with the item specified. $id_obj_or_fields_array
3014
+	 * can be either an EE_Base_Class or an array of fields n values
3015
+	 *
3016
+	 * @param EE_Base_Class|array $obj_or_fields_array
3017
+	 * @param boolean             $include_primary_key whether to use the model object's primary key
3018
+	 *                                                 when looking for conflicts
3019
+	 *                                                 (ie, if false, we ignore the model object's primary key
3020
+	 *                                                 when finding "conflicts". If true, it's also considered).
3021
+	 *                                                 Only works for INT primary key,
3022
+	 *                                                 STRING primary keys cannot be ignored
3023
+	 * @return EE_Base_Class|array
3024
+	 * @throws EE_Error
3025
+	 * @throws ReflectionException
3026
+	 */
3027
+	public function get_one_conflicting($obj_or_fields_array, $include_primary_key = true)
3028
+	{
3029
+		if ($obj_or_fields_array instanceof EE_Base_Class) {
3030
+			$fields_n_values = $obj_or_fields_array->model_field_array();
3031
+		} elseif (is_array($obj_or_fields_array)) {
3032
+			$fields_n_values = $obj_or_fields_array;
3033
+		} else {
3034
+			throw new EE_Error(
3035
+				sprintf(
3036
+					esc_html__(
3037
+						"%s get_all_conflicting should be called with a model object or an array of field names and values, you provided %d",
3038
+						"event_espresso"
3039
+					),
3040
+					$this->class_name,
3041
+					$obj_or_fields_array
3042
+				)
3043
+			);
3044
+		}
3045
+		$query_params = [];
3046
+		if (
3047
+			$this->has_primary_key_field()
3048
+			&& ($include_primary_key
3049
+				|| $this->get_primary_key_field()
3050
+				   instanceof
3051
+				   EE_Primary_Key_String_Field)
3052
+			&& isset($fields_n_values[ $this->primary_key_name() ])
3053
+		) {
3054
+			$query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
3055
+		}
3056
+		foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
3057
+			$uniqueness_where_params                              =
3058
+				array_intersect_key($fields_n_values, $unique_index->fields());
3059
+			$query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
3060
+		}
3061
+		// if there is nothing to base this search on, then we shouldn't find anything
3062
+		if (empty($query_params)) {
3063
+			return [];
3064
+		}
3065
+		return $this->get_one($query_params);
3066
+	}
3067
+
3068
+
3069
+	/**
3070
+	 * Like count, but is optimized and returns a boolean instead of an int
3071
+	 *
3072
+	 * @param array $query_params
3073
+	 * @return boolean
3074
+	 * @throws EE_Error
3075
+	 * @throws ReflectionException
3076
+	 */
3077
+	public function exists($query_params)
3078
+	{
3079
+		$query_params['limit'] = 1;
3080
+		return $this->count($query_params) > 0;
3081
+	}
3082
+
3083
+
3084
+	/**
3085
+	 * Wrapper for exists, except ignores default query parameters so we're only considering ID
3086
+	 *
3087
+	 * @param int|string $id
3088
+	 * @return boolean
3089
+	 * @throws EE_Error
3090
+	 * @throws ReflectionException
3091
+	 */
3092
+	public function exists_by_ID($id)
3093
+	{
3094
+		return $this->exists(
3095
+			[
3096
+				'default_where_conditions' => EE_Default_Where_Conditions::NONE,
3097
+				[
3098
+					$this->primary_key_name() => $id,
3099
+				],
3100
+			]
3101
+		);
3102
+	}
3103
+
3104
+
3105
+	/**
3106
+	 * Inserts a new row in $table, using the $cols_n_values which apply to that table.
3107
+	 * If a $new_id is supplied and if $table is an EE_Other_Table, we assume
3108
+	 * we need to add a foreign key column to point to $new_id (which should be the primary key's value
3109
+	 * on the main table)
3110
+	 * This is protected rather than private because private is not accessible to any child methods and there MAY be
3111
+	 * cases where we want to call it directly rather than via insert().
3112
+	 *
3113
+	 * @access   protected
3114
+	 * @param EE_Table_Base $table
3115
+	 * @param array         $fields_n_values each key should be in field's keys, and value should be an int, string or
3116
+	 *                                       float
3117
+	 * @param int           $new_id          for now we assume only int keys
3118
+	 * @return int ID of new row inserted, or FALSE on failure
3119
+	 * @throws EE_Error
3120
+	 * @global WPDB         $wpdb            only used to get the $wpdb->insert_id after performing an insert
3121
+	 */
3122
+	protected function _insert_into_specific_table(EE_Table_Base $table, $fields_n_values, $new_id = 0)
3123
+	{
3124
+		global $wpdb;
3125
+		$insertion_col_n_values = [];
3126
+		$format_for_insertion   = [];
3127
+		$fields_on_table        = $this->_get_fields_for_table($table->get_table_alias());
3128
+		foreach ($fields_on_table as $field_obj) {
3129
+			// check if its an auto-incrementing column, in which case we should just leave it to do its autoincrement thing
3130
+			if ($field_obj->is_auto_increment()) {
3131
+				continue;
3132
+			}
3133
+			$prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3134
+			// if the value we want to assign it to is NULL, just don't mention it for the insertion
3135
+			if ($prepared_value !== null) {
3136
+				$insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3137
+				$format_for_insertion[]                                   = $field_obj->get_wpdb_data_type();
3138
+			}
3139
+		}
3140
+		if ($table instanceof EE_Secondary_Table && $new_id) {
3141
+			// its not the main table, so we should have already saved the main table's PK which we just inserted
3142
+			// so add the fk to the main table as a column
3143
+			$insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3144
+			$format_for_insertion[]                              =
3145
+				'%d';// yes right now we're only allowing these foreign keys to be INTs
3146
+		}
3147
+
3148
+		// insert the new entry
3149
+		$result = $this->_do_wpdb_query(
3150
+			'insert',
3151
+			[$table->get_table_name(), $insertion_col_n_values, $format_for_insertion]
3152
+		);
3153
+		if ($result === false) {
3154
+			return false;
3155
+		}
3156
+		// ok, now what do we return for the ID of the newly-inserted thing?
3157
+		if ($this->has_primary_key_field()) {
3158
+			if ($this->get_primary_key_field()->is_auto_increment()) {
3159
+				return $wpdb->insert_id;
3160
+			}
3161
+			// it's not an auto-increment primary key, so
3162
+			// it must have been supplied
3163
+			return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3164
+		}
3165
+		// we can't return a  primary key because there is none. instead return
3166
+		// a unique string indicating this model
3167
+		return $this->get_index_primary_key_string($fields_n_values);
3168
+	}
3169
+
3170
+
3171
+	/**
3172
+	 * Prepare the $field_obj 's value in $fields_n_values for use in the database.
3173
+	 * If the field doesn't allow NULL, try to use its default. (If it doesn't allow NULL,
3174
+	 * and there is no default, we pass it along. WPDB will take care of it)
3175
+	 *
3176
+	 * @param EE_Model_Field_Base $field_obj
3177
+	 * @param array               $fields_n_values
3178
+	 * @return mixed string|int|float depending on what the table column will be expecting
3179
+	 * @throws EE_Error
3180
+	 */
3181
+	protected function _prepare_value_or_use_default($field_obj, $fields_n_values)
3182
+	{
3183
+		$field_name = $field_obj->get_name();
3184
+		// if this field doesn't allow nullable, don't allow it
3185
+		if (! $field_obj->is_nullable() && ! isset($fields_n_values[ $field_name ])) {
3186
+			$fields_n_values[ $field_name ] = $field_obj->get_default_value();
3187
+		}
3188
+		$unprepared_value = $fields_n_values[ $field_name ] ?? null;
3189
+		return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3190
+	}
3191
+
3192
+
3193
+	/**
3194
+	 * Consolidates code for preparing  a value supplied to the model for use int eh db. Calls the field's
3195
+	 * prepare_for_use_in_db method on the value, and depending on $value_already_prepare_by_model_obj, may also call
3196
+	 * the field's prepare_for_set() method.
3197
+	 *
3198
+	 * @param mixed               $value value in the client code domain if $value_already_prepared_by_model_object is
3199
+	 *                                   false, otherwise a value in the model object's domain (see lengthy comment at
3200
+	 *                                   top of file)
3201
+	 * @param EE_Model_Field_Base $field field which will be doing the preparing of the value. If null, we assume
3202
+	 *                                   $value is a custom selection
3203
+	 * @return mixed a value ready for use in the database for insertions, updating, or in a where clause
3204
+	 */
3205
+	private function _prepare_value_for_use_in_db($value, $field)
3206
+	{
3207
+		if ($field instanceof EE_Model_Field_Base) {
3208
+			// phpcs:disable PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
3209
+			switch ($this->_values_already_prepared_by_model_object) {
3210
+				/** @noinspection PhpMissingBreakStatementInspection */
3211
+				case self::not_prepared_by_model_object:
3212
+					$value = $field->prepare_for_set($value);
3213
+				// purposefully left out "return"
3214
+				// no break
3215
+				case self::prepared_by_model_object:
3216
+					/** @noinspection SuspiciousAssignmentsInspection */
3217
+					$value = $field->prepare_for_use_in_db($value);
3218
+				// no break
3219
+				case self::prepared_for_use_in_db:
3220
+					// leave the value alone
3221
+			}
3222
+			// phpcs:enable
3223
+		}
3224
+		return $value;
3225
+	}
3226
+
3227
+
3228
+	/**
3229
+	 * Returns the main table on this model
3230
+	 *
3231
+	 * @return EE_Primary_Table
3232
+	 * @throws EE_Error
3233
+	 */
3234
+	protected function _get_main_table()
3235
+	{
3236
+		foreach ($this->_tables as $table) {
3237
+			if ($table instanceof EE_Primary_Table) {
3238
+				return $table;
3239
+			}
3240
+		}
3241
+		throw new EE_Error(
3242
+			sprintf(
3243
+				esc_html__(
3244
+					'There are no main tables on %s. They should be added to _tables array in the constructor',
3245
+					'event_espresso'
3246
+				),
3247
+				$this->class_name
3248
+			)
3249
+		);
3250
+	}
3251
+
3252
+
3253
+	/**
3254
+	 * table
3255
+	 * returns EE_Primary_Table table name
3256
+	 *
3257
+	 * @return string
3258
+	 * @throws EE_Error
3259
+	 */
3260
+	public function table()
3261
+	{
3262
+		return $this->_get_main_table()->get_table_name();
3263
+	}
3264
+
3265
+
3266
+	/**
3267
+	 * table
3268
+	 * returns first EE_Secondary_Table table name
3269
+	 *
3270
+	 * @return string
3271
+	 */
3272
+	public function second_table()
3273
+	{
3274
+		// grab second table from tables array
3275
+		$second_table = end($this->_tables);
3276
+		return $second_table instanceof EE_Secondary_Table
3277
+			? $second_table->get_table_name()
3278
+			: null;
3279
+	}
3280
+
3281
+
3282
+	/**
3283
+	 * get_table_obj_by_alias
3284
+	 * returns table name given it's alias
3285
+	 *
3286
+	 * @param string $table_alias
3287
+	 * @return EE_Primary_Table | EE_Secondary_Table
3288
+	 */
3289
+	public function get_table_obj_by_alias($table_alias = '')
3290
+	{
3291
+		return $this->_tables[ $table_alias ] ?? null;
3292
+	}
3293
+
3294
+
3295
+	/**
3296
+	 * Gets all the tables of type EE_Other_Table from EEM_CPT_Basel_Model::_tables
3297
+	 *
3298
+	 * @return EE_Secondary_Table[]
3299
+	 */
3300
+	protected function _get_other_tables()
3301
+	{
3302
+		$other_tables = [];
3303
+		foreach ($this->_tables as $table_alias => $table) {
3304
+			if ($table instanceof EE_Secondary_Table) {
3305
+				$other_tables[ $table_alias ] = $table;
3306
+			}
3307
+		}
3308
+		return $other_tables;
3309
+	}
3310
+
3311
+
3312
+	/**
3313
+	 * Finds all the fields that correspond to the given table
3314
+	 *
3315
+	 * @param string $table_alias , array key in EEM_Base::_tables
3316
+	 * @return EE_Model_Field_Base[]
3317
+	 */
3318
+	public function _get_fields_for_table($table_alias)
3319
+	{
3320
+		return $this->_fields[ $table_alias ];
3321
+	}
3322
+
3323
+
3324
+	/**
3325
+	 * Recurses through all the where parameters, and finds all the related models we'll need
3326
+	 * to complete this query. Eg, given where parameters like array('EVT_ID'=>3) from within Event model, we won't
3327
+	 * need any related models. But if the array were array('Registrations.REG_ID'=>3), we'd need the related
3328
+	 * Registration model. If it were array('Registrations.Transactions.Payments.PAY_ID'=>3), then we'd need the
3329
+	 * related Registration, Transaction, and Payment models.
3330
+	 *
3331
+	 * @param array $query_params @see
3332
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3333
+	 * @return EE_Model_Query_Info_Carrier
3334
+	 * @throws EE_Error
3335
+	 */
3336
+	public function _extract_related_models_from_query($query_params)
3337
+	{
3338
+		$query_info_carrier = new EE_Model_Query_Info_Carrier();
3339
+		if (array_key_exists(0, $query_params)) {
3340
+			$this->_extract_related_models_from_sub_params_array_keys($query_params[0], $query_info_carrier, 0);
3341
+		}
3342
+		if (array_key_exists('group_by', $query_params)) {
3343
+			if (is_array($query_params['group_by'])) {
3344
+				$this->_extract_related_models_from_sub_params_array_values(
3345
+					$query_params['group_by'],
3346
+					$query_info_carrier,
3347
+					'group_by'
3348
+				);
3349
+			} elseif (! empty($query_params['group_by'])) {
3350
+				$this->_extract_related_model_info_from_query_param(
3351
+					$query_params['group_by'],
3352
+					$query_info_carrier,
3353
+					'group_by'
3354
+				);
3355
+			}
3356
+		}
3357
+		if (array_key_exists('having', $query_params)) {
3358
+			$this->_extract_related_models_from_sub_params_array_keys(
3359
+				$query_params[0],
3360
+				$query_info_carrier,
3361
+				'having'
3362
+			);
3363
+		}
3364
+		if (array_key_exists('order_by', $query_params)) {
3365
+			if (is_array($query_params['order_by'])) {
3366
+				$this->_extract_related_models_from_sub_params_array_keys(
3367
+					$query_params['order_by'],
3368
+					$query_info_carrier,
3369
+					'order_by'
3370
+				);
3371
+			} elseif (! empty($query_params['order_by'])) {
3372
+				$this->_extract_related_model_info_from_query_param(
3373
+					$query_params['order_by'],
3374
+					$query_info_carrier,
3375
+					'order_by'
3376
+				);
3377
+			}
3378
+		}
3379
+		if (array_key_exists('force_join', $query_params)) {
3380
+			$this->_extract_related_models_from_sub_params_array_values(
3381
+				$query_params['force_join'],
3382
+				$query_info_carrier,
3383
+				'force_join'
3384
+			);
3385
+		}
3386
+		$this->extractRelatedModelsFromCustomSelects($query_info_carrier);
3387
+		return $query_info_carrier;
3388
+	}
3389
+
3390
+
3391
+	/**
3392
+	 * For extracting related models from WHERE (0), HAVING (having), ORDER BY (order_by) or forced joins (force_join)
3393
+	 *
3394
+	 * @param array                       $sub_query_params
3395
+	 * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3396
+	 * @param string                      $query_param_type one of $this->_allowed_query_params
3397
+	 * @return EE_Model_Query_Info_Carrier
3398
+	 * @throws EE_Error
3399
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#-0-where-conditions
3400
+	 */
3401
+	private function _extract_related_models_from_sub_params_array_keys(
3402
+		$sub_query_params,
3403
+		EE_Model_Query_Info_Carrier $model_query_info_carrier,
3404
+		$query_param_type
3405
+	) {
3406
+		if (! empty($sub_query_params)) {
3407
+			$sub_query_params = (array) $sub_query_params;
3408
+			foreach ($sub_query_params as $param => $possibly_array_of_params) {
3409
+				// $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3410
+				$this->_extract_related_model_info_from_query_param(
3411
+					$param,
3412
+					$model_query_info_carrier,
3413
+					$query_param_type
3414
+				);
3415
+				// if $possibly_array_of_params is an array, try recursing into it, searching for keys which
3416
+				// indicate needed joins. Eg, array('NOT'=>array('Registration.TXN_ID'=>23)). In this case, we tried
3417
+				// extracting models out of the 'NOT', which obviously wasn't successful, and then we recurse into the value
3418
+				// of array('Registration.TXN_ID'=>23)
3419
+				$query_param_sans_stars =
3420
+					$this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3421
+				if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3422
+					if (! is_array($possibly_array_of_params)) {
3423
+						throw new EE_Error(
3424
+							sprintf(
3425
+								esc_html__(
3426
+									"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'))",
3427
+									"event_espresso"
3428
+								),
3429
+								$param,
3430
+								$possibly_array_of_params
3431
+							)
3432
+						);
3433
+					}
3434
+					$this->_extract_related_models_from_sub_params_array_keys(
3435
+						$possibly_array_of_params,
3436
+						$model_query_info_carrier,
3437
+						$query_param_type
3438
+					);
3439
+				} elseif (
3440
+					$query_param_type === 0 // ie WHERE
3441
+					&& is_array($possibly_array_of_params) // need is_array() check so we don't try to explode a string
3442
+					&& isset($possibly_array_of_params[2])
3443
+					&& $possibly_array_of_params[2]
3444
+				) {
3445
+					// then $possible_array_of_params looks something like array('<','DTT_sold',true)
3446
+					// indicating that $possible_array_of_params[1] is actually a field name,
3447
+					// from which we should extract query parameters!
3448
+					if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3449
+						throw new EE_Error(
3450
+							sprintf(
3451
+								esc_html__(
3452
+									"Improperly formed query parameter %s. It should be numerically indexed like array('<','DTT_sold',true); but you provided %s",
3453
+									"event_espresso"
3454
+								),
3455
+								$query_param_type,
3456
+								implode(",", $possibly_array_of_params)
3457
+							)
3458
+						);
3459
+					}
3460
+					$this->_extract_related_model_info_from_query_param(
3461
+						$possibly_array_of_params[1],
3462
+						$model_query_info_carrier,
3463
+						$query_param_type
3464
+					);
3465
+				}
3466
+			}
3467
+		}
3468
+		return $model_query_info_carrier;
3469
+	}
3470
+
3471
+
3472
+	/**
3473
+	 * For extracting related models from forced_joins, where the array values contain the info about what
3474
+	 * models to join with. Eg an array like array('Attendee','Price.Price_Type');
3475
+	 *
3476
+	 * @param array                       $sub_query_params @see
3477
+	 *                                                      https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3478
+	 * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3479
+	 * @param string                      $query_param_type one of $this->_allowed_query_params
3480
+	 * @return EE_Model_Query_Info_Carrier
3481
+	 * @throws EE_Error
3482
+	 */
3483
+	private function _extract_related_models_from_sub_params_array_values(
3484
+		$sub_query_params,
3485
+		EE_Model_Query_Info_Carrier $model_query_info_carrier,
3486
+		$query_param_type
3487
+	) {
3488
+		if (! empty($sub_query_params)) {
3489
+			if (! is_array($sub_query_params)) {
3490
+				throw new EE_Error(
3491
+					sprintf(
3492
+						esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
3493
+						$sub_query_params
3494
+					)
3495
+				);
3496
+			}
3497
+			foreach ($sub_query_params as $param) {
3498
+				// $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3499
+				$this->_extract_related_model_info_from_query_param(
3500
+					$param,
3501
+					$model_query_info_carrier,
3502
+					$query_param_type
3503
+				);
3504
+			}
3505
+		}
3506
+		return $model_query_info_carrier;
3507
+	}
3508
+
3509
+
3510
+	/**
3511
+	 * Extract all the query parts from  model query params
3512
+	 * and put into a EEM_Related_Model_Info_Carrier for easy extraction into a query. We create this object
3513
+	 * instead of directly constructing the SQL because often we need to extract info from the $query_params
3514
+	 * but use them in a different order. Eg, we need to know what models we are querying
3515
+	 * before we know what joins to perform. However, we need to know what data types correspond to which fields on
3516
+	 * other models before we can finalize the where clause SQL.
3517
+	 *
3518
+	 * @param array $query_params
3519
+	 * @return EE_Model_Query_Info_Carrier
3520
+	 * @throws EE_Error
3521
+	 * @throws ModelConfigurationException
3522
+	 * @throws ReflectionException
3523
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3524
+	 */
3525
+	public function _create_model_query_info_carrier($query_params)
3526
+	{
3527
+		if (! is_array($query_params)) {
3528
+			EE_Error::doing_it_wrong(
3529
+				'EEM_Base::_create_model_query_info_carrier',
3530
+				sprintf(
3531
+					esc_html__(
3532
+						'$query_params should be an array, you passed a variable of type %s',
3533
+						'event_espresso'
3534
+					),
3535
+					gettype($query_params)
3536
+				),
3537
+				'4.6.0'
3538
+			);
3539
+			$query_params = [];
3540
+		}
3541
+		$query_params[0] = $query_params[0] ?? [];
3542
+		// first check if we should alter the query to account for caps or not
3543
+		// because the caps might require us to do extra joins
3544
+		if (isset($query_params['caps']) && $query_params['caps'] !== 'none') {
3545
+			$query_params[0] = array_replace_recursive(
3546
+				$query_params[0],
3547
+				$this->caps_where_conditions($query_params['caps'])
3548
+			);
3549
+		}
3550
+
3551
+		// check if we should alter the query to remove data related to protected
3552
+		// custom post types
3553
+		if (isset($query_params['exclude_protected']) && $query_params['exclude_protected'] === true) {
3554
+			$where_param_key_for_password = $this->modelChainAndPassword();
3555
+			// only include if related to a cpt where no password has been set
3556
+			$query_params[0]['OR*nopassword'] = [
3557
+				$where_param_key_for_password       => '',
3558
+				$where_param_key_for_password . '*' => ['IS_NULL'],
3559
+			];
3560
+		}
3561
+		$query_object = $this->_extract_related_models_from_query($query_params);
3562
+		// verify where_query_params has NO numeric indexes.... that's simply not how you use it!
3563
+		foreach ($query_params[0] as $key => $value) {
3564
+			if (is_int($key)) {
3565
+				throw new EE_Error(
3566
+					sprintf(
3567
+						esc_html__(
3568
+							"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.",
3569
+							"event_espresso"
3570
+						),
3571
+						$key,
3572
+						var_export($value, true),
3573
+						var_export($query_params, true),
3574
+						$this->class_name
3575
+					)
3576
+				);
3577
+			}
3578
+		}
3579
+		if (
3580
+			array_key_exists('default_where_conditions', $query_params)
3581
+			&& ! empty($query_params['default_where_conditions'])
3582
+		) {
3583
+			$use_default_where_conditions = $query_params['default_where_conditions'];
3584
+		} else {
3585
+			$use_default_where_conditions = EE_Default_Where_Conditions::ALL;
3586
+		}
3587
+		$query_params[0] = array_merge(
3588
+			$this->_get_default_where_conditions_for_models_in_query(
3589
+				$query_object,
3590
+				$use_default_where_conditions,
3591
+				$query_params[0]
3592
+			),
3593
+			$query_params[0]
3594
+		);
3595
+		$query_object->set_where_sql($this->_construct_where_clause($query_params[0]));
3596
+		// if this is a "on_join_limit" then we are limiting on on a specific table in a multi_table join.
3597
+		// So we need to setup a subquery and use that for the main join.
3598
+		// Note for now this only works on the primary table for the model.
3599
+		// So for instance, you could set the limit array like this:
3600
+		// array( 'on_join_limit' => array('Primary_Table_Alias', array(1,10) ) )
3601
+		if (array_key_exists('on_join_limit', $query_params) && ! empty($query_params['on_join_limit'])) {
3602
+			$query_object->set_main_model_join_sql(
3603
+				$this->_construct_limit_join_select(
3604
+					$query_params['on_join_limit'][0],
3605
+					$query_params['on_join_limit'][1]
3606
+				)
3607
+			);
3608
+		}
3609
+		// set limit
3610
+		if (array_key_exists('limit', $query_params)) {
3611
+			if (is_array($query_params['limit'])) {
3612
+				if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3613
+					$e = sprintf(
3614
+						esc_html__(
3615
+							"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)",
3616
+							"event_espresso"
3617
+						),
3618
+						http_build_query($query_params['limit'])
3619
+					);
3620
+					throw new EE_Error($e . "|" . $e);
3621
+				}
3622
+				// they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3623
+				$query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3624
+			} elseif (! empty($query_params['limit'])) {
3625
+				$query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3626
+			}
3627
+		}
3628
+		// set order by
3629
+		if (array_key_exists('order_by', $query_params)) {
3630
+			if (is_array($query_params['order_by'])) {
3631
+				// if they're using 'order_by' as an array, they can't use 'order' (because 'order_by' must
3632
+				// specify whether to ascend or descend on each field. Eg 'order_by'=>array('EVT_ID'=>'ASC'). So
3633
+				// including 'order' wouldn't make any sense if 'order_by' has already specified which way to order!
3634
+				if (array_key_exists('order', $query_params)) {
3635
+					throw new EE_Error(
3636
+						sprintf(
3637
+							esc_html__(
3638
+								"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 ",
3639
+								"event_espresso"
3640
+							),
3641
+							$this->class_name,
3642
+							implode(", ", array_keys($query_params['order_by'])),
3643
+							implode(", ", $query_params['order_by']),
3644
+							$query_params['order']
3645
+						)
3646
+					);
3647
+				}
3648
+				$this->_extract_related_models_from_sub_params_array_keys(
3649
+					$query_params['order_by'],
3650
+					$query_object,
3651
+					'order_by'
3652
+				);
3653
+				// assume it's an array of fields to order by
3654
+				$order_array = [];
3655
+				foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3656
+					$order         = $this->_extract_order($order);
3657
+					$order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3658
+				}
3659
+				$query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3660
+			} elseif (! empty($query_params['order_by'])) {
3661
+				$this->_extract_related_model_info_from_query_param(
3662
+					$query_params['order_by'],
3663
+					$query_object,
3664
+					'order',
3665
+					$query_params['order_by']
3666
+				);
3667
+				$order = isset($query_params['order'])
3668
+					? $this->_extract_order($query_params['order'])
3669
+					: 'DESC';
3670
+				$query_object->set_order_by_sql(
3671
+					" ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3672
+				);
3673
+			}
3674
+		}
3675
+		// if 'order_by' wasn't set, maybe they are just using 'order' on its own?
3676
+		if (
3677
+			! array_key_exists('order_by', $query_params)
3678
+			&& array_key_exists('order', $query_params)
3679
+			&& ! empty($query_params['order'])
3680
+		) {
3681
+			$pk_field = $this->get_primary_key_field();
3682
+			$order    = $this->_extract_order($query_params['order']);
3683
+			$query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3684
+		}
3685
+		// set group by
3686
+		if (array_key_exists('group_by', $query_params)) {
3687
+			if (is_array($query_params['group_by'])) {
3688
+				// it's an array, so assume we'll be grouping by a bunch of stuff
3689
+				$group_by_array = [];
3690
+				foreach ($query_params['group_by'] as $field_name_to_group_by) {
3691
+					$group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3692
+				}
3693
+				$query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3694
+			} elseif (! empty($query_params['group_by'])) {
3695
+				$query_object->set_group_by_sql(
3696
+					" GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3697
+				);
3698
+			}
3699
+		}
3700
+		// set having
3701
+		if (array_key_exists('having', $query_params) && $query_params['having']) {
3702
+			$query_object->set_having_sql($this->_construct_having_clause($query_params['having']));
3703
+		}
3704
+		// now, just verify they didn't pass anything wack
3705
+		foreach ($query_params as $query_key => $query_value) {
3706
+			if (! in_array($query_key, $this->_allowed_query_params, true)) {
3707
+				throw new EE_Error(
3708
+					sprintf(
3709
+						esc_html__(
3710
+							"You passed %s as a query parameter to %s, which is illegal! The allowed query parameters are %s",
3711
+							'event_espresso'
3712
+						),
3713
+						$query_key,
3714
+						$this->class_name,
3715
+						//                      print_r( $this->_allowed_query_params, TRUE )
3716
+						implode(',', $this->_allowed_query_params)
3717
+					)
3718
+				);
3719
+			}
3720
+		}
3721
+		$main_model_join_sql = $query_object->get_main_model_join_sql();
3722
+		if (empty($main_model_join_sql)) {
3723
+			$query_object->set_main_model_join_sql($this->_construct_internal_join());
3724
+		}
3725
+		return $query_object;
3726
+	}
3727
+
3728
+
3729
+	/**
3730
+	 * Gets the where conditions that should be imposed on the query based on the
3731
+	 * context (eg reading frontend, backend, edit or delete).
3732
+	 *
3733
+	 * @param string $context one of EEM_Base::valid_cap_contexts()
3734
+	 * @return array @see
3735
+	 *                        https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3736
+	 * @throws EE_Error
3737
+	 */
3738
+	public function caps_where_conditions($context = self::caps_read)
3739
+	{
3740
+		EEM_Base::verify_is_valid_cap_context($context);
3741
+		$cap_where_conditions = [];
3742
+		$cap_restrictions     = $this->caps_missing($context);
3743
+		foreach ($cap_restrictions as $restriction_if_no_cap) {
3744
+			$cap_where_conditions = array_replace_recursive(
3745
+				$cap_where_conditions,
3746
+				$restriction_if_no_cap->get_default_where_conditions()
3747
+			);
3748
+		}
3749
+		return apply_filters(
3750
+			'FHEE__EEM_Base__caps_where_conditions__return',
3751
+			$cap_where_conditions,
3752
+			$this,
3753
+			$context,
3754
+			$cap_restrictions
3755
+		);
3756
+	}
3757
+
3758
+
3759
+	/**
3760
+	 * Verifies that $should_be_order_string is in $this->_allowed_order_values,
3761
+	 * otherwise throws an exception
3762
+	 *
3763
+	 * @param string $should_be_order_string
3764
+	 * @return string either ASC, asc, DESC or desc
3765
+	 * @throws EE_Error
3766
+	 */
3767
+	private function _extract_order($should_be_order_string)
3768
+	{
3769
+		if (in_array($should_be_order_string, $this->_allowed_order_values)) {
3770
+			return $should_be_order_string;
3771
+		}
3772
+		throw new EE_Error(
3773
+			sprintf(
3774
+				esc_html__(
3775
+					"While performing a query on '%s', tried to use '%s' as an order parameter. ",
3776
+					"event_espresso"
3777
+				),
3778
+				$this->class_name,
3779
+				$should_be_order_string
3780
+			)
3781
+		);
3782
+	}
3783
+
3784
+
3785
+	/**
3786
+	 * Looks at all the models which are included in this query, and asks each
3787
+	 * for their universal_where_params, and returns them in the same format as $query_params[0] (where),
3788
+	 * so they can be merged
3789
+	 *
3790
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
3791
+	 * @param string                      $use_default_where_conditions can be 'none','other_models_only', or 'all'.
3792
+	 *                                                                  'none' means NO default where conditions will
3793
+	 *                                                                  be used AT ALL during this query.
3794
+	 *                                                                  'other_models_only' means default where
3795
+	 *                                                                  conditions from other models will be used, but
3796
+	 *                                                                  not for this primary model. 'all', the default,
3797
+	 *                                                                  means default where conditions will apply as
3798
+	 *                                                                  normal
3799
+	 * @param array                       $where_query_params
3800
+	 * @return array
3801
+	 * @throws EE_Error
3802
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params
3803
+	 *                                                                  .md#0-where-conditions
3804
+	 */
3805
+	private function _get_default_where_conditions_for_models_in_query(
3806
+		EE_Model_Query_Info_Carrier $query_info_carrier,
3807
+		$use_default_where_conditions = EE_Default_Where_Conditions::ALL,
3808
+		$where_query_params = []
3809
+	) {
3810
+		$allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3811
+		if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3812
+			throw new EE_Error(
3813
+				sprintf(
3814
+					esc_html__(
3815
+						"You passed an invalid value to the query parameter 'default_where_conditions' of '%s'. Allowed values are %s",
3816
+						"event_espresso"
3817
+					),
3818
+					$use_default_where_conditions,
3819
+					implode(", ", $allowed_used_default_where_conditions_values)
3820
+				)
3821
+			);
3822
+		}
3823
+		$universal_query_params = [];
3824
+		if ($this->_should_use_default_where_conditions($use_default_where_conditions, true)) {
3825
+			$universal_query_params = $this->_get_default_where_conditions();
3826
+		} elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, true)) {
3827
+			$universal_query_params = $this->_get_minimum_where_conditions();
3828
+		}
3829
+		foreach ($query_info_carrier->get_model_names_included() as $model_relation_path => $model_name) {
3830
+			$related_model = $this->get_related_model_obj($model_name);
3831
+			if ($this->_should_use_default_where_conditions($use_default_where_conditions, false)) {
3832
+				$related_model_universal_where_params =
3833
+					$related_model->_get_default_where_conditions($model_relation_path);
3834
+			} elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, false)) {
3835
+				$related_model_universal_where_params =
3836
+					$related_model->_get_minimum_where_conditions($model_relation_path);
3837
+			} else {
3838
+				// we don't want to add full or even minimum default where conditions from this model, so just continue
3839
+				continue;
3840
+			}
3841
+			$overrides              = $this->_override_defaults_or_make_null_friendly(
3842
+				$related_model_universal_where_params,
3843
+				$where_query_params,
3844
+				$related_model,
3845
+				$model_relation_path
3846
+			);
3847
+			$universal_query_params = EEH_Array::merge_arrays_and_overwrite_keys(
3848
+				$universal_query_params,
3849
+				$overrides
3850
+			);
3851
+		}
3852
+		return $universal_query_params;
3853
+	}
3854
+
3855
+
3856
+	/**
3857
+	 * Determines whether we should use default where conditions for the model in question
3858
+	 * (this model, or other related models).
3859
+	 * Basically, we should use default where conditions on this model if they have requested to use them on all models,
3860
+	 * this model only, or to use minimum where conditions on all other models and normal where conditions on this one.
3861
+	 * We should use default where conditions on related models when they requested to use default where conditions
3862
+	 * on all models, or specifically just on other related models
3863
+	 *
3864
+	 * @param      $default_where_conditions_value
3865
+	 * @param bool $for_this_model false means this is for OTHER related models
3866
+	 * @return bool
3867
+	 */
3868
+	private function _should_use_default_where_conditions($default_where_conditions_value, $for_this_model = true)
3869
+	{
3870
+		return (
3871
+				   $for_this_model
3872
+				   && in_array(
3873
+					   $default_where_conditions_value,
3874
+					   [
3875
+						   EE_Default_Where_Conditions::ALL,
3876
+						   EE_Default_Where_Conditions::THIS_MODEL_ONLY,
3877
+						   EE_Default_Where_Conditions::MINIMUM_OTHERS,
3878
+					   ],
3879
+					   true
3880
+				   )
3881
+			   )
3882
+			   || (
3883
+				   ! $for_this_model
3884
+				   && in_array(
3885
+					   $default_where_conditions_value,
3886
+					   [
3887
+						   EE_Default_Where_Conditions::ALL,
3888
+						   EE_Default_Where_Conditions::OTHER_MODELS_ONLY,
3889
+					   ],
3890
+					   true
3891
+				   )
3892
+			   );
3893
+	}
3894
+
3895
+
3896
+	/**
3897
+	 * Determines whether we should use default minimum conditions for the model in question
3898
+	 * (this model, or other related models).
3899
+	 * Basically, we should use minimum where conditions on this model only if they requested all models to use minimum
3900
+	 * where conditions.
3901
+	 * We should use minimum where conditions on related models if they requested to use minimum where conditions
3902
+	 * on this model or others
3903
+	 *
3904
+	 * @param      $default_where_conditions_value
3905
+	 * @param bool $for_this_model false means this is for OTHER related models
3906
+	 * @return bool
3907
+	 */
3908
+	private function _should_use_minimum_where_conditions($default_where_conditions_value, $for_this_model = true)
3909
+	{
3910
+		return (
3911
+				   $for_this_model
3912
+				   && $default_where_conditions_value === EE_Default_Where_Conditions::MINIMUM_ALL
3913
+			   )
3914
+			   || (
3915
+				   ! $for_this_model
3916
+				   && in_array(
3917
+					   $default_where_conditions_value,
3918
+					   [
3919
+						   EE_Default_Where_Conditions::MINIMUM_OTHERS,
3920
+						   EE_Default_Where_Conditions::MINIMUM_ALL,
3921
+					   ],
3922
+					   true
3923
+				   )
3924
+			   );
3925
+	}
3926
+
3927
+
3928
+	/**
3929
+	 * Checks if any of the defaults have been overridden. If there are any that AREN'T overridden,
3930
+	 * then we also add a special where condition which allows for that model's primary key
3931
+	 * to be null (which is important for JOINs. Eg, if you want to see all Events ordered by Venue's name,
3932
+	 * then Event's with NO Venue won't appear unless you allow VNU_ID to be NULL)
3933
+	 *
3934
+	 * @param array    $default_where_conditions
3935
+	 * @param array    $provided_where_conditions
3936
+	 * @param EEM_Base $model
3937
+	 * @param string   $model_relation_path like 'Transaction.Payment.'
3938
+	 * @return array @see
3939
+	 *                                      https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3940
+	 * @throws EE_Error
3941
+	 */
3942
+	private function _override_defaults_or_make_null_friendly(
3943
+		$default_where_conditions,
3944
+		$provided_where_conditions,
3945
+		$model,
3946
+		$model_relation_path
3947
+	) {
3948
+		$null_friendly_where_conditions = [];
3949
+		$none_overridden                = true;
3950
+		$or_condition_key_for_defaults  = 'OR*' . get_class($model);
3951
+		foreach ($default_where_conditions as $key => $val) {
3952
+			if (isset($provided_where_conditions[ $key ])) {
3953
+				$none_overridden = false;
3954
+			} else {
3955
+				$null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3956
+			}
3957
+		}
3958
+		if ($none_overridden && $default_where_conditions) {
3959
+			if ($model->has_primary_key_field()) {
3960
+				$null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3961
+																				   . "."
3962
+																				   . $model->primary_key_name() ] =
3963
+					['IS NULL'];
3964
+			}/*else{
3965 3965
                 //@todo NO PK, use other defaults
3966 3966
             }*/
3967
-        }
3968
-        return $null_friendly_where_conditions;
3969
-    }
3970
-
3971
-
3972
-    /**
3973
-     * Uses the _default_where_conditions_strategy set during __construct() to get
3974
-     * default where conditions on all get_all, update, and delete queries done by this model.
3975
-     * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3976
-     * NOT array('Event_CPT.post_type'=>'esp_event').
3977
-     *
3978
-     * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3979
-     * @return array @see
3980
-     *                                    https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3981
-     * @throws EE_Error
3982
-     * @throws EE_Error
3983
-     */
3984
-    private function _get_default_where_conditions($model_relation_path = '')
3985
-    {
3986
-        if ($this->_ignore_where_strategy) {
3987
-            return [];
3988
-        }
3989
-        return $this->_default_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3990
-    }
3991
-
3992
-
3993
-    /**
3994
-     * Uses the _minimum_where_conditions_strategy set during __construct() to get
3995
-     * minimum where conditions on all get_all, update, and delete queries done by this model.
3996
-     * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3997
-     * NOT array('Event_CPT.post_type'=>'esp_event').
3998
-     * Similar to _get_default_where_conditions
3999
-     *
4000
-     * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
4001
-     * @return array @see
4002
-     *                                    https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4003
-     * @throws EE_Error
4004
-     * @throws EE_Error
4005
-     */
4006
-    protected function _get_minimum_where_conditions($model_relation_path = '')
4007
-    {
4008
-        if ($this->_ignore_where_strategy) {
4009
-            return [];
4010
-        }
4011
-        return $this->_minimum_where_conditions_strategy->get_default_where_conditions($model_relation_path);
4012
-    }
4013
-
4014
-
4015
-    /**
4016
-     * Creates the string of SQL for the select part of a select query, everything behind SELECT and before FROM.
4017
-     * Eg, "Event.post_id, Event.post_name,Event_Detail.EVT_ID..."
4018
-     *
4019
-     * @param EE_Model_Query_Info_Carrier $model_query_info
4020
-     * @return string
4021
-     * @throws EE_Error
4022
-     */
4023
-    private function _construct_default_select_sql(EE_Model_Query_Info_Carrier $model_query_info)
4024
-    {
4025
-        $selects = $this->_get_columns_to_select_for_this_model();
4026
-        foreach (
4027
-            $model_query_info->get_model_names_included() as $model_relation_chain => $name_of_other_model_included
4028
-        ) {
4029
-            $other_model_included = $this->get_related_model_obj($name_of_other_model_included);
4030
-            $other_model_selects  = $other_model_included->_get_columns_to_select_for_this_model($model_relation_chain);
4031
-            foreach ($other_model_selects as $key => $value) {
4032
-                $selects[] = $value;
4033
-            }
4034
-        }
4035
-        return implode(", ", $selects);
4036
-    }
4037
-
4038
-
4039
-    /**
4040
-     * Gets an array of columns to select for this model, which are necessary for it to create its objects.
4041
-     * So that's going to be the columns for all the fields on the model
4042
-     *
4043
-     * @param string $model_relation_chain like 'Question.Question_Group.Event'
4044
-     * @return array numerically indexed, values are columns to select and rename, eg "Event.ID AS 'Event.ID'"
4045
-     */
4046
-    public function _get_columns_to_select_for_this_model($model_relation_chain = '')
4047
-    {
4048
-        $fields                                       = $this->field_settings();
4049
-        $selects                                      = [];
4050
-        $table_alias_with_model_relation_chain_prefix =
4051
-            EE_Model_Parser::extract_table_alias_model_relation_chain_prefix(
4052
-                $model_relation_chain,
4053
-                $this->get_this_model_name()
4054
-            );
4055
-        foreach ($fields as $field_obj) {
4056
-            $selects[] = $table_alias_with_model_relation_chain_prefix
4057
-                         . $field_obj->get_table_alias()
4058
-                         . "."
4059
-                         . $field_obj->get_table_column()
4060
-                         . " AS '"
4061
-                         . $table_alias_with_model_relation_chain_prefix
4062
-                         . $field_obj->get_table_alias()
4063
-                         . "."
4064
-                         . $field_obj->get_table_column()
4065
-                         . "'";
4066
-        }
4067
-        // make sure we are also getting the PKs of each table
4068
-        $tables = $this->get_tables();
4069
-        if (count($tables) > 1) {
4070
-            foreach ($tables as $table_obj) {
4071
-                $qualified_pk_column = $table_alias_with_model_relation_chain_prefix
4072
-                                       . $table_obj->get_fully_qualified_pk_column();
4073
-                if (! in_array($qualified_pk_column, $selects)) {
4074
-                    $selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
4075
-                }
4076
-            }
4077
-        }
4078
-        return $selects;
4079
-    }
4080
-
4081
-
4082
-    /**
4083
-     * Given a $query_param like 'Registration.Transaction.TXN_ID', pops off 'Registration.',
4084
-     * gets the join statement for it; gets the data types for it; and passes the remaining 'Transaction.TXN_ID'
4085
-     * onto its related Transaction object to do the same. Returns an EE_Join_And_Data_Types object which contains the
4086
-     * SQL for joining, and the data types
4087
-     *
4088
-     * @param null|string                 $original_query_param
4089
-     * @param string                      $query_param          like Registration.Transaction.TXN_ID
4090
-     * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4091
-     * @param string                      $query_param_type     like Registration.Transaction.TXN_ID
4092
-     *                                                          or 'PAY_ID'. Otherwise, we don't expect there to be a
4093
-     *                                                          column name. We only want model names, eg 'Event.Venue'
4094
-     *                                                          or 'Registration's
4095
-     * @param string                      $original_query_param what it originally was (eg
4096
-     *                                                          Registration.Transaction.TXN_ID). If null, we assume it
4097
-     *                                                          matches $query_param
4098
-     * @return void only modifies the EEM_Related_Model_Info_Carrier passed into it
4099
-     * @throws EE_Error
4100
-     */
4101
-    private function _extract_related_model_info_from_query_param(
4102
-        $query_param,
4103
-        EE_Model_Query_Info_Carrier $passed_in_query_info,
4104
-        $query_param_type,
4105
-        $original_query_param = null
4106
-    ) {
4107
-        if ($original_query_param === null) {
4108
-            $original_query_param = $query_param;
4109
-        }
4110
-        $query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4111
-        // check to see if we have a field on this model
4112
-        $this_model_fields = $this->field_settings(true);
4113
-        if (array_key_exists($query_param, $this_model_fields)) {
4114
-            $field_is_allowed = in_array(
4115
-                $query_param_type,
4116
-                [0, 'where', 'having', 'order_by', 'group_by', 'order', 'custom_selects'],
4117
-                true
4118
-            );
4119
-            if ($field_is_allowed) {
4120
-                return;
4121
-            }
4122
-            throw new EE_Error(
4123
-                sprintf(
4124
-                    esc_html__(
4125
-                        "Using a field name (%s) on model %s is not allowed on this query param type '%s'. Original query param was %s",
4126
-                        "event_espresso"
4127
-                    ),
4128
-                    $query_param,
4129
-                    $this->class_name,
4130
-                    $query_param_type,
4131
-                    $original_query_param
4132
-                )
4133
-            );
4134
-        }
4135
-        // check if this is a special logic query param
4136
-        if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4137
-            $operator_is_allowed = in_array($query_param_type, ['where', 'having', 0, 'custom_selects'], true);
4138
-            if ($operator_is_allowed) {
4139
-                return;
4140
-            }
4141
-            throw new EE_Error(
4142
-                sprintf(
4143
-                    esc_html__(
4144
-                        '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',
4145
-                        'event_espresso'
4146
-                    ),
4147
-                    implode('", "', $this->_logic_query_param_keys),
4148
-                    $query_param,
4149
-                    $this->class_name,
4150
-                    '<br />',
4151
-                    "\t"
4152
-                    . ' $passed_in_query_info = <pre>'
4153
-                    . print_r($passed_in_query_info, true)
4154
-                    . '</pre>'
4155
-                    . "\n\t"
4156
-                    . ' $query_param_type = '
4157
-                    . $query_param_type
4158
-                    . "\n\t"
4159
-                    . ' $original_query_param = '
4160
-                    . $original_query_param
4161
-                )
4162
-            );
4163
-        }
4164
-        // check if it's a custom selection
4165
-        if (
4166
-            $this->_custom_selections instanceof CustomSelects
4167
-            && in_array($query_param, $this->_custom_selections->columnAliases(), true)
4168
-        ) {
4169
-            return;
4170
-        }
4171
-        // check if has a model name at the beginning
4172
-        // and
4173
-        // check if it's a field on a related model
4174
-        if (
4175
-            $this->extractJoinModelFromQueryParams(
4176
-                $passed_in_query_info,
4177
-                $query_param,
4178
-                $original_query_param,
4179
-                $query_param_type
4180
-            )
4181
-        ) {
4182
-            return;
4183
-        }
4184
-
4185
-        // ok so $query_param didn't start with a model name
4186
-        // and we previously confirmed it wasn't a logic query param or field on the current model
4187
-        // it's wack, that's what it is
4188
-        throw new EE_Error(
4189
-            sprintf(
4190
-                esc_html__(
4191
-                    "There is no model named '%s' related to %s. Query param type is %s and original query param is %s",
4192
-                    "event_espresso"
4193
-                ),
4194
-                $query_param,
4195
-                $this->class_name,
4196
-                $query_param_type,
4197
-                $original_query_param
4198
-            )
4199
-        );
4200
-    }
4201
-
4202
-
4203
-    /**
4204
-     * Extracts any possible join model information from the provided possible_join_string.
4205
-     * This method will read the provided $possible_join_string value and determine if there are any possible model
4206
-     * join
4207
-     * parts that should be added to the query.
4208
-     *
4209
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
4210
-     * @param string                      $possible_join_string  Such as Registration.REG_ID, or Registration
4211
-     * @param null|string                 $original_query_param
4212
-     * @param string                      $query_parameter_type  The type for the source of the $possible_join_string
4213
-     *                                                           ('where', 'order_by', 'group_by', 'custom_selects'
4214
-     *                                                           etc.)
4215
-     * @return bool  returns true if a join was added and false if not.
4216
-     * @throws EE_Error
4217
-     */
4218
-    private function extractJoinModelFromQueryParams(
4219
-        EE_Model_Query_Info_Carrier $query_info_carrier,
4220
-        $possible_join_string,
4221
-        $original_query_param,
4222
-        $query_parameter_type
4223
-    ) {
4224
-        foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4225
-            if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4226
-                $this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4227
-                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4228
-                if ($possible_join_string === '') {
4229
-                    // nothing left to $query_param
4230
-                    // we should actually end in a field name, not a model like this!
4231
-                    throw new EE_Error(
4232
-                        sprintf(
4233
-                            esc_html__(
4234
-                                "Query param '%s' (of type %s on model %s) shouldn't end on a period (.) ",
4235
-                                "event_espresso"
4236
-                            ),
4237
-                            $possible_join_string,
4238
-                            $query_parameter_type,
4239
-                            $this->class_name,
4240
-                            $valid_related_model_name
4241
-                        )
4242
-                    );
4243
-                }
4244
-                $related_model_obj = $this->get_related_model_obj($valid_related_model_name);
4245
-                $related_model_obj->_extract_related_model_info_from_query_param(
4246
-                    $possible_join_string,
4247
-                    $query_info_carrier,
4248
-                    $query_parameter_type,
4249
-                    $original_query_param
4250
-                );
4251
-                return true;
4252
-            }
4253
-            if ($possible_join_string === $valid_related_model_name) {
4254
-                $this->_add_join_to_model(
4255
-                    $valid_related_model_name,
4256
-                    $query_info_carrier,
4257
-                    $original_query_param
4258
-                );
4259
-                return true;
4260
-            }
4261
-        }
4262
-        return false;
4263
-    }
4264
-
4265
-
4266
-    /**
4267
-     * Extracts related models from Custom Selects and sets up any joins for those related models.
4268
-     *
4269
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
4270
-     * @throws EE_Error
4271
-     */
4272
-    private function extractRelatedModelsFromCustomSelects(EE_Model_Query_Info_Carrier $query_info_carrier)
4273
-    {
4274
-        if (
4275
-            $this->_custom_selections instanceof CustomSelects
4276
-            && (
4277
-                $this->_custom_selections->type() === CustomSelects::TYPE_STRUCTURED
4278
-                || $this->_custom_selections->type() == CustomSelects::TYPE_COMPLEX
4279
-            )
4280
-        ) {
4281
-            $original_selects = $this->_custom_selections->originalSelects();
4282
-            foreach ($original_selects as $alias => $select_configuration) {
4283
-                $this->extractJoinModelFromQueryParams(
4284
-                    $query_info_carrier,
4285
-                    $select_configuration[0],
4286
-                    $select_configuration[0],
4287
-                    'custom_selects'
4288
-                );
4289
-            }
4290
-        }
4291
-    }
4292
-
4293
-
4294
-    /**
4295
-     * Privately used by _extract_related_model_info_from_query_param to add a join to $model_name
4296
-     * and store it on $passed_in_query_info
4297
-     *
4298
-     * @param string                      $model_name
4299
-     * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4300
-     * @param string                      $original_query_param used to extract the relation chain between the queried
4301
-     *                                                          model and $model_name. Eg, if we are querying Event,
4302
-     *                                                          and are adding a join to 'Payment' with the original
4303
-     *                                                          query param key
4304
-     *                                                          'Registration.Transaction.Payment.PAY_amount', we want
4305
-     *                                                          to extract 'Registration.Transaction.Payment', in case
4306
-     *                                                          Payment wants to add default query params so that it
4307
-     *                                                          will know what models to prepend onto its default query
4308
-     *                                                          params or in case it wants to rename tables (in case
4309
-     *                                                          there are multiple joins to the same table)
4310
-     * @return void
4311
-     * @throws EE_Error
4312
-     */
4313
-    private function _add_join_to_model(
4314
-        $model_name,
4315
-        EE_Model_Query_Info_Carrier $passed_in_query_info,
4316
-        $original_query_param
4317
-    ) {
4318
-        $relation_obj         = $this->related_settings_for($model_name);
4319
-        $model_relation_chain = EE_Model_Parser::extract_model_relation_chain($model_name, $original_query_param);
4320
-        // check if the relation is HABTM, because then we're essentially doing two joins
4321
-        // If so, join first to the JOIN table, and add its data types, and then continue as normal
4322
-        if ($relation_obj instanceof EE_HABTM_Relation) {
4323
-            $join_model_obj = $relation_obj->get_join_model();
4324
-            // replace the model specified with the join model for this relation chain, whi
4325
-            $relation_chain_to_join_model =
4326
-                EE_Model_Parser::replace_model_name_with_join_model_name_in_model_relation_chain(
4327
-                    $model_name,
4328
-                    $join_model_obj->get_this_model_name(),
4329
-                    $model_relation_chain
4330
-                );
4331
-            $passed_in_query_info->merge(
4332
-                new EE_Model_Query_Info_Carrier(
4333
-                    [$relation_chain_to_join_model => $join_model_obj->get_this_model_name()],
4334
-                    $relation_obj->get_join_to_intermediate_model_statement($relation_chain_to_join_model)
4335
-                )
4336
-            );
4337
-        }
4338
-        // now just join to the other table pointed to by the relation object, and add its data types
4339
-        $passed_in_query_info->merge(
4340
-            new EE_Model_Query_Info_Carrier(
4341
-                [$model_relation_chain => $model_name],
4342
-                $relation_obj->get_join_statement($model_relation_chain)
4343
-            )
4344
-        );
4345
-    }
4346
-
4347
-
4348
-    /**
4349
-     * Constructs SQL for where clause, like "WHERE Event.ID = 23 AND Transaction.amount > 100" etc.
4350
-     *
4351
-     * @param array $where_params @see
4352
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4353
-     * @return string of SQL
4354
-     * @throws EE_Error
4355
-     */
4356
-    private function _construct_where_clause($where_params)
4357
-    {
4358
-        $SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4359
-        if ($SQL) {
4360
-            return " WHERE " . $SQL;
4361
-        }
4362
-        return '';
4363
-    }
4364
-
4365
-
4366
-    /**
4367
-     * Just like the _construct_where_clause, except prepends 'HAVING' instead of 'WHERE',
4368
-     * and should be passed HAVING parameters, not WHERE parameters
4369
-     *
4370
-     * @param array $having_params
4371
-     * @return string
4372
-     * @throws EE_Error
4373
-     */
4374
-    private function _construct_having_clause($having_params)
4375
-    {
4376
-        $SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4377
-        if ($SQL) {
4378
-            return " HAVING " . $SQL;
4379
-        }
4380
-        return '';
4381
-    }
4382
-
4383
-
4384
-    /**
4385
-     * Used for creating nested WHERE conditions. Eg "WHERE ! (Event.ID = 3 OR ( Event_Meta.meta_key = 'bob' AND
4386
-     * Event_Meta.meta_value = 'foo'))"
4387
-     *
4388
-     * @param array  $where_params @see
4389
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4390
-     * @param string $glue         joins each subclause together. Should really only be " AND " or " OR "...
4391
-     * @return string of SQL
4392
-     * @throws EE_Error
4393
-     */
4394
-    private function _construct_condition_clause_recursive($where_params, $glue = ' AND')
4395
-    {
4396
-        $where_clauses = [];
4397
-        foreach ($where_params as $query_param => $op_and_value_or_sub_condition) {
4398
-            $query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4399
-            if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4400
-                switch ($query_param) {
4401
-                    case 'not':
4402
-                    case 'NOT':
4403
-                        $where_clauses[] = "! ("
4404
-                                           . $this->_construct_condition_clause_recursive(
4405
-                                               $op_and_value_or_sub_condition,
4406
-                                               $glue
4407
-                                           )
4408
-                                           . ")";
4409
-                        break;
4410
-                    case 'and':
4411
-                    case 'AND':
4412
-                        $where_clauses[] = " ("
4413
-                                           . $this->_construct_condition_clause_recursive(
4414
-                                               $op_and_value_or_sub_condition,
4415
-                                               ' AND '
4416
-                                           )
4417
-                                           . ")";
4418
-                        break;
4419
-                    case 'or':
4420
-                    case 'OR':
4421
-                        $where_clauses[] = " ("
4422
-                                           . $this->_construct_condition_clause_recursive(
4423
-                                               $op_and_value_or_sub_condition,
4424
-                                               ' OR '
4425
-                                           )
4426
-                                           . ")";
4427
-                        break;
4428
-                }
4429
-            } else {
4430
-                $field_obj = $this->_deduce_field_from_query_param($query_param);
4431
-                // if it's not a normal field, maybe it's a custom selection?
4432
-                if (! $field_obj) {
4433
-                    if ($this->_custom_selections instanceof CustomSelects) {
4434
-                        $field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4435
-                    } else {
4436
-                        throw new EE_Error(
4437
-                            sprintf(
4438
-                                esc_html__(
4439
-                                    "%s is neither a valid model field name, nor a custom selection",
4440
-                                    "event_espresso"
4441
-                                ),
4442
-                                $query_param
4443
-                            )
4444
-                        );
4445
-                    }
4446
-                }
4447
-                $op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4448
-                $where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4449
-            }
4450
-        }
4451
-        return $where_clauses
4452
-            ? implode($glue, $where_clauses)
4453
-            : '';
4454
-    }
4455
-
4456
-
4457
-    /**
4458
-     * Takes the input parameter and extract the table name (alias) and column name
4459
-     *
4460
-     * @param string $query_param like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4461
-     * @return string table alias and column name for SQL, eg "Transaction.TXN_ID"
4462
-     * @throws EE_Error
4463
-     */
4464
-    private function _deduce_column_name_from_query_param($query_param)
4465
-    {
4466
-        $field = $this->_deduce_field_from_query_param($query_param);
4467
-        if ($field) {
4468
-            $table_alias_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_from_query_param(
4469
-                $field->get_model_name(),
4470
-                $query_param
4471
-            );
4472
-            return $table_alias_prefix . $field->get_qualified_column();
4473
-        }
4474
-        if (
4475
-            $this->_custom_selections instanceof CustomSelects
4476
-            && in_array($query_param, $this->_custom_selections->columnAliases(), true)
4477
-        ) {
4478
-            // maybe it's custom selection item?
4479
-            // if so, just use it as the "column name"
4480
-            return $query_param;
4481
-        }
4482
-        $custom_select_aliases = $this->_custom_selections instanceof CustomSelects
4483
-            ? implode(',', $this->_custom_selections->columnAliases())
4484
-            : '';
4485
-        throw new EE_Error(
4486
-            sprintf(
4487
-                esc_html__(
4488
-                    "%s is not a valid field on this model, nor a custom selection (%s)",
4489
-                    "event_espresso"
4490
-                ),
4491
-                $query_param,
4492
-                $custom_select_aliases
4493
-            )
4494
-        );
4495
-    }
4496
-
4497
-
4498
-    /**
4499
-     * Removes the * and anything after it from the condition query param key. It is useful to add the * to condition
4500
-     * query param keys (eg, 'OR*', 'EVT_ID') in order for the array keys to still be unique, so that they don't get
4501
-     * overwritten Takes a string like 'Event.EVT_ID*', 'TXN_total**', 'OR*1st', and 'DTT_reg_start*foobar' to
4502
-     * 'Event.EVT_ID', 'TXN_total', 'OR', and 'DTT_reg_start', respectively.
4503
-     *
4504
-     * @param string $condition_query_param_key
4505
-     * @return string
4506
-     */
4507
-    private function _remove_stars_and_anything_after_from_condition_query_param_key($condition_query_param_key)
4508
-    {
4509
-        $pos_of_star = strpos($condition_query_param_key, '*');
4510
-        if ($pos_of_star === false) {
4511
-            return $condition_query_param_key;
4512
-        }
4513
-        return substr($condition_query_param_key, 0, $pos_of_star);
4514
-    }
4515
-
4516
-
4517
-    /**
4518
-     * creates the SQL for the operator and the value in a WHERE clause, eg "< 23" or "LIKE '%monkey%'"
4519
-     *
4520
-     * @param array|string               $op_and_value
4521
-     * @param EE_Model_Field_Base|string $field_obj . If string, should be one of EEM_Base::_valid_wpdb_data_types
4522
-     * @return string
4523
-     * @throws EE_Error
4524
-     */
4525
-    private function _construct_op_and_value($op_and_value, $field_obj)
4526
-    {
4527
-        $operator = '=';
4528
-        $value    = $op_and_value;
4529
-        if (is_array($op_and_value)) {
4530
-            $operator = isset($op_and_value[0])
4531
-                ? $this->_prepare_operator_for_sql($op_and_value[0])
4532
-                : null;
4533
-            if (! $operator) {
4534
-                $php_array_like_string = [];
4535
-                foreach ($op_and_value as $key => $value) {
4536
-                    $value = is_array($value) ? '[' . implode(",", $value) . ']' : $value;
4537
-                    $php_array_like_string[] = "$key=>$value";
4538
-                }
4539
-                throw new EE_Error(
4540
-                    sprintf(
4541
-                        esc_html__(
4542
-                            "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))",
4543
-                            "event_espresso"
4544
-                        ),
4545
-                        implode(",", $php_array_like_string)
4546
-                    )
4547
-                );
4548
-            }
4549
-            $value = $op_and_value[1] ?? null;
4550
-        }
4551
-
4552
-        // check to see if the value is actually another field
4553
-        if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2]) {
4554
-            return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4555
-        }
4556
-        if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4557
-            // in this case, the value should be an array, or at least a comma-separated list
4558
-            // it will need to handle a little differently
4559
-            $cleaned_value = $this->_construct_in_value($value, $field_obj);
4560
-            // note: $cleaned_value has already been run through $wpdb->prepare()
4561
-            return $operator . SP . $cleaned_value;
4562
-        }
4563
-        if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4564
-            // the value should be an array with count of two.
4565
-            if (count($value) !== 2) {
4566
-                throw new EE_Error(
4567
-                    sprintf(
4568
-                        esc_html__(
4569
-                            "The '%s' operator must be used with an array of values and there must be exactly TWO values in that array.",
4570
-                            'event_espresso'
4571
-                        ),
4572
-                        "BETWEEN"
4573
-                    )
4574
-                );
4575
-            }
4576
-            $cleaned_value = $this->_construct_between_value($value, $field_obj);
4577
-            return $operator . SP . $cleaned_value;
4578
-        }
4579
-        if (in_array($operator, $this->valid_null_style_operators())) {
4580
-            if ($value !== null) {
4581
-                throw new EE_Error(
4582
-                    sprintf(
4583
-                        esc_html__(
4584
-                            "You attempted to give a value  (%s) while using a NULL-style operator (%s). That isn't valid",
4585
-                            "event_espresso"
4586
-                        ),
4587
-                        $value,
4588
-                        $operator
4589
-                    )
4590
-                );
4591
-            }
4592
-            return $operator;
4593
-        }
4594
-        if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4595
-            // if the operator is 'LIKE', we want to allow percent signs (%) and not
4596
-            // remove other junk. So just treat it as a string.
4597
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4598
-        }
4599
-        if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4600
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4601
-        }
4602
-        if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4603
-            throw new EE_Error(
4604
-                sprintf(
4605
-                    esc_html__(
4606
-                        "Operator '%s' must be used with an array of values, eg 'Registration.REG_ID' => array('%s',array(1,2,3))",
4607
-                        'event_espresso'
4608
-                    ),
4609
-                    $operator,
4610
-                    $operator
4611
-                )
4612
-            );
4613
-        }
4614
-        if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4615
-            throw new EE_Error(
4616
-                sprintf(
4617
-                    esc_html__(
4618
-                        "Operator '%s' must be used with a single value, not an array. Eg 'Registration.REG_ID => array('%s',23))",
4619
-                        'event_espresso'
4620
-                    ),
4621
-                    $operator,
4622
-                    $operator
4623
-                )
4624
-            );
4625
-        }
4626
-        throw new EE_Error(
4627
-            sprintf(
4628
-                esc_html__(
4629
-                    "It appears you've provided some totally invalid query parameters. Operator and value were:'%s', which isn't right at all",
4630
-                    "event_espresso"
4631
-                ),
4632
-                http_build_query($op_and_value)
4633
-            )
4634
-        );
4635
-    }
4636
-
4637
-
4638
-    /**
4639
-     * Creates the operands to be used in a BETWEEN query, eg "'2014-12-31 20:23:33' AND '2015-01-23 12:32:54'"
4640
-     *
4641
-     * @param array                      $values
4642
-     * @param EE_Model_Field_Base|string $field_obj if string, it should be the datatype to be used when querying, eg
4643
-     *                                              '%s'
4644
-     * @return string
4645
-     * @throws EE_Error
4646
-     */
4647
-    public function _construct_between_value($values, $field_obj)
4648
-    {
4649
-        $cleaned_values = [];
4650
-        foreach ($values as $value) {
4651
-            $cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4652
-        }
4653
-        return $cleaned_values[0] . " AND " . $cleaned_values[1];
4654
-    }
4655
-
4656
-
4657
-    /**
4658
-     * Takes an array or a comma-separated list of $values and cleans them
4659
-     * according to $data_type using $wpdb->prepare, and then makes the list a
4660
-     * string surrounded by ( and ). Eg, _construct_in_value(array(1,2,3),'%d') would
4661
-     * return '(1,2,3)'; _construct_in_value("1,2,hack",'%d') would return '(1,2,1)' (assuming
4662
-     * I'm right that a string, when interpreted as a digit, becomes a 1. It might become a 0)
4663
-     *
4664
-     * @param mixed                      $values    array or comma-separated string
4665
-     * @param EE_Model_Field_Base|string $field_obj if string, it should be a wpdb data type like '%s', or '%d'
4666
-     * @return string of SQL to follow an 'IN' or 'NOT IN' operator
4667
-     * @throws EE_Error
4668
-     */
4669
-    public function _construct_in_value($values, $field_obj)
4670
-    {
4671
-        $prepped = [];
4672
-        // check if the value is a CSV list
4673
-        if (is_string($values)) {
4674
-            // in which case, turn it into an array
4675
-            $values = explode(',', $values);
4676
-        }
4677
-        // make sure we only have one of each value in the list
4678
-        $values = array_unique($values);
4679
-        foreach ($values as $value) {
4680
-            $prepped[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4681
-        }
4682
-        // we would just LOVE to leave $cleaned_values as an empty array, and return the value as "()",
4683
-        // but unfortunately that's invalid SQL. So instead we return a string which we KNOW will evaluate to be the empty set
4684
-        // which is effectively equivalent to returning "()". We don't return "(0)" because that only works for auto-incrementing columns
4685
-        if (empty($prepped)) {
4686
-            $all_fields  = $this->field_settings();
4687
-            $first_field = reset($all_fields);
4688
-            $main_table  = $this->_get_main_table();
4689
-            $prepped[]   = "SELECT {$first_field->get_table_column()} FROM {$main_table->get_table_name()} WHERE FALSE";
4690
-        }
4691
-        return '(' . implode(',', $prepped) . ')';
4692
-    }
4693
-
4694
-
4695
-    /**
4696
-     * @param mixed                      $value
4697
-     * @param EE_Model_Field_Base|string $field_obj if string it should be a wpdb data type like '%d'
4698
-     * @return false|null|string
4699
-     * @throws EE_Error
4700
-     */
4701
-    private function _wpdb_prepare_using_field($value, $field_obj)
4702
-    {
4703
-        /** @type WPDB $wpdb */
4704
-        global $wpdb;
4705
-        if ($field_obj instanceof EE_Model_Field_Base) {
4706
-            return $wpdb->prepare(
4707
-                $field_obj->get_wpdb_data_type(),
4708
-                $this->_prepare_value_for_use_in_db($value, $field_obj)
4709
-            );
4710
-        } //$field_obj should really just be a data type
4711
-        if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4712
-            throw new EE_Error(
4713
-                sprintf(
4714
-                    esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
4715
-                    $field_obj,
4716
-                    implode(",", $this->_valid_wpdb_data_types)
4717
-                )
4718
-            );
4719
-        }
4720
-        return $wpdb->prepare($field_obj, $value);
4721
-    }
4722
-
4723
-
4724
-    /**
4725
-     * Takes the input parameter and finds the model field that it indicates.
4726
-     *
4727
-     * @param string $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4728
-     * @return EE_Model_Field_Base
4729
-     * @throws EE_Error
4730
-     */
4731
-    protected function _deduce_field_from_query_param($query_param_name)
4732
-    {
4733
-        // ok, now proceed with deducing which part is the model's name, and which is the field's name
4734
-        // which will help us find the database table and column
4735
-        $query_param_parts = explode(".", $query_param_name);
4736
-        if (empty($query_param_parts)) {
4737
-            throw new EE_Error(
4738
-                sprintf(
4739
-                    esc_html__(
4740
-                        "_extract_column_name is empty when trying to extract column and table name from %s",
4741
-                        'event_espresso'
4742
-                    ),
4743
-                    $query_param_name
4744
-                )
4745
-            );
4746
-        }
4747
-        $number_of_parts       = count($query_param_parts);
4748
-        $last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4749
-        if ($number_of_parts === 1) {
4750
-            $field_name = $last_query_param_part;
4751
-            $model_obj  = $this;
4752
-        } else {// $number_of_parts >= 2
4753
-            // the last part is the column name, and there are only 2parts. therefore...
4754
-            $field_name = $last_query_param_part;
4755
-            $model_obj  = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4756
-        }
4757
-        try {
4758
-            return $model_obj->field_settings_for($field_name);
4759
-        } catch (EE_Error $e) {
4760
-            return null;
4761
-        }
4762
-    }
4763
-
4764
-
4765
-    /**
4766
-     * Given a field's name (ie, a key in $this->field_settings()), uses the EE_Model_Field object to get the table's
4767
-     * alias and column which corresponds to it
4768
-     *
4769
-     * @param string $field_name
4770
-     * @return string
4771
-     * @throws EE_Error
4772
-     */
4773
-    public function _get_qualified_column_for_field($field_name)
4774
-    {
4775
-        $all_fields = $this->field_settings();
4776
-        $field      = $all_fields[ $field_name ] ?? false;
4777
-        if ($field) {
4778
-            return $field->get_qualified_column();
4779
-        }
4780
-        throw new EE_Error(
4781
-            sprintf(
4782
-                esc_html__(
4783
-                    "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.",
4784
-                    'event_espresso'
4785
-                ),
4786
-                $field_name,
4787
-                $this->class_name
4788
-            )
4789
-        );
4790
-    }
4791
-
4792
-
4793
-    /**
4794
-     * similar to \EEM_Base::_get_qualified_column_for_field() but returns an array with data for ALL fields.
4795
-     * Example usage:
4796
-     * EEM_Ticket::instance()->get_all_wpdb_results(
4797
-     *      array(),
4798
-     *      ARRAY_A,
4799
-     *      EEM_Ticket::instance()->get_qualified_columns_for_all_fields()
4800
-     *  );
4801
-     * is equivalent to
4802
-     *  EEM_Ticket::instance()->get_all_wpdb_results( array(), ARRAY_A, '*' );
4803
-     * and
4804
-     *  EEM_Event::instance()->get_all_wpdb_results(
4805
-     *      array(
4806
-     *          array(
4807
-     *              'Datetime.Ticket.TKT_ID' => array( '<', 100 ),
4808
-     *          ),
4809
-     *          ARRAY_A,
4810
-     *          implode(
4811
-     *              ', ',
4812
-     *              array_merge(
4813
-     *                  EEM_Event::instance()->get_qualified_columns_for_all_fields( '', false ),
4814
-     *                  EEM_Ticket::instance()->get_qualified_columns_for_all_fields( 'Datetime', false )
4815
-     *              )
4816
-     *          )
4817
-     *      )
4818
-     *  );
4819
-     * selects rows from the database, selecting all the event and ticket columns, where the ticket ID is below 100
4820
-     *
4821
-     * @param string $model_relation_chain        the chain of models used to join between the model you want to query
4822
-     *                                            and the one whose fields you are selecting for example: when querying
4823
-     *                                            tickets model and selecting fields from the tickets model you would
4824
-     *                                            leave this parameter empty, because no models are needed to join
4825
-     *                                            between the queried model and the selected one. Likewise when
4826
-     *                                            querying the datetime model and selecting fields from the tickets
4827
-     *                                            model, it would also be left empty, because there is a direct
4828
-     *                                            relation from datetimes to tickets, so no model is needed to join
4829
-     *                                            them together. However, when querying from the event model and
4830
-     *                                            selecting fields from the ticket model, you should provide the string
4831
-     *                                            'Datetime', indicating that the event model must first join to the
4832
-     *                                            datetime model in order to find its relation to ticket model.
4833
-     *                                            Also, when querying from the venue model and selecting fields from
4834
-     *                                            the ticket model, you should provide the string 'Event.Datetime',
4835
-     *                                            indicating you need to join the venue model to the event model,
4836
-     *                                            to the datetime model, in order to find its relation to the ticket
4837
-     *                                            model. This string is used to deduce the prefix that gets added onto
4838
-     *                                            the models' tables qualified columns
4839
-     * @param bool   $return_string               if true, will return a string with qualified column names separated
4840
-     *                                            by ', ' if false, will simply return a numerically indexed array of
4841
-     *                                            qualified column names
4842
-     * @return array|string
4843
-     */
4844
-    public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4845
-    {
4846
-        $table_prefix      = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain)
4847
-                ? ''
4848
-                : '__');
4849
-        $qualified_columns = [];
4850
-        foreach ($this->field_settings() as $field_name => $field) {
4851
-            $qualified_columns[] = $table_prefix . $field->get_qualified_column();
4852
-        }
4853
-        return $return_string
4854
-            ? implode(', ', $qualified_columns)
4855
-            : $qualified_columns;
4856
-    }
4857
-
4858
-
4859
-    /**
4860
-     * constructs the select use on special limit joins
4861
-     * NOTE: for now this has only been tested and will work when the  table alias is for the PRIMARY table. Although
4862
-     * its setup so the select query will be setup on and just doing the special select join off of the primary table
4863
-     * (as that is typically where the limits would be set).
4864
-     *
4865
-     * @param string       $table_alias The table the select is being built for
4866
-     * @param mixed|string $limit       The limit for this select
4867
-     * @return string                The final select join element for the query.
4868
-     * @throws EE_Error
4869
-     * @throws EE_Error
4870
-     */
4871
-    public function _construct_limit_join_select($table_alias, $limit)
4872
-    {
4873
-        $SQL = '';
4874
-        foreach ($this->_tables as $table_obj) {
4875
-            if ($table_obj instanceof EE_Primary_Table) {
4876
-                $SQL .= $table_alias === $table_obj->get_table_alias()
4877
-                    ? $table_obj->get_select_join_limit($limit)
4878
-                    : SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4879
-            } elseif ($table_obj instanceof EE_Secondary_Table) {
4880
-                $SQL .= $table_alias === $table_obj->get_table_alias()
4881
-                    ? $table_obj->get_select_join_limit_join($limit)
4882
-                    : SP . $table_obj->get_join_sql($table_alias) . SP;
4883
-            }
4884
-        }
4885
-        return $SQL;
4886
-    }
4887
-
4888
-
4889
-    /**
4890
-     * Constructs the internal join if there are multiple tables, or simply the table's name and alias
4891
-     * Eg "wp_post AS Event" or "wp_post AS Event INNER JOIN wp_postmeta Event_Meta ON Event.ID = Event_Meta.post_id"
4892
-     *
4893
-     * @return string SQL
4894
-     * @throws EE_Error
4895
-     */
4896
-    public function _construct_internal_join()
4897
-    {
4898
-        $SQL = $this->_get_main_table()->get_table_sql();
4899
-        $SQL .= $this->_construct_internal_join_to_table_with_alias($this->_get_main_table()->get_table_alias());
4900
-        return $SQL;
4901
-    }
4902
-
4903
-
4904
-    /**
4905
-     * Constructs the SQL for joining all the tables on this model.
4906
-     * Normally $alias should be the primary table's alias, but in cases where
4907
-     * we have already joined to a secondary table (eg, the secondary table has a foreign key and is joined before the
4908
-     * primary table) then we should provide that secondary table's alias. Eg, with $alias being the primary table's
4909
-     * alias, this will construct SQL like:
4910
-     * " INNER JOIN wp_esp_secondary_table AS Secondary_Table ON Primary_Table.pk = Secondary_Table.fk".
4911
-     * With $alias being a secondary table's alias, this will construct SQL like:
4912
-     * " INNER JOIN wp_esp_primary_table AS Primary_Table ON Primary_Table.pk = Secondary_Table.fk".
4913
-     *
4914
-     * @param string $alias_prefixed table alias to join to (this table should already be in the FROM SQL clause)
4915
-     * @return string
4916
-     * @throws EE_Error
4917
-     * @throws EE_Error
4918
-     */
4919
-    public function _construct_internal_join_to_table_with_alias($alias_prefixed)
4920
-    {
4921
-        $SQL               = '';
4922
-        $alias_sans_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($alias_prefixed);
4923
-        foreach ($this->_tables as $table_obj) {
4924
-            if ($table_obj instanceof EE_Secondary_Table) {// table is secondary table
4925
-                if ($alias_sans_prefix === $table_obj->get_table_alias()) {
4926
-                    // so we're joining to this table, meaning the table is already in
4927
-                    // the FROM statement, BUT the primary table isn't. So we want
4928
-                    // to add the inverse join sql
4929
-                    $SQL .= $table_obj->get_inverse_join_sql($alias_prefixed);
4930
-                } else {
4931
-                    // just add a regular JOIN to this table from the primary table
4932
-                    $SQL .= $table_obj->get_join_sql($alias_prefixed);
4933
-                }
4934
-            }// if it's a primary table, dont add any SQL. it should already be in the FROM statement
4935
-        }
4936
-        return $SQL;
4937
-    }
4938
-
4939
-
4940
-    /**
4941
-     * Gets an array for storing all the data types on the next-to-be-executed-query.
4942
-     * This should be a growing array of keys being table-columns (eg 'EVT_ID' and 'Event.EVT_ID'), and values being
4943
-     * their data type (eg, '%s', '%d', etc)
4944
-     *
4945
-     * @return array
4946
-     */
4947
-    public function _get_data_types()
4948
-    {
4949
-        $data_types = [];
4950
-        foreach ($this->field_settings() as $field_obj) {
4951
-            // $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4952
-            /** @var $field_obj EE_Model_Field_Base */
4953
-            $data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4954
-        }
4955
-        return $data_types;
4956
-    }
4957
-
4958
-
4959
-    /**
4960
-     * Gets the model object given the relation's name / model's name (eg, 'Event', 'Registration',etc. Always singular)
4961
-     *
4962
-     * @param string $model_name
4963
-     * @return EEM_Base
4964
-     * @throws EE_Error
4965
-     */
4966
-    public function get_related_model_obj($model_name)
4967
-    {
4968
-        $model_classname = "EEM_" . $model_name;
4969
-        if (! class_exists($model_classname)) {
4970
-            throw new EE_Error(
4971
-                sprintf(
4972
-                    esc_html__(
4973
-                        "You specified a related model named %s in your query. No such model exists, if it did, it would have the classname %s",
4974
-                        'event_espresso'
4975
-                    ),
4976
-                    $model_name,
4977
-                    $model_classname
4978
-                )
4979
-            );
4980
-        }
4981
-        return call_user_func($model_classname . "::instance");
4982
-    }
4983
-
4984
-
4985
-    /**
4986
-     * Returns the array of EE_ModelRelations for this model.
4987
-     *
4988
-     * @return EE_Model_Relation_Base[]
4989
-     */
4990
-    public function relation_settings()
4991
-    {
4992
-        return $this->_model_relations;
4993
-    }
4994
-
4995
-
4996
-    /**
4997
-     * Gets all related models that this model BELONGS TO. Handy to know sometimes
4998
-     * because without THOSE models, this model probably doesn't have much purpose.
4999
-     * (Eg, without an event, datetimes have little purpose.)
5000
-     *
5001
-     * @return EE_Belongs_To_Relation[]
5002
-     */
5003
-    public function belongs_to_relations()
5004
-    {
5005
-        $belongs_to_relations = [];
5006
-        foreach ($this->relation_settings() as $model_name => $relation_obj) {
5007
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
5008
-                $belongs_to_relations[ $model_name ] = $relation_obj;
5009
-            }
5010
-        }
5011
-        return $belongs_to_relations;
5012
-    }
5013
-
5014
-
5015
-    /**
5016
-     * Returns the specified EE_Model_Relation, or throws an exception
5017
-     *
5018
-     * @param string $relation_name name of relation, key in $this->_relatedModels
5019
-     * @return EE_Model_Relation_Base
5020
-     * @throws EE_Error
5021
-     */
5022
-    public function related_settings_for($relation_name)
5023
-    {
5024
-        $relatedModels = $this->relation_settings();
5025
-        if (! array_key_exists($relation_name, $relatedModels)) {
5026
-            throw new EE_Error(
5027
-                sprintf(
5028
-                    esc_html__(
5029
-                        'Cannot get %s related to %s. There is no model relation of that type. There is, however, %s...',
5030
-                        'event_espresso'
5031
-                    ),
5032
-                    $relation_name,
5033
-                    $this->_get_class_name(),
5034
-                    implode(', ', array_keys($relatedModels))
5035
-                )
5036
-            );
5037
-        }
5038
-        return $relatedModels[ $relation_name ];
5039
-    }
5040
-
5041
-
5042
-    /**
5043
-     * A convenience method for getting a specific field's settings, instead of getting all field settings for all
5044
-     * fields
5045
-     *
5046
-     * @param string  $fieldName
5047
-     * @param boolean $include_db_only_fields
5048
-     * @return EE_Model_Field_Base
5049
-     * @throws EE_Error
5050
-     */
5051
-    public function field_settings_for($fieldName, $include_db_only_fields = true)
5052
-    {
5053
-        $fieldSettings = $this->field_settings($include_db_only_fields);
5054
-        if (! array_key_exists($fieldName, $fieldSettings)) {
5055
-            throw new EE_Error(
5056
-                sprintf(
5057
-                    esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
5058
-                    $fieldName,
5059
-                    $this->class_name
5060
-                )
5061
-            );
5062
-        }
5063
-        return $fieldSettings[ $fieldName ];
5064
-    }
5065
-
5066
-
5067
-    /**
5068
-     * Checks if this field exists on this model
5069
-     *
5070
-     * @param string $fieldName a key in the model's _field_settings array
5071
-     * @return boolean
5072
-     */
5073
-    public function has_field($fieldName)
5074
-    {
5075
-        $fieldSettings = $this->field_settings(true);
5076
-        if (isset($fieldSettings[ $fieldName ])) {
5077
-            return true;
5078
-        }
5079
-        return false;
5080
-    }
5081
-
5082
-
5083
-    /**
5084
-     * Returns whether this model has a relation to the specified model
5085
-     *
5086
-     * @param string $relation_name possibly one of the keys in the relation_settings array
5087
-     * @return boolean
5088
-     */
5089
-    public function has_relation($relation_name)
5090
-    {
5091
-        $relations = $this->relation_settings();
5092
-        if (isset($relations[ $relation_name ])) {
5093
-            return true;
5094
-        }
5095
-        return false;
5096
-    }
5097
-
5098
-
5099
-    /**
5100
-     * gets the field object of type 'primary_key' from the fieldsSettings attribute.
5101
-     * Eg, on EE_Answer that would be ANS_ID field object
5102
-     *
5103
-     * @param $field_obj
5104
-     * @return boolean
5105
-     */
5106
-    public function is_primary_key_field($field_obj): bool
5107
-    {
5108
-        return $field_obj instanceof EE_Primary_Key_Field_Base;
5109
-    }
5110
-
5111
-
5112
-    /**
5113
-     * gets the field object of type 'primary_key' from the fieldsSettings attribute.
5114
-     * Eg, on EE_Answer that would be ANS_ID field object
5115
-     *
5116
-     * @return EE_Primary_Key_Field_Base
5117
-     * @throws EE_Error
5118
-     */
5119
-    public function get_primary_key_field()
5120
-    {
5121
-        if ($this->_primary_key_field === null) {
5122
-            foreach ($this->field_settings(true) as $field_obj) {
5123
-                if ($this->is_primary_key_field($field_obj)) {
5124
-                    $this->_primary_key_field = $field_obj;
5125
-                    break;
5126
-                }
5127
-            }
5128
-            if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5129
-                throw new EE_Error(
5130
-                    sprintf(
5131
-                        esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
5132
-                        $this->class_name
5133
-                    )
5134
-                );
5135
-            }
5136
-        }
5137
-        return $this->_primary_key_field;
5138
-    }
5139
-
5140
-
5141
-    /**
5142
-     * Returns whether not there is a primary key on this model.
5143
-     * Internally does some caching.
5144
-     *
5145
-     * @return boolean
5146
-     */
5147
-    public function has_primary_key_field()
5148
-    {
5149
-        if ($this->_has_primary_key_field === null) {
5150
-            try {
5151
-                $this->get_primary_key_field();
5152
-                $this->_has_primary_key_field = true;
5153
-            } catch (EE_Error $e) {
5154
-                $this->_has_primary_key_field = false;
5155
-            }
5156
-        }
5157
-        return $this->_has_primary_key_field;
5158
-    }
5159
-
5160
-
5161
-    /**
5162
-     * Finds the first field of type $field_class_name.
5163
-     *
5164
-     * @param string $field_class_name class name of field that you want to find. Eg, EE_Datetime_Field,
5165
-     *                                 EE_Foreign_Key_Field, etc
5166
-     * @return EE_Model_Field_Base or null if none is found
5167
-     */
5168
-    public function get_a_field_of_type($field_class_name)
5169
-    {
5170
-        foreach ($this->field_settings() as $field) {
5171
-            if ($field instanceof $field_class_name) {
5172
-                return $field;
5173
-            }
5174
-        }
5175
-        return null;
5176
-    }
5177
-
5178
-
5179
-    /**
5180
-     * Gets a foreign key field pointing to model.
5181
-     *
5182
-     * @param string $model_name eg Event, Registration, not EEM_Event
5183
-     * @return EE_Foreign_Key_Field_Base
5184
-     * @throws EE_Error
5185
-     */
5186
-    public function get_foreign_key_to($model_name)
5187
-    {
5188
-        if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5189
-            foreach ($this->field_settings() as $field) {
5190
-                if (
5191
-                    $field instanceof EE_Foreign_Key_Field_Base
5192
-                    && in_array($model_name, $field->get_model_names_pointed_to())
5193
-                ) {
5194
-                    $this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5195
-                    break;
5196
-                }
5197
-            }
5198
-            if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5199
-                throw new EE_Error(
5200
-                    sprintf(
5201
-                        esc_html__(
5202
-                            "There is no foreign key field pointing to model %s on model %s",
5203
-                            'event_espresso'
5204
-                        ),
5205
-                        $model_name,
5206
-                        $this->class_name
5207
-                    )
5208
-                );
5209
-            }
5210
-        }
5211
-        return $this->_cache_foreign_key_to_fields[ $model_name ];
5212
-    }
5213
-
5214
-
5215
-    /**
5216
-     * Gets the table name (including $wpdb->prefix) for the table alias
5217
-     *
5218
-     * @param string $table_alias eg Event, Event_Meta, Registration, Transaction, but maybe
5219
-     *                            a table alias with a model chain prefix, like 'Venue__Event_Venue___Event_Meta'.
5220
-     *                            Either one works
5221
-     * @return string
5222
-     */
5223
-    public function get_table_for_alias($table_alias)
5224
-    {
5225
-        $table_alias_sans_model_relation_chain_prefix =
5226
-            EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5227
-        return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5228
-    }
5229
-
5230
-
5231
-    /**
5232
-     * Returns a flat array of all field son this model, instead of organizing them
5233
-     * by table_alias as they are in the constructor.
5234
-     *
5235
-     * @param bool $include_db_only_fields flag indicating whether to include the db-only fields
5236
-     * @return EE_Model_Field_Base[] where the keys are the field's name
5237
-     */
5238
-    public function field_settings($include_db_only_fields = false)
5239
-    {
5240
-        if ($include_db_only_fields) {
5241
-            if ($this->_cached_fields === null) {
5242
-                $this->_cached_fields = [];
5243
-                foreach ($this->_fields as $fields_corresponding_to_table) {
5244
-                    foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5245
-                        $this->_cached_fields[ $field_name ] = $field_obj;
5246
-                    }
5247
-                }
5248
-            }
5249
-            return $this->_cached_fields;
5250
-        }
5251
-        if ($this->_cached_fields_non_db_only === null) {
5252
-            $this->_cached_fields_non_db_only = [];
5253
-            foreach ($this->_fields as $fields_corresponding_to_table) {
5254
-                foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5255
-                    /** @var $field_obj EE_Model_Field_Base */
5256
-                    if (! $field_obj->is_db_only_field()) {
5257
-                        $this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5258
-                    }
5259
-                }
5260
-            }
5261
-        }
5262
-        return $this->_cached_fields_non_db_only;
5263
-    }
5264
-
5265
-
5266
-    /**
5267
-     *        cycle though array of attendees and create objects out of each item
5268
-     *
5269
-     * @access        private
5270
-     * @param array $rows        of results of $wpdb->get_results($query,ARRAY_A)
5271
-     * @return EE_Base_Class[] array keys are primary keys (if there is a primary key on the model. if not,
5272
-     *                           numerically indexed)
5273
-     * @throws EE_Error
5274
-     * @throws ReflectionException
5275
-     */
5276
-    protected function _create_objects($rows = [])
5277
-    {
5278
-        $array_of_objects = [];
5279
-        if (empty($rows)) {
5280
-            return [];
5281
-        }
5282
-        $count_if_model_has_no_primary_key = 0;
5283
-        $has_primary_key                   = $this->has_primary_key_field();
5284
-        $primary_key_field                 = $has_primary_key
5285
-            ? $this->get_primary_key_field()
5286
-            : null;
5287
-        foreach ((array) $rows as $row) {
5288
-            if (empty($row)) {
5289
-                // wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5290
-                return [];
5291
-            }
5292
-            // check if we've already set this object in the results array,
5293
-            // in which case there's no need to process it further (again)
5294
-            if ($has_primary_key) {
5295
-                $table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5296
-                    $row,
5297
-                    $primary_key_field->get_qualified_column(),
5298
-                    $primary_key_field->get_table_column()
5299
-                );
5300
-                if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5301
-                    continue;
5302
-                }
5303
-            }
5304
-            $classInstance = $this->instantiate_class_from_array_or_object($row);
5305
-            if (! $classInstance) {
5306
-                throw new EE_Error(
5307
-                    sprintf(
5308
-                        esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
5309
-                        $this->get_this_model_name(),
5310
-                        http_build_query($row)
5311
-                    )
5312
-                );
5313
-            }
5314
-            // set the timezone on the instantiated objects
5315
-            $classInstance->set_timezone($this->_timezone);
5316
-            // make sure if there is any timezone setting present that we set the timezone for the object
5317
-            $key                      = $has_primary_key
5318
-                ? $classInstance->ID()
5319
-                : $count_if_model_has_no_primary_key++;
5320
-            $array_of_objects[ $key ] = $classInstance;
5321
-            // also, for all the relations of type BelongsTo, see if we can cache
5322
-            // those related models
5323
-            // (we could do this for other relations too, but if there are conditions
5324
-            // that filtered out some fo the results, then we'd be caching an incomplete set
5325
-            // so it requires a little more thought than just caching them immediately...)
5326
-            foreach ($this->_model_relations as $modelName => $relation_obj) {
5327
-                if ($relation_obj instanceof EE_Belongs_To_Relation) {
5328
-                    // check if this model's INFO is present. If so, cache it on the model
5329
-                    $other_model           = $relation_obj->get_other_model();
5330
-                    $other_model_obj_maybe = $other_model->instantiate_class_from_array_or_object($row);
5331
-                    // if we managed to make a model object from the results, cache it on the main model object
5332
-                    if ($other_model_obj_maybe) {
5333
-                        // set timezone on these other model objects if they are present
5334
-                        $other_model_obj_maybe->set_timezone($this->_timezone);
5335
-                        $classInstance->cache($modelName, $other_model_obj_maybe);
5336
-                    }
5337
-                }
5338
-            }
5339
-            // also, if this was a custom select query, let's see if there are any results for the custom select fields
5340
-            // and add them to the object as well.  We'll convert according to the set data_type if there's any set for
5341
-            // the field in the CustomSelects object
5342
-            if ($this->_custom_selections instanceof CustomSelects) {
5343
-                $classInstance->setCustomSelectsValues(
5344
-                    $this->getValuesForCustomSelectAliasesFromResults($row)
5345
-                );
5346
-            }
5347
-        }
5348
-        return $array_of_objects;
5349
-    }
5350
-
5351
-
5352
-    /**
5353
-     * This will parse a given row of results from the db and see if any keys in the results match an alias within the
5354
-     * current CustomSelects object. This will be used to build an array of values indexed by those keys.
5355
-     *
5356
-     * @param array $db_results_row
5357
-     * @return array
5358
-     */
5359
-    protected function getValuesForCustomSelectAliasesFromResults(array $db_results_row)
5360
-    {
5361
-        $results = [];
5362
-        if ($this->_custom_selections instanceof CustomSelects) {
5363
-            foreach ($this->_custom_selections->columnAliases() as $alias) {
5364
-                if (isset($db_results_row[ $alias ])) {
5365
-                    $results[ $alias ] = $this->convertValueToDataType(
5366
-                        $db_results_row[ $alias ],
5367
-                        $this->_custom_selections->getDataTypeForAlias($alias)
5368
-                    );
5369
-                }
5370
-            }
5371
-        }
5372
-        return $results;
5373
-    }
5374
-
5375
-
5376
-    /**
5377
-     * This will set the value for the given alias
5378
-     *
5379
-     * @param string $value
5380
-     * @param string $datatype (one of %d, %s, %f)
5381
-     * @return int|string|float (int for %d, string for %s, float for %f)
5382
-     */
5383
-    protected function convertValueToDataType($value, $datatype)
5384
-    {
5385
-        switch ($datatype) {
5386
-            case '%f':
5387
-                return (float) $value;
5388
-            case '%d':
5389
-                return (int) $value;
5390
-            default:
5391
-                return (string) $value;
5392
-        }
5393
-    }
5394
-
5395
-
5396
-    /**
5397
-     * The purpose of this method is to allow us to create a model object that is not in the db that holds default
5398
-     * values. A typical example of where this is used is when creating a new item and the initial load of a form.  We
5399
-     * dont' necessarily want to test for if the object is present but just assume it is BUT load the defaults from the
5400
-     * object (as set in the model_field!).
5401
-     *
5402
-     * @return EE_Base_Class single EE_Base_Class object with default values for the properties.
5403
-     * @throws EE_Error
5404
-     * @throws ReflectionException
5405
-     */
5406
-    public function create_default_object()
5407
-    {
5408
-        $this_model_fields_and_values = [];
5409
-        // setup the row using default values;
5410
-        foreach ($this->field_settings() as $field_name => $field_obj) {
5411
-            $this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5412
-        }
5413
-        $className = $this->_get_class_name();
5414
-        return EE_Registry::instance()->load_class($className, [$this_model_fields_and_values], false, false);
5415
-    }
5416
-
5417
-
5418
-    /**
5419
-     * @param mixed $cols_n_values either an array of where each key is the name of a field, and the value is its value
5420
-     *                             or an stdClass where each property is the name of a column,
5421
-     * @return EE_Base_Class
5422
-     * @throws EE_Error
5423
-     * @throws ReflectionException
5424
-     */
5425
-    public function instantiate_class_from_array_or_object($cols_n_values)
5426
-    {
5427
-        if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5428
-            $cols_n_values = get_object_vars($cols_n_values);
5429
-        }
5430
-        $primary_key = null;
5431
-        // make sure the array only has keys that are fields/columns on this model
5432
-        $this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5433
-        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5434
-            $primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5435
-        }
5436
-        $className = $this->_get_class_name();
5437
-        // check we actually found results that we can use to build our model object
5438
-        // if not, return null
5439
-        if ($this->has_primary_key_field()) {
5440
-            if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5441
-                return null;
5442
-            }
5443
-        } elseif ($this->unique_indexes()) {
5444
-            $first_column = reset($this_model_fields_n_values);
5445
-            if (empty($first_column)) {
5446
-                return null;
5447
-            }
5448
-        }
5449
-        // if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5450
-        if ($primary_key) {
5451
-            $classInstance = $this->get_from_entity_map($primary_key);
5452
-            if (! $classInstance) {
5453
-                $classInstance = EE_Registry::instance()
5454
-                                            ->load_class(
5455
-                                                $className,
5456
-                                                [$this_model_fields_n_values, $this->_timezone],
5457
-                                                true,
5458
-                                                false
5459
-                                            );
5460
-                // add this new object to the entity map
5461
-                $classInstance = $this->add_to_entity_map($classInstance);
5462
-            }
5463
-        } else {
5464
-            $classInstance = EE_Registry::instance()->load_class(
5465
-                $className,
5466
-                [$this_model_fields_n_values, $this->_timezone],
5467
-                true,
5468
-                false
5469
-            );
5470
-        }
5471
-        return $classInstance;
5472
-    }
5473
-
5474
-
5475
-    /**
5476
-     * Gets the model object from the  entity map if it exists
5477
-     *
5478
-     * @param int|string $id the ID of the model object
5479
-     * @return EE_Base_Class
5480
-     */
5481
-    public function get_from_entity_map($id)
5482
-    {
5483
-        return $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] ?? null;
5484
-    }
5485
-
5486
-
5487
-    /**
5488
-     * add_to_entity_map
5489
-     * Adds the object to the model's entity mappings
5490
-     *        Effectively tells the models "Hey, this model object is the most up-to-date representation of the data,
5491
-     *        and for the remainder of the request, it's even more up-to-date than what's in the database.
5492
-     *        So, if the database doesn't agree with what's in the entity mapper, ignore the database"
5493
-     *        If the database gets updated directly and you want the entity mapper to reflect that change,
5494
-     *        then this method should be called immediately after the update query
5495
-     * Note: The map is indexed by whatever the current blog id is set (via EEM_Base::$_model_query_blog_id).  This is
5496
-     * so on multisite, the entity map is specific to the query being done for a specific site.
5497
-     *
5498
-     * @param EE_Base_Class $object
5499
-     * @return EE_Base_Class
5500
-     * @throws EE_Error
5501
-     * @throws ReflectionException
5502
-     */
5503
-    public function add_to_entity_map(EE_Base_Class $object)
5504
-    {
5505
-        $className = $this->_get_class_name();
5506
-        if (! $object instanceof $className) {
5507
-            throw new EE_Error(
5508
-                sprintf(
5509
-                    esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
5510
-                    is_object($object)
5511
-                        ? get_class($object)
5512
-                        : $object,
5513
-                    $className
5514
-                )
5515
-            );
5516
-        }
5517
-
5518
-        if (! $object->ID()) {
5519
-            throw new EE_Error(
5520
-                sprintf(
5521
-                    esc_html__(
5522
-                        "You tried storing a model object with NO ID in the %s entity mapper.",
5523
-                        "event_espresso"
5524
-                    ),
5525
-                    $this->class_name
5526
-                )
5527
-            );
5528
-        }
5529
-        // double check it's not already there
5530
-        $classInstance = $this->get_from_entity_map($object->ID());
5531
-        if ($classInstance) {
5532
-            return $classInstance;
5533
-        }
5534
-        $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5535
-        return $object;
5536
-    }
5537
-
5538
-
5539
-    /**
5540
-     * if a valid identifier is provided, then that entity is unset from the entity map,
5541
-     * if no identifier is provided, then the entire entity map is emptied
5542
-     *
5543
-     * @param int|string $id the ID of the model object
5544
-     * @return boolean
5545
-     */
5546
-    public function clear_entity_map($id = null)
5547
-    {
5548
-        if (empty($id)) {
5549
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ] = [];
5550
-            return true;
5551
-        }
5552
-        if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5553
-            unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5554
-            return true;
5555
-        }
5556
-        return false;
5557
-    }
5558
-
5559
-
5560
-    /**
5561
-     * Public wrapper for _deduce_fields_n_values_from_cols_n_values.
5562
-     * Given an array where keys are column (or column alias) names and values,
5563
-     * returns an array of their corresponding field names and database values
5564
-     *
5565
-     * @param array $cols_n_values
5566
-     * @return array
5567
-     * @throws EE_Error
5568
-     * @throws ReflectionException
5569
-     */
5570
-    public function deduce_fields_n_values_from_cols_n_values(array $cols_n_values): array
5571
-    {
5572
-        return $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5573
-    }
5574
-
5575
-
5576
-    /**
5577
-     * _deduce_fields_n_values_from_cols_n_values
5578
-     * Given an array where keys are column (or column alias) names and values,
5579
-     * returns an array of their corresponding field names and database values
5580
-     *
5581
-     * @param array|stdClass $cols_n_values
5582
-     * @return array
5583
-     * @throws EE_Error
5584
-     * @throws ReflectionException
5585
-     */
5586
-    protected function _deduce_fields_n_values_from_cols_n_values($cols_n_values): array
5587
-    {
5588
-        if ($cols_n_values instanceof stdClass) {
5589
-            $cols_n_values = get_object_vars($cols_n_values);
5590
-        }
5591
-        $this_model_fields_n_values = [];
5592
-        foreach ($this->get_tables() as $table_alias => $table_obj) {
5593
-            $table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5594
-                $cols_n_values,
5595
-                $table_obj->get_fully_qualified_pk_column(),
5596
-                $table_obj->get_pk_column()
5597
-            );
5598
-            // there is a primary key on this table and its not set. Use defaults for all its columns
5599
-            if ($table_pk_value === null && $table_obj->get_pk_column()) {
5600
-                foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5601
-                    if (! $field_obj->is_db_only_field()) {
5602
-                        // prepare field as if its coming from db
5603
-                        $prepared_value                            =
5604
-                            $field_obj->prepare_for_set($field_obj->get_default_value());
5605
-                        $this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5606
-                    }
5607
-                }
5608
-            } else {
5609
-                // the table's rows existed. Use their values
5610
-                foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5611
-                    if (! $field_obj->is_db_only_field()) {
5612
-                        $this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5613
-                            $cols_n_values,
5614
-                            $field_obj->get_qualified_column(),
5615
-                            $field_obj->get_table_column()
5616
-                        );
5617
-                    }
5618
-                }
5619
-            }
5620
-        }
5621
-        return $this_model_fields_n_values;
5622
-    }
5623
-
5624
-
5625
-    /**
5626
-     * @param $cols_n_values
5627
-     * @param $qualified_column
5628
-     * @param $regular_column
5629
-     * @return null
5630
-     * @throws EE_Error
5631
-     * @throws ReflectionException
5632
-     */
5633
-    protected function _get_column_value_with_table_alias_or_not($cols_n_values, $qualified_column, $regular_column)
5634
-    {
5635
-        $value = null;
5636
-        // ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5637
-        // does the field on the model relate to this column retrieved from the db?
5638
-        // or is it a db-only field? (not relating to the model)
5639
-        if (isset($cols_n_values[ $qualified_column ])) {
5640
-            $value = $cols_n_values[ $qualified_column ];
5641
-        } elseif (isset($cols_n_values[ $regular_column ])) {
5642
-            $value = $cols_n_values[ $regular_column ];
5643
-        } elseif (! empty($this->foreign_key_aliases)) {
5644
-            // no PK?  ok check if there is a foreign key alias set for this table
5645
-            // then check if that alias exists in the incoming data
5646
-            // AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5647
-            foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5648
-                if ($PK_column === $qualified_column && !empty($cols_n_values[ $FK_alias ])) {
5649
-                    $value = $cols_n_values[ $FK_alias ];
5650
-                    [$pk_class] = explode('.', $PK_column);
5651
-                    $pk_model_name = "EEM_{$pk_class}";
5652
-                    /** @var EEM_Base $pk_model */
5653
-                    $pk_model = EE_Registry::instance()->load_model($pk_model_name);
5654
-                    if ($pk_model instanceof EEM_Base) {
5655
-                        // make sure object is pulled from db and added to entity map
5656
-                        $pk_model->get_one_by_ID($value);
5657
-                    }
5658
-                    break;
5659
-                }
5660
-            }
5661
-        }
5662
-        return $value;
5663
-    }
5664
-
5665
-
5666
-    /**
5667
-     * refresh_entity_map_from_db
5668
-     * Makes sure the model object in the entity map at $id assumes the values
5669
-     * of the database (opposite of EE_base_Class::save())
5670
-     *
5671
-     * @param int|string $id
5672
-     * @return EE_Base_Class|EE_Soft_Delete_Base_Class|mixed|null
5673
-     * @throws EE_Error
5674
-     * @throws ReflectionException
5675
-     */
5676
-    public function refresh_entity_map_from_db($id)
5677
-    {
5678
-        $obj_in_map = $this->get_from_entity_map($id);
5679
-        if ($obj_in_map) {
5680
-            $wpdb_results = $this->_get_all_wpdb_results(
5681
-                [[$this->get_primary_key_field()->get_name() => $id], 'limit' => 1]
5682
-            );
5683
-            if ($wpdb_results && is_array($wpdb_results)) {
5684
-                $one_row = reset($wpdb_results);
5685
-                foreach ($this->_deduce_fields_n_values_from_cols_n_values($one_row) as $field_name => $db_value) {
5686
-                    $obj_in_map->set_from_db($field_name, $db_value);
5687
-                }
5688
-                // clear the cache of related model objects
5689
-                foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5690
-                    $obj_in_map->clear_cache($relation_name, null, true);
5691
-                }
5692
-            }
5693
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5694
-            return $obj_in_map;
5695
-        }
5696
-        return $this->get_one_by_ID($id);
5697
-    }
5698
-
5699
-
5700
-    /**
5701
-     * refresh_entity_map_with
5702
-     * Leaves the entry in the entity map alone, but updates it to match the provided
5703
-     * $replacing_model_obj (which we assume to be its equivalent but somehow NOT in the entity map).
5704
-     * This is useful if you have a model object you want to make authoritative over what's in the entity map currently.
5705
-     * Note: The old $replacing_model_obj should now be destroyed as it's now un-authoritative
5706
-     *
5707
-     * @param int|string    $id
5708
-     * @param EE_Base_Class $replacing_model_obj
5709
-     * @return EE_Base_Class
5710
-     * @throws EE_Error
5711
-     * @throws ReflectionException
5712
-     */
5713
-    public function refresh_entity_map_with($id, $replacing_model_obj)
5714
-    {
5715
-        $obj_in_map = $this->get_from_entity_map($id);
5716
-        if ($obj_in_map) {
5717
-            if ($replacing_model_obj instanceof EE_Base_Class) {
5718
-                foreach ($replacing_model_obj->model_field_array() as $field_name => $value) {
5719
-                    $obj_in_map->set($field_name, $value);
5720
-                }
5721
-                // make the model object in the entity map's cache match the $replacing_model_obj
5722
-                foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5723
-                    $obj_in_map->clear_cache($relation_name, null, true);
5724
-                    foreach ($replacing_model_obj->get_all_from_cache($relation_name) as $cache_id => $cached_obj) {
5725
-                        $obj_in_map->cache($relation_name, $cached_obj, $cache_id);
5726
-                    }
5727
-                }
5728
-            }
5729
-            return $obj_in_map;
5730
-        }
5731
-        $this->add_to_entity_map($replacing_model_obj);
5732
-        return $replacing_model_obj;
5733
-    }
5734
-
5735
-
5736
-    /**
5737
-     * Gets the EE class that corresponds to this model. Eg, for EEM_Answer that
5738
-     * would be EE_Answer.To import that class, you'd just add ".class.php" to the name, like so
5739
-     * require_once($this->_getClassName().".class.php");
5740
-     *
5741
-     * @return string
5742
-     */
5743
-    private function _get_class_name()
5744
-    {
5745
-        return "EE_" . $this->get_this_model_name();
5746
-    }
5747
-
5748
-
5749
-    /**
5750
-     * Get the name of the items this model represents, for the quantity specified. Eg,
5751
-     * if $quantity==1, on EEM_Event, it would 'Event' (internationalized), otherwise
5752
-     * it would be 'Events'.
5753
-     *
5754
-     * @param int|float|null $quantity
5755
-     * @return string
5756
-     */
5757
-    public function item_name($quantity = 1): string
5758
-    {
5759
-        $quantity = floor($quantity);
5760
-        return apply_filters(
5761
-            'FHEE__EEM_Base__item_name__plural_or_singular',
5762
-            $quantity > 1
5763
-                ? $this->plural_item
5764
-                : $this->singular_item,
5765
-            $quantity,
5766
-            $this->plural_item,
5767
-            $this->singular_item
5768
-        );
5769
-    }
5770
-
5771
-
5772
-    /**
5773
-     * Very handy general function to allow for plugins to extend any child of EE_TempBase.
5774
-     * If a method is called on a child of EE_TempBase that doesn't exist, this function is called
5775
-     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
5776
-     * requiring a plugin to extend the EE_TempBase (which works fine is there's only 1 plugin, but when will that
5777
-     * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
5778
-     * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
5779
-     * was called, and an array of the original arguments passed to the function. Whatever their callback function
5780
-     * returns will be returned by this function. Example: in functions.php (or in a plugin):
5781
-     * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
5782
-     * my_callback($previousReturnValue,EE_TempBase $object,$argsArray){
5783
-     * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
5784
-     *        return $previousReturnValue.$returnString;
5785
-     * }
5786
-     * require('EEM_Answer.model.php');
5787
-     * echo EEM_Answer::instance()->my_callback('monkeys',100);
5788
-     * // will output "you called my_callback! and passed args:monkeys,100"
5789
-     *
5790
-     * @param string $methodName name of method which was called on a child of EE_TempBase, but which
5791
-     * @param array  $args       array of original arguments passed to the function
5792
-     * @return mixed whatever the plugin which calls add_filter decides
5793
-     * @throws EE_Error
5794
-     */
5795
-    public function __call($methodName, $args)
5796
-    {
5797
-        $className = $this->class_name;
5798
-        $tagName   = "FHEE__{$className}__{$methodName}";
5799
-        if (! has_filter($tagName)) {
5800
-            throw new EE_Error(
5801
-                sprintf(
5802
-                    esc_html__(
5803
-                        '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 );',
5804
-                        'event_espresso'
5805
-                    ),
5806
-                    $methodName,
5807
-                    $className,
5808
-                    $tagName,
5809
-                    '<br />'
5810
-                )
5811
-            );
5812
-        }
5813
-        return apply_filters($tagName, null, $this, $args);
5814
-    }
5815
-
5816
-
5817
-    /**
5818
-     * Ensures $base_class_obj_or_id is of the EE_Base_Class child that corresponds ot this model.
5819
-     * If not, assumes its an ID, and uses $this->get_one_by_ID() to get the EE_Base_Class.
5820
-     *
5821
-     * @param EE_Base_Class|string|int $base_class_obj_or_id either:
5822
-     *                                                       the EE_Base_Class object that corresponds to this Model,
5823
-     *                                                       the object's class name
5824
-     *                                                       or object's ID
5825
-     * @param boolean                  $ensure_is_in_db      if set, we will also verify this model object
5826
-     *                                                       exists in the database. If it does not, we add it
5827
-     * @return EE_Base_Class
5828
-     * @throws EE_Error
5829
-     * @throws ReflectionException
5830
-     */
5831
-    public function ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db = false)
5832
-    {
5833
-        $className = $this->_get_class_name();
5834
-        if ($base_class_obj_or_id instanceof $className) {
5835
-            $model_object = $base_class_obj_or_id;
5836
-        } else {
5837
-            $primary_key_field = $this->get_primary_key_field();
5838
-            if (
5839
-                $primary_key_field instanceof EE_Primary_Key_Int_Field
5840
-                && (
5841
-                    is_int($base_class_obj_or_id)
5842
-                    || is_string($base_class_obj_or_id)
5843
-                )
5844
-            ) {
5845
-                // assume it's an ID.
5846
-                // either a proper integer or a string representing an integer (eg "101" instead of 101)
5847
-                $model_object = $this->get_one_by_ID($base_class_obj_or_id);
5848
-            } elseif (
5849
-                $primary_key_field instanceof EE_Primary_Key_String_Field
5850
-                && is_string($base_class_obj_or_id)
5851
-            ) {
5852
-                // assume it's a string representation of the object
5853
-                $model_object = $this->get_one_by_ID($base_class_obj_or_id);
5854
-            } else {
5855
-                throw new EE_Error(
5856
-                    sprintf(
5857
-                        esc_html__(
5858
-                            "'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5859
-                            'event_espresso'
5860
-                        ),
5861
-                        $base_class_obj_or_id,
5862
-                        $this->_get_class_name(),
5863
-                        print_r($base_class_obj_or_id, true)
5864
-                    )
5865
-                );
5866
-            }
5867
-        }
5868
-        if ($ensure_is_in_db && $model_object instanceof EE_Base_Class && $model_object->ID() !== null) {
5869
-            $model_object->save();
5870
-        }
5871
-        return $model_object;
5872
-    }
5873
-
5874
-
5875
-    /**
5876
-     * Similar to ensure_is_obj(), this method makes sure $base_class_obj_or_id
5877
-     * is a value of the this model's primary key. If it's an EE_Base_Class child,
5878
-     * returns it ID.
5879
-     *
5880
-     * @param EE_Base_Class|int|string $base_class_obj_or_id
5881
-     * @return int|string depending on the type of this model object's ID
5882
-     * @throws EE_Error
5883
-     * @throws ReflectionException
5884
-     */
5885
-    public function ensure_is_ID($base_class_obj_or_id)
5886
-    {
5887
-        $className = $this->_get_class_name();
5888
-        if ($base_class_obj_or_id instanceof $className) {
5889
-            /** @var $base_class_obj_or_id EE_Base_Class */
5890
-            $id = $base_class_obj_or_id->ID();
5891
-        } elseif (is_int($base_class_obj_or_id)) {
5892
-            // assume it's an ID
5893
-            $id = $base_class_obj_or_id;
5894
-        } elseif (is_string($base_class_obj_or_id)) {
5895
-            // assume its a string representation of the object
5896
-            $id = $base_class_obj_or_id;
5897
-        } else {
5898
-            throw new EE_Error(
5899
-                sprintf(
5900
-                    esc_html__(
5901
-                        "'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5902
-                        'event_espresso'
5903
-                    ),
5904
-                    $base_class_obj_or_id,
5905
-                    $this->_get_class_name(),
5906
-                    print_r($base_class_obj_or_id, true)
5907
-                )
5908
-            );
5909
-        }
5910
-        return $id;
5911
-    }
5912
-
5913
-
5914
-    /**
5915
-     * Sets whether the values passed to the model (eg, values in WHERE, values in INSERT, UPDATE, etc)
5916
-     * have already been ran through the appropriate model field's prepare_for_use_in_db method. IE, they have
5917
-     * been sanitized and converted into the appropriate domain.
5918
-     * Usually the only place you'll want to change the default (which is to assume values have NOT been sanitized by
5919
-     * the model object/model field) is when making a method call from WITHIN a model object, which has direct access
5920
-     * to its sanitized values. Note: after changing this setting, you should set it back to its previous value (using
5921
-     * get_assumption_concerning_values_already_prepared_by_model_object()) eg.
5922
-     * $EVT = EEM_Event::instance(); $old_setting =
5923
-     * $EVT->get_assumption_concerning_values_already_prepared_by_model_object();
5924
-     * $EVT->assume_values_already_prepared_by_model_object(true);
5925
-     * $EVT->update(array('foo'=>'bar'),array(array('foo'=>'monkey')));
5926
-     * $EVT->assume_values_already_prepared_by_model_object($old_setting);
5927
-     *
5928
-     * @param int $values_already_prepared like one of the constants on EEM_Base
5929
-     * @return void
5930
-     */
5931
-    public function assume_values_already_prepared_by_model_object(
5932
-        $values_already_prepared = self::not_prepared_by_model_object
5933
-    ) {
5934
-        $this->_values_already_prepared_by_model_object = $values_already_prepared;
5935
-    }
5936
-
5937
-
5938
-    /**
5939
-     * Read comments for assume_values_already_prepared_by_model_object()
5940
-     *
5941
-     * @return int
5942
-     */
5943
-    public function get_assumption_concerning_values_already_prepared_by_model_object()
5944
-    {
5945
-        return $this->_values_already_prepared_by_model_object;
5946
-    }
5947
-
5948
-
5949
-    /**
5950
-     * Gets all the indexes on this model
5951
-     *
5952
-     * @return EE_Index[]
5953
-     */
5954
-    public function indexes()
5955
-    {
5956
-        return $this->_indexes;
5957
-    }
5958
-
5959
-
5960
-    /**
5961
-     * Gets all the Unique Indexes on this model
5962
-     *
5963
-     * @return EE_Unique_Index[]
5964
-     */
5965
-    public function unique_indexes()
5966
-    {
5967
-        $unique_indexes = [];
5968
-        foreach ($this->_indexes as $name => $index) {
5969
-            if ($index instanceof EE_Unique_Index) {
5970
-                $unique_indexes [ $name ] = $index;
5971
-            }
5972
-        }
5973
-        return $unique_indexes;
5974
-    }
5975
-
5976
-
5977
-    /**
5978
-     * Gets all the fields which, when combined, make the primary key.
5979
-     * This is usually just an array with 1 element (the primary key), but in cases
5980
-     * where there is no primary key, it's a combination of fields as defined
5981
-     * on a primary index
5982
-     *
5983
-     * @return EE_Model_Field_Base[] indexed by the field's name
5984
-     * @throws EE_Error
5985
-     */
5986
-    public function get_combined_primary_key_fields()
5987
-    {
5988
-        foreach ($this->indexes() as $index) {
5989
-            if ($index instanceof EE_Primary_Key_Index) {
5990
-                return $index->fields();
5991
-            }
5992
-        }
5993
-        return [$this->primary_key_name() => $this->get_primary_key_field()];
5994
-    }
5995
-
5996
-
5997
-    /**
5998
-     * Used to build a primary key string (when the model has no primary key),
5999
-     * which can be used a unique string to identify this model object.
6000
-     *
6001
-     * @param array $fields_n_values keys are field names, values are their values.
6002
-     *                               Note: if you have results from `EEM_Base::get_all_wpdb_results()`, you need to
6003
-     *                               run it through `EEM_Base::deduce_fields_n_values_from_cols_n_values()`
6004
-     *                               before passing it to this function (that will convert it from columns-n-values
6005
-     *                               to field-names-n-values).
6006
-     * @return string
6007
-     * @throws EE_Error
6008
-     */
6009
-    public function get_index_primary_key_string($fields_n_values)
6010
-    {
6011
-        $cols_n_values_for_primary_key_index = array_intersect_key(
6012
-            $fields_n_values,
6013
-            $this->get_combined_primary_key_fields()
6014
-        );
6015
-        return http_build_query($cols_n_values_for_primary_key_index);
6016
-    }
6017
-
6018
-
6019
-    /**
6020
-     * Gets the field values from the primary key string
6021
-     *
6022
-     * @param string $index_primary_key_string
6023
-     * @return null|array
6024
-     * @throws EE_Error
6025
-     * @see EEM_Base::get_combined_primary_key_fields() and EEM_Base::get_index_primary_key_string()
6026
-     */
6027
-    public function parse_index_primary_key_string($index_primary_key_string)
6028
-    {
6029
-        $key_fields = $this->get_combined_primary_key_fields();
6030
-        // check all of them are in the $id
6031
-        $key_vals_in_combined_pk = [];
6032
-        parse_str($index_primary_key_string, $key_vals_in_combined_pk);
6033
-        foreach ($key_fields as $key_field_name => $field_obj) {
6034
-            if (! isset($key_vals_in_combined_pk[ $key_field_name ])) {
6035
-                return null;
6036
-            }
6037
-        }
6038
-        return $key_vals_in_combined_pk;
6039
-    }
6040
-
6041
-
6042
-    /**
6043
-     * verifies that an array of key-value pairs for model fields has a key
6044
-     * for each field comprising the primary key index
6045
-     *
6046
-     * @param array $key_vals
6047
-     * @return boolean
6048
-     * @throws EE_Error
6049
-     */
6050
-    public function has_all_combined_primary_key_fields($key_vals)
6051
-    {
6052
-        $keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
6053
-        foreach ($keys_it_should_have as $key) {
6054
-            if (! isset($key_vals[ $key ])) {
6055
-                return false;
6056
-            }
6057
-        }
6058
-        return true;
6059
-    }
6060
-
6061
-
6062
-    /**
6063
-     * Finds all model objects in the DB that appear to be a copy of $model_object_or_attributes_array.
6064
-     * We consider something to be a copy if all the attributes match (except the ID, of course).
6065
-     *
6066
-     * @param array|EE_Base_Class $model_object_or_attributes_array If its an array, it's field-value pairs
6067
-     * @param array               $query_params                     @see
6068
-     *                                                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
6069
-     * @throws EE_Error
6070
-     * @throws ReflectionException
6071
-     * @return EE_Base_Class[] Array keys are object IDs (if there is a primary key on the model. if not, numerically
6072
-     *                                                              indexed)
6073
-     */
6074
-    public function get_all_copies($model_object_or_attributes_array, $query_params = [])
6075
-    {
6076
-        if ($model_object_or_attributes_array instanceof EE_Base_Class) {
6077
-            $attributes_array = $model_object_or_attributes_array->model_field_array();
6078
-        } elseif (is_array($model_object_or_attributes_array)) {
6079
-            $attributes_array = $model_object_or_attributes_array;
6080
-        } else {
6081
-            throw new EE_Error(
6082
-                sprintf(
6083
-                    esc_html__(
6084
-                        "get_all_copies should be provided with either a model object or an array of field-value-pairs, but was given %s",
6085
-                        "event_espresso"
6086
-                    ),
6087
-                    $model_object_or_attributes_array
6088
-                )
6089
-            );
6090
-        }
6091
-        // even copies obviously won't have the same ID, so remove the primary key
6092
-        // from the WHERE conditions for finding copies (if there is a primary key, of course)
6093
-        if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
6094
-            unset($attributes_array[ $this->primary_key_name() ]);
6095
-        }
6096
-        if (isset($query_params[0])) {
6097
-            $query_params[0] = array_merge($attributes_array, $query_params);
6098
-        } else {
6099
-            $query_params[0] = $attributes_array;
6100
-        }
6101
-        return $this->get_all($query_params);
6102
-    }
6103
-
6104
-
6105
-    /**
6106
-     * Gets the first copy we find. See get_all_copies for more details
6107
-     *
6108
-     * @param mixed EE_Base_Class | array        $model_object_or_attributes_array
6109
-     * @param array $query_params
6110
-     * @return EE_Base_Class
6111
-     * @throws EE_Error
6112
-     * @throws ReflectionException
6113
-     */
6114
-    public function get_one_copy($model_object_or_attributes_array, $query_params = [])
6115
-    {
6116
-        if (! is_array($query_params)) {
6117
-            EE_Error::doing_it_wrong(
6118
-                'EEM_Base::get_one_copy',
6119
-                sprintf(
6120
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
6121
-                    gettype($query_params)
6122
-                ),
6123
-                '4.6.0'
6124
-            );
6125
-            $query_params = [];
6126
-        }
6127
-        $query_params['limit'] = 1;
6128
-        $copies                = $this->get_all_copies($model_object_or_attributes_array, $query_params);
6129
-        if (is_array($copies)) {
6130
-            return array_shift($copies);
6131
-        }
6132
-        return null;
6133
-    }
6134
-
6135
-
6136
-    /**
6137
-     * Updates the item with the specified id. Ignores default query parameters because
6138
-     * we have specified the ID, and its assumed we KNOW what we're doing
6139
-     *
6140
-     * @param array      $fields_n_values keys are field names, values are their new values
6141
-     * @param int|string $id              the value of the primary key to update
6142
-     * @return int number of rows updated
6143
-     * @throws EE_Error
6144
-     * @throws ReflectionException
6145
-     */
6146
-    public function update_by_ID($fields_n_values, $id)
6147
-    {
6148
-        $query_params = [
6149
-            0                          => [$this->get_primary_key_field()->get_name() => $id],
6150
-            'default_where_conditions' => EE_Default_Where_Conditions::OTHER_MODELS_ONLY,
6151
-        ];
6152
-        return $this->update($fields_n_values, $query_params);
6153
-    }
6154
-
6155
-
6156
-    /**
6157
-     * Changes an operator which was supplied to the models into one usable in SQL
6158
-     *
6159
-     * @param string $operator_supplied
6160
-     * @return string an operator which can be used in SQL
6161
-     * @throws EE_Error
6162
-     */
6163
-    private function _prepare_operator_for_sql($operator_supplied)
6164
-    {
6165
-        $sql_operator = $this->_valid_operators[ $operator_supplied ] ?? null;
6166
-        if ($sql_operator) {
6167
-            return $sql_operator;
6168
-        }
6169
-        throw new EE_Error(
6170
-            sprintf(
6171
-                esc_html__(
6172
-                    "The operator '%s' is not in the list of valid operators: %s",
6173
-                    "event_espresso"
6174
-                ),
6175
-                $operator_supplied,
6176
-                implode(",", array_keys($this->_valid_operators))
6177
-            )
6178
-        );
6179
-    }
6180
-
6181
-
6182
-    /**
6183
-     * Gets the valid operators
6184
-     *
6185
-     * @return array keys are accepted strings, values are the SQL they are converted to
6186
-     */
6187
-    public function valid_operators()
6188
-    {
6189
-        return $this->_valid_operators;
6190
-    }
6191
-
6192
-
6193
-    /**
6194
-     * Gets the between-style operators (take 2 arguments).
6195
-     *
6196
-     * @return array keys are accepted strings, values are the SQL they are converted to
6197
-     */
6198
-    public function valid_between_style_operators()
6199
-    {
6200
-        return array_intersect(
6201
-            $this->valid_operators(),
6202
-            $this->_between_style_operators
6203
-        );
6204
-    }
6205
-
6206
-
6207
-    /**
6208
-     * Gets the "like"-style operators (take a single argument, but it may contain wildcards)
6209
-     *
6210
-     * @return array keys are accepted strings, values are the SQL they are converted to
6211
-     */
6212
-    public function valid_like_style_operators()
6213
-    {
6214
-        return array_intersect(
6215
-            $this->valid_operators(),
6216
-            $this->_like_style_operators
6217
-        );
6218
-    }
6219
-
6220
-
6221
-    /**
6222
-     * Gets the "in"-style operators
6223
-     *
6224
-     * @return array keys are accepted strings, values are the SQL they are converted to
6225
-     */
6226
-    public function valid_in_style_operators()
6227
-    {
6228
-        return array_intersect(
6229
-            $this->valid_operators(),
6230
-            $this->_in_style_operators
6231
-        );
6232
-    }
6233
-
6234
-
6235
-    /**
6236
-     * Gets the "null"-style operators (accept no arguments)
6237
-     *
6238
-     * @return array keys are accepted strings, values are the SQL they are converted to
6239
-     */
6240
-    public function valid_null_style_operators()
6241
-    {
6242
-        return array_intersect(
6243
-            $this->valid_operators(),
6244
-            $this->_null_style_operators
6245
-        );
6246
-    }
6247
-
6248
-
6249
-    /**
6250
-     * Gets an array where keys are the primary keys and values are their 'names'
6251
-     * (as determined by the model object's name() function, which is often overridden)
6252
-     *
6253
-     * @param array $query_params like get_all's
6254
-     * @return string[]
6255
-     * @throws EE_Error
6256
-     * @throws ReflectionException
6257
-     */
6258
-    public function get_all_names($query_params = [])
6259
-    {
6260
-        $objs  = $this->get_all($query_params);
6261
-        $names = [];
6262
-        foreach ($objs as $obj) {
6263
-            $names[ $obj->ID() ] = $obj->name();
6264
-        }
6265
-        return $names;
6266
-    }
6267
-
6268
-
6269
-    /**
6270
-     * Gets an array of primary keys from the model objects. If you acquired the model objects
6271
-     * using EEM_Base::get_all() you don't need to call this (and probably shouldn't because
6272
-     * this is duplicated effort and reduces efficiency) you would be better to use
6273
-     * array_keys() on $model_objects.
6274
-     *
6275
-     * @param \EE_Base_Class[] $model_objects
6276
-     * @param boolean          $filter_out_empty_ids if a model object has an ID of '' or 0, don't bother including it
6277
-     *                                               in the returned array
6278
-     * @return array
6279
-     * @throws EE_Error
6280
-     * @throws ReflectionException
6281
-     */
6282
-    public function get_IDs($model_objects, $filter_out_empty_ids = false)
6283
-    {
6284
-        if (! $this->has_primary_key_field()) {
6285
-            if (defined('WP_DEBUG') && WP_DEBUG) {
6286
-                EE_Error::add_error(
6287
-                    esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
6288
-                    __FILE__,
6289
-                    __FUNCTION__,
6290
-                    __LINE__
6291
-                );
6292
-            }
6293
-        }
6294
-        $IDs = [];
6295
-        foreach ($model_objects as $model_object) {
6296
-            $id = $model_object->ID();
6297
-            if (! $id) {
6298
-                if ($filter_out_empty_ids) {
6299
-                    continue;
6300
-                }
6301
-                if (defined('WP_DEBUG') && WP_DEBUG) {
6302
-                    EE_Error::add_error(
6303
-                        esc_html__(
6304
-                            'Called %1$s on a model object that has no ID and so probably hasn\'t been saved to the database',
6305
-                            'event_espresso'
6306
-                        ),
6307
-                        __FILE__,
6308
-                        __FUNCTION__,
6309
-                        __LINE__
6310
-                    );
6311
-                }
6312
-            }
6313
-            $IDs[] = $id;
6314
-        }
6315
-        return $IDs;
6316
-    }
6317
-
6318
-
6319
-    /**
6320
-     * Returns the string used in capabilities relating to this model. If there
6321
-     * are no capabilities that relate to this model returns false
6322
-     *
6323
-     * @return string|false
6324
-     */
6325
-    public function cap_slug()
6326
-    {
6327
-        return apply_filters('FHEE__EEM_Base__cap_slug', $this->_caps_slug, $this);
6328
-    }
6329
-
6330
-
6331
-    /**
6332
-     * Returns the capability-restrictions array (@param string $context
6333
-     *
6334
-     * @return EE_Default_Where_Conditions[] indexed by associated capability
6335
-     * @throws EE_Error
6336
-     * @see EEM_Base::_cap_restrictions).
6337
-     *      If $context is provided (which should be set to one of EEM_Base::valid_cap_contexts())
6338
-     *      only returns the cap restrictions array in that context (ie, the array
6339
-     *      at that key)
6340
-     */
6341
-    public function cap_restrictions($context = EEM_Base::caps_read)
6342
-    {
6343
-        EEM_Base::verify_is_valid_cap_context($context);
6344
-        // check if we ought to run the restriction generator first
6345
-        if (
6346
-            isset($this->_cap_restriction_generators[ $context ])
6347
-            && $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6348
-            && ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6349
-        ) {
6350
-            $this->_cap_restrictions[ $context ] = array_merge(
6351
-                $this->_cap_restrictions[ $context ],
6352
-                $this->_cap_restriction_generators[ $context ]->generate_restrictions()
6353
-            );
6354
-        }
6355
-        // and make sure we've finalized the construction of each restriction
6356
-        foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6357
-            if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6358
-                $where_conditions_obj->_finalize_construct($this);
6359
-            }
6360
-        }
6361
-        return $this->_cap_restrictions[ $context ];
6362
-    }
6363
-
6364
-
6365
-    /**
6366
-     * Indicating whether this model thinks its a wp core model
6367
-     *
6368
-     * @return boolean
6369
-     */
6370
-    public function is_wp_core_model()
6371
-    {
6372
-        return $this->_wp_core_model;
6373
-    }
6374
-
6375
-
6376
-    /**
6377
-     * Gets all the caps that are missing which impose a restriction on
6378
-     * queries made in this context
6379
-     *
6380
-     * @param string $context one of EEM_Base::caps_ constants
6381
-     * @return EE_Default_Where_Conditions[] indexed by capability name
6382
-     * @throws EE_Error
6383
-     */
6384
-    public function caps_missing($context = EEM_Base::caps_read)
6385
-    {
6386
-        $missing_caps     = [];
6387
-        $cap_restrictions = $this->cap_restrictions($context);
6388
-        foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6389
-            if (
6390
-                ! EE_Capabilities::instance()
6391
-                                 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6392
-            ) {
6393
-                $missing_caps[ $cap ] = $restriction_if_no_cap;
6394
-            }
6395
-        }
6396
-        return $missing_caps;
6397
-    }
6398
-
6399
-
6400
-    /**
6401
-     * Gets the mapping from capability contexts to action strings used in capability names
6402
-     *
6403
-     * @return array keys are one of EEM_Base::valid_cap_contexts(), and values are usually
6404
-     * one of 'read', 'edit', or 'delete'
6405
-     */
6406
-    public function cap_contexts_to_cap_action_map()
6407
-    {
6408
-        return apply_filters(
6409
-            'FHEE__EEM_Base__cap_contexts_to_cap_action_map',
6410
-            $this->_cap_contexts_to_cap_action_map,
6411
-            $this
6412
-        );
6413
-    }
6414
-
6415
-
6416
-    /**
6417
-     * Gets the action string for the specified capability context
6418
-     *
6419
-     * @param string $context
6420
-     * @return string one of EEM_Base::cap_contexts_to_cap_action_map() values
6421
-     * @throws EE_Error
6422
-     */
6423
-    public function cap_action_for_context($context)
6424
-    {
6425
-        $mapping = $this->cap_contexts_to_cap_action_map();
6426
-        if (isset($mapping[ $context ])) {
6427
-            return $mapping[ $context ];
6428
-        }
6429
-        if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6430
-            return $action;
6431
-        }
6432
-        throw new EE_Error(
6433
-            sprintf(
6434
-                esc_html__(
6435
-                    'Cannot find capability restrictions for context "%1$s", allowed values are:%2$s',
6436
-                    'event_espresso'
6437
-                ),
6438
-                $context,
6439
-                implode(',', array_keys($this->cap_contexts_to_cap_action_map()))
6440
-            )
6441
-        );
6442
-    }
6443
-
6444
-
6445
-    /**
6446
-     * Returns all the capability contexts which are valid when querying models
6447
-     *
6448
-     * @return array
6449
-     */
6450
-    public static function valid_cap_contexts(): array
6451
-    {
6452
-        return (array) apply_filters(
6453
-            'FHEE__EEM_Base__valid_cap_contexts',
6454
-            [
6455
-                self::caps_read,
6456
-                self::caps_read_admin,
6457
-                self::caps_edit,
6458
-                self::caps_delete,
6459
-            ]
6460
-        );
6461
-    }
6462
-
6463
-
6464
-    /**
6465
-     * Returns all valid options for 'default_where_conditions'
6466
-     *
6467
-     * @return array
6468
-     */
6469
-    public static function valid_default_where_conditions(): array
6470
-    {
6471
-        return [
6472
-            EE_Default_Where_Conditions::ALL,
6473
-            EE_Default_Where_Conditions::THIS_MODEL_ONLY,
6474
-            EE_Default_Where_Conditions::OTHER_MODELS_ONLY,
6475
-            EE_Default_Where_Conditions::MINIMUM_ALL,
6476
-            EE_Default_Where_Conditions::MINIMUM_OTHERS,
6477
-            EE_Default_Where_Conditions::NONE,
6478
-        ];
6479
-    }
6480
-
6481
-    // public static function default_where_conditions_full
6482
-
6483
-
6484
-    /**
6485
-     * Verifies $context is one of EEM_Base::valid_cap_contexts(), if not it throws an exception
6486
-     *
6487
-     * @param string $context
6488
-     * @return bool
6489
-     * @throws EE_Error
6490
-     */
6491
-    public static function verify_is_valid_cap_context($context): bool
6492
-    {
6493
-        $valid_cap_contexts = EEM_Base::valid_cap_contexts();
6494
-        if (in_array($context, $valid_cap_contexts)) {
6495
-            return true;
6496
-        }
6497
-        throw new EE_Error(
6498
-            sprintf(
6499
-                esc_html__(
6500
-                    'Context "%1$s" passed into model "%2$s" is not a valid context. They are: %3$s',
6501
-                    'event_espresso'
6502
-                ),
6503
-                $context,
6504
-                'EEM_Base',
6505
-                implode(',', $valid_cap_contexts)
6506
-            )
6507
-        );
6508
-    }
6509
-
6510
-
6511
-    /**
6512
-     * Clears all the models field caches. This is only useful when a sub-class
6513
-     * might have added a field or something and these caches might be invalidated
6514
-     */
6515
-    protected function _invalidate_field_caches()
6516
-    {
6517
-        $this->_cache_foreign_key_to_fields = [];
6518
-        $this->_cached_fields               = null;
6519
-        $this->_cached_fields_non_db_only   = null;
6520
-    }
6521
-
6522
-
6523
-    /**
6524
-     * Gets the list of all the where query param keys that relate to logic instead of field names
6525
-     * (eg "and", "or", "not").
6526
-     *
6527
-     * @return array
6528
-     */
6529
-    public function logic_query_param_keys(): array
6530
-    {
6531
-        return $this->_logic_query_param_keys;
6532
-    }
6533
-
6534
-
6535
-    /**
6536
-     * Determines whether the where query param array key is for a logic query param.
6537
-     * Eg 'OR', 'not*', and 'and*because-i-say-so' should all return true, whereas
6538
-     * 'ATT_fname', 'EVT_name*not-you-or-me', and 'ORG_name' should return false
6539
-     *
6540
-     * @param $query_param_key
6541
-     * @return bool
6542
-     */
6543
-    public function is_logic_query_param_key($query_param_key): bool
6544
-    {
6545
-        foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6546
-            if (
6547
-                $query_param_key === $logic_query_param_key
6548
-                || strpos($query_param_key, $logic_query_param_key . '*') === 0
6549
-            ) {
6550
-                return true;
6551
-            }
6552
-        }
6553
-        return false;
6554
-    }
6555
-
6556
-
6557
-    /**
6558
-     * Returns true if this model has a password field on it (regardless of whether that password field has any content)
6559
-     *
6560
-     * @return boolean
6561
-     * @since 4.9.74.p
6562
-     */
6563
-    public function hasPassword(): bool
6564
-    {
6565
-        // if we don't yet know if there's a password field, find out and remember it for next time.
6566
-        if ($this->has_password_field === null) {
6567
-            $password_field           = $this->getPasswordField();
6568
-            $this->has_password_field = $password_field instanceof EE_Password_Field;
6569
-        }
6570
-        return $this->has_password_field;
6571
-    }
6572
-
6573
-
6574
-    /**
6575
-     * Returns the password field on this model, if there is one
6576
-     *
6577
-     * @return EE_Password_Field|null
6578
-     * @since 4.9.74.p
6579
-     */
6580
-    public function getPasswordField()
6581
-    {
6582
-        // if we definetely already know there is a password field or not (because has_password_field is true or false)
6583
-        // there's no need to search for it. If we don't know yet, then find out
6584
-        if ($this->has_password_field === null && $this->password_field === null) {
6585
-            $this->password_field = $this->get_a_field_of_type('EE_Password_Field');
6586
-        }
6587
-        // don't bother setting has_password_field because that's hasPassword()'s job.
6588
-        return $this->password_field;
6589
-    }
6590
-
6591
-
6592
-    /**
6593
-     * Returns the list of field (as EE_Model_Field_Bases) that are protected by the password
6594
-     *
6595
-     * @return EE_Model_Field_Base[]
6596
-     * @throws EE_Error
6597
-     * @since 4.9.74.p
6598
-     */
6599
-    public function getPasswordProtectedFields()
6600
-    {
6601
-        $password_field = $this->getPasswordField();
6602
-        $fields         = [];
6603
-        if ($password_field instanceof EE_Password_Field) {
6604
-            $field_names = $password_field->protectedFields();
6605
-            foreach ($field_names as $field_name) {
6606
-                $fields[ $field_name ] = $this->field_settings_for($field_name);
6607
-            }
6608
-        }
6609
-        return $fields;
6610
-    }
6611
-
6612
-
6613
-    /**
6614
-     * Checks if the current user can perform the requested action on this model
6615
-     *
6616
-     * @param string              $cap_to_check one of the array keys from _cap_contexts_to_cap_action_map
6617
-     * @param EE_Base_Class|array $model_obj_or_fields_n_values
6618
-     * @return bool
6619
-     * @throws EE_Error
6620
-     * @throws InvalidArgumentException
6621
-     * @throws InvalidDataTypeException
6622
-     * @throws InvalidInterfaceException
6623
-     * @throws ReflectionException
6624
-     * @throws UnexpectedEntityException
6625
-     * @since 4.9.74.p
6626
-     */
6627
-    public function currentUserCan($cap_to_check, $model_obj_or_fields_n_values)
6628
-    {
6629
-        if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6630
-            $model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6631
-        }
6632
-        if (! is_array($model_obj_or_fields_n_values)) {
6633
-            throw new UnexpectedEntityException(
6634
-                $model_obj_or_fields_n_values,
6635
-                'EE_Base_Class',
6636
-                sprintf(
6637
-                    esc_html__(
6638
-                        '%1$s must be passed an `EE_Base_Class or an array of fields names with their values. You passed in something different.',
6639
-                        'event_espresso'
6640
-                    ),
6641
-                    __FUNCTION__
6642
-                )
6643
-            );
6644
-        }
6645
-        return $this->exists(
6646
-            $this->alter_query_params_to_restrict_by_ID(
6647
-                $this->get_index_primary_key_string($model_obj_or_fields_n_values),
6648
-                [
6649
-                    'default_where_conditions' => 'none',
6650
-                    'caps'                     => $cap_to_check,
6651
-                ]
6652
-            )
6653
-        );
6654
-    }
6655
-
6656
-
6657
-    /**
6658
-     * Returns the query param where conditions key to the password affecting this model.
6659
-     * Eg on EEM_Event this would just be "password", on EEM_Datetime this would be "Event.password", etc.
6660
-     *
6661
-     * @return null|string
6662
-     * @throws EE_Error
6663
-     * @throws InvalidArgumentException
6664
-     * @throws InvalidDataTypeException
6665
-     * @throws InvalidInterfaceException
6666
-     * @throws ModelConfigurationException
6667
-     * @throws ReflectionException
6668
-     * @since 4.9.74.p
6669
-     */
6670
-    public function modelChainAndPassword()
6671
-    {
6672
-        if ($this->model_chain_to_password === null) {
6673
-            throw new ModelConfigurationException(
6674
-                $this,
6675
-                esc_html_x(
6676
-                // @codingStandardsIgnoreStart
6677
-                    'Cannot exclude protected data because the model has not specified which model has the password.',
6678
-                    // @codingStandardsIgnoreEnd
6679
-                    '1: model name',
6680
-                    'event_espresso'
6681
-                )
6682
-            );
6683
-        }
6684
-        if ($this->model_chain_to_password === '') {
6685
-            $model_with_password = $this;
6686
-        } else {
6687
-            if ($pos_of_period = strrpos($this->model_chain_to_password, '.')) {
6688
-                $last_model_in_chain = substr($this->model_chain_to_password, $pos_of_period + 1);
6689
-            } else {
6690
-                $last_model_in_chain = $this->model_chain_to_password;
6691
-            }
6692
-            $model_with_password = EE_Registry::instance()->load_model($last_model_in_chain);
6693
-        }
6694
-
6695
-        $password_field = $model_with_password->getPasswordField();
6696
-        if ($password_field instanceof EE_Password_Field) {
6697
-            $password_field_name = $password_field->get_name();
6698
-        } else {
6699
-            throw new ModelConfigurationException(
6700
-                $this,
6701
-                sprintf(
6702
-                    esc_html_x(
6703
-                        '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"',
6704
-                        '1: model name, 2: special string',
6705
-                        'event_espresso'
6706
-                    ),
6707
-                    $model_with_password->get_this_model_name(),
6708
-                    $this->model_chain_to_password
6709
-                )
6710
-            );
6711
-        }
6712
-        return (
6713
-               $this->model_chain_to_password
6714
-                   ? $this->model_chain_to_password . '.'
6715
-                   : ''
6716
-               ) . $password_field_name;
6717
-    }
6718
-
6719
-
6720
-    /**
6721
-     * Returns true if there is a password on a related model which restricts access to some of this model's rows,
6722
-     * or if this model itself has a password affecting access to some of its other fields.
6723
-     *
6724
-     * @return boolean
6725
-     * @since 4.9.74.p
6726
-     */
6727
-    public function restrictedByRelatedModelPassword(): bool
6728
-    {
6729
-        return $this->model_chain_to_password !== null;
6730
-    }
6731
-
6732
-
6733
-    public function __sleep()
6734
-    {
6735
-        $vars = (array) $this;
6736
-        $remove = [
6737
-            '_custom_selections',
6738
-            '_entity_map',
6739
-        ];
6740
-        foreach ($vars as $key => $val)
6741
-        {
6742
-            // removing null properties improves serialization performance
6743
-            if (is_null($val) || in_array($val, $remove, true)) {
6744
-                unset($vars[$key]);
6745
-            }
6746
-        }
6747
-        return array_keys($vars);
6748
-    }
6749
-
6750
-    private function normalizePropertyName(string $key): string
3967
+		}
3968
+		return $null_friendly_where_conditions;
3969
+	}
3970
+
3971
+
3972
+	/**
3973
+	 * Uses the _default_where_conditions_strategy set during __construct() to get
3974
+	 * default where conditions on all get_all, update, and delete queries done by this model.
3975
+	 * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3976
+	 * NOT array('Event_CPT.post_type'=>'esp_event').
3977
+	 *
3978
+	 * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3979
+	 * @return array @see
3980
+	 *                                    https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3981
+	 * @throws EE_Error
3982
+	 * @throws EE_Error
3983
+	 */
3984
+	private function _get_default_where_conditions($model_relation_path = '')
3985
+	{
3986
+		if ($this->_ignore_where_strategy) {
3987
+			return [];
3988
+		}
3989
+		return $this->_default_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3990
+	}
3991
+
3992
+
3993
+	/**
3994
+	 * Uses the _minimum_where_conditions_strategy set during __construct() to get
3995
+	 * minimum where conditions on all get_all, update, and delete queries done by this model.
3996
+	 * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3997
+	 * NOT array('Event_CPT.post_type'=>'esp_event').
3998
+	 * Similar to _get_default_where_conditions
3999
+	 *
4000
+	 * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
4001
+	 * @return array @see
4002
+	 *                                    https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4003
+	 * @throws EE_Error
4004
+	 * @throws EE_Error
4005
+	 */
4006
+	protected function _get_minimum_where_conditions($model_relation_path = '')
4007
+	{
4008
+		if ($this->_ignore_where_strategy) {
4009
+			return [];
4010
+		}
4011
+		return $this->_minimum_where_conditions_strategy->get_default_where_conditions($model_relation_path);
4012
+	}
4013
+
4014
+
4015
+	/**
4016
+	 * Creates the string of SQL for the select part of a select query, everything behind SELECT and before FROM.
4017
+	 * Eg, "Event.post_id, Event.post_name,Event_Detail.EVT_ID..."
4018
+	 *
4019
+	 * @param EE_Model_Query_Info_Carrier $model_query_info
4020
+	 * @return string
4021
+	 * @throws EE_Error
4022
+	 */
4023
+	private function _construct_default_select_sql(EE_Model_Query_Info_Carrier $model_query_info)
4024
+	{
4025
+		$selects = $this->_get_columns_to_select_for_this_model();
4026
+		foreach (
4027
+			$model_query_info->get_model_names_included() as $model_relation_chain => $name_of_other_model_included
4028
+		) {
4029
+			$other_model_included = $this->get_related_model_obj($name_of_other_model_included);
4030
+			$other_model_selects  = $other_model_included->_get_columns_to_select_for_this_model($model_relation_chain);
4031
+			foreach ($other_model_selects as $key => $value) {
4032
+				$selects[] = $value;
4033
+			}
4034
+		}
4035
+		return implode(", ", $selects);
4036
+	}
4037
+
4038
+
4039
+	/**
4040
+	 * Gets an array of columns to select for this model, which are necessary for it to create its objects.
4041
+	 * So that's going to be the columns for all the fields on the model
4042
+	 *
4043
+	 * @param string $model_relation_chain like 'Question.Question_Group.Event'
4044
+	 * @return array numerically indexed, values are columns to select and rename, eg "Event.ID AS 'Event.ID'"
4045
+	 */
4046
+	public function _get_columns_to_select_for_this_model($model_relation_chain = '')
4047
+	{
4048
+		$fields                                       = $this->field_settings();
4049
+		$selects                                      = [];
4050
+		$table_alias_with_model_relation_chain_prefix =
4051
+			EE_Model_Parser::extract_table_alias_model_relation_chain_prefix(
4052
+				$model_relation_chain,
4053
+				$this->get_this_model_name()
4054
+			);
4055
+		foreach ($fields as $field_obj) {
4056
+			$selects[] = $table_alias_with_model_relation_chain_prefix
4057
+						 . $field_obj->get_table_alias()
4058
+						 . "."
4059
+						 . $field_obj->get_table_column()
4060
+						 . " AS '"
4061
+						 . $table_alias_with_model_relation_chain_prefix
4062
+						 . $field_obj->get_table_alias()
4063
+						 . "."
4064
+						 . $field_obj->get_table_column()
4065
+						 . "'";
4066
+		}
4067
+		// make sure we are also getting the PKs of each table
4068
+		$tables = $this->get_tables();
4069
+		if (count($tables) > 1) {
4070
+			foreach ($tables as $table_obj) {
4071
+				$qualified_pk_column = $table_alias_with_model_relation_chain_prefix
4072
+									   . $table_obj->get_fully_qualified_pk_column();
4073
+				if (! in_array($qualified_pk_column, $selects)) {
4074
+					$selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
4075
+				}
4076
+			}
4077
+		}
4078
+		return $selects;
4079
+	}
4080
+
4081
+
4082
+	/**
4083
+	 * Given a $query_param like 'Registration.Transaction.TXN_ID', pops off 'Registration.',
4084
+	 * gets the join statement for it; gets the data types for it; and passes the remaining 'Transaction.TXN_ID'
4085
+	 * onto its related Transaction object to do the same. Returns an EE_Join_And_Data_Types object which contains the
4086
+	 * SQL for joining, and the data types
4087
+	 *
4088
+	 * @param null|string                 $original_query_param
4089
+	 * @param string                      $query_param          like Registration.Transaction.TXN_ID
4090
+	 * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4091
+	 * @param string                      $query_param_type     like Registration.Transaction.TXN_ID
4092
+	 *                                                          or 'PAY_ID'. Otherwise, we don't expect there to be a
4093
+	 *                                                          column name. We only want model names, eg 'Event.Venue'
4094
+	 *                                                          or 'Registration's
4095
+	 * @param string                      $original_query_param what it originally was (eg
4096
+	 *                                                          Registration.Transaction.TXN_ID). If null, we assume it
4097
+	 *                                                          matches $query_param
4098
+	 * @return void only modifies the EEM_Related_Model_Info_Carrier passed into it
4099
+	 * @throws EE_Error
4100
+	 */
4101
+	private function _extract_related_model_info_from_query_param(
4102
+		$query_param,
4103
+		EE_Model_Query_Info_Carrier $passed_in_query_info,
4104
+		$query_param_type,
4105
+		$original_query_param = null
4106
+	) {
4107
+		if ($original_query_param === null) {
4108
+			$original_query_param = $query_param;
4109
+		}
4110
+		$query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4111
+		// check to see if we have a field on this model
4112
+		$this_model_fields = $this->field_settings(true);
4113
+		if (array_key_exists($query_param, $this_model_fields)) {
4114
+			$field_is_allowed = in_array(
4115
+				$query_param_type,
4116
+				[0, 'where', 'having', 'order_by', 'group_by', 'order', 'custom_selects'],
4117
+				true
4118
+			);
4119
+			if ($field_is_allowed) {
4120
+				return;
4121
+			}
4122
+			throw new EE_Error(
4123
+				sprintf(
4124
+					esc_html__(
4125
+						"Using a field name (%s) on model %s is not allowed on this query param type '%s'. Original query param was %s",
4126
+						"event_espresso"
4127
+					),
4128
+					$query_param,
4129
+					$this->class_name,
4130
+					$query_param_type,
4131
+					$original_query_param
4132
+				)
4133
+			);
4134
+		}
4135
+		// check if this is a special logic query param
4136
+		if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4137
+			$operator_is_allowed = in_array($query_param_type, ['where', 'having', 0, 'custom_selects'], true);
4138
+			if ($operator_is_allowed) {
4139
+				return;
4140
+			}
4141
+			throw new EE_Error(
4142
+				sprintf(
4143
+					esc_html__(
4144
+						'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',
4145
+						'event_espresso'
4146
+					),
4147
+					implode('", "', $this->_logic_query_param_keys),
4148
+					$query_param,
4149
+					$this->class_name,
4150
+					'<br />',
4151
+					"\t"
4152
+					. ' $passed_in_query_info = <pre>'
4153
+					. print_r($passed_in_query_info, true)
4154
+					. '</pre>'
4155
+					. "\n\t"
4156
+					. ' $query_param_type = '
4157
+					. $query_param_type
4158
+					. "\n\t"
4159
+					. ' $original_query_param = '
4160
+					. $original_query_param
4161
+				)
4162
+			);
4163
+		}
4164
+		// check if it's a custom selection
4165
+		if (
4166
+			$this->_custom_selections instanceof CustomSelects
4167
+			&& in_array($query_param, $this->_custom_selections->columnAliases(), true)
4168
+		) {
4169
+			return;
4170
+		}
4171
+		// check if has a model name at the beginning
4172
+		// and
4173
+		// check if it's a field on a related model
4174
+		if (
4175
+			$this->extractJoinModelFromQueryParams(
4176
+				$passed_in_query_info,
4177
+				$query_param,
4178
+				$original_query_param,
4179
+				$query_param_type
4180
+			)
4181
+		) {
4182
+			return;
4183
+		}
4184
+
4185
+		// ok so $query_param didn't start with a model name
4186
+		// and we previously confirmed it wasn't a logic query param or field on the current model
4187
+		// it's wack, that's what it is
4188
+		throw new EE_Error(
4189
+			sprintf(
4190
+				esc_html__(
4191
+					"There is no model named '%s' related to %s. Query param type is %s and original query param is %s",
4192
+					"event_espresso"
4193
+				),
4194
+				$query_param,
4195
+				$this->class_name,
4196
+				$query_param_type,
4197
+				$original_query_param
4198
+			)
4199
+		);
4200
+	}
4201
+
4202
+
4203
+	/**
4204
+	 * Extracts any possible join model information from the provided possible_join_string.
4205
+	 * This method will read the provided $possible_join_string value and determine if there are any possible model
4206
+	 * join
4207
+	 * parts that should be added to the query.
4208
+	 *
4209
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
4210
+	 * @param string                      $possible_join_string  Such as Registration.REG_ID, or Registration
4211
+	 * @param null|string                 $original_query_param
4212
+	 * @param string                      $query_parameter_type  The type for the source of the $possible_join_string
4213
+	 *                                                           ('where', 'order_by', 'group_by', 'custom_selects'
4214
+	 *                                                           etc.)
4215
+	 * @return bool  returns true if a join was added and false if not.
4216
+	 * @throws EE_Error
4217
+	 */
4218
+	private function extractJoinModelFromQueryParams(
4219
+		EE_Model_Query_Info_Carrier $query_info_carrier,
4220
+		$possible_join_string,
4221
+		$original_query_param,
4222
+		$query_parameter_type
4223
+	) {
4224
+		foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4225
+			if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4226
+				$this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4227
+				$possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4228
+				if ($possible_join_string === '') {
4229
+					// nothing left to $query_param
4230
+					// we should actually end in a field name, not a model like this!
4231
+					throw new EE_Error(
4232
+						sprintf(
4233
+							esc_html__(
4234
+								"Query param '%s' (of type %s on model %s) shouldn't end on a period (.) ",
4235
+								"event_espresso"
4236
+							),
4237
+							$possible_join_string,
4238
+							$query_parameter_type,
4239
+							$this->class_name,
4240
+							$valid_related_model_name
4241
+						)
4242
+					);
4243
+				}
4244
+				$related_model_obj = $this->get_related_model_obj($valid_related_model_name);
4245
+				$related_model_obj->_extract_related_model_info_from_query_param(
4246
+					$possible_join_string,
4247
+					$query_info_carrier,
4248
+					$query_parameter_type,
4249
+					$original_query_param
4250
+				);
4251
+				return true;
4252
+			}
4253
+			if ($possible_join_string === $valid_related_model_name) {
4254
+				$this->_add_join_to_model(
4255
+					$valid_related_model_name,
4256
+					$query_info_carrier,
4257
+					$original_query_param
4258
+				);
4259
+				return true;
4260
+			}
4261
+		}
4262
+		return false;
4263
+	}
4264
+
4265
+
4266
+	/**
4267
+	 * Extracts related models from Custom Selects and sets up any joins for those related models.
4268
+	 *
4269
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
4270
+	 * @throws EE_Error
4271
+	 */
4272
+	private function extractRelatedModelsFromCustomSelects(EE_Model_Query_Info_Carrier $query_info_carrier)
4273
+	{
4274
+		if (
4275
+			$this->_custom_selections instanceof CustomSelects
4276
+			&& (
4277
+				$this->_custom_selections->type() === CustomSelects::TYPE_STRUCTURED
4278
+				|| $this->_custom_selections->type() == CustomSelects::TYPE_COMPLEX
4279
+			)
4280
+		) {
4281
+			$original_selects = $this->_custom_selections->originalSelects();
4282
+			foreach ($original_selects as $alias => $select_configuration) {
4283
+				$this->extractJoinModelFromQueryParams(
4284
+					$query_info_carrier,
4285
+					$select_configuration[0],
4286
+					$select_configuration[0],
4287
+					'custom_selects'
4288
+				);
4289
+			}
4290
+		}
4291
+	}
4292
+
4293
+
4294
+	/**
4295
+	 * Privately used by _extract_related_model_info_from_query_param to add a join to $model_name
4296
+	 * and store it on $passed_in_query_info
4297
+	 *
4298
+	 * @param string                      $model_name
4299
+	 * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4300
+	 * @param string                      $original_query_param used to extract the relation chain between the queried
4301
+	 *                                                          model and $model_name. Eg, if we are querying Event,
4302
+	 *                                                          and are adding a join to 'Payment' with the original
4303
+	 *                                                          query param key
4304
+	 *                                                          'Registration.Transaction.Payment.PAY_amount', we want
4305
+	 *                                                          to extract 'Registration.Transaction.Payment', in case
4306
+	 *                                                          Payment wants to add default query params so that it
4307
+	 *                                                          will know what models to prepend onto its default query
4308
+	 *                                                          params or in case it wants to rename tables (in case
4309
+	 *                                                          there are multiple joins to the same table)
4310
+	 * @return void
4311
+	 * @throws EE_Error
4312
+	 */
4313
+	private function _add_join_to_model(
4314
+		$model_name,
4315
+		EE_Model_Query_Info_Carrier $passed_in_query_info,
4316
+		$original_query_param
4317
+	) {
4318
+		$relation_obj         = $this->related_settings_for($model_name);
4319
+		$model_relation_chain = EE_Model_Parser::extract_model_relation_chain($model_name, $original_query_param);
4320
+		// check if the relation is HABTM, because then we're essentially doing two joins
4321
+		// If so, join first to the JOIN table, and add its data types, and then continue as normal
4322
+		if ($relation_obj instanceof EE_HABTM_Relation) {
4323
+			$join_model_obj = $relation_obj->get_join_model();
4324
+			// replace the model specified with the join model for this relation chain, whi
4325
+			$relation_chain_to_join_model =
4326
+				EE_Model_Parser::replace_model_name_with_join_model_name_in_model_relation_chain(
4327
+					$model_name,
4328
+					$join_model_obj->get_this_model_name(),
4329
+					$model_relation_chain
4330
+				);
4331
+			$passed_in_query_info->merge(
4332
+				new EE_Model_Query_Info_Carrier(
4333
+					[$relation_chain_to_join_model => $join_model_obj->get_this_model_name()],
4334
+					$relation_obj->get_join_to_intermediate_model_statement($relation_chain_to_join_model)
4335
+				)
4336
+			);
4337
+		}
4338
+		// now just join to the other table pointed to by the relation object, and add its data types
4339
+		$passed_in_query_info->merge(
4340
+			new EE_Model_Query_Info_Carrier(
4341
+				[$model_relation_chain => $model_name],
4342
+				$relation_obj->get_join_statement($model_relation_chain)
4343
+			)
4344
+		);
4345
+	}
4346
+
4347
+
4348
+	/**
4349
+	 * Constructs SQL for where clause, like "WHERE Event.ID = 23 AND Transaction.amount > 100" etc.
4350
+	 *
4351
+	 * @param array $where_params @see
4352
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4353
+	 * @return string of SQL
4354
+	 * @throws EE_Error
4355
+	 */
4356
+	private function _construct_where_clause($where_params)
4357
+	{
4358
+		$SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4359
+		if ($SQL) {
4360
+			return " WHERE " . $SQL;
4361
+		}
4362
+		return '';
4363
+	}
4364
+
4365
+
4366
+	/**
4367
+	 * Just like the _construct_where_clause, except prepends 'HAVING' instead of 'WHERE',
4368
+	 * and should be passed HAVING parameters, not WHERE parameters
4369
+	 *
4370
+	 * @param array $having_params
4371
+	 * @return string
4372
+	 * @throws EE_Error
4373
+	 */
4374
+	private function _construct_having_clause($having_params)
4375
+	{
4376
+		$SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4377
+		if ($SQL) {
4378
+			return " HAVING " . $SQL;
4379
+		}
4380
+		return '';
4381
+	}
4382
+
4383
+
4384
+	/**
4385
+	 * Used for creating nested WHERE conditions. Eg "WHERE ! (Event.ID = 3 OR ( Event_Meta.meta_key = 'bob' AND
4386
+	 * Event_Meta.meta_value = 'foo'))"
4387
+	 *
4388
+	 * @param array  $where_params @see
4389
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4390
+	 * @param string $glue         joins each subclause together. Should really only be " AND " or " OR "...
4391
+	 * @return string of SQL
4392
+	 * @throws EE_Error
4393
+	 */
4394
+	private function _construct_condition_clause_recursive($where_params, $glue = ' AND')
4395
+	{
4396
+		$where_clauses = [];
4397
+		foreach ($where_params as $query_param => $op_and_value_or_sub_condition) {
4398
+			$query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4399
+			if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4400
+				switch ($query_param) {
4401
+					case 'not':
4402
+					case 'NOT':
4403
+						$where_clauses[] = "! ("
4404
+										   . $this->_construct_condition_clause_recursive(
4405
+											   $op_and_value_or_sub_condition,
4406
+											   $glue
4407
+										   )
4408
+										   . ")";
4409
+						break;
4410
+					case 'and':
4411
+					case 'AND':
4412
+						$where_clauses[] = " ("
4413
+										   . $this->_construct_condition_clause_recursive(
4414
+											   $op_and_value_or_sub_condition,
4415
+											   ' AND '
4416
+										   )
4417
+										   . ")";
4418
+						break;
4419
+					case 'or':
4420
+					case 'OR':
4421
+						$where_clauses[] = " ("
4422
+										   . $this->_construct_condition_clause_recursive(
4423
+											   $op_and_value_or_sub_condition,
4424
+											   ' OR '
4425
+										   )
4426
+										   . ")";
4427
+						break;
4428
+				}
4429
+			} else {
4430
+				$field_obj = $this->_deduce_field_from_query_param($query_param);
4431
+				// if it's not a normal field, maybe it's a custom selection?
4432
+				if (! $field_obj) {
4433
+					if ($this->_custom_selections instanceof CustomSelects) {
4434
+						$field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4435
+					} else {
4436
+						throw new EE_Error(
4437
+							sprintf(
4438
+								esc_html__(
4439
+									"%s is neither a valid model field name, nor a custom selection",
4440
+									"event_espresso"
4441
+								),
4442
+								$query_param
4443
+							)
4444
+						);
4445
+					}
4446
+				}
4447
+				$op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4448
+				$where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4449
+			}
4450
+		}
4451
+		return $where_clauses
4452
+			? implode($glue, $where_clauses)
4453
+			: '';
4454
+	}
4455
+
4456
+
4457
+	/**
4458
+	 * Takes the input parameter and extract the table name (alias) and column name
4459
+	 *
4460
+	 * @param string $query_param like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4461
+	 * @return string table alias and column name for SQL, eg "Transaction.TXN_ID"
4462
+	 * @throws EE_Error
4463
+	 */
4464
+	private function _deduce_column_name_from_query_param($query_param)
4465
+	{
4466
+		$field = $this->_deduce_field_from_query_param($query_param);
4467
+		if ($field) {
4468
+			$table_alias_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_from_query_param(
4469
+				$field->get_model_name(),
4470
+				$query_param
4471
+			);
4472
+			return $table_alias_prefix . $field->get_qualified_column();
4473
+		}
4474
+		if (
4475
+			$this->_custom_selections instanceof CustomSelects
4476
+			&& in_array($query_param, $this->_custom_selections->columnAliases(), true)
4477
+		) {
4478
+			// maybe it's custom selection item?
4479
+			// if so, just use it as the "column name"
4480
+			return $query_param;
4481
+		}
4482
+		$custom_select_aliases = $this->_custom_selections instanceof CustomSelects
4483
+			? implode(',', $this->_custom_selections->columnAliases())
4484
+			: '';
4485
+		throw new EE_Error(
4486
+			sprintf(
4487
+				esc_html__(
4488
+					"%s is not a valid field on this model, nor a custom selection (%s)",
4489
+					"event_espresso"
4490
+				),
4491
+				$query_param,
4492
+				$custom_select_aliases
4493
+			)
4494
+		);
4495
+	}
4496
+
4497
+
4498
+	/**
4499
+	 * Removes the * and anything after it from the condition query param key. It is useful to add the * to condition
4500
+	 * query param keys (eg, 'OR*', 'EVT_ID') in order for the array keys to still be unique, so that they don't get
4501
+	 * overwritten Takes a string like 'Event.EVT_ID*', 'TXN_total**', 'OR*1st', and 'DTT_reg_start*foobar' to
4502
+	 * 'Event.EVT_ID', 'TXN_total', 'OR', and 'DTT_reg_start', respectively.
4503
+	 *
4504
+	 * @param string $condition_query_param_key
4505
+	 * @return string
4506
+	 */
4507
+	private function _remove_stars_and_anything_after_from_condition_query_param_key($condition_query_param_key)
4508
+	{
4509
+		$pos_of_star = strpos($condition_query_param_key, '*');
4510
+		if ($pos_of_star === false) {
4511
+			return $condition_query_param_key;
4512
+		}
4513
+		return substr($condition_query_param_key, 0, $pos_of_star);
4514
+	}
4515
+
4516
+
4517
+	/**
4518
+	 * creates the SQL for the operator and the value in a WHERE clause, eg "< 23" or "LIKE '%monkey%'"
4519
+	 *
4520
+	 * @param array|string               $op_and_value
4521
+	 * @param EE_Model_Field_Base|string $field_obj . If string, should be one of EEM_Base::_valid_wpdb_data_types
4522
+	 * @return string
4523
+	 * @throws EE_Error
4524
+	 */
4525
+	private function _construct_op_and_value($op_and_value, $field_obj)
4526
+	{
4527
+		$operator = '=';
4528
+		$value    = $op_and_value;
4529
+		if (is_array($op_and_value)) {
4530
+			$operator = isset($op_and_value[0])
4531
+				? $this->_prepare_operator_for_sql($op_and_value[0])
4532
+				: null;
4533
+			if (! $operator) {
4534
+				$php_array_like_string = [];
4535
+				foreach ($op_and_value as $key => $value) {
4536
+					$value = is_array($value) ? '[' . implode(",", $value) . ']' : $value;
4537
+					$php_array_like_string[] = "$key=>$value";
4538
+				}
4539
+				throw new EE_Error(
4540
+					sprintf(
4541
+						esc_html__(
4542
+							"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))",
4543
+							"event_espresso"
4544
+						),
4545
+						implode(",", $php_array_like_string)
4546
+					)
4547
+				);
4548
+			}
4549
+			$value = $op_and_value[1] ?? null;
4550
+		}
4551
+
4552
+		// check to see if the value is actually another field
4553
+		if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2]) {
4554
+			return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4555
+		}
4556
+		if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4557
+			// in this case, the value should be an array, or at least a comma-separated list
4558
+			// it will need to handle a little differently
4559
+			$cleaned_value = $this->_construct_in_value($value, $field_obj);
4560
+			// note: $cleaned_value has already been run through $wpdb->prepare()
4561
+			return $operator . SP . $cleaned_value;
4562
+		}
4563
+		if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4564
+			// the value should be an array with count of two.
4565
+			if (count($value) !== 2) {
4566
+				throw new EE_Error(
4567
+					sprintf(
4568
+						esc_html__(
4569
+							"The '%s' operator must be used with an array of values and there must be exactly TWO values in that array.",
4570
+							'event_espresso'
4571
+						),
4572
+						"BETWEEN"
4573
+					)
4574
+				);
4575
+			}
4576
+			$cleaned_value = $this->_construct_between_value($value, $field_obj);
4577
+			return $operator . SP . $cleaned_value;
4578
+		}
4579
+		if (in_array($operator, $this->valid_null_style_operators())) {
4580
+			if ($value !== null) {
4581
+				throw new EE_Error(
4582
+					sprintf(
4583
+						esc_html__(
4584
+							"You attempted to give a value  (%s) while using a NULL-style operator (%s). That isn't valid",
4585
+							"event_espresso"
4586
+						),
4587
+						$value,
4588
+						$operator
4589
+					)
4590
+				);
4591
+			}
4592
+			return $operator;
4593
+		}
4594
+		if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4595
+			// if the operator is 'LIKE', we want to allow percent signs (%) and not
4596
+			// remove other junk. So just treat it as a string.
4597
+			return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4598
+		}
4599
+		if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4600
+			return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4601
+		}
4602
+		if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4603
+			throw new EE_Error(
4604
+				sprintf(
4605
+					esc_html__(
4606
+						"Operator '%s' must be used with an array of values, eg 'Registration.REG_ID' => array('%s',array(1,2,3))",
4607
+						'event_espresso'
4608
+					),
4609
+					$operator,
4610
+					$operator
4611
+				)
4612
+			);
4613
+		}
4614
+		if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4615
+			throw new EE_Error(
4616
+				sprintf(
4617
+					esc_html__(
4618
+						"Operator '%s' must be used with a single value, not an array. Eg 'Registration.REG_ID => array('%s',23))",
4619
+						'event_espresso'
4620
+					),
4621
+					$operator,
4622
+					$operator
4623
+				)
4624
+			);
4625
+		}
4626
+		throw new EE_Error(
4627
+			sprintf(
4628
+				esc_html__(
4629
+					"It appears you've provided some totally invalid query parameters. Operator and value were:'%s', which isn't right at all",
4630
+					"event_espresso"
4631
+				),
4632
+				http_build_query($op_and_value)
4633
+			)
4634
+		);
4635
+	}
4636
+
4637
+
4638
+	/**
4639
+	 * Creates the operands to be used in a BETWEEN query, eg "'2014-12-31 20:23:33' AND '2015-01-23 12:32:54'"
4640
+	 *
4641
+	 * @param array                      $values
4642
+	 * @param EE_Model_Field_Base|string $field_obj if string, it should be the datatype to be used when querying, eg
4643
+	 *                                              '%s'
4644
+	 * @return string
4645
+	 * @throws EE_Error
4646
+	 */
4647
+	public function _construct_between_value($values, $field_obj)
4648
+	{
4649
+		$cleaned_values = [];
4650
+		foreach ($values as $value) {
4651
+			$cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4652
+		}
4653
+		return $cleaned_values[0] . " AND " . $cleaned_values[1];
4654
+	}
4655
+
4656
+
4657
+	/**
4658
+	 * Takes an array or a comma-separated list of $values and cleans them
4659
+	 * according to $data_type using $wpdb->prepare, and then makes the list a
4660
+	 * string surrounded by ( and ). Eg, _construct_in_value(array(1,2,3),'%d') would
4661
+	 * return '(1,2,3)'; _construct_in_value("1,2,hack",'%d') would return '(1,2,1)' (assuming
4662
+	 * I'm right that a string, when interpreted as a digit, becomes a 1. It might become a 0)
4663
+	 *
4664
+	 * @param mixed                      $values    array or comma-separated string
4665
+	 * @param EE_Model_Field_Base|string $field_obj if string, it should be a wpdb data type like '%s', or '%d'
4666
+	 * @return string of SQL to follow an 'IN' or 'NOT IN' operator
4667
+	 * @throws EE_Error
4668
+	 */
4669
+	public function _construct_in_value($values, $field_obj)
4670
+	{
4671
+		$prepped = [];
4672
+		// check if the value is a CSV list
4673
+		if (is_string($values)) {
4674
+			// in which case, turn it into an array
4675
+			$values = explode(',', $values);
4676
+		}
4677
+		// make sure we only have one of each value in the list
4678
+		$values = array_unique($values);
4679
+		foreach ($values as $value) {
4680
+			$prepped[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4681
+		}
4682
+		// we would just LOVE to leave $cleaned_values as an empty array, and return the value as "()",
4683
+		// but unfortunately that's invalid SQL. So instead we return a string which we KNOW will evaluate to be the empty set
4684
+		// which is effectively equivalent to returning "()". We don't return "(0)" because that only works for auto-incrementing columns
4685
+		if (empty($prepped)) {
4686
+			$all_fields  = $this->field_settings();
4687
+			$first_field = reset($all_fields);
4688
+			$main_table  = $this->_get_main_table();
4689
+			$prepped[]   = "SELECT {$first_field->get_table_column()} FROM {$main_table->get_table_name()} WHERE FALSE";
4690
+		}
4691
+		return '(' . implode(',', $prepped) . ')';
4692
+	}
4693
+
4694
+
4695
+	/**
4696
+	 * @param mixed                      $value
4697
+	 * @param EE_Model_Field_Base|string $field_obj if string it should be a wpdb data type like '%d'
4698
+	 * @return false|null|string
4699
+	 * @throws EE_Error
4700
+	 */
4701
+	private function _wpdb_prepare_using_field($value, $field_obj)
4702
+	{
4703
+		/** @type WPDB $wpdb */
4704
+		global $wpdb;
4705
+		if ($field_obj instanceof EE_Model_Field_Base) {
4706
+			return $wpdb->prepare(
4707
+				$field_obj->get_wpdb_data_type(),
4708
+				$this->_prepare_value_for_use_in_db($value, $field_obj)
4709
+			);
4710
+		} //$field_obj should really just be a data type
4711
+		if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4712
+			throw new EE_Error(
4713
+				sprintf(
4714
+					esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
4715
+					$field_obj,
4716
+					implode(",", $this->_valid_wpdb_data_types)
4717
+				)
4718
+			);
4719
+		}
4720
+		return $wpdb->prepare($field_obj, $value);
4721
+	}
4722
+
4723
+
4724
+	/**
4725
+	 * Takes the input parameter and finds the model field that it indicates.
4726
+	 *
4727
+	 * @param string $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4728
+	 * @return EE_Model_Field_Base
4729
+	 * @throws EE_Error
4730
+	 */
4731
+	protected function _deduce_field_from_query_param($query_param_name)
4732
+	{
4733
+		// ok, now proceed with deducing which part is the model's name, and which is the field's name
4734
+		// which will help us find the database table and column
4735
+		$query_param_parts = explode(".", $query_param_name);
4736
+		if (empty($query_param_parts)) {
4737
+			throw new EE_Error(
4738
+				sprintf(
4739
+					esc_html__(
4740
+						"_extract_column_name is empty when trying to extract column and table name from %s",
4741
+						'event_espresso'
4742
+					),
4743
+					$query_param_name
4744
+				)
4745
+			);
4746
+		}
4747
+		$number_of_parts       = count($query_param_parts);
4748
+		$last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4749
+		if ($number_of_parts === 1) {
4750
+			$field_name = $last_query_param_part;
4751
+			$model_obj  = $this;
4752
+		} else {// $number_of_parts >= 2
4753
+			// the last part is the column name, and there are only 2parts. therefore...
4754
+			$field_name = $last_query_param_part;
4755
+			$model_obj  = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4756
+		}
4757
+		try {
4758
+			return $model_obj->field_settings_for($field_name);
4759
+		} catch (EE_Error $e) {
4760
+			return null;
4761
+		}
4762
+	}
4763
+
4764
+
4765
+	/**
4766
+	 * Given a field's name (ie, a key in $this->field_settings()), uses the EE_Model_Field object to get the table's
4767
+	 * alias and column which corresponds to it
4768
+	 *
4769
+	 * @param string $field_name
4770
+	 * @return string
4771
+	 * @throws EE_Error
4772
+	 */
4773
+	public function _get_qualified_column_for_field($field_name)
4774
+	{
4775
+		$all_fields = $this->field_settings();
4776
+		$field      = $all_fields[ $field_name ] ?? false;
4777
+		if ($field) {
4778
+			return $field->get_qualified_column();
4779
+		}
4780
+		throw new EE_Error(
4781
+			sprintf(
4782
+				esc_html__(
4783
+					"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.",
4784
+					'event_espresso'
4785
+				),
4786
+				$field_name,
4787
+				$this->class_name
4788
+			)
4789
+		);
4790
+	}
4791
+
4792
+
4793
+	/**
4794
+	 * similar to \EEM_Base::_get_qualified_column_for_field() but returns an array with data for ALL fields.
4795
+	 * Example usage:
4796
+	 * EEM_Ticket::instance()->get_all_wpdb_results(
4797
+	 *      array(),
4798
+	 *      ARRAY_A,
4799
+	 *      EEM_Ticket::instance()->get_qualified_columns_for_all_fields()
4800
+	 *  );
4801
+	 * is equivalent to
4802
+	 *  EEM_Ticket::instance()->get_all_wpdb_results( array(), ARRAY_A, '*' );
4803
+	 * and
4804
+	 *  EEM_Event::instance()->get_all_wpdb_results(
4805
+	 *      array(
4806
+	 *          array(
4807
+	 *              'Datetime.Ticket.TKT_ID' => array( '<', 100 ),
4808
+	 *          ),
4809
+	 *          ARRAY_A,
4810
+	 *          implode(
4811
+	 *              ', ',
4812
+	 *              array_merge(
4813
+	 *                  EEM_Event::instance()->get_qualified_columns_for_all_fields( '', false ),
4814
+	 *                  EEM_Ticket::instance()->get_qualified_columns_for_all_fields( 'Datetime', false )
4815
+	 *              )
4816
+	 *          )
4817
+	 *      )
4818
+	 *  );
4819
+	 * selects rows from the database, selecting all the event and ticket columns, where the ticket ID is below 100
4820
+	 *
4821
+	 * @param string $model_relation_chain        the chain of models used to join between the model you want to query
4822
+	 *                                            and the one whose fields you are selecting for example: when querying
4823
+	 *                                            tickets model and selecting fields from the tickets model you would
4824
+	 *                                            leave this parameter empty, because no models are needed to join
4825
+	 *                                            between the queried model and the selected one. Likewise when
4826
+	 *                                            querying the datetime model and selecting fields from the tickets
4827
+	 *                                            model, it would also be left empty, because there is a direct
4828
+	 *                                            relation from datetimes to tickets, so no model is needed to join
4829
+	 *                                            them together. However, when querying from the event model and
4830
+	 *                                            selecting fields from the ticket model, you should provide the string
4831
+	 *                                            'Datetime', indicating that the event model must first join to the
4832
+	 *                                            datetime model in order to find its relation to ticket model.
4833
+	 *                                            Also, when querying from the venue model and selecting fields from
4834
+	 *                                            the ticket model, you should provide the string 'Event.Datetime',
4835
+	 *                                            indicating you need to join the venue model to the event model,
4836
+	 *                                            to the datetime model, in order to find its relation to the ticket
4837
+	 *                                            model. This string is used to deduce the prefix that gets added onto
4838
+	 *                                            the models' tables qualified columns
4839
+	 * @param bool   $return_string               if true, will return a string with qualified column names separated
4840
+	 *                                            by ', ' if false, will simply return a numerically indexed array of
4841
+	 *                                            qualified column names
4842
+	 * @return array|string
4843
+	 */
4844
+	public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4845
+	{
4846
+		$table_prefix      = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain)
4847
+				? ''
4848
+				: '__');
4849
+		$qualified_columns = [];
4850
+		foreach ($this->field_settings() as $field_name => $field) {
4851
+			$qualified_columns[] = $table_prefix . $field->get_qualified_column();
4852
+		}
4853
+		return $return_string
4854
+			? implode(', ', $qualified_columns)
4855
+			: $qualified_columns;
4856
+	}
4857
+
4858
+
4859
+	/**
4860
+	 * constructs the select use on special limit joins
4861
+	 * NOTE: for now this has only been tested and will work when the  table alias is for the PRIMARY table. Although
4862
+	 * its setup so the select query will be setup on and just doing the special select join off of the primary table
4863
+	 * (as that is typically where the limits would be set).
4864
+	 *
4865
+	 * @param string       $table_alias The table the select is being built for
4866
+	 * @param mixed|string $limit       The limit for this select
4867
+	 * @return string                The final select join element for the query.
4868
+	 * @throws EE_Error
4869
+	 * @throws EE_Error
4870
+	 */
4871
+	public function _construct_limit_join_select($table_alias, $limit)
4872
+	{
4873
+		$SQL = '';
4874
+		foreach ($this->_tables as $table_obj) {
4875
+			if ($table_obj instanceof EE_Primary_Table) {
4876
+				$SQL .= $table_alias === $table_obj->get_table_alias()
4877
+					? $table_obj->get_select_join_limit($limit)
4878
+					: SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4879
+			} elseif ($table_obj instanceof EE_Secondary_Table) {
4880
+				$SQL .= $table_alias === $table_obj->get_table_alias()
4881
+					? $table_obj->get_select_join_limit_join($limit)
4882
+					: SP . $table_obj->get_join_sql($table_alias) . SP;
4883
+			}
4884
+		}
4885
+		return $SQL;
4886
+	}
4887
+
4888
+
4889
+	/**
4890
+	 * Constructs the internal join if there are multiple tables, or simply the table's name and alias
4891
+	 * Eg "wp_post AS Event" or "wp_post AS Event INNER JOIN wp_postmeta Event_Meta ON Event.ID = Event_Meta.post_id"
4892
+	 *
4893
+	 * @return string SQL
4894
+	 * @throws EE_Error
4895
+	 */
4896
+	public function _construct_internal_join()
4897
+	{
4898
+		$SQL = $this->_get_main_table()->get_table_sql();
4899
+		$SQL .= $this->_construct_internal_join_to_table_with_alias($this->_get_main_table()->get_table_alias());
4900
+		return $SQL;
4901
+	}
4902
+
4903
+
4904
+	/**
4905
+	 * Constructs the SQL for joining all the tables on this model.
4906
+	 * Normally $alias should be the primary table's alias, but in cases where
4907
+	 * we have already joined to a secondary table (eg, the secondary table has a foreign key and is joined before the
4908
+	 * primary table) then we should provide that secondary table's alias. Eg, with $alias being the primary table's
4909
+	 * alias, this will construct SQL like:
4910
+	 * " INNER JOIN wp_esp_secondary_table AS Secondary_Table ON Primary_Table.pk = Secondary_Table.fk".
4911
+	 * With $alias being a secondary table's alias, this will construct SQL like:
4912
+	 * " INNER JOIN wp_esp_primary_table AS Primary_Table ON Primary_Table.pk = Secondary_Table.fk".
4913
+	 *
4914
+	 * @param string $alias_prefixed table alias to join to (this table should already be in the FROM SQL clause)
4915
+	 * @return string
4916
+	 * @throws EE_Error
4917
+	 * @throws EE_Error
4918
+	 */
4919
+	public function _construct_internal_join_to_table_with_alias($alias_prefixed)
4920
+	{
4921
+		$SQL               = '';
4922
+		$alias_sans_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($alias_prefixed);
4923
+		foreach ($this->_tables as $table_obj) {
4924
+			if ($table_obj instanceof EE_Secondary_Table) {// table is secondary table
4925
+				if ($alias_sans_prefix === $table_obj->get_table_alias()) {
4926
+					// so we're joining to this table, meaning the table is already in
4927
+					// the FROM statement, BUT the primary table isn't. So we want
4928
+					// to add the inverse join sql
4929
+					$SQL .= $table_obj->get_inverse_join_sql($alias_prefixed);
4930
+				} else {
4931
+					// just add a regular JOIN to this table from the primary table
4932
+					$SQL .= $table_obj->get_join_sql($alias_prefixed);
4933
+				}
4934
+			}// if it's a primary table, dont add any SQL. it should already be in the FROM statement
4935
+		}
4936
+		return $SQL;
4937
+	}
4938
+
4939
+
4940
+	/**
4941
+	 * Gets an array for storing all the data types on the next-to-be-executed-query.
4942
+	 * This should be a growing array of keys being table-columns (eg 'EVT_ID' and 'Event.EVT_ID'), and values being
4943
+	 * their data type (eg, '%s', '%d', etc)
4944
+	 *
4945
+	 * @return array
4946
+	 */
4947
+	public function _get_data_types()
4948
+	{
4949
+		$data_types = [];
4950
+		foreach ($this->field_settings() as $field_obj) {
4951
+			// $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4952
+			/** @var $field_obj EE_Model_Field_Base */
4953
+			$data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4954
+		}
4955
+		return $data_types;
4956
+	}
4957
+
4958
+
4959
+	/**
4960
+	 * Gets the model object given the relation's name / model's name (eg, 'Event', 'Registration',etc. Always singular)
4961
+	 *
4962
+	 * @param string $model_name
4963
+	 * @return EEM_Base
4964
+	 * @throws EE_Error
4965
+	 */
4966
+	public function get_related_model_obj($model_name)
4967
+	{
4968
+		$model_classname = "EEM_" . $model_name;
4969
+		if (! class_exists($model_classname)) {
4970
+			throw new EE_Error(
4971
+				sprintf(
4972
+					esc_html__(
4973
+						"You specified a related model named %s in your query. No such model exists, if it did, it would have the classname %s",
4974
+						'event_espresso'
4975
+					),
4976
+					$model_name,
4977
+					$model_classname
4978
+				)
4979
+			);
4980
+		}
4981
+		return call_user_func($model_classname . "::instance");
4982
+	}
4983
+
4984
+
4985
+	/**
4986
+	 * Returns the array of EE_ModelRelations for this model.
4987
+	 *
4988
+	 * @return EE_Model_Relation_Base[]
4989
+	 */
4990
+	public function relation_settings()
4991
+	{
4992
+		return $this->_model_relations;
4993
+	}
4994
+
4995
+
4996
+	/**
4997
+	 * Gets all related models that this model BELONGS TO. Handy to know sometimes
4998
+	 * because without THOSE models, this model probably doesn't have much purpose.
4999
+	 * (Eg, without an event, datetimes have little purpose.)
5000
+	 *
5001
+	 * @return EE_Belongs_To_Relation[]
5002
+	 */
5003
+	public function belongs_to_relations()
5004
+	{
5005
+		$belongs_to_relations = [];
5006
+		foreach ($this->relation_settings() as $model_name => $relation_obj) {
5007
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
5008
+				$belongs_to_relations[ $model_name ] = $relation_obj;
5009
+			}
5010
+		}
5011
+		return $belongs_to_relations;
5012
+	}
5013
+
5014
+
5015
+	/**
5016
+	 * Returns the specified EE_Model_Relation, or throws an exception
5017
+	 *
5018
+	 * @param string $relation_name name of relation, key in $this->_relatedModels
5019
+	 * @return EE_Model_Relation_Base
5020
+	 * @throws EE_Error
5021
+	 */
5022
+	public function related_settings_for($relation_name)
5023
+	{
5024
+		$relatedModels = $this->relation_settings();
5025
+		if (! array_key_exists($relation_name, $relatedModels)) {
5026
+			throw new EE_Error(
5027
+				sprintf(
5028
+					esc_html__(
5029
+						'Cannot get %s related to %s. There is no model relation of that type. There is, however, %s...',
5030
+						'event_espresso'
5031
+					),
5032
+					$relation_name,
5033
+					$this->_get_class_name(),
5034
+					implode(', ', array_keys($relatedModels))
5035
+				)
5036
+			);
5037
+		}
5038
+		return $relatedModels[ $relation_name ];
5039
+	}
5040
+
5041
+
5042
+	/**
5043
+	 * A convenience method for getting a specific field's settings, instead of getting all field settings for all
5044
+	 * fields
5045
+	 *
5046
+	 * @param string  $fieldName
5047
+	 * @param boolean $include_db_only_fields
5048
+	 * @return EE_Model_Field_Base
5049
+	 * @throws EE_Error
5050
+	 */
5051
+	public function field_settings_for($fieldName, $include_db_only_fields = true)
5052
+	{
5053
+		$fieldSettings = $this->field_settings($include_db_only_fields);
5054
+		if (! array_key_exists($fieldName, $fieldSettings)) {
5055
+			throw new EE_Error(
5056
+				sprintf(
5057
+					esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
5058
+					$fieldName,
5059
+					$this->class_name
5060
+				)
5061
+			);
5062
+		}
5063
+		return $fieldSettings[ $fieldName ];
5064
+	}
5065
+
5066
+
5067
+	/**
5068
+	 * Checks if this field exists on this model
5069
+	 *
5070
+	 * @param string $fieldName a key in the model's _field_settings array
5071
+	 * @return boolean
5072
+	 */
5073
+	public function has_field($fieldName)
5074
+	{
5075
+		$fieldSettings = $this->field_settings(true);
5076
+		if (isset($fieldSettings[ $fieldName ])) {
5077
+			return true;
5078
+		}
5079
+		return false;
5080
+	}
5081
+
5082
+
5083
+	/**
5084
+	 * Returns whether this model has a relation to the specified model
5085
+	 *
5086
+	 * @param string $relation_name possibly one of the keys in the relation_settings array
5087
+	 * @return boolean
5088
+	 */
5089
+	public function has_relation($relation_name)
5090
+	{
5091
+		$relations = $this->relation_settings();
5092
+		if (isset($relations[ $relation_name ])) {
5093
+			return true;
5094
+		}
5095
+		return false;
5096
+	}
5097
+
5098
+
5099
+	/**
5100
+	 * gets the field object of type 'primary_key' from the fieldsSettings attribute.
5101
+	 * Eg, on EE_Answer that would be ANS_ID field object
5102
+	 *
5103
+	 * @param $field_obj
5104
+	 * @return boolean
5105
+	 */
5106
+	public function is_primary_key_field($field_obj): bool
5107
+	{
5108
+		return $field_obj instanceof EE_Primary_Key_Field_Base;
5109
+	}
5110
+
5111
+
5112
+	/**
5113
+	 * gets the field object of type 'primary_key' from the fieldsSettings attribute.
5114
+	 * Eg, on EE_Answer that would be ANS_ID field object
5115
+	 *
5116
+	 * @return EE_Primary_Key_Field_Base
5117
+	 * @throws EE_Error
5118
+	 */
5119
+	public function get_primary_key_field()
5120
+	{
5121
+		if ($this->_primary_key_field === null) {
5122
+			foreach ($this->field_settings(true) as $field_obj) {
5123
+				if ($this->is_primary_key_field($field_obj)) {
5124
+					$this->_primary_key_field = $field_obj;
5125
+					break;
5126
+				}
5127
+			}
5128
+			if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5129
+				throw new EE_Error(
5130
+					sprintf(
5131
+						esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
5132
+						$this->class_name
5133
+					)
5134
+				);
5135
+			}
5136
+		}
5137
+		return $this->_primary_key_field;
5138
+	}
5139
+
5140
+
5141
+	/**
5142
+	 * Returns whether not there is a primary key on this model.
5143
+	 * Internally does some caching.
5144
+	 *
5145
+	 * @return boolean
5146
+	 */
5147
+	public function has_primary_key_field()
5148
+	{
5149
+		if ($this->_has_primary_key_field === null) {
5150
+			try {
5151
+				$this->get_primary_key_field();
5152
+				$this->_has_primary_key_field = true;
5153
+			} catch (EE_Error $e) {
5154
+				$this->_has_primary_key_field = false;
5155
+			}
5156
+		}
5157
+		return $this->_has_primary_key_field;
5158
+	}
5159
+
5160
+
5161
+	/**
5162
+	 * Finds the first field of type $field_class_name.
5163
+	 *
5164
+	 * @param string $field_class_name class name of field that you want to find. Eg, EE_Datetime_Field,
5165
+	 *                                 EE_Foreign_Key_Field, etc
5166
+	 * @return EE_Model_Field_Base or null if none is found
5167
+	 */
5168
+	public function get_a_field_of_type($field_class_name)
5169
+	{
5170
+		foreach ($this->field_settings() as $field) {
5171
+			if ($field instanceof $field_class_name) {
5172
+				return $field;
5173
+			}
5174
+		}
5175
+		return null;
5176
+	}
5177
+
5178
+
5179
+	/**
5180
+	 * Gets a foreign key field pointing to model.
5181
+	 *
5182
+	 * @param string $model_name eg Event, Registration, not EEM_Event
5183
+	 * @return EE_Foreign_Key_Field_Base
5184
+	 * @throws EE_Error
5185
+	 */
5186
+	public function get_foreign_key_to($model_name)
5187
+	{
5188
+		if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5189
+			foreach ($this->field_settings() as $field) {
5190
+				if (
5191
+					$field instanceof EE_Foreign_Key_Field_Base
5192
+					&& in_array($model_name, $field->get_model_names_pointed_to())
5193
+				) {
5194
+					$this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5195
+					break;
5196
+				}
5197
+			}
5198
+			if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5199
+				throw new EE_Error(
5200
+					sprintf(
5201
+						esc_html__(
5202
+							"There is no foreign key field pointing to model %s on model %s",
5203
+							'event_espresso'
5204
+						),
5205
+						$model_name,
5206
+						$this->class_name
5207
+					)
5208
+				);
5209
+			}
5210
+		}
5211
+		return $this->_cache_foreign_key_to_fields[ $model_name ];
5212
+	}
5213
+
5214
+
5215
+	/**
5216
+	 * Gets the table name (including $wpdb->prefix) for the table alias
5217
+	 *
5218
+	 * @param string $table_alias eg Event, Event_Meta, Registration, Transaction, but maybe
5219
+	 *                            a table alias with a model chain prefix, like 'Venue__Event_Venue___Event_Meta'.
5220
+	 *                            Either one works
5221
+	 * @return string
5222
+	 */
5223
+	public function get_table_for_alias($table_alias)
5224
+	{
5225
+		$table_alias_sans_model_relation_chain_prefix =
5226
+			EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5227
+		return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5228
+	}
5229
+
5230
+
5231
+	/**
5232
+	 * Returns a flat array of all field son this model, instead of organizing them
5233
+	 * by table_alias as they are in the constructor.
5234
+	 *
5235
+	 * @param bool $include_db_only_fields flag indicating whether to include the db-only fields
5236
+	 * @return EE_Model_Field_Base[] where the keys are the field's name
5237
+	 */
5238
+	public function field_settings($include_db_only_fields = false)
5239
+	{
5240
+		if ($include_db_only_fields) {
5241
+			if ($this->_cached_fields === null) {
5242
+				$this->_cached_fields = [];
5243
+				foreach ($this->_fields as $fields_corresponding_to_table) {
5244
+					foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5245
+						$this->_cached_fields[ $field_name ] = $field_obj;
5246
+					}
5247
+				}
5248
+			}
5249
+			return $this->_cached_fields;
5250
+		}
5251
+		if ($this->_cached_fields_non_db_only === null) {
5252
+			$this->_cached_fields_non_db_only = [];
5253
+			foreach ($this->_fields as $fields_corresponding_to_table) {
5254
+				foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5255
+					/** @var $field_obj EE_Model_Field_Base */
5256
+					if (! $field_obj->is_db_only_field()) {
5257
+						$this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5258
+					}
5259
+				}
5260
+			}
5261
+		}
5262
+		return $this->_cached_fields_non_db_only;
5263
+	}
5264
+
5265
+
5266
+	/**
5267
+	 *        cycle though array of attendees and create objects out of each item
5268
+	 *
5269
+	 * @access        private
5270
+	 * @param array $rows        of results of $wpdb->get_results($query,ARRAY_A)
5271
+	 * @return EE_Base_Class[] array keys are primary keys (if there is a primary key on the model. if not,
5272
+	 *                           numerically indexed)
5273
+	 * @throws EE_Error
5274
+	 * @throws ReflectionException
5275
+	 */
5276
+	protected function _create_objects($rows = [])
5277
+	{
5278
+		$array_of_objects = [];
5279
+		if (empty($rows)) {
5280
+			return [];
5281
+		}
5282
+		$count_if_model_has_no_primary_key = 0;
5283
+		$has_primary_key                   = $this->has_primary_key_field();
5284
+		$primary_key_field                 = $has_primary_key
5285
+			? $this->get_primary_key_field()
5286
+			: null;
5287
+		foreach ((array) $rows as $row) {
5288
+			if (empty($row)) {
5289
+				// wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5290
+				return [];
5291
+			}
5292
+			// check if we've already set this object in the results array,
5293
+			// in which case there's no need to process it further (again)
5294
+			if ($has_primary_key) {
5295
+				$table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5296
+					$row,
5297
+					$primary_key_field->get_qualified_column(),
5298
+					$primary_key_field->get_table_column()
5299
+				);
5300
+				if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5301
+					continue;
5302
+				}
5303
+			}
5304
+			$classInstance = $this->instantiate_class_from_array_or_object($row);
5305
+			if (! $classInstance) {
5306
+				throw new EE_Error(
5307
+					sprintf(
5308
+						esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
5309
+						$this->get_this_model_name(),
5310
+						http_build_query($row)
5311
+					)
5312
+				);
5313
+			}
5314
+			// set the timezone on the instantiated objects
5315
+			$classInstance->set_timezone($this->_timezone);
5316
+			// make sure if there is any timezone setting present that we set the timezone for the object
5317
+			$key                      = $has_primary_key
5318
+				? $classInstance->ID()
5319
+				: $count_if_model_has_no_primary_key++;
5320
+			$array_of_objects[ $key ] = $classInstance;
5321
+			// also, for all the relations of type BelongsTo, see if we can cache
5322
+			// those related models
5323
+			// (we could do this for other relations too, but if there are conditions
5324
+			// that filtered out some fo the results, then we'd be caching an incomplete set
5325
+			// so it requires a little more thought than just caching them immediately...)
5326
+			foreach ($this->_model_relations as $modelName => $relation_obj) {
5327
+				if ($relation_obj instanceof EE_Belongs_To_Relation) {
5328
+					// check if this model's INFO is present. If so, cache it on the model
5329
+					$other_model           = $relation_obj->get_other_model();
5330
+					$other_model_obj_maybe = $other_model->instantiate_class_from_array_or_object($row);
5331
+					// if we managed to make a model object from the results, cache it on the main model object
5332
+					if ($other_model_obj_maybe) {
5333
+						// set timezone on these other model objects if they are present
5334
+						$other_model_obj_maybe->set_timezone($this->_timezone);
5335
+						$classInstance->cache($modelName, $other_model_obj_maybe);
5336
+					}
5337
+				}
5338
+			}
5339
+			// also, if this was a custom select query, let's see if there are any results for the custom select fields
5340
+			// and add them to the object as well.  We'll convert according to the set data_type if there's any set for
5341
+			// the field in the CustomSelects object
5342
+			if ($this->_custom_selections instanceof CustomSelects) {
5343
+				$classInstance->setCustomSelectsValues(
5344
+					$this->getValuesForCustomSelectAliasesFromResults($row)
5345
+				);
5346
+			}
5347
+		}
5348
+		return $array_of_objects;
5349
+	}
5350
+
5351
+
5352
+	/**
5353
+	 * This will parse a given row of results from the db and see if any keys in the results match an alias within the
5354
+	 * current CustomSelects object. This will be used to build an array of values indexed by those keys.
5355
+	 *
5356
+	 * @param array $db_results_row
5357
+	 * @return array
5358
+	 */
5359
+	protected function getValuesForCustomSelectAliasesFromResults(array $db_results_row)
5360
+	{
5361
+		$results = [];
5362
+		if ($this->_custom_selections instanceof CustomSelects) {
5363
+			foreach ($this->_custom_selections->columnAliases() as $alias) {
5364
+				if (isset($db_results_row[ $alias ])) {
5365
+					$results[ $alias ] = $this->convertValueToDataType(
5366
+						$db_results_row[ $alias ],
5367
+						$this->_custom_selections->getDataTypeForAlias($alias)
5368
+					);
5369
+				}
5370
+			}
5371
+		}
5372
+		return $results;
5373
+	}
5374
+
5375
+
5376
+	/**
5377
+	 * This will set the value for the given alias
5378
+	 *
5379
+	 * @param string $value
5380
+	 * @param string $datatype (one of %d, %s, %f)
5381
+	 * @return int|string|float (int for %d, string for %s, float for %f)
5382
+	 */
5383
+	protected function convertValueToDataType($value, $datatype)
5384
+	{
5385
+		switch ($datatype) {
5386
+			case '%f':
5387
+				return (float) $value;
5388
+			case '%d':
5389
+				return (int) $value;
5390
+			default:
5391
+				return (string) $value;
5392
+		}
5393
+	}
5394
+
5395
+
5396
+	/**
5397
+	 * The purpose of this method is to allow us to create a model object that is not in the db that holds default
5398
+	 * values. A typical example of where this is used is when creating a new item and the initial load of a form.  We
5399
+	 * dont' necessarily want to test for if the object is present but just assume it is BUT load the defaults from the
5400
+	 * object (as set in the model_field!).
5401
+	 *
5402
+	 * @return EE_Base_Class single EE_Base_Class object with default values for the properties.
5403
+	 * @throws EE_Error
5404
+	 * @throws ReflectionException
5405
+	 */
5406
+	public function create_default_object()
5407
+	{
5408
+		$this_model_fields_and_values = [];
5409
+		// setup the row using default values;
5410
+		foreach ($this->field_settings() as $field_name => $field_obj) {
5411
+			$this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5412
+		}
5413
+		$className = $this->_get_class_name();
5414
+		return EE_Registry::instance()->load_class($className, [$this_model_fields_and_values], false, false);
5415
+	}
5416
+
5417
+
5418
+	/**
5419
+	 * @param mixed $cols_n_values either an array of where each key is the name of a field, and the value is its value
5420
+	 *                             or an stdClass where each property is the name of a column,
5421
+	 * @return EE_Base_Class
5422
+	 * @throws EE_Error
5423
+	 * @throws ReflectionException
5424
+	 */
5425
+	public function instantiate_class_from_array_or_object($cols_n_values)
5426
+	{
5427
+		if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5428
+			$cols_n_values = get_object_vars($cols_n_values);
5429
+		}
5430
+		$primary_key = null;
5431
+		// make sure the array only has keys that are fields/columns on this model
5432
+		$this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5433
+		if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5434
+			$primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5435
+		}
5436
+		$className = $this->_get_class_name();
5437
+		// check we actually found results that we can use to build our model object
5438
+		// if not, return null
5439
+		if ($this->has_primary_key_field()) {
5440
+			if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5441
+				return null;
5442
+			}
5443
+		} elseif ($this->unique_indexes()) {
5444
+			$first_column = reset($this_model_fields_n_values);
5445
+			if (empty($first_column)) {
5446
+				return null;
5447
+			}
5448
+		}
5449
+		// if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5450
+		if ($primary_key) {
5451
+			$classInstance = $this->get_from_entity_map($primary_key);
5452
+			if (! $classInstance) {
5453
+				$classInstance = EE_Registry::instance()
5454
+											->load_class(
5455
+												$className,
5456
+												[$this_model_fields_n_values, $this->_timezone],
5457
+												true,
5458
+												false
5459
+											);
5460
+				// add this new object to the entity map
5461
+				$classInstance = $this->add_to_entity_map($classInstance);
5462
+			}
5463
+		} else {
5464
+			$classInstance = EE_Registry::instance()->load_class(
5465
+				$className,
5466
+				[$this_model_fields_n_values, $this->_timezone],
5467
+				true,
5468
+				false
5469
+			);
5470
+		}
5471
+		return $classInstance;
5472
+	}
5473
+
5474
+
5475
+	/**
5476
+	 * Gets the model object from the  entity map if it exists
5477
+	 *
5478
+	 * @param int|string $id the ID of the model object
5479
+	 * @return EE_Base_Class
5480
+	 */
5481
+	public function get_from_entity_map($id)
5482
+	{
5483
+		return $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] ?? null;
5484
+	}
5485
+
5486
+
5487
+	/**
5488
+	 * add_to_entity_map
5489
+	 * Adds the object to the model's entity mappings
5490
+	 *        Effectively tells the models "Hey, this model object is the most up-to-date representation of the data,
5491
+	 *        and for the remainder of the request, it's even more up-to-date than what's in the database.
5492
+	 *        So, if the database doesn't agree with what's in the entity mapper, ignore the database"
5493
+	 *        If the database gets updated directly and you want the entity mapper to reflect that change,
5494
+	 *        then this method should be called immediately after the update query
5495
+	 * Note: The map is indexed by whatever the current blog id is set (via EEM_Base::$_model_query_blog_id).  This is
5496
+	 * so on multisite, the entity map is specific to the query being done for a specific site.
5497
+	 *
5498
+	 * @param EE_Base_Class $object
5499
+	 * @return EE_Base_Class
5500
+	 * @throws EE_Error
5501
+	 * @throws ReflectionException
5502
+	 */
5503
+	public function add_to_entity_map(EE_Base_Class $object)
5504
+	{
5505
+		$className = $this->_get_class_name();
5506
+		if (! $object instanceof $className) {
5507
+			throw new EE_Error(
5508
+				sprintf(
5509
+					esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
5510
+					is_object($object)
5511
+						? get_class($object)
5512
+						: $object,
5513
+					$className
5514
+				)
5515
+			);
5516
+		}
5517
+
5518
+		if (! $object->ID()) {
5519
+			throw new EE_Error(
5520
+				sprintf(
5521
+					esc_html__(
5522
+						"You tried storing a model object with NO ID in the %s entity mapper.",
5523
+						"event_espresso"
5524
+					),
5525
+					$this->class_name
5526
+				)
5527
+			);
5528
+		}
5529
+		// double check it's not already there
5530
+		$classInstance = $this->get_from_entity_map($object->ID());
5531
+		if ($classInstance) {
5532
+			return $classInstance;
5533
+		}
5534
+		$this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5535
+		return $object;
5536
+	}
5537
+
5538
+
5539
+	/**
5540
+	 * if a valid identifier is provided, then that entity is unset from the entity map,
5541
+	 * if no identifier is provided, then the entire entity map is emptied
5542
+	 *
5543
+	 * @param int|string $id the ID of the model object
5544
+	 * @return boolean
5545
+	 */
5546
+	public function clear_entity_map($id = null)
5547
+	{
5548
+		if (empty($id)) {
5549
+			$this->_entity_map[ EEM_Base::$_model_query_blog_id ] = [];
5550
+			return true;
5551
+		}
5552
+		if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5553
+			unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5554
+			return true;
5555
+		}
5556
+		return false;
5557
+	}
5558
+
5559
+
5560
+	/**
5561
+	 * Public wrapper for _deduce_fields_n_values_from_cols_n_values.
5562
+	 * Given an array where keys are column (or column alias) names and values,
5563
+	 * returns an array of their corresponding field names and database values
5564
+	 *
5565
+	 * @param array $cols_n_values
5566
+	 * @return array
5567
+	 * @throws EE_Error
5568
+	 * @throws ReflectionException
5569
+	 */
5570
+	public function deduce_fields_n_values_from_cols_n_values(array $cols_n_values): array
5571
+	{
5572
+		return $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5573
+	}
5574
+
5575
+
5576
+	/**
5577
+	 * _deduce_fields_n_values_from_cols_n_values
5578
+	 * Given an array where keys are column (or column alias) names and values,
5579
+	 * returns an array of their corresponding field names and database values
5580
+	 *
5581
+	 * @param array|stdClass $cols_n_values
5582
+	 * @return array
5583
+	 * @throws EE_Error
5584
+	 * @throws ReflectionException
5585
+	 */
5586
+	protected function _deduce_fields_n_values_from_cols_n_values($cols_n_values): array
5587
+	{
5588
+		if ($cols_n_values instanceof stdClass) {
5589
+			$cols_n_values = get_object_vars($cols_n_values);
5590
+		}
5591
+		$this_model_fields_n_values = [];
5592
+		foreach ($this->get_tables() as $table_alias => $table_obj) {
5593
+			$table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5594
+				$cols_n_values,
5595
+				$table_obj->get_fully_qualified_pk_column(),
5596
+				$table_obj->get_pk_column()
5597
+			);
5598
+			// there is a primary key on this table and its not set. Use defaults for all its columns
5599
+			if ($table_pk_value === null && $table_obj->get_pk_column()) {
5600
+				foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5601
+					if (! $field_obj->is_db_only_field()) {
5602
+						// prepare field as if its coming from db
5603
+						$prepared_value                            =
5604
+							$field_obj->prepare_for_set($field_obj->get_default_value());
5605
+						$this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5606
+					}
5607
+				}
5608
+			} else {
5609
+				// the table's rows existed. Use their values
5610
+				foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5611
+					if (! $field_obj->is_db_only_field()) {
5612
+						$this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5613
+							$cols_n_values,
5614
+							$field_obj->get_qualified_column(),
5615
+							$field_obj->get_table_column()
5616
+						);
5617
+					}
5618
+				}
5619
+			}
5620
+		}
5621
+		return $this_model_fields_n_values;
5622
+	}
5623
+
5624
+
5625
+	/**
5626
+	 * @param $cols_n_values
5627
+	 * @param $qualified_column
5628
+	 * @param $regular_column
5629
+	 * @return null
5630
+	 * @throws EE_Error
5631
+	 * @throws ReflectionException
5632
+	 */
5633
+	protected function _get_column_value_with_table_alias_or_not($cols_n_values, $qualified_column, $regular_column)
5634
+	{
5635
+		$value = null;
5636
+		// ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5637
+		// does the field on the model relate to this column retrieved from the db?
5638
+		// or is it a db-only field? (not relating to the model)
5639
+		if (isset($cols_n_values[ $qualified_column ])) {
5640
+			$value = $cols_n_values[ $qualified_column ];
5641
+		} elseif (isset($cols_n_values[ $regular_column ])) {
5642
+			$value = $cols_n_values[ $regular_column ];
5643
+		} elseif (! empty($this->foreign_key_aliases)) {
5644
+			// no PK?  ok check if there is a foreign key alias set for this table
5645
+			// then check if that alias exists in the incoming data
5646
+			// AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5647
+			foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5648
+				if ($PK_column === $qualified_column && !empty($cols_n_values[ $FK_alias ])) {
5649
+					$value = $cols_n_values[ $FK_alias ];
5650
+					[$pk_class] = explode('.', $PK_column);
5651
+					$pk_model_name = "EEM_{$pk_class}";
5652
+					/** @var EEM_Base $pk_model */
5653
+					$pk_model = EE_Registry::instance()->load_model($pk_model_name);
5654
+					if ($pk_model instanceof EEM_Base) {
5655
+						// make sure object is pulled from db and added to entity map
5656
+						$pk_model->get_one_by_ID($value);
5657
+					}
5658
+					break;
5659
+				}
5660
+			}
5661
+		}
5662
+		return $value;
5663
+	}
5664
+
5665
+
5666
+	/**
5667
+	 * refresh_entity_map_from_db
5668
+	 * Makes sure the model object in the entity map at $id assumes the values
5669
+	 * of the database (opposite of EE_base_Class::save())
5670
+	 *
5671
+	 * @param int|string $id
5672
+	 * @return EE_Base_Class|EE_Soft_Delete_Base_Class|mixed|null
5673
+	 * @throws EE_Error
5674
+	 * @throws ReflectionException
5675
+	 */
5676
+	public function refresh_entity_map_from_db($id)
5677
+	{
5678
+		$obj_in_map = $this->get_from_entity_map($id);
5679
+		if ($obj_in_map) {
5680
+			$wpdb_results = $this->_get_all_wpdb_results(
5681
+				[[$this->get_primary_key_field()->get_name() => $id], 'limit' => 1]
5682
+			);
5683
+			if ($wpdb_results && is_array($wpdb_results)) {
5684
+				$one_row = reset($wpdb_results);
5685
+				foreach ($this->_deduce_fields_n_values_from_cols_n_values($one_row) as $field_name => $db_value) {
5686
+					$obj_in_map->set_from_db($field_name, $db_value);
5687
+				}
5688
+				// clear the cache of related model objects
5689
+				foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5690
+					$obj_in_map->clear_cache($relation_name, null, true);
5691
+				}
5692
+			}
5693
+			$this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5694
+			return $obj_in_map;
5695
+		}
5696
+		return $this->get_one_by_ID($id);
5697
+	}
5698
+
5699
+
5700
+	/**
5701
+	 * refresh_entity_map_with
5702
+	 * Leaves the entry in the entity map alone, but updates it to match the provided
5703
+	 * $replacing_model_obj (which we assume to be its equivalent but somehow NOT in the entity map).
5704
+	 * This is useful if you have a model object you want to make authoritative over what's in the entity map currently.
5705
+	 * Note: The old $replacing_model_obj should now be destroyed as it's now un-authoritative
5706
+	 *
5707
+	 * @param int|string    $id
5708
+	 * @param EE_Base_Class $replacing_model_obj
5709
+	 * @return EE_Base_Class
5710
+	 * @throws EE_Error
5711
+	 * @throws ReflectionException
5712
+	 */
5713
+	public function refresh_entity_map_with($id, $replacing_model_obj)
5714
+	{
5715
+		$obj_in_map = $this->get_from_entity_map($id);
5716
+		if ($obj_in_map) {
5717
+			if ($replacing_model_obj instanceof EE_Base_Class) {
5718
+				foreach ($replacing_model_obj->model_field_array() as $field_name => $value) {
5719
+					$obj_in_map->set($field_name, $value);
5720
+				}
5721
+				// make the model object in the entity map's cache match the $replacing_model_obj
5722
+				foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5723
+					$obj_in_map->clear_cache($relation_name, null, true);
5724
+					foreach ($replacing_model_obj->get_all_from_cache($relation_name) as $cache_id => $cached_obj) {
5725
+						$obj_in_map->cache($relation_name, $cached_obj, $cache_id);
5726
+					}
5727
+				}
5728
+			}
5729
+			return $obj_in_map;
5730
+		}
5731
+		$this->add_to_entity_map($replacing_model_obj);
5732
+		return $replacing_model_obj;
5733
+	}
5734
+
5735
+
5736
+	/**
5737
+	 * Gets the EE class that corresponds to this model. Eg, for EEM_Answer that
5738
+	 * would be EE_Answer.To import that class, you'd just add ".class.php" to the name, like so
5739
+	 * require_once($this->_getClassName().".class.php");
5740
+	 *
5741
+	 * @return string
5742
+	 */
5743
+	private function _get_class_name()
5744
+	{
5745
+		return "EE_" . $this->get_this_model_name();
5746
+	}
5747
+
5748
+
5749
+	/**
5750
+	 * Get the name of the items this model represents, for the quantity specified. Eg,
5751
+	 * if $quantity==1, on EEM_Event, it would 'Event' (internationalized), otherwise
5752
+	 * it would be 'Events'.
5753
+	 *
5754
+	 * @param int|float|null $quantity
5755
+	 * @return string
5756
+	 */
5757
+	public function item_name($quantity = 1): string
5758
+	{
5759
+		$quantity = floor($quantity);
5760
+		return apply_filters(
5761
+			'FHEE__EEM_Base__item_name__plural_or_singular',
5762
+			$quantity > 1
5763
+				? $this->plural_item
5764
+				: $this->singular_item,
5765
+			$quantity,
5766
+			$this->plural_item,
5767
+			$this->singular_item
5768
+		);
5769
+	}
5770
+
5771
+
5772
+	/**
5773
+	 * Very handy general function to allow for plugins to extend any child of EE_TempBase.
5774
+	 * If a method is called on a child of EE_TempBase that doesn't exist, this function is called
5775
+	 * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
5776
+	 * requiring a plugin to extend the EE_TempBase (which works fine is there's only 1 plugin, but when will that
5777
+	 * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
5778
+	 * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
5779
+	 * was called, and an array of the original arguments passed to the function. Whatever their callback function
5780
+	 * returns will be returned by this function. Example: in functions.php (or in a plugin):
5781
+	 * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
5782
+	 * my_callback($previousReturnValue,EE_TempBase $object,$argsArray){
5783
+	 * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
5784
+	 *        return $previousReturnValue.$returnString;
5785
+	 * }
5786
+	 * require('EEM_Answer.model.php');
5787
+	 * echo EEM_Answer::instance()->my_callback('monkeys',100);
5788
+	 * // will output "you called my_callback! and passed args:monkeys,100"
5789
+	 *
5790
+	 * @param string $methodName name of method which was called on a child of EE_TempBase, but which
5791
+	 * @param array  $args       array of original arguments passed to the function
5792
+	 * @return mixed whatever the plugin which calls add_filter decides
5793
+	 * @throws EE_Error
5794
+	 */
5795
+	public function __call($methodName, $args)
5796
+	{
5797
+		$className = $this->class_name;
5798
+		$tagName   = "FHEE__{$className}__{$methodName}";
5799
+		if (! has_filter($tagName)) {
5800
+			throw new EE_Error(
5801
+				sprintf(
5802
+					esc_html__(
5803
+						'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 );',
5804
+						'event_espresso'
5805
+					),
5806
+					$methodName,
5807
+					$className,
5808
+					$tagName,
5809
+					'<br />'
5810
+				)
5811
+			);
5812
+		}
5813
+		return apply_filters($tagName, null, $this, $args);
5814
+	}
5815
+
5816
+
5817
+	/**
5818
+	 * Ensures $base_class_obj_or_id is of the EE_Base_Class child that corresponds ot this model.
5819
+	 * If not, assumes its an ID, and uses $this->get_one_by_ID() to get the EE_Base_Class.
5820
+	 *
5821
+	 * @param EE_Base_Class|string|int $base_class_obj_or_id either:
5822
+	 *                                                       the EE_Base_Class object that corresponds to this Model,
5823
+	 *                                                       the object's class name
5824
+	 *                                                       or object's ID
5825
+	 * @param boolean                  $ensure_is_in_db      if set, we will also verify this model object
5826
+	 *                                                       exists in the database. If it does not, we add it
5827
+	 * @return EE_Base_Class
5828
+	 * @throws EE_Error
5829
+	 * @throws ReflectionException
5830
+	 */
5831
+	public function ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db = false)
5832
+	{
5833
+		$className = $this->_get_class_name();
5834
+		if ($base_class_obj_or_id instanceof $className) {
5835
+			$model_object = $base_class_obj_or_id;
5836
+		} else {
5837
+			$primary_key_field = $this->get_primary_key_field();
5838
+			if (
5839
+				$primary_key_field instanceof EE_Primary_Key_Int_Field
5840
+				&& (
5841
+					is_int($base_class_obj_or_id)
5842
+					|| is_string($base_class_obj_or_id)
5843
+				)
5844
+			) {
5845
+				// assume it's an ID.
5846
+				// either a proper integer or a string representing an integer (eg "101" instead of 101)
5847
+				$model_object = $this->get_one_by_ID($base_class_obj_or_id);
5848
+			} elseif (
5849
+				$primary_key_field instanceof EE_Primary_Key_String_Field
5850
+				&& is_string($base_class_obj_or_id)
5851
+			) {
5852
+				// assume it's a string representation of the object
5853
+				$model_object = $this->get_one_by_ID($base_class_obj_or_id);
5854
+			} else {
5855
+				throw new EE_Error(
5856
+					sprintf(
5857
+						esc_html__(
5858
+							"'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5859
+							'event_espresso'
5860
+						),
5861
+						$base_class_obj_or_id,
5862
+						$this->_get_class_name(),
5863
+						print_r($base_class_obj_or_id, true)
5864
+					)
5865
+				);
5866
+			}
5867
+		}
5868
+		if ($ensure_is_in_db && $model_object instanceof EE_Base_Class && $model_object->ID() !== null) {
5869
+			$model_object->save();
5870
+		}
5871
+		return $model_object;
5872
+	}
5873
+
5874
+
5875
+	/**
5876
+	 * Similar to ensure_is_obj(), this method makes sure $base_class_obj_or_id
5877
+	 * is a value of the this model's primary key. If it's an EE_Base_Class child,
5878
+	 * returns it ID.
5879
+	 *
5880
+	 * @param EE_Base_Class|int|string $base_class_obj_or_id
5881
+	 * @return int|string depending on the type of this model object's ID
5882
+	 * @throws EE_Error
5883
+	 * @throws ReflectionException
5884
+	 */
5885
+	public function ensure_is_ID($base_class_obj_or_id)
5886
+	{
5887
+		$className = $this->_get_class_name();
5888
+		if ($base_class_obj_or_id instanceof $className) {
5889
+			/** @var $base_class_obj_or_id EE_Base_Class */
5890
+			$id = $base_class_obj_or_id->ID();
5891
+		} elseif (is_int($base_class_obj_or_id)) {
5892
+			// assume it's an ID
5893
+			$id = $base_class_obj_or_id;
5894
+		} elseif (is_string($base_class_obj_or_id)) {
5895
+			// assume its a string representation of the object
5896
+			$id = $base_class_obj_or_id;
5897
+		} else {
5898
+			throw new EE_Error(
5899
+				sprintf(
5900
+					esc_html__(
5901
+						"'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5902
+						'event_espresso'
5903
+					),
5904
+					$base_class_obj_or_id,
5905
+					$this->_get_class_name(),
5906
+					print_r($base_class_obj_or_id, true)
5907
+				)
5908
+			);
5909
+		}
5910
+		return $id;
5911
+	}
5912
+
5913
+
5914
+	/**
5915
+	 * Sets whether the values passed to the model (eg, values in WHERE, values in INSERT, UPDATE, etc)
5916
+	 * have already been ran through the appropriate model field's prepare_for_use_in_db method. IE, they have
5917
+	 * been sanitized and converted into the appropriate domain.
5918
+	 * Usually the only place you'll want to change the default (which is to assume values have NOT been sanitized by
5919
+	 * the model object/model field) is when making a method call from WITHIN a model object, which has direct access
5920
+	 * to its sanitized values. Note: after changing this setting, you should set it back to its previous value (using
5921
+	 * get_assumption_concerning_values_already_prepared_by_model_object()) eg.
5922
+	 * $EVT = EEM_Event::instance(); $old_setting =
5923
+	 * $EVT->get_assumption_concerning_values_already_prepared_by_model_object();
5924
+	 * $EVT->assume_values_already_prepared_by_model_object(true);
5925
+	 * $EVT->update(array('foo'=>'bar'),array(array('foo'=>'monkey')));
5926
+	 * $EVT->assume_values_already_prepared_by_model_object($old_setting);
5927
+	 *
5928
+	 * @param int $values_already_prepared like one of the constants on EEM_Base
5929
+	 * @return void
5930
+	 */
5931
+	public function assume_values_already_prepared_by_model_object(
5932
+		$values_already_prepared = self::not_prepared_by_model_object
5933
+	) {
5934
+		$this->_values_already_prepared_by_model_object = $values_already_prepared;
5935
+	}
5936
+
5937
+
5938
+	/**
5939
+	 * Read comments for assume_values_already_prepared_by_model_object()
5940
+	 *
5941
+	 * @return int
5942
+	 */
5943
+	public function get_assumption_concerning_values_already_prepared_by_model_object()
5944
+	{
5945
+		return $this->_values_already_prepared_by_model_object;
5946
+	}
5947
+
5948
+
5949
+	/**
5950
+	 * Gets all the indexes on this model
5951
+	 *
5952
+	 * @return EE_Index[]
5953
+	 */
5954
+	public function indexes()
5955
+	{
5956
+		return $this->_indexes;
5957
+	}
5958
+
5959
+
5960
+	/**
5961
+	 * Gets all the Unique Indexes on this model
5962
+	 *
5963
+	 * @return EE_Unique_Index[]
5964
+	 */
5965
+	public function unique_indexes()
5966
+	{
5967
+		$unique_indexes = [];
5968
+		foreach ($this->_indexes as $name => $index) {
5969
+			if ($index instanceof EE_Unique_Index) {
5970
+				$unique_indexes [ $name ] = $index;
5971
+			}
5972
+		}
5973
+		return $unique_indexes;
5974
+	}
5975
+
5976
+
5977
+	/**
5978
+	 * Gets all the fields which, when combined, make the primary key.
5979
+	 * This is usually just an array with 1 element (the primary key), but in cases
5980
+	 * where there is no primary key, it's a combination of fields as defined
5981
+	 * on a primary index
5982
+	 *
5983
+	 * @return EE_Model_Field_Base[] indexed by the field's name
5984
+	 * @throws EE_Error
5985
+	 */
5986
+	public function get_combined_primary_key_fields()
5987
+	{
5988
+		foreach ($this->indexes() as $index) {
5989
+			if ($index instanceof EE_Primary_Key_Index) {
5990
+				return $index->fields();
5991
+			}
5992
+		}
5993
+		return [$this->primary_key_name() => $this->get_primary_key_field()];
5994
+	}
5995
+
5996
+
5997
+	/**
5998
+	 * Used to build a primary key string (when the model has no primary key),
5999
+	 * which can be used a unique string to identify this model object.
6000
+	 *
6001
+	 * @param array $fields_n_values keys are field names, values are their values.
6002
+	 *                               Note: if you have results from `EEM_Base::get_all_wpdb_results()`, you need to
6003
+	 *                               run it through `EEM_Base::deduce_fields_n_values_from_cols_n_values()`
6004
+	 *                               before passing it to this function (that will convert it from columns-n-values
6005
+	 *                               to field-names-n-values).
6006
+	 * @return string
6007
+	 * @throws EE_Error
6008
+	 */
6009
+	public function get_index_primary_key_string($fields_n_values)
6010
+	{
6011
+		$cols_n_values_for_primary_key_index = array_intersect_key(
6012
+			$fields_n_values,
6013
+			$this->get_combined_primary_key_fields()
6014
+		);
6015
+		return http_build_query($cols_n_values_for_primary_key_index);
6016
+	}
6017
+
6018
+
6019
+	/**
6020
+	 * Gets the field values from the primary key string
6021
+	 *
6022
+	 * @param string $index_primary_key_string
6023
+	 * @return null|array
6024
+	 * @throws EE_Error
6025
+	 * @see EEM_Base::get_combined_primary_key_fields() and EEM_Base::get_index_primary_key_string()
6026
+	 */
6027
+	public function parse_index_primary_key_string($index_primary_key_string)
6028
+	{
6029
+		$key_fields = $this->get_combined_primary_key_fields();
6030
+		// check all of them are in the $id
6031
+		$key_vals_in_combined_pk = [];
6032
+		parse_str($index_primary_key_string, $key_vals_in_combined_pk);
6033
+		foreach ($key_fields as $key_field_name => $field_obj) {
6034
+			if (! isset($key_vals_in_combined_pk[ $key_field_name ])) {
6035
+				return null;
6036
+			}
6037
+		}
6038
+		return $key_vals_in_combined_pk;
6039
+	}
6040
+
6041
+
6042
+	/**
6043
+	 * verifies that an array of key-value pairs for model fields has a key
6044
+	 * for each field comprising the primary key index
6045
+	 *
6046
+	 * @param array $key_vals
6047
+	 * @return boolean
6048
+	 * @throws EE_Error
6049
+	 */
6050
+	public function has_all_combined_primary_key_fields($key_vals)
6051
+	{
6052
+		$keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
6053
+		foreach ($keys_it_should_have as $key) {
6054
+			if (! isset($key_vals[ $key ])) {
6055
+				return false;
6056
+			}
6057
+		}
6058
+		return true;
6059
+	}
6060
+
6061
+
6062
+	/**
6063
+	 * Finds all model objects in the DB that appear to be a copy of $model_object_or_attributes_array.
6064
+	 * We consider something to be a copy if all the attributes match (except the ID, of course).
6065
+	 *
6066
+	 * @param array|EE_Base_Class $model_object_or_attributes_array If its an array, it's field-value pairs
6067
+	 * @param array               $query_params                     @see
6068
+	 *                                                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
6069
+	 * @throws EE_Error
6070
+	 * @throws ReflectionException
6071
+	 * @return EE_Base_Class[] Array keys are object IDs (if there is a primary key on the model. if not, numerically
6072
+	 *                                                              indexed)
6073
+	 */
6074
+	public function get_all_copies($model_object_or_attributes_array, $query_params = [])
6075
+	{
6076
+		if ($model_object_or_attributes_array instanceof EE_Base_Class) {
6077
+			$attributes_array = $model_object_or_attributes_array->model_field_array();
6078
+		} elseif (is_array($model_object_or_attributes_array)) {
6079
+			$attributes_array = $model_object_or_attributes_array;
6080
+		} else {
6081
+			throw new EE_Error(
6082
+				sprintf(
6083
+					esc_html__(
6084
+						"get_all_copies should be provided with either a model object or an array of field-value-pairs, but was given %s",
6085
+						"event_espresso"
6086
+					),
6087
+					$model_object_or_attributes_array
6088
+				)
6089
+			);
6090
+		}
6091
+		// even copies obviously won't have the same ID, so remove the primary key
6092
+		// from the WHERE conditions for finding copies (if there is a primary key, of course)
6093
+		if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
6094
+			unset($attributes_array[ $this->primary_key_name() ]);
6095
+		}
6096
+		if (isset($query_params[0])) {
6097
+			$query_params[0] = array_merge($attributes_array, $query_params);
6098
+		} else {
6099
+			$query_params[0] = $attributes_array;
6100
+		}
6101
+		return $this->get_all($query_params);
6102
+	}
6103
+
6104
+
6105
+	/**
6106
+	 * Gets the first copy we find. See get_all_copies for more details
6107
+	 *
6108
+	 * @param mixed EE_Base_Class | array        $model_object_or_attributes_array
6109
+	 * @param array $query_params
6110
+	 * @return EE_Base_Class
6111
+	 * @throws EE_Error
6112
+	 * @throws ReflectionException
6113
+	 */
6114
+	public function get_one_copy($model_object_or_attributes_array, $query_params = [])
6115
+	{
6116
+		if (! is_array($query_params)) {
6117
+			EE_Error::doing_it_wrong(
6118
+				'EEM_Base::get_one_copy',
6119
+				sprintf(
6120
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
6121
+					gettype($query_params)
6122
+				),
6123
+				'4.6.0'
6124
+			);
6125
+			$query_params = [];
6126
+		}
6127
+		$query_params['limit'] = 1;
6128
+		$copies                = $this->get_all_copies($model_object_or_attributes_array, $query_params);
6129
+		if (is_array($copies)) {
6130
+			return array_shift($copies);
6131
+		}
6132
+		return null;
6133
+	}
6134
+
6135
+
6136
+	/**
6137
+	 * Updates the item with the specified id. Ignores default query parameters because
6138
+	 * we have specified the ID, and its assumed we KNOW what we're doing
6139
+	 *
6140
+	 * @param array      $fields_n_values keys are field names, values are their new values
6141
+	 * @param int|string $id              the value of the primary key to update
6142
+	 * @return int number of rows updated
6143
+	 * @throws EE_Error
6144
+	 * @throws ReflectionException
6145
+	 */
6146
+	public function update_by_ID($fields_n_values, $id)
6147
+	{
6148
+		$query_params = [
6149
+			0                          => [$this->get_primary_key_field()->get_name() => $id],
6150
+			'default_where_conditions' => EE_Default_Where_Conditions::OTHER_MODELS_ONLY,
6151
+		];
6152
+		return $this->update($fields_n_values, $query_params);
6153
+	}
6154
+
6155
+
6156
+	/**
6157
+	 * Changes an operator which was supplied to the models into one usable in SQL
6158
+	 *
6159
+	 * @param string $operator_supplied
6160
+	 * @return string an operator which can be used in SQL
6161
+	 * @throws EE_Error
6162
+	 */
6163
+	private function _prepare_operator_for_sql($operator_supplied)
6164
+	{
6165
+		$sql_operator = $this->_valid_operators[ $operator_supplied ] ?? null;
6166
+		if ($sql_operator) {
6167
+			return $sql_operator;
6168
+		}
6169
+		throw new EE_Error(
6170
+			sprintf(
6171
+				esc_html__(
6172
+					"The operator '%s' is not in the list of valid operators: %s",
6173
+					"event_espresso"
6174
+				),
6175
+				$operator_supplied,
6176
+				implode(",", array_keys($this->_valid_operators))
6177
+			)
6178
+		);
6179
+	}
6180
+
6181
+
6182
+	/**
6183
+	 * Gets the valid operators
6184
+	 *
6185
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6186
+	 */
6187
+	public function valid_operators()
6188
+	{
6189
+		return $this->_valid_operators;
6190
+	}
6191
+
6192
+
6193
+	/**
6194
+	 * Gets the between-style operators (take 2 arguments).
6195
+	 *
6196
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6197
+	 */
6198
+	public function valid_between_style_operators()
6199
+	{
6200
+		return array_intersect(
6201
+			$this->valid_operators(),
6202
+			$this->_between_style_operators
6203
+		);
6204
+	}
6205
+
6206
+
6207
+	/**
6208
+	 * Gets the "like"-style operators (take a single argument, but it may contain wildcards)
6209
+	 *
6210
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6211
+	 */
6212
+	public function valid_like_style_operators()
6213
+	{
6214
+		return array_intersect(
6215
+			$this->valid_operators(),
6216
+			$this->_like_style_operators
6217
+		);
6218
+	}
6219
+
6220
+
6221
+	/**
6222
+	 * Gets the "in"-style operators
6223
+	 *
6224
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6225
+	 */
6226
+	public function valid_in_style_operators()
6227
+	{
6228
+		return array_intersect(
6229
+			$this->valid_operators(),
6230
+			$this->_in_style_operators
6231
+		);
6232
+	}
6233
+
6234
+
6235
+	/**
6236
+	 * Gets the "null"-style operators (accept no arguments)
6237
+	 *
6238
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6239
+	 */
6240
+	public function valid_null_style_operators()
6241
+	{
6242
+		return array_intersect(
6243
+			$this->valid_operators(),
6244
+			$this->_null_style_operators
6245
+		);
6246
+	}
6247
+
6248
+
6249
+	/**
6250
+	 * Gets an array where keys are the primary keys and values are their 'names'
6251
+	 * (as determined by the model object's name() function, which is often overridden)
6252
+	 *
6253
+	 * @param array $query_params like get_all's
6254
+	 * @return string[]
6255
+	 * @throws EE_Error
6256
+	 * @throws ReflectionException
6257
+	 */
6258
+	public function get_all_names($query_params = [])
6259
+	{
6260
+		$objs  = $this->get_all($query_params);
6261
+		$names = [];
6262
+		foreach ($objs as $obj) {
6263
+			$names[ $obj->ID() ] = $obj->name();
6264
+		}
6265
+		return $names;
6266
+	}
6267
+
6268
+
6269
+	/**
6270
+	 * Gets an array of primary keys from the model objects. If you acquired the model objects
6271
+	 * using EEM_Base::get_all() you don't need to call this (and probably shouldn't because
6272
+	 * this is duplicated effort and reduces efficiency) you would be better to use
6273
+	 * array_keys() on $model_objects.
6274
+	 *
6275
+	 * @param \EE_Base_Class[] $model_objects
6276
+	 * @param boolean          $filter_out_empty_ids if a model object has an ID of '' or 0, don't bother including it
6277
+	 *                                               in the returned array
6278
+	 * @return array
6279
+	 * @throws EE_Error
6280
+	 * @throws ReflectionException
6281
+	 */
6282
+	public function get_IDs($model_objects, $filter_out_empty_ids = false)
6283
+	{
6284
+		if (! $this->has_primary_key_field()) {
6285
+			if (defined('WP_DEBUG') && WP_DEBUG) {
6286
+				EE_Error::add_error(
6287
+					esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
6288
+					__FILE__,
6289
+					__FUNCTION__,
6290
+					__LINE__
6291
+				);
6292
+			}
6293
+		}
6294
+		$IDs = [];
6295
+		foreach ($model_objects as $model_object) {
6296
+			$id = $model_object->ID();
6297
+			if (! $id) {
6298
+				if ($filter_out_empty_ids) {
6299
+					continue;
6300
+				}
6301
+				if (defined('WP_DEBUG') && WP_DEBUG) {
6302
+					EE_Error::add_error(
6303
+						esc_html__(
6304
+							'Called %1$s on a model object that has no ID and so probably hasn\'t been saved to the database',
6305
+							'event_espresso'
6306
+						),
6307
+						__FILE__,
6308
+						__FUNCTION__,
6309
+						__LINE__
6310
+					);
6311
+				}
6312
+			}
6313
+			$IDs[] = $id;
6314
+		}
6315
+		return $IDs;
6316
+	}
6317
+
6318
+
6319
+	/**
6320
+	 * Returns the string used in capabilities relating to this model. If there
6321
+	 * are no capabilities that relate to this model returns false
6322
+	 *
6323
+	 * @return string|false
6324
+	 */
6325
+	public function cap_slug()
6326
+	{
6327
+		return apply_filters('FHEE__EEM_Base__cap_slug', $this->_caps_slug, $this);
6328
+	}
6329
+
6330
+
6331
+	/**
6332
+	 * Returns the capability-restrictions array (@param string $context
6333
+	 *
6334
+	 * @return EE_Default_Where_Conditions[] indexed by associated capability
6335
+	 * @throws EE_Error
6336
+	 * @see EEM_Base::_cap_restrictions).
6337
+	 *      If $context is provided (which should be set to one of EEM_Base::valid_cap_contexts())
6338
+	 *      only returns the cap restrictions array in that context (ie, the array
6339
+	 *      at that key)
6340
+	 */
6341
+	public function cap_restrictions($context = EEM_Base::caps_read)
6342
+	{
6343
+		EEM_Base::verify_is_valid_cap_context($context);
6344
+		// check if we ought to run the restriction generator first
6345
+		if (
6346
+			isset($this->_cap_restriction_generators[ $context ])
6347
+			&& $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6348
+			&& ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6349
+		) {
6350
+			$this->_cap_restrictions[ $context ] = array_merge(
6351
+				$this->_cap_restrictions[ $context ],
6352
+				$this->_cap_restriction_generators[ $context ]->generate_restrictions()
6353
+			);
6354
+		}
6355
+		// and make sure we've finalized the construction of each restriction
6356
+		foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6357
+			if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6358
+				$where_conditions_obj->_finalize_construct($this);
6359
+			}
6360
+		}
6361
+		return $this->_cap_restrictions[ $context ];
6362
+	}
6363
+
6364
+
6365
+	/**
6366
+	 * Indicating whether this model thinks its a wp core model
6367
+	 *
6368
+	 * @return boolean
6369
+	 */
6370
+	public function is_wp_core_model()
6371
+	{
6372
+		return $this->_wp_core_model;
6373
+	}
6374
+
6375
+
6376
+	/**
6377
+	 * Gets all the caps that are missing which impose a restriction on
6378
+	 * queries made in this context
6379
+	 *
6380
+	 * @param string $context one of EEM_Base::caps_ constants
6381
+	 * @return EE_Default_Where_Conditions[] indexed by capability name
6382
+	 * @throws EE_Error
6383
+	 */
6384
+	public function caps_missing($context = EEM_Base::caps_read)
6385
+	{
6386
+		$missing_caps     = [];
6387
+		$cap_restrictions = $this->cap_restrictions($context);
6388
+		foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6389
+			if (
6390
+				! EE_Capabilities::instance()
6391
+								 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6392
+			) {
6393
+				$missing_caps[ $cap ] = $restriction_if_no_cap;
6394
+			}
6395
+		}
6396
+		return $missing_caps;
6397
+	}
6398
+
6399
+
6400
+	/**
6401
+	 * Gets the mapping from capability contexts to action strings used in capability names
6402
+	 *
6403
+	 * @return array keys are one of EEM_Base::valid_cap_contexts(), and values are usually
6404
+	 * one of 'read', 'edit', or 'delete'
6405
+	 */
6406
+	public function cap_contexts_to_cap_action_map()
6407
+	{
6408
+		return apply_filters(
6409
+			'FHEE__EEM_Base__cap_contexts_to_cap_action_map',
6410
+			$this->_cap_contexts_to_cap_action_map,
6411
+			$this
6412
+		);
6413
+	}
6414
+
6415
+
6416
+	/**
6417
+	 * Gets the action string for the specified capability context
6418
+	 *
6419
+	 * @param string $context
6420
+	 * @return string one of EEM_Base::cap_contexts_to_cap_action_map() values
6421
+	 * @throws EE_Error
6422
+	 */
6423
+	public function cap_action_for_context($context)
6424
+	{
6425
+		$mapping = $this->cap_contexts_to_cap_action_map();
6426
+		if (isset($mapping[ $context ])) {
6427
+			return $mapping[ $context ];
6428
+		}
6429
+		if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6430
+			return $action;
6431
+		}
6432
+		throw new EE_Error(
6433
+			sprintf(
6434
+				esc_html__(
6435
+					'Cannot find capability restrictions for context "%1$s", allowed values are:%2$s',
6436
+					'event_espresso'
6437
+				),
6438
+				$context,
6439
+				implode(',', array_keys($this->cap_contexts_to_cap_action_map()))
6440
+			)
6441
+		);
6442
+	}
6443
+
6444
+
6445
+	/**
6446
+	 * Returns all the capability contexts which are valid when querying models
6447
+	 *
6448
+	 * @return array
6449
+	 */
6450
+	public static function valid_cap_contexts(): array
6451
+	{
6452
+		return (array) apply_filters(
6453
+			'FHEE__EEM_Base__valid_cap_contexts',
6454
+			[
6455
+				self::caps_read,
6456
+				self::caps_read_admin,
6457
+				self::caps_edit,
6458
+				self::caps_delete,
6459
+			]
6460
+		);
6461
+	}
6462
+
6463
+
6464
+	/**
6465
+	 * Returns all valid options for 'default_where_conditions'
6466
+	 *
6467
+	 * @return array
6468
+	 */
6469
+	public static function valid_default_where_conditions(): array
6470
+	{
6471
+		return [
6472
+			EE_Default_Where_Conditions::ALL,
6473
+			EE_Default_Where_Conditions::THIS_MODEL_ONLY,
6474
+			EE_Default_Where_Conditions::OTHER_MODELS_ONLY,
6475
+			EE_Default_Where_Conditions::MINIMUM_ALL,
6476
+			EE_Default_Where_Conditions::MINIMUM_OTHERS,
6477
+			EE_Default_Where_Conditions::NONE,
6478
+		];
6479
+	}
6480
+
6481
+	// public static function default_where_conditions_full
6482
+
6483
+
6484
+	/**
6485
+	 * Verifies $context is one of EEM_Base::valid_cap_contexts(), if not it throws an exception
6486
+	 *
6487
+	 * @param string $context
6488
+	 * @return bool
6489
+	 * @throws EE_Error
6490
+	 */
6491
+	public static function verify_is_valid_cap_context($context): bool
6492
+	{
6493
+		$valid_cap_contexts = EEM_Base::valid_cap_contexts();
6494
+		if (in_array($context, $valid_cap_contexts)) {
6495
+			return true;
6496
+		}
6497
+		throw new EE_Error(
6498
+			sprintf(
6499
+				esc_html__(
6500
+					'Context "%1$s" passed into model "%2$s" is not a valid context. They are: %3$s',
6501
+					'event_espresso'
6502
+				),
6503
+				$context,
6504
+				'EEM_Base',
6505
+				implode(',', $valid_cap_contexts)
6506
+			)
6507
+		);
6508
+	}
6509
+
6510
+
6511
+	/**
6512
+	 * Clears all the models field caches. This is only useful when a sub-class
6513
+	 * might have added a field or something and these caches might be invalidated
6514
+	 */
6515
+	protected function _invalidate_field_caches()
6516
+	{
6517
+		$this->_cache_foreign_key_to_fields = [];
6518
+		$this->_cached_fields               = null;
6519
+		$this->_cached_fields_non_db_only   = null;
6520
+	}
6521
+
6522
+
6523
+	/**
6524
+	 * Gets the list of all the where query param keys that relate to logic instead of field names
6525
+	 * (eg "and", "or", "not").
6526
+	 *
6527
+	 * @return array
6528
+	 */
6529
+	public function logic_query_param_keys(): array
6530
+	{
6531
+		return $this->_logic_query_param_keys;
6532
+	}
6533
+
6534
+
6535
+	/**
6536
+	 * Determines whether the where query param array key is for a logic query param.
6537
+	 * Eg 'OR', 'not*', and 'and*because-i-say-so' should all return true, whereas
6538
+	 * 'ATT_fname', 'EVT_name*not-you-or-me', and 'ORG_name' should return false
6539
+	 *
6540
+	 * @param $query_param_key
6541
+	 * @return bool
6542
+	 */
6543
+	public function is_logic_query_param_key($query_param_key): bool
6544
+	{
6545
+		foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6546
+			if (
6547
+				$query_param_key === $logic_query_param_key
6548
+				|| strpos($query_param_key, $logic_query_param_key . '*') === 0
6549
+			) {
6550
+				return true;
6551
+			}
6552
+		}
6553
+		return false;
6554
+	}
6555
+
6556
+
6557
+	/**
6558
+	 * Returns true if this model has a password field on it (regardless of whether that password field has any content)
6559
+	 *
6560
+	 * @return boolean
6561
+	 * @since 4.9.74.p
6562
+	 */
6563
+	public function hasPassword(): bool
6564
+	{
6565
+		// if we don't yet know if there's a password field, find out and remember it for next time.
6566
+		if ($this->has_password_field === null) {
6567
+			$password_field           = $this->getPasswordField();
6568
+			$this->has_password_field = $password_field instanceof EE_Password_Field;
6569
+		}
6570
+		return $this->has_password_field;
6571
+	}
6572
+
6573
+
6574
+	/**
6575
+	 * Returns the password field on this model, if there is one
6576
+	 *
6577
+	 * @return EE_Password_Field|null
6578
+	 * @since 4.9.74.p
6579
+	 */
6580
+	public function getPasswordField()
6581
+	{
6582
+		// if we definetely already know there is a password field or not (because has_password_field is true or false)
6583
+		// there's no need to search for it. If we don't know yet, then find out
6584
+		if ($this->has_password_field === null && $this->password_field === null) {
6585
+			$this->password_field = $this->get_a_field_of_type('EE_Password_Field');
6586
+		}
6587
+		// don't bother setting has_password_field because that's hasPassword()'s job.
6588
+		return $this->password_field;
6589
+	}
6590
+
6591
+
6592
+	/**
6593
+	 * Returns the list of field (as EE_Model_Field_Bases) that are protected by the password
6594
+	 *
6595
+	 * @return EE_Model_Field_Base[]
6596
+	 * @throws EE_Error
6597
+	 * @since 4.9.74.p
6598
+	 */
6599
+	public function getPasswordProtectedFields()
6600
+	{
6601
+		$password_field = $this->getPasswordField();
6602
+		$fields         = [];
6603
+		if ($password_field instanceof EE_Password_Field) {
6604
+			$field_names = $password_field->protectedFields();
6605
+			foreach ($field_names as $field_name) {
6606
+				$fields[ $field_name ] = $this->field_settings_for($field_name);
6607
+			}
6608
+		}
6609
+		return $fields;
6610
+	}
6611
+
6612
+
6613
+	/**
6614
+	 * Checks if the current user can perform the requested action on this model
6615
+	 *
6616
+	 * @param string              $cap_to_check one of the array keys from _cap_contexts_to_cap_action_map
6617
+	 * @param EE_Base_Class|array $model_obj_or_fields_n_values
6618
+	 * @return bool
6619
+	 * @throws EE_Error
6620
+	 * @throws InvalidArgumentException
6621
+	 * @throws InvalidDataTypeException
6622
+	 * @throws InvalidInterfaceException
6623
+	 * @throws ReflectionException
6624
+	 * @throws UnexpectedEntityException
6625
+	 * @since 4.9.74.p
6626
+	 */
6627
+	public function currentUserCan($cap_to_check, $model_obj_or_fields_n_values)
6628
+	{
6629
+		if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6630
+			$model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6631
+		}
6632
+		if (! is_array($model_obj_or_fields_n_values)) {
6633
+			throw new UnexpectedEntityException(
6634
+				$model_obj_or_fields_n_values,
6635
+				'EE_Base_Class',
6636
+				sprintf(
6637
+					esc_html__(
6638
+						'%1$s must be passed an `EE_Base_Class or an array of fields names with their values. You passed in something different.',
6639
+						'event_espresso'
6640
+					),
6641
+					__FUNCTION__
6642
+				)
6643
+			);
6644
+		}
6645
+		return $this->exists(
6646
+			$this->alter_query_params_to_restrict_by_ID(
6647
+				$this->get_index_primary_key_string($model_obj_or_fields_n_values),
6648
+				[
6649
+					'default_where_conditions' => 'none',
6650
+					'caps'                     => $cap_to_check,
6651
+				]
6652
+			)
6653
+		);
6654
+	}
6655
+
6656
+
6657
+	/**
6658
+	 * Returns the query param where conditions key to the password affecting this model.
6659
+	 * Eg on EEM_Event this would just be "password", on EEM_Datetime this would be "Event.password", etc.
6660
+	 *
6661
+	 * @return null|string
6662
+	 * @throws EE_Error
6663
+	 * @throws InvalidArgumentException
6664
+	 * @throws InvalidDataTypeException
6665
+	 * @throws InvalidInterfaceException
6666
+	 * @throws ModelConfigurationException
6667
+	 * @throws ReflectionException
6668
+	 * @since 4.9.74.p
6669
+	 */
6670
+	public function modelChainAndPassword()
6671
+	{
6672
+		if ($this->model_chain_to_password === null) {
6673
+			throw new ModelConfigurationException(
6674
+				$this,
6675
+				esc_html_x(
6676
+				// @codingStandardsIgnoreStart
6677
+					'Cannot exclude protected data because the model has not specified which model has the password.',
6678
+					// @codingStandardsIgnoreEnd
6679
+					'1: model name',
6680
+					'event_espresso'
6681
+				)
6682
+			);
6683
+		}
6684
+		if ($this->model_chain_to_password === '') {
6685
+			$model_with_password = $this;
6686
+		} else {
6687
+			if ($pos_of_period = strrpos($this->model_chain_to_password, '.')) {
6688
+				$last_model_in_chain = substr($this->model_chain_to_password, $pos_of_period + 1);
6689
+			} else {
6690
+				$last_model_in_chain = $this->model_chain_to_password;
6691
+			}
6692
+			$model_with_password = EE_Registry::instance()->load_model($last_model_in_chain);
6693
+		}
6694
+
6695
+		$password_field = $model_with_password->getPasswordField();
6696
+		if ($password_field instanceof EE_Password_Field) {
6697
+			$password_field_name = $password_field->get_name();
6698
+		} else {
6699
+			throw new ModelConfigurationException(
6700
+				$this,
6701
+				sprintf(
6702
+					esc_html_x(
6703
+						'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"',
6704
+						'1: model name, 2: special string',
6705
+						'event_espresso'
6706
+					),
6707
+					$model_with_password->get_this_model_name(),
6708
+					$this->model_chain_to_password
6709
+				)
6710
+			);
6711
+		}
6712
+		return (
6713
+			   $this->model_chain_to_password
6714
+				   ? $this->model_chain_to_password . '.'
6715
+				   : ''
6716
+			   ) . $password_field_name;
6717
+	}
6718
+
6719
+
6720
+	/**
6721
+	 * Returns true if there is a password on a related model which restricts access to some of this model's rows,
6722
+	 * or if this model itself has a password affecting access to some of its other fields.
6723
+	 *
6724
+	 * @return boolean
6725
+	 * @since 4.9.74.p
6726
+	 */
6727
+	public function restrictedByRelatedModelPassword(): bool
6728
+	{
6729
+		return $this->model_chain_to_password !== null;
6730
+	}
6731
+
6732
+
6733
+	public function __sleep()
6734
+	{
6735
+		$vars = (array) $this;
6736
+		$remove = [
6737
+			'_custom_selections',
6738
+			'_entity_map',
6739
+		];
6740
+		foreach ($vars as $key => $val)
6741
+		{
6742
+			// removing null properties improves serialization performance
6743
+			if (is_null($val) || in_array($val, $remove, true)) {
6744
+				unset($vars[$key]);
6745
+			}
6746
+		}
6747
+		return array_keys($vars);
6748
+	}
6749
+
6750
+	private function normalizePropertyName(string $key): string
6751 6751
 {
6752
-    // protected: "\0*\0_property"
6753
-    if (strpos($key, "\0*\0") === 0) {
6754
-        return substr($key, 3);
6755
-    }
6756
-    // private: "\0ClassName\0_property"
6757
-    if (strpos($key, "\0") === 0) {
6758
-        $parts = explode("\0", $key, 3);
6759
-        return $parts[2] ?? $key;
6760
-    }
6761
-    // public property
6762
-    return $key;
6752
+	// protected: "\0*\0_property"
6753
+	if (strpos($key, "\0*\0") === 0) {
6754
+		return substr($key, 3);
6755
+	}
6756
+	// private: "\0ClassName\0_property"
6757
+	if (strpos($key, "\0") === 0) {
6758
+		$parts = explode("\0", $key, 3);
6759
+		return $parts[2] ?? $key;
6760
+	}
6761
+	// public property
6762
+	return $key;
6763 6763
 }
6764 6764
 
6765 6765
 
6766
-    public function __unserialize(array $data): void
6767
-    {
6768
-        $array_props = [
6769
-            '_allowed_order_values',
6770
-            '_allowed_query_params',
6771
-            '_between_style_operators',
6772
-            '_cache_foreign_key_to_fields',
6773
-            '_cap_contexts_to_cap_action_map',
6774
-            '_cap_restriction_generators',
6775
-            '_cap_restrictions',
6776
-            '_entity_map',
6777
-            '_fields',
6778
-            '_in_style_operators',
6779
-            '_indexes',
6780
-            '_like_style_operators',
6781
-            '_logic_query_param_keys',
6782
-            '_model_relations',
6783
-            '_null_style_operators',
6784
-            '_tables',
6785
-            '_valid_operators',
6786
-            '_valid_wpdb_data_types',
6787
-            'foreign_key_aliases',
6788
-        ];
6789
-        $nullable_array_props = [
6790
-            '_cached_fields',
6791
-            '_cached_fields_non_db_only',
6792
-        ];
6793
-        $bool_props = [
6794
-            '_ignore_where_strategy',
6795
-            '_wp_core_model',
6796
-        ];
6797
-        $nullable_bool_props = [
6798
-            '_has_primary_key_field',
6799
-            'has_password_field',
6800
-        ];
6801
-        $string_props = [
6802
-            '_model_chain_to_wp_user',
6803
-            '_timezone',
6804
-            'plural_item',
6805
-            'singular_item',
6806
-        ];
6807
-        $nullable_string_props = [
6808
-            'model_chain_to_password',
6809
-        ];
6810
-        $remove = [
6811
-            '_custom_selections',
6812
-            '_entity_map',
6813
-        ];
6814
-        foreach ($data as $key => $val)
6815
-        {
6816
-            $key = $this->normalizePropertyName($key);
6817
-            if (in_array($key, $remove, true)) {
6818
-                continue;
6819
-            }
6820
-            if (property_exists($this, $key)) {
6821
-                if (in_array($key, $array_props, true)) {
6822
-                    $this->{$key} = (array) $val;
6823
-                } elseif (in_array($key, $nullable_array_props, true)) {
6824
-                    $this->{$key} = ! is_null($val) ? (array) $val : null;
6825
-                } elseif (in_array($key, $bool_props, true)) {
6826
-                    $this->{$key} = (bool) $val;
6827
-                } elseif (in_array($key, $nullable_bool_props, true)) {
6828
-                    $this->{$key} = ! is_null($val) ? (bool) $val : null;
6829
-                } elseif (in_array($key, $string_props, true)) {
6830
-                    $this->{$key} = (string) $val;
6831
-                } elseif (in_array($key, $nullable_string_props, true)) {
6832
-                    $this->{$key} = ! is_null($val) ? (string) $val : null;
6833
-                } else {
6834
-                    $this->{$key} = $val;
6835
-                }
6836
-            }
6837
-        }
6838
-    }
6766
+	public function __unserialize(array $data): void
6767
+	{
6768
+		$array_props = [
6769
+			'_allowed_order_values',
6770
+			'_allowed_query_params',
6771
+			'_between_style_operators',
6772
+			'_cache_foreign_key_to_fields',
6773
+			'_cap_contexts_to_cap_action_map',
6774
+			'_cap_restriction_generators',
6775
+			'_cap_restrictions',
6776
+			'_entity_map',
6777
+			'_fields',
6778
+			'_in_style_operators',
6779
+			'_indexes',
6780
+			'_like_style_operators',
6781
+			'_logic_query_param_keys',
6782
+			'_model_relations',
6783
+			'_null_style_operators',
6784
+			'_tables',
6785
+			'_valid_operators',
6786
+			'_valid_wpdb_data_types',
6787
+			'foreign_key_aliases',
6788
+		];
6789
+		$nullable_array_props = [
6790
+			'_cached_fields',
6791
+			'_cached_fields_non_db_only',
6792
+		];
6793
+		$bool_props = [
6794
+			'_ignore_where_strategy',
6795
+			'_wp_core_model',
6796
+		];
6797
+		$nullable_bool_props = [
6798
+			'_has_primary_key_field',
6799
+			'has_password_field',
6800
+		];
6801
+		$string_props = [
6802
+			'_model_chain_to_wp_user',
6803
+			'_timezone',
6804
+			'plural_item',
6805
+			'singular_item',
6806
+		];
6807
+		$nullable_string_props = [
6808
+			'model_chain_to_password',
6809
+		];
6810
+		$remove = [
6811
+			'_custom_selections',
6812
+			'_entity_map',
6813
+		];
6814
+		foreach ($data as $key => $val)
6815
+		{
6816
+			$key = $this->normalizePropertyName($key);
6817
+			if (in_array($key, $remove, true)) {
6818
+				continue;
6819
+			}
6820
+			if (property_exists($this, $key)) {
6821
+				if (in_array($key, $array_props, true)) {
6822
+					$this->{$key} = (array) $val;
6823
+				} elseif (in_array($key, $nullable_array_props, true)) {
6824
+					$this->{$key} = ! is_null($val) ? (array) $val : null;
6825
+				} elseif (in_array($key, $bool_props, true)) {
6826
+					$this->{$key} = (bool) $val;
6827
+				} elseif (in_array($key, $nullable_bool_props, true)) {
6828
+					$this->{$key} = ! is_null($val) ? (bool) $val : null;
6829
+				} elseif (in_array($key, $string_props, true)) {
6830
+					$this->{$key} = (string) $val;
6831
+				} elseif (in_array($key, $nullable_string_props, true)) {
6832
+					$this->{$key} = ! is_null($val) ? (string) $val : null;
6833
+				} else {
6834
+					$this->{$key} = $val;
6835
+				}
6836
+			}
6837
+		}
6838
+	}
6839 6839
 }
Please login to merge, or discard this patch.
Spacing   +228 added lines, -228 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
         }
@@ -658,8 +658,8 @@  discard block
 block discarded – undo
658 658
         // initialize the standard cap restriction generators if none were specified by the child constructor
659 659
         if (is_array($this->_cap_restriction_generators)) {
660 660
             foreach ($this->_cap_contexts_to_cap_action_map as $cap_context => $action) {
661
-                if (! isset($this->_cap_restriction_generators[ $cap_context ])) {
662
-                    $this->_cap_restriction_generators[ $cap_context ] = apply_filters(
661
+                if ( ! isset($this->_cap_restriction_generators[$cap_context])) {
662
+                    $this->_cap_restriction_generators[$cap_context] = apply_filters(
663 663
                         'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
664 664
                         new EE_Restriction_Generator_Protected(),
665 665
                         $cap_context,
@@ -671,10 +671,10 @@  discard block
 block discarded – undo
671 671
         // if there are cap restriction generators, use them to make the default cap restrictions
672 672
         if (is_array($this->_cap_restriction_generators)) {
673 673
             foreach ($this->_cap_restriction_generators as $context => $generator_object) {
674
-                if (! $generator_object) {
674
+                if ( ! $generator_object) {
675 675
                     continue;
676 676
                 }
677
-                if (! $generator_object instanceof EE_Restriction_Generator_Base) {
677
+                if ( ! $generator_object instanceof EE_Restriction_Generator_Base) {
678 678
                     throw new EE_Error(
679 679
                         sprintf(
680 680
                             esc_html__(
@@ -687,7 +687,7 @@  discard block
 block discarded – undo
687 687
                     );
688 688
                 }
689 689
                 $action = $this->cap_action_for_context($context);
690
-                if (! $generator_object->construction_finalized()) {
690
+                if ( ! $generator_object->construction_finalized()) {
691 691
                     $generator_object->_construct_finalize($this, $action);
692 692
                 }
693 693
             }
@@ -704,7 +704,7 @@  discard block
 block discarded – undo
704 704
      */
705 705
     protected static function getLoader(): LoaderInterface
706 706
     {
707
-        if (! EEM_Base::$loader instanceof LoaderInterface) {
707
+        if ( ! EEM_Base::$loader instanceof LoaderInterface) {
708 708
             EEM_Base::$loader = LoaderFactory::getLoader();
709 709
         }
710 710
         return EEM_Base::$loader;
@@ -717,7 +717,7 @@  discard block
 block discarded – undo
717 717
      */
718 718
     private static function getMirror(): Mirror
719 719
     {
720
-        if (! EEM_Base::$mirror instanceof Mirror) {
720
+        if ( ! EEM_Base::$mirror instanceof Mirror) {
721 721
             EEM_Base::$mirror = EEM_Base::getLoader()->getShared(Mirror::class);
722 722
         }
723 723
         return EEM_Base::$mirror;
@@ -773,7 +773,7 @@  discard block
 block discarded – undo
773 773
     public static function instance(?string $timezone = '')
774 774
     {
775 775
         // check if instance of Espresso_model already exists
776
-        if (! static::$_instance instanceof static) {
776
+        if ( ! static::$_instance instanceof static) {
777 777
             /**
778 778
              * Set blog id for models to current blog. However we ONLY do this if $_model_query_blog_id is not already set.
779 779
              */
@@ -810,7 +810,7 @@  discard block
 block discarded – undo
810 810
      */
811 811
     public static function reset(?string $timezone = ''): ?EEM_Base
812 812
     {
813
-        if (! static::$_instance instanceof EEM_Base) {
813
+        if ( ! static::$_instance instanceof EEM_Base) {
814 814
             return null;
815 815
         }
816 816
         // Let's NOT swap out the current instance for a new one
@@ -821,7 +821,7 @@  discard block
 block discarded – undo
821 821
         foreach (EEM_Base::getMirror()->getDefaultProperties(static::class) as $property => $value) {
822 822
             // don't set instance to null like it was originally,
823 823
             // but it's static anyways, and we're ignoring static properties (for now at least)
824
-            if (! isset($static_properties[ $property ])) {
824
+            if ( ! isset($static_properties[$property])) {
825 825
                 static::$_instance->{$property} = $value;
826 826
             }
827 827
         }
@@ -868,7 +868,7 @@  discard block
 block discarded – undo
868 868
      */
869 869
     public function status_array($translated = false)
870 870
     {
871
-        if (! array_key_exists('Status', $this->_model_relations)) {
871
+        if ( ! array_key_exists('Status', $this->_model_relations)) {
872 872
             return [];
873 873
         }
874 874
         $model_name   = $this->get_this_model_name();
@@ -876,7 +876,7 @@  discard block
 block discarded – undo
876 876
         $stati        = EEM_Status::instance()->get_all([['STS_type' => $status_type]]);
877 877
         $status_array = [];
878 878
         foreach ($stati as $status) {
879
-            $status_array[ $status->ID() ] = $status->get('STS_code');
879
+            $status_array[$status->ID()] = $status->get('STS_code');
880 880
         }
881 881
         return $translated
882 882
             ? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
@@ -948,7 +948,7 @@  discard block
 block discarded – undo
948 948
     {
949 949
         $wp_user_field_name = $this->wp_user_field_name();
950 950
         if ($wp_user_field_name) {
951
-            $query_params[0][ $wp_user_field_name ] = get_current_user_id();
951
+            $query_params[0][$wp_user_field_name] = get_current_user_id();
952 952
         }
953 953
         return $query_params;
954 954
     }
@@ -967,17 +967,17 @@  discard block
 block discarded – undo
967 967
     public function wp_user_field_name()
968 968
     {
969 969
         try {
970
-            if (! empty($this->_model_chain_to_wp_user)) {
970
+            if ( ! empty($this->_model_chain_to_wp_user)) {
971 971
                 $models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
972 972
                 $last_model_name              = end($models_to_follow_to_wp_users);
973 973
                 $model_with_fk_to_wp_users    = EE_Registry::instance()->load_model($last_model_name);
974
-                $model_chain_to_wp_user       = $this->_model_chain_to_wp_user . '.';
974
+                $model_chain_to_wp_user       = $this->_model_chain_to_wp_user.'.';
975 975
             } else {
976 976
                 $model_with_fk_to_wp_users = $this;
977 977
                 $model_chain_to_wp_user    = '';
978 978
             }
979 979
             $wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
980
-            return $model_chain_to_wp_user . $wp_user_field->get_name();
980
+            return $model_chain_to_wp_user.$wp_user_field->get_name();
981 981
         } catch (EE_Error $e) {
982 982
             return false;
983 983
         }
@@ -1053,11 +1053,11 @@  discard block
 block discarded – undo
1053 1053
         if ($this->_custom_selections instanceof CustomSelects) {
1054 1054
             $custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1055 1055
             $select_expressions .= $select_expressions
1056
-                ? ', ' . $custom_expressions
1056
+                ? ', '.$custom_expressions
1057 1057
                 : $custom_expressions;
1058 1058
         }
1059 1059
 
1060
-        $SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1060
+        $SQL = "SELECT $select_expressions ".$this->_construct_2nd_half_of_select_query($model_query_info);
1061 1061
         return $this->_do_wpdb_query('get_results', [$SQL, $output]);
1062 1062
     }
1063 1063
 
@@ -1074,7 +1074,7 @@  discard block
 block discarded – undo
1074 1074
      */
1075 1075
     protected function getCustomSelection(array $query_params, $columns_to_select = null): ?CustomSelects
1076 1076
     {
1077
-        if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1077
+        if ( ! isset($query_params['extra_selects']) && $columns_to_select === null) {
1078 1078
             return null;
1079 1079
         }
1080 1080
         $selects = $query_params['extra_selects'] ?? $columns_to_select;
@@ -1125,7 +1125,7 @@  discard block
 block discarded – undo
1125 1125
         if (is_array($columns_to_select)) {
1126 1126
             $select_sql_array = [];
1127 1127
             foreach ($columns_to_select as $alias => $selection_and_datatype) {
1128
-                if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1128
+                if ( ! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1129 1129
                     throw new EE_Error(
1130 1130
                         sprintf(
1131 1131
                             esc_html__(
@@ -1137,7 +1137,7 @@  discard block
 block discarded – undo
1137 1137
                         )
1138 1138
                     );
1139 1139
                 }
1140
-                if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1140
+                if ( ! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1141 1141
                     throw new EE_Error(
1142 1142
                         sprintf(
1143 1143
                             esc_html__(
@@ -1199,7 +1199,7 @@  discard block
 block discarded – undo
1199 1199
                 ['default_where_conditions' => EE_Default_Where_Conditions::MINIMUM_ALL]
1200 1200
             )
1201 1201
         );
1202
-        $className    = $this->_get_class_name();
1202
+        $className = $this->_get_class_name();
1203 1203
         if ($model_object instanceof $className) {
1204 1204
             // make sure valid objects get added to the entity map
1205 1205
             // so that the next call to this method doesn't trigger another trip to the db
@@ -1222,12 +1222,12 @@  discard block
 block discarded – undo
1222 1222
      */
1223 1223
     public function alter_query_params_to_restrict_by_ID($id, $query_params = [])
1224 1224
     {
1225
-        if (! isset($query_params[0])) {
1225
+        if ( ! isset($query_params[0])) {
1226 1226
             $query_params[0] = [];
1227 1227
         }
1228 1228
         $conditions_from_id = $this->parse_index_primary_key_string($id);
1229 1229
         if ($conditions_from_id === null) {
1230
-            $query_params[0][ $this->primary_key_name() ] = $id;
1230
+            $query_params[0][$this->primary_key_name()] = $id;
1231 1231
         } else {
1232 1232
             // no primary key, so the $id must be from the get_index_primary_key_string()
1233 1233
             $query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
@@ -1247,7 +1247,7 @@  discard block
 block discarded – undo
1247 1247
      */
1248 1248
     public function get_one($query_params = [])
1249 1249
     {
1250
-        if (! is_array($query_params)) {
1250
+        if ( ! is_array($query_params)) {
1251 1251
             EE_Error::doing_it_wrong(
1252 1252
                 'EEM_Base::get_one',
1253 1253
                 sprintf(
@@ -1456,7 +1456,7 @@  discard block
 block discarded – undo
1456 1456
                 return [];
1457 1457
             }
1458 1458
         }
1459
-        if (! is_array($query_params)) {
1459
+        if ( ! is_array($query_params)) {
1460 1460
             EE_Error::doing_it_wrong(
1461 1461
                 'EEM_Base::_get_consecutive',
1462 1462
                 sprintf(
@@ -1468,7 +1468,7 @@  discard block
 block discarded – undo
1468 1468
             $query_params = [];
1469 1469
         }
1470 1470
         // let's add the where query param for consecutive look up.
1471
-        $query_params[0][ $field_to_order_by ] = [$operand, $current_field_value];
1471
+        $query_params[0][$field_to_order_by] = [$operand, $current_field_value];
1472 1472
         $query_params['limit']                 = $limit;
1473 1473
         // set direction
1474 1474
         $incoming_orderby         = isset($query_params['order_by'])
@@ -1494,7 +1494,7 @@  discard block
 block discarded – undo
1494 1494
      */
1495 1495
     public function set_timezone(?string $timezone = '')
1496 1496
     {
1497
-        if (! $timezone) {
1497
+        if ( ! $timezone) {
1498 1498
             return;
1499 1499
         }
1500 1500
         $this->_timezone = $timezone;
@@ -1551,7 +1551,7 @@  discard block
 block discarded – undo
1551 1551
     {
1552 1552
         $field_settings = $this->field_settings_for($field_name);
1553 1553
         // if not a valid EE_Datetime_Field then throw error
1554
-        if (! $field_settings instanceof EE_Datetime_Field) {
1554
+        if ( ! $field_settings instanceof EE_Datetime_Field) {
1555 1555
             throw new EE_Error(
1556 1556
                 sprintf(
1557 1557
                     esc_html__(
@@ -1638,7 +1638,7 @@  discard block
 block discarded – undo
1638 1638
         // just using this to ensure the timezone is set correctly internally
1639 1639
         $this->get_formats_for($field_name);
1640 1640
         // load EEH_DTT_Helper
1641
-        $timezone_string     = ! empty($timezone_string) ? $timezone_string : EEH_DTT_Helper::get_timezone();
1641
+        $timezone_string = ! empty($timezone_string) ? $timezone_string : EEH_DTT_Helper::get_timezone();
1642 1642
         $incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($timezone_string));
1643 1643
         EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1644 1644
         return DbSafeDateTime::createFromDateTime($incomingDateTime);
@@ -1708,7 +1708,7 @@  discard block
 block discarded – undo
1708 1708
      */
1709 1709
     public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1710 1710
     {
1711
-        if (! is_array($query_params)) {
1711
+        if ( ! is_array($query_params)) {
1712 1712
             EE_Error::doing_it_wrong(
1713 1713
                 'EEM_Base::update',
1714 1714
                 sprintf(
@@ -1756,7 +1756,7 @@  discard block
 block discarded – undo
1756 1756
             $wpdb_result = (array) $wpdb_result;
1757 1757
             // get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1758 1758
             if ($this->has_primary_key_field()) {
1759
-                $main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1759
+                $main_table_pk_value = $wpdb_result[$this->get_primary_key_field()->get_qualified_column()];
1760 1760
             } else {
1761 1761
                 // 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)
1762 1762
                 $main_table_pk_value = null;
@@ -1772,7 +1772,7 @@  discard block
 block discarded – undo
1772 1772
                     // in this table, right? so insert a row in the current table, using any fields available
1773 1773
                     if (
1774 1774
                         ! (array_key_exists($this_table_pk_column, $wpdb_result)
1775
-                           && $wpdb_result[ $this_table_pk_column ])
1775
+                           && $wpdb_result[$this_table_pk_column])
1776 1776
                     ) {
1777 1777
                         $success = $this->_insert_into_specific_table(
1778 1778
                             $table_obj,
@@ -1780,7 +1780,7 @@  discard block
 block discarded – undo
1780 1780
                             $main_table_pk_value
1781 1781
                         );
1782 1782
                         // if we died here, report the error
1783
-                        if (! $success) {
1783
+                        if ( ! $success) {
1784 1784
                             return false;
1785 1785
                         }
1786 1786
                     }
@@ -1808,10 +1808,10 @@  discard block
 block discarded – undo
1808 1808
                 $model_objs_affected_ids     = [];
1809 1809
                 foreach ($models_affected_key_columns as $row) {
1810 1810
                     $combined_index_key                             = $this->get_index_primary_key_string($row);
1811
-                    $model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1811
+                    $model_objs_affected_ids[$combined_index_key] = $combined_index_key;
1812 1812
                 }
1813 1813
             }
1814
-            if (! $model_objs_affected_ids) {
1814
+            if ( ! $model_objs_affected_ids) {
1815 1815
                 // wait wait wait- if nothing was affected let's stop here
1816 1816
                 return 0;
1817 1817
             }
@@ -1840,8 +1840,8 @@  discard block
 block discarded – undo
1840 1840
         $rows_affected = $this->_do_wpdb_query(
1841 1841
             'query',
1842 1842
             [
1843
-                "UPDATE " . $model_query_info->get_full_join_sql()
1844
-                . " SET " . $this->_construct_update_sql($fields_n_values)
1843
+                "UPDATE ".$model_query_info->get_full_join_sql()
1844
+                . " SET ".$this->_construct_update_sql($fields_n_values)
1845 1845
                 . $model_query_info->get_where_sql(),
1846 1846
             ]
1847 1847
         );
@@ -1855,7 +1855,7 @@  discard block
 block discarded – undo
1855 1855
          * @param int      $rows_affected
1856 1856
          */
1857 1857
         do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1858
-        return $rows_affected;// how many supposedly got updated
1858
+        return $rows_affected; // how many supposedly got updated
1859 1859
     }
1860 1860
 
1861 1861
 
@@ -1888,7 +1888,7 @@  discard block
 block discarded – undo
1888 1888
         $model_query_info   = $this->_create_model_query_info_carrier($query_params);
1889 1889
         $select_expressions = $field->get_qualified_column();
1890 1890
         $SQL                =
1891
-            "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1891
+            "SELECT $select_expressions ".$this->_construct_2nd_half_of_select_query($model_query_info);
1892 1892
         return $this->_do_wpdb_query('get_col', [$SQL]);
1893 1893
     }
1894 1894
 
@@ -1907,7 +1907,7 @@  discard block
 block discarded – undo
1907 1907
     {
1908 1908
         $query_params['limit'] = 1;
1909 1909
         $col                   = $this->get_col($query_params, $field_to_select);
1910
-        if (! empty($col)) {
1910
+        if ( ! empty($col)) {
1911 1911
             return reset($col);
1912 1912
         }
1913 1913
         return null;
@@ -1938,7 +1938,7 @@  discard block
 block discarded – undo
1938 1938
             $value_sql       = $prepared_value === null
1939 1939
                 ? 'NULL'
1940 1940
                 : $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1941
-            $cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1941
+            $cols_n_values[] = $field_obj->get_qualified_column()."=".$value_sql;
1942 1942
         }
1943 1943
         return implode(",", $cols_n_values);
1944 1944
     }
@@ -2076,7 +2076,7 @@  discard block
 block discarded – undo
2076 2076
                                 . $model_query_info->get_full_join_sql()
2077 2077
                                 . " WHERE "
2078 2078
                                 . $deletion_where_query_part;
2079
-            $rows_deleted     = $this->_do_wpdb_query('query', [$SQL]);
2079
+            $rows_deleted = $this->_do_wpdb_query('query', [$SQL]);
2080 2080
         }
2081 2081
 
2082 2082
         // Next, make sure those items are removed from the entity map; if they could be put into it at all; and if
@@ -2084,12 +2084,12 @@  discard block
 block discarded – undo
2084 2084
         if (
2085 2085
             $this->has_primary_key_field()
2086 2086
             && $rows_deleted !== false
2087
-            && isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2087
+            && isset($columns_and_ids_for_deleting[$this->get_primary_key_field()->get_qualified_column()])
2088 2088
         ) {
2089
-            $ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2089
+            $ids_for_removal = $columns_and_ids_for_deleting[$this->get_primary_key_field()->get_qualified_column()];
2090 2090
             foreach ($ids_for_removal as $id) {
2091
-                if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2092
-                    unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2091
+                if (isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])) {
2092
+                    unset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id]);
2093 2093
                 }
2094 2094
             }
2095 2095
 
@@ -2129,7 +2129,7 @@  discard block
 block discarded – undo
2129 2129
          * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2130 2130
          */
2131 2131
         do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2132
-        return (int) $rows_deleted;// how many supposedly got deleted
2132
+        return (int) $rows_deleted; // how many supposedly got deleted
2133 2133
     }
2134 2134
 
2135 2135
 
@@ -2233,15 +2233,15 @@  discard block
 block discarded – undo
2233 2233
                 if (
2234 2234
                     $block_deletes
2235 2235
                     && $this->delete_is_blocked_by_related_models(
2236
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2236
+                        $item_to_delete[$primary_table->get_fully_qualified_pk_column()]
2237 2237
                     )
2238 2238
                 ) {
2239 2239
                     continue;
2240 2240
                 }
2241 2241
                 // primary table deletes
2242
-                if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2243
-                    $ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2244
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2242
+                if (isset($item_to_delete[$primary_table->get_fully_qualified_pk_column()])) {
2243
+                    $ids_to_delete_indexed_by_column[$primary_table->get_fully_qualified_pk_column()][] =
2244
+                        $item_to_delete[$primary_table->get_fully_qualified_pk_column()];
2245 2245
                 }
2246 2246
             }
2247 2247
         } elseif (count($this->get_combined_primary_key_fields()) > 1) {
@@ -2250,8 +2250,8 @@  discard block
 block discarded – undo
2250 2250
                 $ids_to_delete_indexed_by_column_for_row = [];
2251 2251
                 foreach ($fields as $cpk_field) {
2252 2252
                     if ($cpk_field instanceof EE_Model_Field_Base) {
2253
-                        $ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2254
-                            $item_to_delete[ $cpk_field->get_qualified_column() ];
2253
+                        $ids_to_delete_indexed_by_column_for_row[$cpk_field->get_qualified_column()] =
2254
+                            $item_to_delete[$cpk_field->get_qualified_column()];
2255 2255
                     }
2256 2256
                 }
2257 2257
                 $ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
@@ -2289,7 +2289,7 @@  discard block
 block discarded – undo
2289 2289
         } elseif ($this->has_primary_key_field()) {
2290 2290
             $query = [];
2291 2291
             foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2292
-                $query[] = $column . ' IN' . $this->_construct_in_value($ids, $this->_primary_key_field);
2292
+                $query[] = $column.' IN'.$this->_construct_in_value($ids, $this->_primary_key_field);
2293 2293
             }
2294 2294
             $query_part = ! empty($query)
2295 2295
                 ? implode(' AND ', $query)
@@ -2299,7 +2299,7 @@  discard block
 block discarded – undo
2299 2299
             foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2300 2300
                 $values_for_each_combined_primary_key_for_a_row = [];
2301 2301
                 foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2302
-                    $values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2302
+                    $values_for_each_combined_primary_key_for_a_row[] = $column.'='.$id;
2303 2303
                 }
2304 2304
                 $ways_to_identify_a_row[] = '('
2305 2305
                                             . implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
@@ -2375,10 +2375,10 @@  discard block
 block discarded – undo
2375 2375
             }
2376 2376
         }
2377 2377
         $column_to_count = $distinct
2378
-            ? "DISTINCT " . $column_to_count
2378
+            ? "DISTINCT ".$column_to_count
2379 2379
             : $column_to_count;
2380 2380
         $SQL             =
2381
-            "SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2381
+            "SELECT COUNT(".$column_to_count.")".$this->_construct_2nd_half_of_select_query($model_query_info);
2382 2382
         return (int) $this->_do_wpdb_query('get_var', [$SQL]);
2383 2383
     }
2384 2384
 
@@ -2403,7 +2403,7 @@  discard block
 block discarded – undo
2403 2403
         }
2404 2404
         $column_to_count = $field_obj->get_qualified_column();
2405 2405
         $SQL             =
2406
-            "SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2406
+            "SELECT SUM(".$column_to_count.")".$this->_construct_2nd_half_of_select_query($model_query_info);
2407 2407
         $return_value    = $this->_do_wpdb_query('get_var', [$SQL]);
2408 2408
         $data_type       = $field_obj->get_wpdb_data_type();
2409 2409
         if ($data_type === '%d' || $data_type === '%s') {
@@ -2439,7 +2439,7 @@  discard block
 block discarded – undo
2439 2439
         }
2440 2440
         /** @type WPDB $wpdb */
2441 2441
         global $wpdb;
2442
-        if (! method_exists($wpdb, $wpdb_method)) {
2442
+        if ( ! method_exists($wpdb, $wpdb_method)) {
2443 2443
             throw new DomainException(
2444 2444
                 sprintf(
2445 2445
                     esc_html__(
@@ -2458,7 +2458,7 @@  discard block
 block discarded – undo
2458 2458
         $this->show_db_query_if_previously_requested($wpdb->last_query);
2459 2459
         if (defined('WP_DEBUG') && WP_DEBUG) {
2460 2460
             $wpdb->show_errors($old_show_errors_value);
2461
-            if (! empty($wpdb->last_error)) {
2461
+            if ( ! empty($wpdb->last_error)) {
2462 2462
                 throw new EE_Error(sprintf(esc_html__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2463 2463
             }
2464 2464
             if ($result === false) {
@@ -2528,7 +2528,7 @@  discard block
 block discarded – undo
2528 2528
                     // ummmm... you in trouble
2529 2529
                     return $result;
2530 2530
             }
2531
-            if (! empty($error_message)) {
2531
+            if ( ! empty($error_message)) {
2532 2532
                 EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2533 2533
                 trigger_error($error_message);
2534 2534
             }
@@ -2609,11 +2609,11 @@  discard block
 block discarded – undo
2609 2609
      */
2610 2610
     private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2611 2611
     {
2612
-        return " FROM " . $model_query_info->get_full_join_sql() .
2613
-               $model_query_info->get_where_sql() .
2614
-               $model_query_info->get_group_by_sql() .
2615
-               $model_query_info->get_having_sql() .
2616
-               $model_query_info->get_order_by_sql() .
2612
+        return " FROM ".$model_query_info->get_full_join_sql().
2613
+               $model_query_info->get_where_sql().
2614
+               $model_query_info->get_group_by_sql().
2615
+               $model_query_info->get_having_sql().
2616
+               $model_query_info->get_order_by_sql().
2617 2617
                $model_query_info->get_limit_sql();
2618 2618
     }
2619 2619
 
@@ -2638,7 +2638,7 @@  discard block
 block discarded – undo
2638 2638
             $left = is_admin() ? '12rem' : '2rem';
2639 2639
             echo "
2640 2640
             <div class='ee-status-outline ee-status-bg--attention' style='margin: 2rem 2rem 2rem $left;'>
2641
-                " . esc_html($sql_query) . "
2641
+                ".esc_html($sql_query)."
2642 2642
             </div>";
2643 2643
             $this->_show_next_x_db_queries--;
2644 2644
         }
@@ -2814,12 +2814,12 @@  discard block
 block discarded – undo
2814 2814
         $related_model = $this->get_related_model_obj($model_name);
2815 2815
         // we're just going to use the query params on the related model's normal get_all query,
2816 2816
         // except add a condition to say to match the current mod
2817
-        if (! isset($query_params['default_where_conditions'])) {
2817
+        if ( ! isset($query_params['default_where_conditions'])) {
2818 2818
             $query_params['default_where_conditions'] = EE_Default_Where_Conditions::NONE;
2819 2819
         }
2820 2820
         $this_model_name                                                 = $this->get_this_model_name();
2821 2821
         $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2822
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2822
+        $query_params[0][$this_model_name.".".$this_pk_field_name] = $id_or_obj;
2823 2823
         return $related_model->count($query_params, $field_to_count, $distinct);
2824 2824
     }
2825 2825
 
@@ -2840,7 +2840,7 @@  discard block
 block discarded – undo
2840 2840
     public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2841 2841
     {
2842 2842
         $related_model = $this->get_related_model_obj($model_name);
2843
-        if (! is_array($query_params)) {
2843
+        if ( ! is_array($query_params)) {
2844 2844
             EE_Error::doing_it_wrong(
2845 2845
                 'EEM_Base::sum_related',
2846 2846
                 sprintf(
@@ -2853,12 +2853,12 @@  discard block
 block discarded – undo
2853 2853
         }
2854 2854
         // we're just going to use the query params on the related model's normal get_all query,
2855 2855
         // except add a condition to say to match the current mod
2856
-        if (! isset($query_params['default_where_conditions'])) {
2856
+        if ( ! isset($query_params['default_where_conditions'])) {
2857 2857
             $query_params['default_where_conditions'] = EE_Default_Where_Conditions::NONE;
2858 2858
         }
2859 2859
         $this_model_name                                                 = $this->get_this_model_name();
2860 2860
         $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2861
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2861
+        $query_params[0][$this_model_name.".".$this_pk_field_name] = $id_or_obj;
2862 2862
         return $related_model->sum($query_params, $field_to_sum);
2863 2863
     }
2864 2864
 
@@ -2910,7 +2910,7 @@  discard block
 block discarded – undo
2910 2910
                 $field_with_model_name = $field;
2911 2911
             }
2912 2912
         }
2913
-        if (! isset($field_with_model_name) || ! $field_with_model_name) {
2913
+        if ( ! isset($field_with_model_name) || ! $field_with_model_name) {
2914 2914
             throw new EE_Error(
2915 2915
                 sprintf(
2916 2916
                     esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
@@ -3049,14 +3049,14 @@  discard block
 block discarded – undo
3049 3049
                 || $this->get_primary_key_field()
3050 3050
                    instanceof
3051 3051
                    EE_Primary_Key_String_Field)
3052
-            && isset($fields_n_values[ $this->primary_key_name() ])
3052
+            && isset($fields_n_values[$this->primary_key_name()])
3053 3053
         ) {
3054
-            $query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
3054
+            $query_params[0]['OR'][$this->primary_key_name()] = $fields_n_values[$this->primary_key_name()];
3055 3055
         }
3056 3056
         foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
3057 3057
             $uniqueness_where_params                              =
3058 3058
                 array_intersect_key($fields_n_values, $unique_index->fields());
3059
-            $query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
3059
+            $query_params[0]['OR']['AND*'.$unique_index_name] = $uniqueness_where_params;
3060 3060
         }
3061 3061
         // if there is nothing to base this search on, then we shouldn't find anything
3062 3062
         if (empty($query_params)) {
@@ -3133,16 +3133,16 @@  discard block
 block discarded – undo
3133 3133
             $prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3134 3134
             // if the value we want to assign it to is NULL, just don't mention it for the insertion
3135 3135
             if ($prepared_value !== null) {
3136
-                $insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3136
+                $insertion_col_n_values[$field_obj->get_table_column()] = $prepared_value;
3137 3137
                 $format_for_insertion[]                                   = $field_obj->get_wpdb_data_type();
3138 3138
             }
3139 3139
         }
3140 3140
         if ($table instanceof EE_Secondary_Table && $new_id) {
3141 3141
             // its not the main table, so we should have already saved the main table's PK which we just inserted
3142 3142
             // so add the fk to the main table as a column
3143
-            $insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3143
+            $insertion_col_n_values[$table->get_fk_on_table()] = $new_id;
3144 3144
             $format_for_insertion[]                              =
3145
-                '%d';// yes right now we're only allowing these foreign keys to be INTs
3145
+                '%d'; // yes right now we're only allowing these foreign keys to be INTs
3146 3146
         }
3147 3147
 
3148 3148
         // insert the new entry
@@ -3160,7 +3160,7 @@  discard block
 block discarded – undo
3160 3160
             }
3161 3161
             // it's not an auto-increment primary key, so
3162 3162
             // it must have been supplied
3163
-            return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3163
+            return $fields_n_values[$this->get_primary_key_field()->get_name()];
3164 3164
         }
3165 3165
         // we can't return a  primary key because there is none. instead return
3166 3166
         // a unique string indicating this model
@@ -3182,10 +3182,10 @@  discard block
 block discarded – undo
3182 3182
     {
3183 3183
         $field_name = $field_obj->get_name();
3184 3184
         // if this field doesn't allow nullable, don't allow it
3185
-        if (! $field_obj->is_nullable() && ! isset($fields_n_values[ $field_name ])) {
3186
-            $fields_n_values[ $field_name ] = $field_obj->get_default_value();
3185
+        if ( ! $field_obj->is_nullable() && ! isset($fields_n_values[$field_name])) {
3186
+            $fields_n_values[$field_name] = $field_obj->get_default_value();
3187 3187
         }
3188
-        $unprepared_value = $fields_n_values[ $field_name ] ?? null;
3188
+        $unprepared_value = $fields_n_values[$field_name] ?? null;
3189 3189
         return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3190 3190
     }
3191 3191
 
@@ -3288,7 +3288,7 @@  discard block
 block discarded – undo
3288 3288
      */
3289 3289
     public function get_table_obj_by_alias($table_alias = '')
3290 3290
     {
3291
-        return $this->_tables[ $table_alias ] ?? null;
3291
+        return $this->_tables[$table_alias] ?? null;
3292 3292
     }
3293 3293
 
3294 3294
 
@@ -3302,7 +3302,7 @@  discard block
 block discarded – undo
3302 3302
         $other_tables = [];
3303 3303
         foreach ($this->_tables as $table_alias => $table) {
3304 3304
             if ($table instanceof EE_Secondary_Table) {
3305
-                $other_tables[ $table_alias ] = $table;
3305
+                $other_tables[$table_alias] = $table;
3306 3306
             }
3307 3307
         }
3308 3308
         return $other_tables;
@@ -3317,7 +3317,7 @@  discard block
 block discarded – undo
3317 3317
      */
3318 3318
     public function _get_fields_for_table($table_alias)
3319 3319
     {
3320
-        return $this->_fields[ $table_alias ];
3320
+        return $this->_fields[$table_alias];
3321 3321
     }
3322 3322
 
3323 3323
 
@@ -3346,7 +3346,7 @@  discard block
 block discarded – undo
3346 3346
                     $query_info_carrier,
3347 3347
                     'group_by'
3348 3348
                 );
3349
-            } elseif (! empty($query_params['group_by'])) {
3349
+            } elseif ( ! empty($query_params['group_by'])) {
3350 3350
                 $this->_extract_related_model_info_from_query_param(
3351 3351
                     $query_params['group_by'],
3352 3352
                     $query_info_carrier,
@@ -3368,7 +3368,7 @@  discard block
 block discarded – undo
3368 3368
                     $query_info_carrier,
3369 3369
                     'order_by'
3370 3370
                 );
3371
-            } elseif (! empty($query_params['order_by'])) {
3371
+            } elseif ( ! empty($query_params['order_by'])) {
3372 3372
                 $this->_extract_related_model_info_from_query_param(
3373 3373
                     $query_params['order_by'],
3374 3374
                     $query_info_carrier,
@@ -3403,7 +3403,7 @@  discard block
 block discarded – undo
3403 3403
         EE_Model_Query_Info_Carrier $model_query_info_carrier,
3404 3404
         $query_param_type
3405 3405
     ) {
3406
-        if (! empty($sub_query_params)) {
3406
+        if ( ! empty($sub_query_params)) {
3407 3407
             $sub_query_params = (array) $sub_query_params;
3408 3408
             foreach ($sub_query_params as $param => $possibly_array_of_params) {
3409 3409
                 // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
@@ -3419,7 +3419,7 @@  discard block
 block discarded – undo
3419 3419
                 $query_param_sans_stars =
3420 3420
                     $this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3421 3421
                 if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3422
-                    if (! is_array($possibly_array_of_params)) {
3422
+                    if ( ! is_array($possibly_array_of_params)) {
3423 3423
                         throw new EE_Error(
3424 3424
                             sprintf(
3425 3425
                                 esc_html__(
@@ -3445,7 +3445,7 @@  discard block
 block discarded – undo
3445 3445
                     // then $possible_array_of_params looks something like array('<','DTT_sold',true)
3446 3446
                     // indicating that $possible_array_of_params[1] is actually a field name,
3447 3447
                     // from which we should extract query parameters!
3448
-                    if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3448
+                    if ( ! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3449 3449
                         throw new EE_Error(
3450 3450
                             sprintf(
3451 3451
                                 esc_html__(
@@ -3485,8 +3485,8 @@  discard block
 block discarded – undo
3485 3485
         EE_Model_Query_Info_Carrier $model_query_info_carrier,
3486 3486
         $query_param_type
3487 3487
     ) {
3488
-        if (! empty($sub_query_params)) {
3489
-            if (! is_array($sub_query_params)) {
3488
+        if ( ! empty($sub_query_params)) {
3489
+            if ( ! is_array($sub_query_params)) {
3490 3490
                 throw new EE_Error(
3491 3491
                     sprintf(
3492 3492
                         esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
@@ -3524,7 +3524,7 @@  discard block
 block discarded – undo
3524 3524
      */
3525 3525
     public function _create_model_query_info_carrier($query_params)
3526 3526
     {
3527
-        if (! is_array($query_params)) {
3527
+        if ( ! is_array($query_params)) {
3528 3528
             EE_Error::doing_it_wrong(
3529 3529
                 'EEM_Base::_create_model_query_info_carrier',
3530 3530
                 sprintf(
@@ -3555,7 +3555,7 @@  discard block
 block discarded – undo
3555 3555
             // only include if related to a cpt where no password has been set
3556 3556
             $query_params[0]['OR*nopassword'] = [
3557 3557
                 $where_param_key_for_password       => '',
3558
-                $where_param_key_for_password . '*' => ['IS_NULL'],
3558
+                $where_param_key_for_password.'*' => ['IS_NULL'],
3559 3559
             ];
3560 3560
         }
3561 3561
         $query_object = $this->_extract_related_models_from_query($query_params);
@@ -3609,7 +3609,7 @@  discard block
 block discarded – undo
3609 3609
         // set limit
3610 3610
         if (array_key_exists('limit', $query_params)) {
3611 3611
             if (is_array($query_params['limit'])) {
3612
-                if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3612
+                if ( ! isset($query_params['limit'][0], $query_params['limit'][1])) {
3613 3613
                     $e = sprintf(
3614 3614
                         esc_html__(
3615 3615
                             "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)",
@@ -3617,12 +3617,12 @@  discard block
 block discarded – undo
3617 3617
                         ),
3618 3618
                         http_build_query($query_params['limit'])
3619 3619
                     );
3620
-                    throw new EE_Error($e . "|" . $e);
3620
+                    throw new EE_Error($e."|".$e);
3621 3621
                 }
3622 3622
                 // they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3623
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3624
-            } elseif (! empty($query_params['limit'])) {
3625
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3623
+                $query_object->set_limit_sql(" LIMIT ".$query_params['limit'][0].",".$query_params['limit'][1]);
3624
+            } elseif ( ! empty($query_params['limit'])) {
3625
+                $query_object->set_limit_sql(" LIMIT ".$query_params['limit']);
3626 3626
             }
3627 3627
         }
3628 3628
         // set order by
@@ -3654,10 +3654,10 @@  discard block
 block discarded – undo
3654 3654
                 $order_array = [];
3655 3655
                 foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3656 3656
                     $order         = $this->_extract_order($order);
3657
-                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3657
+                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by).SP.$order;
3658 3658
                 }
3659
-                $query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3660
-            } elseif (! empty($query_params['order_by'])) {
3659
+                $query_object->set_order_by_sql(" ORDER BY ".implode(",", $order_array));
3660
+            } elseif ( ! empty($query_params['order_by'])) {
3661 3661
                 $this->_extract_related_model_info_from_query_param(
3662 3662
                     $query_params['order_by'],
3663 3663
                     $query_object,
@@ -3668,7 +3668,7 @@  discard block
 block discarded – undo
3668 3668
                     ? $this->_extract_order($query_params['order'])
3669 3669
                     : 'DESC';
3670 3670
                 $query_object->set_order_by_sql(
3671
-                    " ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3671
+                    " ORDER BY ".$this->_deduce_column_name_from_query_param($query_params['order_by']).SP.$order
3672 3672
                 );
3673 3673
             }
3674 3674
         }
@@ -3680,7 +3680,7 @@  discard block
 block discarded – undo
3680 3680
         ) {
3681 3681
             $pk_field = $this->get_primary_key_field();
3682 3682
             $order    = $this->_extract_order($query_params['order']);
3683
-            $query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3683
+            $query_object->set_order_by_sql(" ORDER BY ".$pk_field->get_qualified_column().SP.$order);
3684 3684
         }
3685 3685
         // set group by
3686 3686
         if (array_key_exists('group_by', $query_params)) {
@@ -3690,10 +3690,10 @@  discard block
 block discarded – undo
3690 3690
                 foreach ($query_params['group_by'] as $field_name_to_group_by) {
3691 3691
                     $group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3692 3692
                 }
3693
-                $query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3694
-            } elseif (! empty($query_params['group_by'])) {
3693
+                $query_object->set_group_by_sql(" GROUP BY ".implode(", ", $group_by_array));
3694
+            } elseif ( ! empty($query_params['group_by'])) {
3695 3695
                 $query_object->set_group_by_sql(
3696
-                    " GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3696
+                    " GROUP BY ".$this->_deduce_column_name_from_query_param($query_params['group_by'])
3697 3697
                 );
3698 3698
             }
3699 3699
         }
@@ -3703,7 +3703,7 @@  discard block
 block discarded – undo
3703 3703
         }
3704 3704
         // now, just verify they didn't pass anything wack
3705 3705
         foreach ($query_params as $query_key => $query_value) {
3706
-            if (! in_array($query_key, $this->_allowed_query_params, true)) {
3706
+            if ( ! in_array($query_key, $this->_allowed_query_params, true)) {
3707 3707
                 throw new EE_Error(
3708 3708
                     sprintf(
3709 3709
                         esc_html__(
@@ -3808,7 +3808,7 @@  discard block
 block discarded – undo
3808 3808
         $where_query_params = []
3809 3809
     ) {
3810 3810
         $allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3811
-        if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3811
+        if ( ! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3812 3812
             throw new EE_Error(
3813 3813
                 sprintf(
3814 3814
                     esc_html__(
@@ -3838,7 +3838,7 @@  discard block
 block discarded – undo
3838 3838
                 // we don't want to add full or even minimum default where conditions from this model, so just continue
3839 3839
                 continue;
3840 3840
             }
3841
-            $overrides              = $this->_override_defaults_or_make_null_friendly(
3841
+            $overrides = $this->_override_defaults_or_make_null_friendly(
3842 3842
                 $related_model_universal_where_params,
3843 3843
                 $where_query_params,
3844 3844
                 $related_model,
@@ -3947,19 +3947,19 @@  discard block
 block discarded – undo
3947 3947
     ) {
3948 3948
         $null_friendly_where_conditions = [];
3949 3949
         $none_overridden                = true;
3950
-        $or_condition_key_for_defaults  = 'OR*' . get_class($model);
3950
+        $or_condition_key_for_defaults  = 'OR*'.get_class($model);
3951 3951
         foreach ($default_where_conditions as $key => $val) {
3952
-            if (isset($provided_where_conditions[ $key ])) {
3952
+            if (isset($provided_where_conditions[$key])) {
3953 3953
                 $none_overridden = false;
3954 3954
             } else {
3955
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3955
+                $null_friendly_where_conditions[$or_condition_key_for_defaults]['AND'][$key] = $val;
3956 3956
             }
3957 3957
         }
3958 3958
         if ($none_overridden && $default_where_conditions) {
3959 3959
             if ($model->has_primary_key_field()) {
3960
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3960
+                $null_friendly_where_conditions[$or_condition_key_for_defaults][$model_relation_path
3961 3961
                                                                                    . "."
3962
-                                                                                   . $model->primary_key_name() ] =
3962
+                                                                                   . $model->primary_key_name()] =
3963 3963
                     ['IS NULL'];
3964 3964
             }/*else{
3965 3965
                 //@todo NO PK, use other defaults
@@ -4070,7 +4070,7 @@  discard block
 block discarded – undo
4070 4070
             foreach ($tables as $table_obj) {
4071 4071
                 $qualified_pk_column = $table_alias_with_model_relation_chain_prefix
4072 4072
                                        . $table_obj->get_fully_qualified_pk_column();
4073
-                if (! in_array($qualified_pk_column, $selects)) {
4073
+                if ( ! in_array($qualified_pk_column, $selects)) {
4074 4074
                     $selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
4075 4075
                 }
4076 4076
             }
@@ -4222,9 +4222,9 @@  discard block
 block discarded – undo
4222 4222
         $query_parameter_type
4223 4223
     ) {
4224 4224
         foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4225
-            if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4225
+            if (strpos($possible_join_string, $valid_related_model_name.".") === 0) {
4226 4226
                 $this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4227
-                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4227
+                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name."."));
4228 4228
                 if ($possible_join_string === '') {
4229 4229
                     // nothing left to $query_param
4230 4230
                     // we should actually end in a field name, not a model like this!
@@ -4357,7 +4357,7 @@  discard block
 block discarded – undo
4357 4357
     {
4358 4358
         $SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4359 4359
         if ($SQL) {
4360
-            return " WHERE " . $SQL;
4360
+            return " WHERE ".$SQL;
4361 4361
         }
4362 4362
         return '';
4363 4363
     }
@@ -4375,7 +4375,7 @@  discard block
 block discarded – undo
4375 4375
     {
4376 4376
         $SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4377 4377
         if ($SQL) {
4378
-            return " HAVING " . $SQL;
4378
+            return " HAVING ".$SQL;
4379 4379
         }
4380 4380
         return '';
4381 4381
     }
@@ -4429,7 +4429,7 @@  discard block
 block discarded – undo
4429 4429
             } else {
4430 4430
                 $field_obj = $this->_deduce_field_from_query_param($query_param);
4431 4431
                 // if it's not a normal field, maybe it's a custom selection?
4432
-                if (! $field_obj) {
4432
+                if ( ! $field_obj) {
4433 4433
                     if ($this->_custom_selections instanceof CustomSelects) {
4434 4434
                         $field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4435 4435
                     } else {
@@ -4445,7 +4445,7 @@  discard block
 block discarded – undo
4445 4445
                     }
4446 4446
                 }
4447 4447
                 $op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4448
-                $where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4448
+                $where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param).SP.$op_and_value_sql;
4449 4449
             }
4450 4450
         }
4451 4451
         return $where_clauses
@@ -4469,7 +4469,7 @@  discard block
 block discarded – undo
4469 4469
                 $field->get_model_name(),
4470 4470
                 $query_param
4471 4471
             );
4472
-            return $table_alias_prefix . $field->get_qualified_column();
4472
+            return $table_alias_prefix.$field->get_qualified_column();
4473 4473
         }
4474 4474
         if (
4475 4475
             $this->_custom_selections instanceof CustomSelects
@@ -4530,10 +4530,10 @@  discard block
 block discarded – undo
4530 4530
             $operator = isset($op_and_value[0])
4531 4531
                 ? $this->_prepare_operator_for_sql($op_and_value[0])
4532 4532
                 : null;
4533
-            if (! $operator) {
4533
+            if ( ! $operator) {
4534 4534
                 $php_array_like_string = [];
4535 4535
                 foreach ($op_and_value as $key => $value) {
4536
-                    $value = is_array($value) ? '[' . implode(",", $value) . ']' : $value;
4536
+                    $value = is_array($value) ? '['.implode(",", $value).']' : $value;
4537 4537
                     $php_array_like_string[] = "$key=>$value";
4538 4538
                 }
4539 4539
                 throw new EE_Error(
@@ -4551,14 +4551,14 @@  discard block
 block discarded – undo
4551 4551
 
4552 4552
         // check to see if the value is actually another field
4553 4553
         if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2]) {
4554
-            return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4554
+            return $operator.SP.$this->_deduce_column_name_from_query_param($value);
4555 4555
         }
4556 4556
         if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4557 4557
             // in this case, the value should be an array, or at least a comma-separated list
4558 4558
             // it will need to handle a little differently
4559 4559
             $cleaned_value = $this->_construct_in_value($value, $field_obj);
4560 4560
             // note: $cleaned_value has already been run through $wpdb->prepare()
4561
-            return $operator . SP . $cleaned_value;
4561
+            return $operator.SP.$cleaned_value;
4562 4562
         }
4563 4563
         if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4564 4564
             // the value should be an array with count of two.
@@ -4574,7 +4574,7 @@  discard block
 block discarded – undo
4574 4574
                 );
4575 4575
             }
4576 4576
             $cleaned_value = $this->_construct_between_value($value, $field_obj);
4577
-            return $operator . SP . $cleaned_value;
4577
+            return $operator.SP.$cleaned_value;
4578 4578
         }
4579 4579
         if (in_array($operator, $this->valid_null_style_operators())) {
4580 4580
             if ($value !== null) {
@@ -4594,10 +4594,10 @@  discard block
 block discarded – undo
4594 4594
         if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4595 4595
             // if the operator is 'LIKE', we want to allow percent signs (%) and not
4596 4596
             // remove other junk. So just treat it as a string.
4597
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4597
+            return $operator.SP.$this->_wpdb_prepare_using_field($value, '%s');
4598 4598
         }
4599
-        if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4600
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4599
+        if ( ! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4600
+            return $operator.SP.$this->_wpdb_prepare_using_field($value, $field_obj);
4601 4601
         }
4602 4602
         if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4603 4603
             throw new EE_Error(
@@ -4611,7 +4611,7 @@  discard block
 block discarded – undo
4611 4611
                 )
4612 4612
             );
4613 4613
         }
4614
-        if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4614
+        if ( ! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4615 4615
             throw new EE_Error(
4616 4616
                 sprintf(
4617 4617
                     esc_html__(
@@ -4650,7 +4650,7 @@  discard block
 block discarded – undo
4650 4650
         foreach ($values as $value) {
4651 4651
             $cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4652 4652
         }
4653
-        return $cleaned_values[0] . " AND " . $cleaned_values[1];
4653
+        return $cleaned_values[0]." AND ".$cleaned_values[1];
4654 4654
     }
4655 4655
 
4656 4656
 
@@ -4688,7 +4688,7 @@  discard block
 block discarded – undo
4688 4688
             $main_table  = $this->_get_main_table();
4689 4689
             $prepped[]   = "SELECT {$first_field->get_table_column()} FROM {$main_table->get_table_name()} WHERE FALSE";
4690 4690
         }
4691
-        return '(' . implode(',', $prepped) . ')';
4691
+        return '('.implode(',', $prepped).')';
4692 4692
     }
4693 4693
 
4694 4694
 
@@ -4708,7 +4708,7 @@  discard block
 block discarded – undo
4708 4708
                 $this->_prepare_value_for_use_in_db($value, $field_obj)
4709 4709
             );
4710 4710
         } //$field_obj should really just be a data type
4711
-        if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4711
+        if ( ! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4712 4712
             throw new EE_Error(
4713 4713
                 sprintf(
4714 4714
                     esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
@@ -4745,14 +4745,14 @@  discard block
 block discarded – undo
4745 4745
             );
4746 4746
         }
4747 4747
         $number_of_parts       = count($query_param_parts);
4748
-        $last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4748
+        $last_query_param_part = $query_param_parts[count($query_param_parts) - 1];
4749 4749
         if ($number_of_parts === 1) {
4750 4750
             $field_name = $last_query_param_part;
4751 4751
             $model_obj  = $this;
4752 4752
         } else {// $number_of_parts >= 2
4753 4753
             // the last part is the column name, and there are only 2parts. therefore...
4754 4754
             $field_name = $last_query_param_part;
4755
-            $model_obj  = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4755
+            $model_obj  = $this->get_related_model_obj($query_param_parts[$number_of_parts - 2]);
4756 4756
         }
4757 4757
         try {
4758 4758
             return $model_obj->field_settings_for($field_name);
@@ -4773,7 +4773,7 @@  discard block
 block discarded – undo
4773 4773
     public function _get_qualified_column_for_field($field_name)
4774 4774
     {
4775 4775
         $all_fields = $this->field_settings();
4776
-        $field      = $all_fields[ $field_name ] ?? false;
4776
+        $field      = $all_fields[$field_name] ?? false;
4777 4777
         if ($field) {
4778 4778
             return $field->get_qualified_column();
4779 4779
         }
@@ -4843,12 +4843,12 @@  discard block
 block discarded – undo
4843 4843
      */
4844 4844
     public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4845 4845
     {
4846
-        $table_prefix      = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain)
4846
+        $table_prefix      = str_replace('.', '__', $model_relation_chain).(empty($model_relation_chain)
4847 4847
                 ? ''
4848 4848
                 : '__');
4849 4849
         $qualified_columns = [];
4850 4850
         foreach ($this->field_settings() as $field_name => $field) {
4851
-            $qualified_columns[] = $table_prefix . $field->get_qualified_column();
4851
+            $qualified_columns[] = $table_prefix.$field->get_qualified_column();
4852 4852
         }
4853 4853
         return $return_string
4854 4854
             ? implode(', ', $qualified_columns)
@@ -4875,11 +4875,11 @@  discard block
 block discarded – undo
4875 4875
             if ($table_obj instanceof EE_Primary_Table) {
4876 4876
                 $SQL .= $table_alias === $table_obj->get_table_alias()
4877 4877
                     ? $table_obj->get_select_join_limit($limit)
4878
-                    : SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4878
+                    : SP.$table_obj->get_table_name()." AS ".$table_obj->get_table_alias().SP;
4879 4879
             } elseif ($table_obj instanceof EE_Secondary_Table) {
4880 4880
                 $SQL .= $table_alias === $table_obj->get_table_alias()
4881 4881
                     ? $table_obj->get_select_join_limit_join($limit)
4882
-                    : SP . $table_obj->get_join_sql($table_alias) . SP;
4882
+                    : SP.$table_obj->get_join_sql($table_alias).SP;
4883 4883
             }
4884 4884
         }
4885 4885
         return $SQL;
@@ -4950,7 +4950,7 @@  discard block
 block discarded – undo
4950 4950
         foreach ($this->field_settings() as $field_obj) {
4951 4951
             // $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4952 4952
             /** @var $field_obj EE_Model_Field_Base */
4953
-            $data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4953
+            $data_types[$field_obj->get_qualified_column()] = $field_obj->get_wpdb_data_type();
4954 4954
         }
4955 4955
         return $data_types;
4956 4956
     }
@@ -4965,8 +4965,8 @@  discard block
 block discarded – undo
4965 4965
      */
4966 4966
     public function get_related_model_obj($model_name)
4967 4967
     {
4968
-        $model_classname = "EEM_" . $model_name;
4969
-        if (! class_exists($model_classname)) {
4968
+        $model_classname = "EEM_".$model_name;
4969
+        if ( ! class_exists($model_classname)) {
4970 4970
             throw new EE_Error(
4971 4971
                 sprintf(
4972 4972
                     esc_html__(
@@ -4978,7 +4978,7 @@  discard block
 block discarded – undo
4978 4978
                 )
4979 4979
             );
4980 4980
         }
4981
-        return call_user_func($model_classname . "::instance");
4981
+        return call_user_func($model_classname."::instance");
4982 4982
     }
4983 4983
 
4984 4984
 
@@ -5005,7 +5005,7 @@  discard block
 block discarded – undo
5005 5005
         $belongs_to_relations = [];
5006 5006
         foreach ($this->relation_settings() as $model_name => $relation_obj) {
5007 5007
             if ($relation_obj instanceof EE_Belongs_To_Relation) {
5008
-                $belongs_to_relations[ $model_name ] = $relation_obj;
5008
+                $belongs_to_relations[$model_name] = $relation_obj;
5009 5009
             }
5010 5010
         }
5011 5011
         return $belongs_to_relations;
@@ -5022,7 +5022,7 @@  discard block
 block discarded – undo
5022 5022
     public function related_settings_for($relation_name)
5023 5023
     {
5024 5024
         $relatedModels = $this->relation_settings();
5025
-        if (! array_key_exists($relation_name, $relatedModels)) {
5025
+        if ( ! array_key_exists($relation_name, $relatedModels)) {
5026 5026
             throw new EE_Error(
5027 5027
                 sprintf(
5028 5028
                     esc_html__(
@@ -5035,7 +5035,7 @@  discard block
 block discarded – undo
5035 5035
                 )
5036 5036
             );
5037 5037
         }
5038
-        return $relatedModels[ $relation_name ];
5038
+        return $relatedModels[$relation_name];
5039 5039
     }
5040 5040
 
5041 5041
 
@@ -5051,7 +5051,7 @@  discard block
 block discarded – undo
5051 5051
     public function field_settings_for($fieldName, $include_db_only_fields = true)
5052 5052
     {
5053 5053
         $fieldSettings = $this->field_settings($include_db_only_fields);
5054
-        if (! array_key_exists($fieldName, $fieldSettings)) {
5054
+        if ( ! array_key_exists($fieldName, $fieldSettings)) {
5055 5055
             throw new EE_Error(
5056 5056
                 sprintf(
5057 5057
                     esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
@@ -5060,7 +5060,7 @@  discard block
 block discarded – undo
5060 5060
                 )
5061 5061
             );
5062 5062
         }
5063
-        return $fieldSettings[ $fieldName ];
5063
+        return $fieldSettings[$fieldName];
5064 5064
     }
5065 5065
 
5066 5066
 
@@ -5073,7 +5073,7 @@  discard block
 block discarded – undo
5073 5073
     public function has_field($fieldName)
5074 5074
     {
5075 5075
         $fieldSettings = $this->field_settings(true);
5076
-        if (isset($fieldSettings[ $fieldName ])) {
5076
+        if (isset($fieldSettings[$fieldName])) {
5077 5077
             return true;
5078 5078
         }
5079 5079
         return false;
@@ -5089,7 +5089,7 @@  discard block
 block discarded – undo
5089 5089
     public function has_relation($relation_name)
5090 5090
     {
5091 5091
         $relations = $this->relation_settings();
5092
-        if (isset($relations[ $relation_name ])) {
5092
+        if (isset($relations[$relation_name])) {
5093 5093
             return true;
5094 5094
         }
5095 5095
         return false;
@@ -5125,7 +5125,7 @@  discard block
 block discarded – undo
5125 5125
                     break;
5126 5126
                 }
5127 5127
             }
5128
-            if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5128
+            if ( ! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5129 5129
                 throw new EE_Error(
5130 5130
                     sprintf(
5131 5131
                         esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
@@ -5185,17 +5185,17 @@  discard block
 block discarded – undo
5185 5185
      */
5186 5186
     public function get_foreign_key_to($model_name)
5187 5187
     {
5188
-        if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5188
+        if ( ! isset($this->_cache_foreign_key_to_fields[$model_name])) {
5189 5189
             foreach ($this->field_settings() as $field) {
5190 5190
                 if (
5191 5191
                     $field instanceof EE_Foreign_Key_Field_Base
5192 5192
                     && in_array($model_name, $field->get_model_names_pointed_to())
5193 5193
                 ) {
5194
-                    $this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5194
+                    $this->_cache_foreign_key_to_fields[$model_name] = $field;
5195 5195
                     break;
5196 5196
                 }
5197 5197
             }
5198
-            if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5198
+            if ( ! isset($this->_cache_foreign_key_to_fields[$model_name])) {
5199 5199
                 throw new EE_Error(
5200 5200
                     sprintf(
5201 5201
                         esc_html__(
@@ -5208,7 +5208,7 @@  discard block
 block discarded – undo
5208 5208
                 );
5209 5209
             }
5210 5210
         }
5211
-        return $this->_cache_foreign_key_to_fields[ $model_name ];
5211
+        return $this->_cache_foreign_key_to_fields[$model_name];
5212 5212
     }
5213 5213
 
5214 5214
 
@@ -5224,7 +5224,7 @@  discard block
 block discarded – undo
5224 5224
     {
5225 5225
         $table_alias_sans_model_relation_chain_prefix =
5226 5226
             EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5227
-        return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5227
+        return $this->_tables[$table_alias_sans_model_relation_chain_prefix]->get_table_name();
5228 5228
     }
5229 5229
 
5230 5230
 
@@ -5242,7 +5242,7 @@  discard block
 block discarded – undo
5242 5242
                 $this->_cached_fields = [];
5243 5243
                 foreach ($this->_fields as $fields_corresponding_to_table) {
5244 5244
                     foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5245
-                        $this->_cached_fields[ $field_name ] = $field_obj;
5245
+                        $this->_cached_fields[$field_name] = $field_obj;
5246 5246
                     }
5247 5247
                 }
5248 5248
             }
@@ -5253,8 +5253,8 @@  discard block
 block discarded – undo
5253 5253
             foreach ($this->_fields as $fields_corresponding_to_table) {
5254 5254
                 foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5255 5255
                     /** @var $field_obj EE_Model_Field_Base */
5256
-                    if (! $field_obj->is_db_only_field()) {
5257
-                        $this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5256
+                    if ( ! $field_obj->is_db_only_field()) {
5257
+                        $this->_cached_fields_non_db_only[$field_name] = $field_obj;
5258 5258
                     }
5259 5259
                 }
5260 5260
             }
@@ -5297,12 +5297,12 @@  discard block
 block discarded – undo
5297 5297
                     $primary_key_field->get_qualified_column(),
5298 5298
                     $primary_key_field->get_table_column()
5299 5299
                 );
5300
-                if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5300
+                if ($table_pk_value && isset($array_of_objects[$table_pk_value])) {
5301 5301
                     continue;
5302 5302
                 }
5303 5303
             }
5304 5304
             $classInstance = $this->instantiate_class_from_array_or_object($row);
5305
-            if (! $classInstance) {
5305
+            if ( ! $classInstance) {
5306 5306
                 throw new EE_Error(
5307 5307
                     sprintf(
5308 5308
                         esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
@@ -5317,7 +5317,7 @@  discard block
 block discarded – undo
5317 5317
             $key                      = $has_primary_key
5318 5318
                 ? $classInstance->ID()
5319 5319
                 : $count_if_model_has_no_primary_key++;
5320
-            $array_of_objects[ $key ] = $classInstance;
5320
+            $array_of_objects[$key] = $classInstance;
5321 5321
             // also, for all the relations of type BelongsTo, see if we can cache
5322 5322
             // those related models
5323 5323
             // (we could do this for other relations too, but if there are conditions
@@ -5361,9 +5361,9 @@  discard block
 block discarded – undo
5361 5361
         $results = [];
5362 5362
         if ($this->_custom_selections instanceof CustomSelects) {
5363 5363
             foreach ($this->_custom_selections->columnAliases() as $alias) {
5364
-                if (isset($db_results_row[ $alias ])) {
5365
-                    $results[ $alias ] = $this->convertValueToDataType(
5366
-                        $db_results_row[ $alias ],
5364
+                if (isset($db_results_row[$alias])) {
5365
+                    $results[$alias] = $this->convertValueToDataType(
5366
+                        $db_results_row[$alias],
5367 5367
                         $this->_custom_selections->getDataTypeForAlias($alias)
5368 5368
                     );
5369 5369
                 }
@@ -5408,7 +5408,7 @@  discard block
 block discarded – undo
5408 5408
         $this_model_fields_and_values = [];
5409 5409
         // setup the row using default values;
5410 5410
         foreach ($this->field_settings() as $field_name => $field_obj) {
5411
-            $this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5411
+            $this_model_fields_and_values[$field_name] = $field_obj->get_default_value();
5412 5412
         }
5413 5413
         $className = $this->_get_class_name();
5414 5414
         return EE_Registry::instance()->load_class($className, [$this_model_fields_and_values], false, false);
@@ -5424,20 +5424,20 @@  discard block
 block discarded – undo
5424 5424
      */
5425 5425
     public function instantiate_class_from_array_or_object($cols_n_values)
5426 5426
     {
5427
-        if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5427
+        if ( ! is_array($cols_n_values) && is_object($cols_n_values)) {
5428 5428
             $cols_n_values = get_object_vars($cols_n_values);
5429 5429
         }
5430 5430
         $primary_key = null;
5431 5431
         // make sure the array only has keys that are fields/columns on this model
5432 5432
         $this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5433
-        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5434
-            $primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5433
+        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[$this->primary_key_name()])) {
5434
+            $primary_key = $this_model_fields_n_values[$this->primary_key_name()];
5435 5435
         }
5436 5436
         $className = $this->_get_class_name();
5437 5437
         // check we actually found results that we can use to build our model object
5438 5438
         // if not, return null
5439 5439
         if ($this->has_primary_key_field()) {
5440
-            if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5440
+            if (empty($this_model_fields_n_values[$this->primary_key_name()])) {
5441 5441
                 return null;
5442 5442
             }
5443 5443
         } elseif ($this->unique_indexes()) {
@@ -5449,7 +5449,7 @@  discard block
 block discarded – undo
5449 5449
         // if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5450 5450
         if ($primary_key) {
5451 5451
             $classInstance = $this->get_from_entity_map($primary_key);
5452
-            if (! $classInstance) {
5452
+            if ( ! $classInstance) {
5453 5453
                 $classInstance = EE_Registry::instance()
5454 5454
                                             ->load_class(
5455 5455
                                                 $className,
@@ -5480,7 +5480,7 @@  discard block
 block discarded – undo
5480 5480
      */
5481 5481
     public function get_from_entity_map($id)
5482 5482
     {
5483
-        return $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] ?? null;
5483
+        return $this->_entity_map[EEM_Base::$_model_query_blog_id][$id] ?? null;
5484 5484
     }
5485 5485
 
5486 5486
 
@@ -5503,7 +5503,7 @@  discard block
 block discarded – undo
5503 5503
     public function add_to_entity_map(EE_Base_Class $object)
5504 5504
     {
5505 5505
         $className = $this->_get_class_name();
5506
-        if (! $object instanceof $className) {
5506
+        if ( ! $object instanceof $className) {
5507 5507
             throw new EE_Error(
5508 5508
                 sprintf(
5509 5509
                     esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
@@ -5515,7 +5515,7 @@  discard block
 block discarded – undo
5515 5515
             );
5516 5516
         }
5517 5517
 
5518
-        if (! $object->ID()) {
5518
+        if ( ! $object->ID()) {
5519 5519
             throw new EE_Error(
5520 5520
                 sprintf(
5521 5521
                     esc_html__(
@@ -5531,7 +5531,7 @@  discard block
 block discarded – undo
5531 5531
         if ($classInstance) {
5532 5532
             return $classInstance;
5533 5533
         }
5534
-        $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5534
+        $this->_entity_map[EEM_Base::$_model_query_blog_id][$object->ID()] = $object;
5535 5535
         return $object;
5536 5536
     }
5537 5537
 
@@ -5546,11 +5546,11 @@  discard block
 block discarded – undo
5546 5546
     public function clear_entity_map($id = null)
5547 5547
     {
5548 5548
         if (empty($id)) {
5549
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ] = [];
5549
+            $this->_entity_map[EEM_Base::$_model_query_blog_id] = [];
5550 5550
             return true;
5551 5551
         }
5552
-        if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5553
-            unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5552
+        if (isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])) {
5553
+            unset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id]);
5554 5554
             return true;
5555 5555
         }
5556 5556
         return false;
@@ -5598,18 +5598,18 @@  discard block
 block discarded – undo
5598 5598
             // there is a primary key on this table and its not set. Use defaults for all its columns
5599 5599
             if ($table_pk_value === null && $table_obj->get_pk_column()) {
5600 5600
                 foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5601
-                    if (! $field_obj->is_db_only_field()) {
5601
+                    if ( ! $field_obj->is_db_only_field()) {
5602 5602
                         // prepare field as if its coming from db
5603 5603
                         $prepared_value                            =
5604 5604
                             $field_obj->prepare_for_set($field_obj->get_default_value());
5605
-                        $this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5605
+                        $this_model_fields_n_values[$field_name] = $field_obj->prepare_for_use_in_db($prepared_value);
5606 5606
                     }
5607 5607
                 }
5608 5608
             } else {
5609 5609
                 // the table's rows existed. Use their values
5610 5610
                 foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5611
-                    if (! $field_obj->is_db_only_field()) {
5612
-                        $this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5611
+                    if ( ! $field_obj->is_db_only_field()) {
5612
+                        $this_model_fields_n_values[$field_name] = $this->_get_column_value_with_table_alias_or_not(
5613 5613
                             $cols_n_values,
5614 5614
                             $field_obj->get_qualified_column(),
5615 5615
                             $field_obj->get_table_column()
@@ -5636,17 +5636,17 @@  discard block
 block discarded – undo
5636 5636
         // ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5637 5637
         // does the field on the model relate to this column retrieved from the db?
5638 5638
         // or is it a db-only field? (not relating to the model)
5639
-        if (isset($cols_n_values[ $qualified_column ])) {
5640
-            $value = $cols_n_values[ $qualified_column ];
5641
-        } elseif (isset($cols_n_values[ $regular_column ])) {
5642
-            $value = $cols_n_values[ $regular_column ];
5643
-        } elseif (! empty($this->foreign_key_aliases)) {
5639
+        if (isset($cols_n_values[$qualified_column])) {
5640
+            $value = $cols_n_values[$qualified_column];
5641
+        } elseif (isset($cols_n_values[$regular_column])) {
5642
+            $value = $cols_n_values[$regular_column];
5643
+        } elseif ( ! empty($this->foreign_key_aliases)) {
5644 5644
             // no PK?  ok check if there is a foreign key alias set for this table
5645 5645
             // then check if that alias exists in the incoming data
5646 5646
             // AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5647 5647
             foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5648
-                if ($PK_column === $qualified_column && !empty($cols_n_values[ $FK_alias ])) {
5649
-                    $value = $cols_n_values[ $FK_alias ];
5648
+                if ($PK_column === $qualified_column && ! empty($cols_n_values[$FK_alias])) {
5649
+                    $value = $cols_n_values[$FK_alias];
5650 5650
                     [$pk_class] = explode('.', $PK_column);
5651 5651
                     $pk_model_name = "EEM_{$pk_class}";
5652 5652
                     /** @var EEM_Base $pk_model */
@@ -5690,7 +5690,7 @@  discard block
 block discarded – undo
5690 5690
                     $obj_in_map->clear_cache($relation_name, null, true);
5691 5691
                 }
5692 5692
             }
5693
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5693
+            $this->_entity_map[EEM_Base::$_model_query_blog_id][$id] = $obj_in_map;
5694 5694
             return $obj_in_map;
5695 5695
         }
5696 5696
         return $this->get_one_by_ID($id);
@@ -5742,7 +5742,7 @@  discard block
 block discarded – undo
5742 5742
      */
5743 5743
     private function _get_class_name()
5744 5744
     {
5745
-        return "EE_" . $this->get_this_model_name();
5745
+        return "EE_".$this->get_this_model_name();
5746 5746
     }
5747 5747
 
5748 5748
 
@@ -5796,7 +5796,7 @@  discard block
 block discarded – undo
5796 5796
     {
5797 5797
         $className = $this->class_name;
5798 5798
         $tagName   = "FHEE__{$className}__{$methodName}";
5799
-        if (! has_filter($tagName)) {
5799
+        if ( ! has_filter($tagName)) {
5800 5800
             throw new EE_Error(
5801 5801
                 sprintf(
5802 5802
                     esc_html__(
@@ -5967,7 +5967,7 @@  discard block
 block discarded – undo
5967 5967
         $unique_indexes = [];
5968 5968
         foreach ($this->_indexes as $name => $index) {
5969 5969
             if ($index instanceof EE_Unique_Index) {
5970
-                $unique_indexes [ $name ] = $index;
5970
+                $unique_indexes [$name] = $index;
5971 5971
             }
5972 5972
         }
5973 5973
         return $unique_indexes;
@@ -6031,7 +6031,7 @@  discard block
 block discarded – undo
6031 6031
         $key_vals_in_combined_pk = [];
6032 6032
         parse_str($index_primary_key_string, $key_vals_in_combined_pk);
6033 6033
         foreach ($key_fields as $key_field_name => $field_obj) {
6034
-            if (! isset($key_vals_in_combined_pk[ $key_field_name ])) {
6034
+            if ( ! isset($key_vals_in_combined_pk[$key_field_name])) {
6035 6035
                 return null;
6036 6036
             }
6037 6037
         }
@@ -6051,7 +6051,7 @@  discard block
 block discarded – undo
6051 6051
     {
6052 6052
         $keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
6053 6053
         foreach ($keys_it_should_have as $key) {
6054
-            if (! isset($key_vals[ $key ])) {
6054
+            if ( ! isset($key_vals[$key])) {
6055 6055
                 return false;
6056 6056
             }
6057 6057
         }
@@ -6090,8 +6090,8 @@  discard block
 block discarded – undo
6090 6090
         }
6091 6091
         // even copies obviously won't have the same ID, so remove the primary key
6092 6092
         // from the WHERE conditions for finding copies (if there is a primary key, of course)
6093
-        if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
6094
-            unset($attributes_array[ $this->primary_key_name() ]);
6093
+        if ($this->has_primary_key_field() && isset($attributes_array[$this->primary_key_name()])) {
6094
+            unset($attributes_array[$this->primary_key_name()]);
6095 6095
         }
6096 6096
         if (isset($query_params[0])) {
6097 6097
             $query_params[0] = array_merge($attributes_array, $query_params);
@@ -6113,7 +6113,7 @@  discard block
 block discarded – undo
6113 6113
      */
6114 6114
     public function get_one_copy($model_object_or_attributes_array, $query_params = [])
6115 6115
     {
6116
-        if (! is_array($query_params)) {
6116
+        if ( ! is_array($query_params)) {
6117 6117
             EE_Error::doing_it_wrong(
6118 6118
                 'EEM_Base::get_one_copy',
6119 6119
                 sprintf(
@@ -6162,7 +6162,7 @@  discard block
 block discarded – undo
6162 6162
      */
6163 6163
     private function _prepare_operator_for_sql($operator_supplied)
6164 6164
     {
6165
-        $sql_operator = $this->_valid_operators[ $operator_supplied ] ?? null;
6165
+        $sql_operator = $this->_valid_operators[$operator_supplied] ?? null;
6166 6166
         if ($sql_operator) {
6167 6167
             return $sql_operator;
6168 6168
         }
@@ -6260,7 +6260,7 @@  discard block
 block discarded – undo
6260 6260
         $objs  = $this->get_all($query_params);
6261 6261
         $names = [];
6262 6262
         foreach ($objs as $obj) {
6263
-            $names[ $obj->ID() ] = $obj->name();
6263
+            $names[$obj->ID()] = $obj->name();
6264 6264
         }
6265 6265
         return $names;
6266 6266
     }
@@ -6281,7 +6281,7 @@  discard block
 block discarded – undo
6281 6281
      */
6282 6282
     public function get_IDs($model_objects, $filter_out_empty_ids = false)
6283 6283
     {
6284
-        if (! $this->has_primary_key_field()) {
6284
+        if ( ! $this->has_primary_key_field()) {
6285 6285
             if (defined('WP_DEBUG') && WP_DEBUG) {
6286 6286
                 EE_Error::add_error(
6287 6287
                     esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
@@ -6294,7 +6294,7 @@  discard block
 block discarded – undo
6294 6294
         $IDs = [];
6295 6295
         foreach ($model_objects as $model_object) {
6296 6296
             $id = $model_object->ID();
6297
-            if (! $id) {
6297
+            if ( ! $id) {
6298 6298
                 if ($filter_out_empty_ids) {
6299 6299
                     continue;
6300 6300
                 }
@@ -6343,22 +6343,22 @@  discard block
 block discarded – undo
6343 6343
         EEM_Base::verify_is_valid_cap_context($context);
6344 6344
         // check if we ought to run the restriction generator first
6345 6345
         if (
6346
-            isset($this->_cap_restriction_generators[ $context ])
6347
-            && $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6348
-            && ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6346
+            isset($this->_cap_restriction_generators[$context])
6347
+            && $this->_cap_restriction_generators[$context] instanceof EE_Restriction_Generator_Base
6348
+            && ! $this->_cap_restriction_generators[$context]->has_generated_cap_restrictions()
6349 6349
         ) {
6350
-            $this->_cap_restrictions[ $context ] = array_merge(
6351
-                $this->_cap_restrictions[ $context ],
6352
-                $this->_cap_restriction_generators[ $context ]->generate_restrictions()
6350
+            $this->_cap_restrictions[$context] = array_merge(
6351
+                $this->_cap_restrictions[$context],
6352
+                $this->_cap_restriction_generators[$context]->generate_restrictions()
6353 6353
             );
6354 6354
         }
6355 6355
         // and make sure we've finalized the construction of each restriction
6356
-        foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6356
+        foreach ($this->_cap_restrictions[$context] as $where_conditions_obj) {
6357 6357
             if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6358 6358
                 $where_conditions_obj->_finalize_construct($this);
6359 6359
             }
6360 6360
         }
6361
-        return $this->_cap_restrictions[ $context ];
6361
+        return $this->_cap_restrictions[$context];
6362 6362
     }
6363 6363
 
6364 6364
 
@@ -6388,9 +6388,9 @@  discard block
 block discarded – undo
6388 6388
         foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6389 6389
             if (
6390 6390
                 ! EE_Capabilities::instance()
6391
-                                 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6391
+                                 ->current_user_can($cap, $this->get_this_model_name().'_model_applying_caps')
6392 6392
             ) {
6393
-                $missing_caps[ $cap ] = $restriction_if_no_cap;
6393
+                $missing_caps[$cap] = $restriction_if_no_cap;
6394 6394
             }
6395 6395
         }
6396 6396
         return $missing_caps;
@@ -6423,8 +6423,8 @@  discard block
 block discarded – undo
6423 6423
     public function cap_action_for_context($context)
6424 6424
     {
6425 6425
         $mapping = $this->cap_contexts_to_cap_action_map();
6426
-        if (isset($mapping[ $context ])) {
6427
-            return $mapping[ $context ];
6426
+        if (isset($mapping[$context])) {
6427
+            return $mapping[$context];
6428 6428
         }
6429 6429
         if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6430 6430
             return $action;
@@ -6545,7 +6545,7 @@  discard block
 block discarded – undo
6545 6545
         foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6546 6546
             if (
6547 6547
                 $query_param_key === $logic_query_param_key
6548
-                || strpos($query_param_key, $logic_query_param_key . '*') === 0
6548
+                || strpos($query_param_key, $logic_query_param_key.'*') === 0
6549 6549
             ) {
6550 6550
                 return true;
6551 6551
             }
@@ -6603,7 +6603,7 @@  discard block
 block discarded – undo
6603 6603
         if ($password_field instanceof EE_Password_Field) {
6604 6604
             $field_names = $password_field->protectedFields();
6605 6605
             foreach ($field_names as $field_name) {
6606
-                $fields[ $field_name ] = $this->field_settings_for($field_name);
6606
+                $fields[$field_name] = $this->field_settings_for($field_name);
6607 6607
             }
6608 6608
         }
6609 6609
         return $fields;
@@ -6629,7 +6629,7 @@  discard block
 block discarded – undo
6629 6629
         if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6630 6630
             $model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6631 6631
         }
6632
-        if (! is_array($model_obj_or_fields_n_values)) {
6632
+        if ( ! is_array($model_obj_or_fields_n_values)) {
6633 6633
             throw new UnexpectedEntityException(
6634 6634
                 $model_obj_or_fields_n_values,
6635 6635
                 'EE_Base_Class',
@@ -6711,9 +6711,9 @@  discard block
 block discarded – undo
6711 6711
         }
6712 6712
         return (
6713 6713
                $this->model_chain_to_password
6714
-                   ? $this->model_chain_to_password . '.'
6714
+                   ? $this->model_chain_to_password.'.'
6715 6715
                    : ''
6716
-               ) . $password_field_name;
6716
+               ).$password_field_name;
6717 6717
     }
6718 6718
 
6719 6719
 
Please login to merge, or discard this patch.
core/db_models/EEM_Change_Log.model.php 2 patches
Indentation   +220 added lines, -220 removed lines patch added patch discarded remove patch
@@ -9,89 +9,89 @@  discard block
 block discarded – undo
9 9
  */
10 10
 class EEM_Change_Log extends EEM_Base
11 11
 {
12
-    /**
13
-     * the related object was created log type
14
-     */
15
-    const type_create = 'create';
12
+	/**
13
+	 * the related object was created log type
14
+	 */
15
+	const type_create = 'create';
16 16
 
17
-    /**
18
-     * the related object was updated (changed, or soft-deleted)
19
-     */
20
-    const type_update = 'update';
17
+	/**
18
+	 * the related object was updated (changed, or soft-deleted)
19
+	 */
20
+	const type_update = 'update';
21 21
 
22
-    /**
23
-     * the related object was trashed/restored/deleted
24
-     */
25
-    const type_delete = 'delete';
22
+	/**
23
+	 * the related object was trashed/restored/deleted
24
+	 */
25
+	const type_delete = 'delete';
26 26
 
27
-    /**
28
-     * the related item had something worth noting happen on it, but
29
-     * only for the purposes of debugging problems
30
-     */
31
-    const type_debug = 'debug';
27
+	/**
28
+	 * the related item had something worth noting happen on it, but
29
+	 * only for the purposes of debugging problems
30
+	 */
31
+	const type_debug = 'debug';
32 32
 
33
-    /**
34
-     * the related item had an error occur on it
35
-     */
36
-    const type_error = 'error';
33
+	/**
34
+	 * the related item had an error occur on it
35
+	 */
36
+	const type_error = 'error';
37 37
 
38
-    /**
39
-     * the related item is regarding some gateway interaction, like an IPN
40
-     * or request to process a payment
41
-     */
42
-    const type_gateway = 'gateway';
38
+	/**
39
+	 * the related item is regarding some gateway interaction, like an IPN
40
+	 * or request to process a payment
41
+	 */
42
+	const type_gateway = 'gateway';
43 43
 
44 44
 
45
-    protected static ?EEM_Change_Log $_instance = null;
45
+	protected static ?EEM_Change_Log $_instance = null;
46 46
 
47 47
 
48
-    /**
49
-     * @param string|null $timezone
50
-     * @throws EE_Error
51
-     */
52
-    protected function __construct(?string $timezone = '')
53
-    {
54
-        $this->singular_item       = esc_html__('Log', 'event_espresso');
55
-        $this->plural_item         = esc_html__('Logs', 'event_espresso');
56
-        $this->_tables             = [
57
-            'Log' => new EE_Primary_Table('esp_log', 'LOG_ID'),
58
-        ];
59
-        $models_this_can_attach_to = array_keys(EE_Registry::instance()->non_abstract_db_models);
60
-        $this->_fields             = [
61
-            'Log' => [
62
-                'LOG_ID'      => new EE_Primary_Key_Int_Field('LOG_ID', esc_html__('Log ID', 'event_espresso')),
63
-                'LOG_time'    => new EE_Datetime_Field(
64
-                    'LOG_time',
65
-                    esc_html__("Log Time", 'event_espresso'),
66
-                    false,
67
-                    EE_Datetime_Field::now
68
-                ),
69
-                'OBJ_ID'      => new EE_Foreign_Key_String_Field(
70
-                    'OBJ_ID',
71
-                    esc_html__("Object ID (int or string)", 'event_espresso'),
72
-                    true,
73
-                    null,
74
-                    $models_this_can_attach_to
75
-                ),
76
-                'OBJ_type'    => new EE_Any_Foreign_Model_Name_Field(
77
-                    'OBJ_type',
78
-                    esc_html__("Object Type", 'event_espresso'),
79
-                    true,
80
-                    null,
81
-                    $models_this_can_attach_to
82
-                ),
83
-                'LOG_type'    => new EE_Plain_Text_Field(
84
-                    'LOG_type',
85
-                    esc_html__("Type of log entry", "event_espresso"),
86
-                    false,
87
-                    self::type_debug
88
-                ),
89
-                'LOG_message' => new EE_Maybe_Serialized_Text_Field(
90
-                    'LOG_message',
91
-                    esc_html__("Log Message (body)", 'event_espresso'),
92
-                    true
93
-                ),
94
-                /*
48
+	/**
49
+	 * @param string|null $timezone
50
+	 * @throws EE_Error
51
+	 */
52
+	protected function __construct(?string $timezone = '')
53
+	{
54
+		$this->singular_item       = esc_html__('Log', 'event_espresso');
55
+		$this->plural_item         = esc_html__('Logs', 'event_espresso');
56
+		$this->_tables             = [
57
+			'Log' => new EE_Primary_Table('esp_log', 'LOG_ID'),
58
+		];
59
+		$models_this_can_attach_to = array_keys(EE_Registry::instance()->non_abstract_db_models);
60
+		$this->_fields             = [
61
+			'Log' => [
62
+				'LOG_ID'      => new EE_Primary_Key_Int_Field('LOG_ID', esc_html__('Log ID', 'event_espresso')),
63
+				'LOG_time'    => new EE_Datetime_Field(
64
+					'LOG_time',
65
+					esc_html__("Log Time", 'event_espresso'),
66
+					false,
67
+					EE_Datetime_Field::now
68
+				),
69
+				'OBJ_ID'      => new EE_Foreign_Key_String_Field(
70
+					'OBJ_ID',
71
+					esc_html__("Object ID (int or string)", 'event_espresso'),
72
+					true,
73
+					null,
74
+					$models_this_can_attach_to
75
+				),
76
+				'OBJ_type'    => new EE_Any_Foreign_Model_Name_Field(
77
+					'OBJ_type',
78
+					esc_html__("Object Type", 'event_espresso'),
79
+					true,
80
+					null,
81
+					$models_this_can_attach_to
82
+				),
83
+				'LOG_type'    => new EE_Plain_Text_Field(
84
+					'LOG_type',
85
+					esc_html__("Type of log entry", "event_espresso"),
86
+					false,
87
+					self::type_debug
88
+				),
89
+				'LOG_message' => new EE_Maybe_Serialized_Text_Field(
90
+					'LOG_message',
91
+					esc_html__("Log Message (body)", 'event_espresso'),
92
+					true
93
+				),
94
+				/*
95 95
                  * Note: when querying for a change log's user, the OBJ_ID and OBJ_type fields are used,
96 96
                  * not the LOG_wp_user field. E.g.,
97 97
                  * `EEM_Change_Log::instance()->get_all(array(array('WP_User.ID'=>1)))` will actually return
@@ -100,162 +100,162 @@  discard block
 block discarded – undo
100 100
                  *  If you want the latter, you can't use the model's magic joining. E.g, you would need to do
101 101
                  * `EEM_Change_Log::instance()->get_all(array(array('LOG_wp_user' => 1)))`.
102 102
                  */
103
-                'LOG_wp_user' => new EE_WP_User_Field(
104
-                    'LOG_wp_user',
105
-                    esc_html__("User who was logged in while this occurred", 'event_espresso'),
106
-                    true
107
-                ),
108
-            ],
109
-        ];
110
-        $this->_model_relations    = [];
111
-        foreach ($models_this_can_attach_to as $model) {
112
-            if ($model != 'Change_Log') {
113
-                $this->_model_relations[ $model ] = new EE_Belongs_To_Any_Relation();
114
-            }
115
-        }
116
-        // use completely custom caps for this
117
-        $this->_cap_restriction_generators = null;
118
-        // caps-wise this is all-or-nothing: if you have the default role you can access anything, otherwise nothing
119
-        foreach ($this->_cap_contexts_to_cap_action_map as $cap_context => $action) {
120
-            $this->_cap_restrictions[ $cap_context ][ EE_Restriction_Generator_Base::get_default_restrictions_cap() ]
121
-                = new EE_Return_None_Where_Conditions();
122
-        }
123
-        parent::__construct($timezone);
124
-    }
103
+				'LOG_wp_user' => new EE_WP_User_Field(
104
+					'LOG_wp_user',
105
+					esc_html__("User who was logged in while this occurred", 'event_espresso'),
106
+					true
107
+				),
108
+			],
109
+		];
110
+		$this->_model_relations    = [];
111
+		foreach ($models_this_can_attach_to as $model) {
112
+			if ($model != 'Change_Log') {
113
+				$this->_model_relations[ $model ] = new EE_Belongs_To_Any_Relation();
114
+			}
115
+		}
116
+		// use completely custom caps for this
117
+		$this->_cap_restriction_generators = null;
118
+		// caps-wise this is all-or-nothing: if you have the default role you can access anything, otherwise nothing
119
+		foreach ($this->_cap_contexts_to_cap_action_map as $cap_context => $action) {
120
+			$this->_cap_restrictions[ $cap_context ][ EE_Restriction_Generator_Base::get_default_restrictions_cap() ]
121
+				= new EE_Return_None_Where_Conditions();
122
+		}
123
+		parent::__construct($timezone);
124
+	}
125 125
 
126 126
 
127
-    /**
128
-     * @param string             $log_type !see the acceptable values of LOG_type in EEM__Change_Log::__construct
129
-     * @param array|string       $message  array|string of the message you want to record
130
-     * @param EE_Base_Class|null $related_model_obj
131
-     * @return EE_Change_Log
132
-     * @throws EE_Error
133
-     * @throws ReflectionException
134
-     */
135
-    public function log(string $log_type, $message, ?EE_Base_Class $related_model_obj): EE_Change_Log
136
-    {
137
-        $obj_id   = null;
138
-        $obj_type = null;
139
-        if ($related_model_obj instanceof EE_Base_Class) {
140
-            $obj_id   = $related_model_obj->ID();
141
-            $obj_type = $related_model_obj->get_model()->get_this_model_name();
142
-        }
143
-        $log = EE_Change_Log::new_instance(
144
-            [
145
-                'LOG_type'    => $log_type,
146
-                'LOG_message' => $message,
147
-                'OBJ_ID'      => $obj_id,
148
-                'OBJ_type'    => $obj_type,
149
-            ]
150
-        );
151
-        $log->save();
152
-        return $log;
153
-    }
127
+	/**
128
+	 * @param string             $log_type !see the acceptable values of LOG_type in EEM__Change_Log::__construct
129
+	 * @param array|string       $message  array|string of the message you want to record
130
+	 * @param EE_Base_Class|null $related_model_obj
131
+	 * @return EE_Change_Log
132
+	 * @throws EE_Error
133
+	 * @throws ReflectionException
134
+	 */
135
+	public function log(string $log_type, $message, ?EE_Base_Class $related_model_obj): EE_Change_Log
136
+	{
137
+		$obj_id   = null;
138
+		$obj_type = null;
139
+		if ($related_model_obj instanceof EE_Base_Class) {
140
+			$obj_id   = $related_model_obj->ID();
141
+			$obj_type = $related_model_obj->get_model()->get_this_model_name();
142
+		}
143
+		$log = EE_Change_Log::new_instance(
144
+			[
145
+				'LOG_type'    => $log_type,
146
+				'LOG_message' => $message,
147
+				'OBJ_ID'      => $obj_id,
148
+				'OBJ_type'    => $obj_type,
149
+			]
150
+		);
151
+		$log->save();
152
+		return $log;
153
+	}
154 154
 
155 155
 
156
-    /**
157
-     * Adds a gateway log for the specified object, given its ID and type
158
-     *
159
-     * @param array|string $message
160
-     * @param int|string   $related_obj_id
161
-     * @param string       $related_obj_type
162
-     * @return EE_Change_Log
163
-     * @throws ReflectionException
164
-     * @throws EE_Error
165
-     */
166
-    public function gateway_log($message, $related_obj_id, string $related_obj_type): EE_Change_Log
167
-    {
168
-        if (! EE_Registry::instance()->is_model_name($related_obj_type)) {
169
-            throw new EE_Error(
170
-                sprintf(
171
-                    esc_html__(
172
-                        "'%s' is not a model name. A model name must be provided when making a gateway log. Eg, 'Payment', 'Payment_Method', etc",
173
-                        "event_espresso"
174
-                    ),
175
-                    $related_obj_type
176
-                )
177
-            );
178
-        }
179
-        $log = EE_Change_Log::new_instance(
180
-            [
181
-                'LOG_type'    => EEM_Change_Log::type_gateway,
182
-                'LOG_message' => $message,
183
-                'OBJ_ID'      => $related_obj_id,
184
-                'OBJ_type'    => $related_obj_type,
185
-            ]
186
-        );
187
-        $log->save();
188
-        return $log;
189
-    }
156
+	/**
157
+	 * Adds a gateway log for the specified object, given its ID and type
158
+	 *
159
+	 * @param array|string $message
160
+	 * @param int|string   $related_obj_id
161
+	 * @param string       $related_obj_type
162
+	 * @return EE_Change_Log
163
+	 * @throws ReflectionException
164
+	 * @throws EE_Error
165
+	 */
166
+	public function gateway_log($message, $related_obj_id, string $related_obj_type): EE_Change_Log
167
+	{
168
+		if (! EE_Registry::instance()->is_model_name($related_obj_type)) {
169
+			throw new EE_Error(
170
+				sprintf(
171
+					esc_html__(
172
+						"'%s' is not a model name. A model name must be provided when making a gateway log. Eg, 'Payment', 'Payment_Method', etc",
173
+						"event_espresso"
174
+					),
175
+					$related_obj_type
176
+				)
177
+			);
178
+		}
179
+		$log = EE_Change_Log::new_instance(
180
+			[
181
+				'LOG_type'    => EEM_Change_Log::type_gateway,
182
+				'LOG_message' => $message,
183
+				'OBJ_ID'      => $related_obj_id,
184
+				'OBJ_type'    => $related_obj_type,
185
+			]
186
+		);
187
+		$log->save();
188
+		return $log;
189
+	}
190 190
 
191 191
 
192
-    /**
193
-     * Just gets the bare-bones wpdb results as an array in cases where efficiency is essential
194
-     *
195
-     * @param array $query_params
196
-     * @return array of arrays
197
-     * @throws EE_Error
198
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
199
-     */
200
-    public function get_all_efficiently(array $query_params): array
201
-    {
202
-        return $this->_get_all_wpdb_results($query_params);
203
-    }
192
+	/**
193
+	 * Just gets the bare-bones wpdb results as an array in cases where efficiency is essential
194
+	 *
195
+	 * @param array $query_params
196
+	 * @return array of arrays
197
+	 * @throws EE_Error
198
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
199
+	 */
200
+	public function get_all_efficiently(array $query_params): array
201
+	{
202
+		return $this->_get_all_wpdb_results($query_params);
203
+	}
204 204
 
205 205
 
206
-    /**
207
-     * Executes a database query to delete gateway logs. Does not affect model objects, so if you attempt to use
208
-     * models after this, they may be out-of-sync with the database
209
-     *
210
-     * @param DateTime $datetime
211
-     * @return false|int
212
-     * @throws EE_Error
213
-     */
214
-    public function delete_gateway_logs_older_than(DateTime $datetime)
215
-    {
216
-        global $wpdb;
217
-        return $wpdb->query(
218
-            $wpdb->prepare(
219
-                'DELETE FROM ' . $this->table() . ' WHERE LOG_type = %s AND LOG_time < %s',
220
-                EEM_Change_Log::type_gateway,
221
-                $datetime->format(EE_Datetime_Field::mysql_timestamp_format)
222
-            )
223
-        );
224
-    }
206
+	/**
207
+	 * Executes a database query to delete gateway logs. Does not affect model objects, so if you attempt to use
208
+	 * models after this, they may be out-of-sync with the database
209
+	 *
210
+	 * @param DateTime $datetime
211
+	 * @return false|int
212
+	 * @throws EE_Error
213
+	 */
214
+	public function delete_gateway_logs_older_than(DateTime $datetime)
215
+	{
216
+		global $wpdb;
217
+		return $wpdb->query(
218
+			$wpdb->prepare(
219
+				'DELETE FROM ' . $this->table() . ' WHERE LOG_type = %s AND LOG_time < %s',
220
+				EEM_Change_Log::type_gateway,
221
+				$datetime->format(EE_Datetime_Field::mysql_timestamp_format)
222
+			)
223
+		);
224
+	}
225 225
 
226 226
 
227
-    /**
228
-     * Returns the map of type to pretty label for identifiers used for `LOG_type`.  Client code can register their own
229
-     * map vai the given filter.
230
-     *
231
-     * @return array
232
-     */
233
-    public static function get_pretty_label_map_for_registered_types(): array
234
-    {
235
-        return apply_filters(
236
-            'FHEE__EEM_Change_Log__get_pretty_label_map_for_registered_types',
237
-            [
238
-                self::type_create  => esc_html__("Create", "event_espresso"),
239
-                self::type_update  => esc_html__("Update", "event_espresso"),
240
-                self::type_delete  => esc_html__("Delete", "event_espresso"),
241
-                self::type_debug   => esc_html__("Debug", "event_espresso"),
242
-                self::type_error   => esc_html__("Error", "event_espresso"),
243
-                self::type_gateway => esc_html__("Gateway Interaction (IPN or Direct Payment)", 'event_espresso'),
244
-            ]
245
-        );
246
-    }
227
+	/**
228
+	 * Returns the map of type to pretty label for identifiers used for `LOG_type`.  Client code can register their own
229
+	 * map vai the given filter.
230
+	 *
231
+	 * @return array
232
+	 */
233
+	public static function get_pretty_label_map_for_registered_types(): array
234
+	{
235
+		return apply_filters(
236
+			'FHEE__EEM_Change_Log__get_pretty_label_map_for_registered_types',
237
+			[
238
+				self::type_create  => esc_html__("Create", "event_espresso"),
239
+				self::type_update  => esc_html__("Update", "event_espresso"),
240
+				self::type_delete  => esc_html__("Delete", "event_espresso"),
241
+				self::type_debug   => esc_html__("Debug", "event_espresso"),
242
+				self::type_error   => esc_html__("Error", "event_espresso"),
243
+				self::type_gateway => esc_html__("Gateway Interaction (IPN or Direct Payment)", 'event_espresso'),
244
+			]
245
+		);
246
+	}
247 247
 
248 248
 
249
-    /**
250
-     * Return the pretty (localized) label for the given log type identifier.
251
-     *
252
-     * @param string $type_identifier
253
-     * @return string
254
-     */
255
-    public static function get_pretty_label_for_type(string $type_identifier): string
256
-    {
257
-        $type_identifier_map = self::get_pretty_label_map_for_registered_types();
258
-        // we fall back to the incoming type identifier if there is no localized label for it.
259
-        return $type_identifier_map[ $type_identifier ] ?? $type_identifier;
260
-    }
249
+	/**
250
+	 * Return the pretty (localized) label for the given log type identifier.
251
+	 *
252
+	 * @param string $type_identifier
253
+	 * @return string
254
+	 */
255
+	public static function get_pretty_label_for_type(string $type_identifier): string
256
+	{
257
+		$type_identifier_map = self::get_pretty_label_map_for_registered_types();
258
+		// we fall back to the incoming type identifier if there is no localized label for it.
259
+		return $type_identifier_map[ $type_identifier ] ?? $type_identifier;
260
+	}
261 261
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -107,17 +107,17 @@  discard block
 block discarded – undo
107 107
                 ),
108 108
             ],
109 109
         ];
110
-        $this->_model_relations    = [];
110
+        $this->_model_relations = [];
111 111
         foreach ($models_this_can_attach_to as $model) {
112 112
             if ($model != 'Change_Log') {
113
-                $this->_model_relations[ $model ] = new EE_Belongs_To_Any_Relation();
113
+                $this->_model_relations[$model] = new EE_Belongs_To_Any_Relation();
114 114
             }
115 115
         }
116 116
         // use completely custom caps for this
117 117
         $this->_cap_restriction_generators = null;
118 118
         // caps-wise this is all-or-nothing: if you have the default role you can access anything, otherwise nothing
119 119
         foreach ($this->_cap_contexts_to_cap_action_map as $cap_context => $action) {
120
-            $this->_cap_restrictions[ $cap_context ][ EE_Restriction_Generator_Base::get_default_restrictions_cap() ]
120
+            $this->_cap_restrictions[$cap_context][EE_Restriction_Generator_Base::get_default_restrictions_cap()]
121 121
                 = new EE_Return_None_Where_Conditions();
122 122
         }
123 123
         parent::__construct($timezone);
@@ -165,7 +165,7 @@  discard block
 block discarded – undo
165 165
      */
166 166
     public function gateway_log($message, $related_obj_id, string $related_obj_type): EE_Change_Log
167 167
     {
168
-        if (! EE_Registry::instance()->is_model_name($related_obj_type)) {
168
+        if ( ! EE_Registry::instance()->is_model_name($related_obj_type)) {
169 169
             throw new EE_Error(
170 170
                 sprintf(
171 171
                     esc_html__(
@@ -216,7 +216,7 @@  discard block
 block discarded – undo
216 216
         global $wpdb;
217 217
         return $wpdb->query(
218 218
             $wpdb->prepare(
219
-                'DELETE FROM ' . $this->table() . ' WHERE LOG_type = %s AND LOG_time < %s',
219
+                'DELETE FROM '.$this->table().' WHERE LOG_type = %s AND LOG_time < %s',
220 220
                 EEM_Change_Log::type_gateway,
221 221
                 $datetime->format(EE_Datetime_Field::mysql_timestamp_format)
222 222
             )
@@ -256,6 +256,6 @@  discard block
 block discarded – undo
256 256
     {
257 257
         $type_identifier_map = self::get_pretty_label_map_for_registered_types();
258 258
         // we fall back to the incoming type identifier if there is no localized label for it.
259
-        return $type_identifier_map[ $type_identifier ] ?? $type_identifier;
259
+        return $type_identifier_map[$type_identifier] ?? $type_identifier;
260 260
     }
261 261
 }
Please login to merge, or discard this patch.
core/db_models/strategies/EE_Restriction_Generator_Base.strategy.php 2 patches
Indentation   +217 added lines, -217 removed lines patch added patch discarded remove patch
@@ -36,247 +36,247 @@
 block discarded – undo
36 36
  */
37 37
 abstract class EE_Restriction_Generator_Base
38 38
 {
39
-    /**
40
-     * One of EEM_Base::valid_cap_contexts()
41
-     *
42
-     * @var string
43
-     */
44
-    protected string $_action = '';
39
+	/**
40
+	 * One of EEM_Base::valid_cap_contexts()
41
+	 *
42
+	 * @var string
43
+	 */
44
+	protected string $_action = '';
45 45
 
46
-    /**
47
-     * The restrictions generated by this object, null before any are made (previously false).
48
-     *
49
-     * @var EE_Default_Where_Conditions[]|bool|null
50
-     */
51
-    protected $_cap_restrictions_generated = null;
46
+	/**
47
+	 * The restrictions generated by this object, null before any are made (previously false).
48
+	 *
49
+	 * @var EE_Default_Where_Conditions[]|bool|null
50
+	 */
51
+	protected $_cap_restrictions_generated = null;
52 52
 
53
-    /**
54
-     * Model for which restrictions are generated
55
-     *
56
-     * @var EEM_Base|null
57
-     */
58
-    protected ?EEM_Base $_model = null;
53
+	/**
54
+	 * Model for which restrictions are generated
55
+	 *
56
+	 * @var EEM_Base|null
57
+	 */
58
+	protected ?EEM_Base $_model = null;
59 59
 
60 60
 
61
-    private static ?array $caps_for_admin = null;
61
+	private static ?array $caps_for_admin = null;
62 62
 
63 63
 
64
-    /**
65
-     * Puts the last necessary info onto the restriction generator class. This
66
-     * is usually called by EEM_Base during its constructor, so child classes
67
-     * don't have to always provide this info.
68
-     *
69
-     * @param EEM_Base $model
70
-     * @param string   $action
71
-     */
72
-    public function _construct_finalize(EEM_Base $model, string $action)
73
-    {
74
-        $this->_model  = $model;
75
-        $this->_action = $action;
76
-    }
64
+	/**
65
+	 * Puts the last necessary info onto the restriction generator class. This
66
+	 * is usually called by EEM_Base during its constructor, so child classes
67
+	 * don't have to always provide this info.
68
+	 *
69
+	 * @param EEM_Base $model
70
+	 * @param string   $action
71
+	 */
72
+	public function _construct_finalize(EEM_Base $model, string $action)
73
+	{
74
+		$this->_model  = $model;
75
+		$this->_action = $action;
76
+	}
77 77
 
78 78
 
79
-    /**
80
-     * Returns the model set for this restriction generator
81
-     *
82
-     * @return EEM_Base
83
-     * @throws EE_Error
84
-     */
85
-    public function model(): EEM_Base
86
-    {
87
-        if (! $this->_model instanceof EEM_Base) {
88
-            throw new EE_Error(
89
-                sprintf(
90
-                    esc_html__(
91
-                        'Cannot generate capability restrictions because model has not yet been set on the %s. Please ensure _construct_finalize() was called',
92
-                        'event_espresso'
93
-                    ),
94
-                    get_class($this)
95
-                )
96
-            );
97
-        }
98
-        return $this->_model;
99
-    }
79
+	/**
80
+	 * Returns the model set for this restriction generator
81
+	 *
82
+	 * @return EEM_Base
83
+	 * @throws EE_Error
84
+	 */
85
+	public function model(): EEM_Base
86
+	{
87
+		if (! $this->_model instanceof EEM_Base) {
88
+			throw new EE_Error(
89
+				sprintf(
90
+					esc_html__(
91
+						'Cannot generate capability restrictions because model has not yet been set on the %s. Please ensure _construct_finalize() was called',
92
+						'event_espresso'
93
+					),
94
+					get_class($this)
95
+				)
96
+			);
97
+		}
98
+		return $this->_model;
99
+	}
100 100
 
101 101
 
102
-    /**
103
-     * Returns the action this restriction generator will generate restrictions
104
-     * for. It should be one of EEM_Base::valid_cap_contexts()
105
-     *
106
-     * @return string
107
-     * @throws EE_Error
108
-     */
109
-    public function action(): string
110
-    {
111
-        if (! $this->_action) {
112
-            throw new EE_Error(
113
-                sprintf(
114
-                    esc_html__(
115
-                        'Cannot generate capability restrictions because model has not yet been set on the %s. Please ensure _construct_finalize() was called',
116
-                        'event_espresso'
117
-                    ),
118
-                    get_class($this)
119
-                )
120
-            );
121
-        }
122
-        return $this->_action;
123
-    }
102
+	/**
103
+	 * Returns the action this restriction generator will generate restrictions
104
+	 * for. It should be one of EEM_Base::valid_cap_contexts()
105
+	 *
106
+	 * @return string
107
+	 * @throws EE_Error
108
+	 */
109
+	public function action(): string
110
+	{
111
+		if (! $this->_action) {
112
+			throw new EE_Error(
113
+				sprintf(
114
+					esc_html__(
115
+						'Cannot generate capability restrictions because model has not yet been set on the %s. Please ensure _construct_finalize() was called',
116
+						'event_espresso'
117
+					),
118
+					get_class($this)
119
+				)
120
+			);
121
+		}
122
+		return $this->_action;
123
+	}
124 124
 
125 125
 
126
-    /**
127
-     * Returns whether _construct_finalize() has been called on this
128
-     * restriction generator object
129
-     *
130
-     * @return boolean
131
-     */
132
-    public function construction_finalized(): bool
133
-    {
134
-        return $this->_model instanceof EEM_Base && $this->_action;
135
-    }
126
+	/**
127
+	 * Returns whether _construct_finalize() has been called on this
128
+	 * restriction generator object
129
+	 *
130
+	 * @return boolean
131
+	 */
132
+	public function construction_finalized(): bool
133
+	{
134
+		return $this->_model instanceof EEM_Base && $this->_action;
135
+	}
136 136
 
137 137
 
138
-    /**
139
-     * Gets the capability restrictions generated by this object. Caches them in
140
-     * case they are required for subsequent requests
141
-     *
142
-     * @return array @see EEM_Base::_cap_restrictions
143
-     */
144
-    public function generate_restrictions(): array
145
-    {
146
-        if ($this->_cap_restrictions_generated === null) {
147
-            $this->_cap_restrictions_generated = apply_filters(
148
-                'FHEE__EE_Restriction_Generator_Base__generate_restrictions__first_time',
149
-                $this->_generate_restrictions(),
150
-                $this
151
-            );
152
-        }
153
-        return apply_filters(
154
-            'FHEE__EE_Restriction_Generator_Base__generate_restrictions__every_time',
155
-            $this->_cap_restrictions_generated,
156
-            $this
157
-        );
158
-    }
138
+	/**
139
+	 * Gets the capability restrictions generated by this object. Caches them in
140
+	 * case they are required for subsequent requests
141
+	 *
142
+	 * @return array @see EEM_Base::_cap_restrictions
143
+	 */
144
+	public function generate_restrictions(): array
145
+	{
146
+		if ($this->_cap_restrictions_generated === null) {
147
+			$this->_cap_restrictions_generated = apply_filters(
148
+				'FHEE__EE_Restriction_Generator_Base__generate_restrictions__first_time',
149
+				$this->_generate_restrictions(),
150
+				$this
151
+			);
152
+		}
153
+		return apply_filters(
154
+			'FHEE__EE_Restriction_Generator_Base__generate_restrictions__every_time',
155
+			$this->_cap_restrictions_generated,
156
+			$this
157
+		);
158
+	}
159 159
 
160 160
 
161
-    /**
162
-     * Provided with the model, and using global knowledge about what
163
-     * capabilities exist, generates an array for use in one of the sub-arrays
164
-     * in EEM_Base::_cap_restrictions, where keys are capability names, and
165
-     * values are children of EE_Default_Where_Conditions
166
-     *
167
-     * @return EE_Default_Where_Conditions[]|EE_Return_None_Where_Conditions[]
168
-     * @see EEM_Base::_cap_restrictions
169
-     */
170
-    abstract protected function _generate_restrictions(): array;
161
+	/**
162
+	 * Provided with the model, and using global knowledge about what
163
+	 * capabilities exist, generates an array for use in one of the sub-arrays
164
+	 * in EEM_Base::_cap_restrictions, where keys are capability names, and
165
+	 * values are children of EE_Default_Where_Conditions
166
+	 *
167
+	 * @return EE_Default_Where_Conditions[]|EE_Return_None_Where_Conditions[]
168
+	 * @see EEM_Base::_cap_restrictions
169
+	 */
170
+	abstract protected function _generate_restrictions(): array;
171 171
 
172 172
 
173
-    /**
174
-     * Whether this restriction generator has already done its job of
175
-     * making restrictions and caching them on itself in case its asked later
176
-     *
177
-     * @return boolean
178
-     */
179
-    public function has_generated_cap_restrictions(): bool
180
-    {
181
-        return $this->_cap_restrictions_generated !== null;
182
-    }
173
+	/**
174
+	 * Whether this restriction generator has already done its job of
175
+	 * making restrictions and caching them on itself in case its asked later
176
+	 *
177
+	 * @return boolean
178
+	 */
179
+	public function has_generated_cap_restrictions(): bool
180
+	{
181
+		return $this->_cap_restrictions_generated !== null;
182
+	}
183 183
 
184 184
 
185
-    /**
186
-     * Given an action like 'edit' generates the cap name based off
187
-     * the EEM_Base::_cap_slug, which for events would be 'events', to generate
188
-     * the cap name like 'ee_edit_events'. If a $qualifier is passed,
189
-     *
190
-     * @param EEM_Base $model
191
-     * @param string   $action
192
-     * @return string
193
-     */
194
-    public static function get_cap_name(EEM_Base $model, string $action): string
195
-    {
196
-        $prefix   = $model->is_wp_core_model() ? '' : 'ee_';
197
-        $cap_slug = $model->cap_slug();
198
-        return apply_filters(
199
-            'FHEE__EE_Restriction_Generator__get_cap_name',
200
-            "$prefix{$action}_$cap_slug",
201
-            $model,
202
-            $action
203
-        );
204
-    }
185
+	/**
186
+	 * Given an action like 'edit' generates the cap name based off
187
+	 * the EEM_Base::_cap_slug, which for events would be 'events', to generate
188
+	 * the cap name like 'ee_edit_events'. If a $qualifier is passed,
189
+	 *
190
+	 * @param EEM_Base $model
191
+	 * @param string   $action
192
+	 * @return string
193
+	 */
194
+	public static function get_cap_name(EEM_Base $model, string $action): string
195
+	{
196
+		$prefix   = $model->is_wp_core_model() ? '' : 'ee_';
197
+		$cap_slug = $model->cap_slug();
198
+		return apply_filters(
199
+			'FHEE__EE_Restriction_Generator__get_cap_name',
200
+			"$prefix{$action}_$cap_slug",
201
+			$model,
202
+			$action
203
+		);
204
+	}
205 205
 
206 206
 
207
-    /**
208
-     * Checks if there is a cap for this model and this action
209
-     *
210
-     * @param EEM_Base $model
211
-     * @param string   $action
212
-     * @return boolean
213
-     * @throws EE_Error
214
-     */
215
-    public static function is_cap(EEM_Base $model, string $action): bool
216
-    {
217
-        if (self::$caps_for_admin === null) {
218
-            self::$caps_for_admin = EE_Registry::instance()->CAP->get_ee_capabilities();
219
-        }
220
-        return in_array(self::get_cap_name($model, $action), self::$caps_for_admin);
221
-    }
207
+	/**
208
+	 * Checks if there is a cap for this model and this action
209
+	 *
210
+	 * @param EEM_Base $model
211
+	 * @param string   $action
212
+	 * @return boolean
213
+	 * @throws EE_Error
214
+	 */
215
+	public static function is_cap(EEM_Base $model, string $action): bool
216
+	{
217
+		if (self::$caps_for_admin === null) {
218
+			self::$caps_for_admin = EE_Registry::instance()->CAP->get_ee_capabilities();
219
+		}
220
+		return in_array(self::get_cap_name($model, $action), self::$caps_for_admin);
221
+	}
222 222
 
223 223
 
224
-    /**
225
-     * Returns the default capability used to determine if the current user can
226
-     * access something.
227
-     *
228
-     * @return string
229
-     */
230
-    public static function get_default_restrictions_cap(): string
231
-    {
232
-        return apply_filters(
233
-            'FHEE__EE_Restriction_Generator_Base__default_restrictions_cap',
234
-            'manage_options'
235
-        );
236
-    }
224
+	/**
225
+	 * Returns the default capability used to determine if the current user can
226
+	 * access something.
227
+	 *
228
+	 * @return string
229
+	 */
230
+	public static function get_default_restrictions_cap(): string
231
+	{
232
+		return apply_filters(
233
+			'FHEE__EE_Restriction_Generator_Base__default_restrictions_cap',
234
+			'manage_options'
235
+		);
236
+	}
237 237
 
238 238
 
239
-    /**
240
-     * Gets WHERE conditions for the query that show the post model is
241
-     * published, or that it's sold out and it was previously published
242
-     *
243
-     * @param array   $where_conditions
244
-     * @param boolean $check_if_published  if true, will add conditions like
245
-     *                                     status=publish if false, will add
246
-     *                                     conditions like status!=private
247
-     * @param string  $path_to_event_model including the period at the end
248
-     * @return array
249
-     * @throws EE_Error
250
-     */
251
-    protected function addPublishedPostConditions(
252
-        array $where_conditions = [],
253
-        bool $check_if_published = true,
254
-        string $path_to_event_model = ''
255
-    ): array {
256
-        if ($check_if_published) {
257
-            $published_value = 'publish';
258
-        } else {
259
-            $published_value = ['!=', 'private'];
260
-        }
261
-        // only add a check for the previous event status
262
-        // if the model is the event or it's related to the event model
263
-        if (
264
-            $this->model() instanceof EEM_Event
265
-            || strpos($path_to_event_model, 'Event') !== false
266
-        ) {
267
-            $where_conditions['OR*status'] = [
268
-                $path_to_event_model . 'status' => $published_value,
269
-                'AND'                           => [
270
-                    $path_to_event_model .
271
-                    'Post_Meta.meta_key'   => '_previous_event_status',
272
-                    $path_to_event_model .
273
-                    'Post_Meta.meta_value' => $published_value,
274
-                ],
275
-            ];
276
-        } else {
277
-            $where_conditions[ $path_to_event_model . 'status' ] =
278
-                $published_value;
279
-        }
280
-        return $where_conditions;
281
-    }
239
+	/**
240
+	 * Gets WHERE conditions for the query that show the post model is
241
+	 * published, or that it's sold out and it was previously published
242
+	 *
243
+	 * @param array   $where_conditions
244
+	 * @param boolean $check_if_published  if true, will add conditions like
245
+	 *                                     status=publish if false, will add
246
+	 *                                     conditions like status!=private
247
+	 * @param string  $path_to_event_model including the period at the end
248
+	 * @return array
249
+	 * @throws EE_Error
250
+	 */
251
+	protected function addPublishedPostConditions(
252
+		array $where_conditions = [],
253
+		bool $check_if_published = true,
254
+		string $path_to_event_model = ''
255
+	): array {
256
+		if ($check_if_published) {
257
+			$published_value = 'publish';
258
+		} else {
259
+			$published_value = ['!=', 'private'];
260
+		}
261
+		// only add a check for the previous event status
262
+		// if the model is the event or it's related to the event model
263
+		if (
264
+			$this->model() instanceof EEM_Event
265
+			|| strpos($path_to_event_model, 'Event') !== false
266
+		) {
267
+			$where_conditions['OR*status'] = [
268
+				$path_to_event_model . 'status' => $published_value,
269
+				'AND'                           => [
270
+					$path_to_event_model .
271
+					'Post_Meta.meta_key'   => '_previous_event_status',
272
+					$path_to_event_model .
273
+					'Post_Meta.meta_value' => $published_value,
274
+				],
275
+			];
276
+		} else {
277
+			$where_conditions[ $path_to_event_model . 'status' ] =
278
+				$published_value;
279
+		}
280
+		return $where_conditions;
281
+	}
282 282
 }
Please login to merge, or discard this patch.
Spacing   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -58,7 +58,7 @@  discard block
 block discarded – undo
58 58
     protected ?EEM_Base $_model = null;
59 59
 
60 60
 
61
-    private static ?array $caps_for_admin = null;
61
+    private static ? array $caps_for_admin = null;
62 62
 
63 63
 
64 64
     /**
@@ -84,7 +84,7 @@  discard block
 block discarded – undo
84 84
      */
85 85
     public function model(): EEM_Base
86 86
     {
87
-        if (! $this->_model instanceof EEM_Base) {
87
+        if ( ! $this->_model instanceof EEM_Base) {
88 88
             throw new EE_Error(
89 89
                 sprintf(
90 90
                     esc_html__(
@@ -108,7 +108,7 @@  discard block
 block discarded – undo
108 108
      */
109 109
     public function action(): string
110 110
     {
111
-        if (! $this->_action) {
111
+        if ( ! $this->_action) {
112 112
             throw new EE_Error(
113 113
                 sprintf(
114 114
                     esc_html__(
@@ -265,16 +265,16 @@  discard block
 block discarded – undo
265 265
             || strpos($path_to_event_model, 'Event') !== false
266 266
         ) {
267 267
             $where_conditions['OR*status'] = [
268
-                $path_to_event_model . 'status' => $published_value,
268
+                $path_to_event_model.'status' => $published_value,
269 269
                 'AND'                           => [
270
-                    $path_to_event_model .
270
+                    $path_to_event_model.
271 271
                     'Post_Meta.meta_key'   => '_previous_event_status',
272
-                    $path_to_event_model .
272
+                    $path_to_event_model.
273 273
                     'Post_Meta.meta_value' => $published_value,
274 274
                 ],
275 275
             ];
276 276
         } else {
277
-            $where_conditions[ $path_to_event_model . 'status' ] =
277
+            $where_conditions[$path_to_event_model.'status'] =
278 278
                 $published_value;
279 279
         }
280 280
         return $where_conditions;
Please login to merge, or discard this patch.
core/libraries/messages/EE_messenger.lib.php 2 patches
Indentation   +965 added lines, -965 removed lines patch added patch discarded remove patch
@@ -15,585 +15,585 @@  discard block
 block discarded – undo
15 15
  */
16 16
 abstract class EE_messenger extends EE_Messages_Base
17 17
 {
18
-    /**
19
-     * @var EEM_Message_Template_Group|EEM_Base
20
-     * @since 5.0.46
21
-     */
22
-    protected EEM_Message_Template_Group $_EEM_data;
23
-
24
-    private string $class_name;
25
-
26
-    /**
27
-     * This property holds the default message types associated with this messenger when it is activated. The values of
28
-     * the array must match a valid message type. This property gets set by the _set_default_message_types() method.
29
-     *
30
-     * @var array
31
-     */
32
-    protected $_default_message_types = [];
33
-
34
-
35
-    /**
36
-     * This property holds the message types that are valid for use with this messenger.
37
-     * It gets set by the _set_valid_message_types() method.
38
-     *
39
-     * @var array
40
-     */
41
-    protected $_valid_message_types = [];
42
-
43
-
44
-    /**
45
-     * Holds the configuration for the EE_Messages_Validator class to know how to validated the different fields. Note
46
-     * that the Validator will match each field here with the allowed shortcodes set in the "valid_shortcodes" array
47
-     * for the matched message type context.  So message types don't need to set a $_validator_config property.
48
-     *
49
-     * Remember, ALL fields must be declared in this array.  However, an empty value for the field means that the field
50
-     * will accept all valid shortcodes set for the given context in the message type (by default).
51
-     *
52
-     * Array should be in this format:
53
-     *
54
-     * array(
55
-     *  'field_name(i.e.to)' => array(
56
-     *      'shortcodes' => array('email'), //an array of shortcode groups (correspond to EE_Shortcodes library class)
57
-     *      that are allowed in the field. Typically you can just include $this->_valid_shortcodes['field_name'] as the
58
-     *      value here (because they will match).
59
-     *      'specific_shortcodes' => array( array('[EVENT_AUTHOR_EMAIL]' => esc_html__('Admin Email',
60
-     *      'event_espresso')), //if this index is present you can further restrict the field to ONLY specific
61
-     *      shortcodes if an entire group isn't sufficient. Specific shortcodes need to be listed as an array with the
62
-     *      index the shortcode and the value = the label.
63
-     *      'type' => 'email' //this is the field type and should match one of the validator types (see
64
-     *      EE_Messages_Validator::validator() for all the possible types).  If not required you can just leave empty.,
65
-     *      'required' => array'[SHORTCODE]') //this is used to indicate the shortcodes that MUST be in the assembled
66
-     *      array of shortcodes by the validator in order for this field to be included in validation.  Otherwise the
67
-     *      validator will always assign shortcodes for this field (regardless of whether the field settings for the
68
-     *      given messenger/message_type/context use the field or not.).. please note, this does NOT mean that the
69
-     *      shortcodes listed here MUST be in the given field.
70
-     *  )
71
-     * )
72
-     *
73
-     * @var array
74
-     */
75
-    protected $_validator_config = [];
76
-
77
-
78
-    /**
79
-     * this property just holds an array of the various template refs.
80
-     *
81
-     * @var array
82
-     */
83
-    protected $_template_fields = [];
84
-
85
-
86
-    /**
87
-     * This holds an array of the arguments used in parsing a template for the sender.
88
-     *
89
-     * @var array
90
-     */
91
-    protected $_template_args = [];
92
-
93
-
94
-    /**
95
-     * This property will hold the configuration for any test settings fields that are required for the "test" button
96
-     * that is used to trigger an actual test of this messenger
97
-     *
98
-     * @protected
99
-     * @var array
100
-     */
101
-    protected $_test_settings_fields = [];
102
-
103
-
104
-    /**
105
-     * This will hold the EE_Messages_Template_Pack object when set on the messenger.  This is set via the validate and
106
-     * setup method which grabs the template pack from the incoming messages object.
107
-     *
108
-     * @since 4.5.0
109
-     *
110
-     * @var EE_Messages_Template_Pack
111
-     */
112
-    protected $_tmp_pack;
113
-
114
-
115
-    /**
116
-     * This will hold the variation to use when performing a send.  It is set via the validate and setup method which
117
-     * grabs the variation from the incoming messages object on the send method.
118
-     *
119
-     * @since 4.5.0
120
-     *
121
-     * @var string
122
-     */
123
-    protected $_variation;
124
-
125
-
126
-    /**
127
-     * This property is a stdClass that holds labels for all the various supporting properties for this messenger.
128
-     * These labels are set via the _set_supports_labels() method in children classes. Initially this will include the
129
-     * label for:
130
-     *
131
-     *  - template pack
132
-     *  - template variation
133
-     *
134
-     * @since 4.5.0
135
-     *
136
-     * @var stdClass
137
-     */
138
-    protected $_supports_labels;
139
-
140
-
141
-    /**
142
-     * This property is set when the send_message() method is called and holds the Message Type used to generate
143
-     * templates with this messenger for the messages.
144
-     *
145
-     * @var EE_message_type
146
-     */
147
-    protected $_incoming_message_type;
148
-
149
-
150
-    /**
151
-     * This flag sets whether a messenger is activated by default  on installation (or reactivation) of EE core or not.
152
-     *
153
-     * @var bool
154
-     */
155
-    public $activate_on_install = false;
156
-
157
-
158
-    /**
159
-     * @throws EE_Error
160
-     * @throws ReflectionException
161
-     */
162
-    public function __construct()
163
-    {
164
-        $this->class_name          = get_class($this);
165
-        $this->_EEM_data           = EEM_Message_Template_Group::instance();
166
-        $this->_messages_item_type = 'messenger';
167
-
168
-        parent::__construct();
169
-
170
-        $this->_set_test_settings_fields();
171
-        $this->_set_template_fields();
172
-        $this->_set_default_message_types();
173
-        $this->_set_valid_message_types();
174
-        $this->_set_validator_config();
175
-
176
-
177
-        $this->_supports_labels = new stdClass();
178
-        $this->_set_supports_labels();
179
-    }
180
-
181
-
182
-    /**
183
-     * _set_template_fields
184
-     * This sets up the fields that a messenger requires for the message to go out.
185
-     *
186
-     * @abstract
187
-     * @access  protected
188
-     * @return void
189
-     */
190
-    abstract protected function _set_template_fields();
191
-
192
-
193
-    /**
194
-     * This method sets the _default_message_type property (see definition in docs attached to property)
195
-     *
196
-     * @abstract
197
-     * @access protected
198
-     * @return void
199
-     */
200
-    abstract protected function _set_default_message_types();
201
-
202
-
203
-    /**
204
-     * Sets the _valid_message_types property (see definition in cods attached to property)
205
-     *
206
-     * @return void
207
-     * @since 4.5.0
208
-     *
209
-     * @abstract
210
-     */
211
-    abstract protected function _set_valid_message_types();
212
-
213
-
214
-    /**
215
-     * Child classes must declare the $_validator_config property using this method.
216
-     * See comments for $_validator_config for details on what it is used for.
217
-     *
218
-     * NOTE:  messengers should set an array of valid shortcodes for ALL scenarios.  The corresponding validator class
219
-     * (validators/{messenger}) can be used to restrict only certain shortcodes per template so users cannot add
220
-     * certain shortcodes.
221
-     *
222
-     * @access protected
223
-     * @return void
224
-     */
225
-    abstract protected function _set_validator_config();
226
-
227
-
228
-    /**
229
-     * We just deliver the messages don't kill us!!  This method will need to be modified by child classes for whatever
230
-     * action is taken to actually send a message.
231
-     *
232
-     * @return bool|WP_Error
233
-     * @throw \Exception
234
-     */
235
-    abstract protected function _send_message();
236
-
237
-
238
-    /**
239
-     * We give you pretty previews of the messages!
240
-     *
241
-     * @return string HTML body for message content.
242
-     */
243
-    abstract protected function _preview();
244
-
245
-
246
-    /**
247
-     * Used by messengers (or preview) for enqueueing any scripts or styles need in message generation.
248
-     *
249
-     * @return void
250
-     * @since 4.5.0
251
-     *
252
-     */
253
-    public function enqueue_scripts_styles()
254
-    {
255
-        do_action('AHEE__EE_messenger__enqueue_scripts_styles');
256
-    }
257
-
258
-
259
-    /**
260
-     * This is used to indicate whether a messenger must be sent immediately or not.
261
-     * eg. The HTML messenger will override this to return true because it should be displayed in user's browser right
262
-     * away.  The PDF messenger is similar.
263
-     *
264
-     * This flag thus overrides any priorities that may be set on the message type used to generate the message.
265
-     *
266
-     * Default for this is false.  So children classes must override this if they want a message to be executed
267
-     * immediately.
268
-     *
269
-     * @return bool
270
-     * @since  4.9.0
271
-     */
272
-    public function send_now()
273
-    {
274
-        return false;
275
-    }
276
-
277
-
278
-    /**
279
-     * This is a way for a messenger to indicate whether it allows an empty to field or not.
280
-     * Note: If the generated message is a for a preview, this value is ignored.
281
-     *
282
-     * @return bool
283
-     * @since 4.9.0
284
-     */
285
-    public function allow_empty_to_field()
286
-    {
287
-        return false;
288
-    }
289
-
290
-
291
-    /**
292
-     * Sets the defaults for the _supports_labels property.  Can be overridden by child classes.
293
-     *
294
-     * @return void
295
-     * @since 4.5.0;
296
-     * @see   property definition for info on how its formatted.
297
-     *
298
-     */
299
-    protected function _set_supports_labels()
300
-    {
301
-        $this->_set_supports_labels_defaults();
302
-    }
303
-
304
-
305
-    /**
306
-     * Sets the defaults for the _supports_labels property.
307
-     *
308
-     * @return void
309
-     * @since 4.5.0
310
-     *
311
-     */
312
-    private function _set_supports_labels_defaults()
313
-    {
314
-        $this->_supports_labels->template_pack             = esc_html__('Template Structure', 'event_espresso');
315
-        $this->_supports_labels->template_variation        = esc_html__('Template Style', 'event_espresso');
316
-        $this->_supports_labels->template_pack_description = esc_html__(
317
-            'Template Structure options are bundled structural changes for templates.',
318
-            'event_espresso'
319
-        );
320
-
321
-        $this->_supports_labels->template_variation_description = esc_html__(
322
-            'These are different styles to choose from for the selected template structure.  Usually these affect things like font style, color, borders etc.  In some cases the styles will also make minor layout changes.',
323
-            'event_espresso'
324
-        );
325
-
326
-        $this->_supports_labels = apply_filters(
327
-            'FHEE__EE_messenger___set_supports_labels_defaults___supports_labels',
328
-            $this->_supports_labels,
329
-            $this
330
-        );
331
-    }
332
-
333
-
334
-    /**
335
-     * This returns the _supports_labels property.
336
-     *
337
-     * @return stdClass
338
-     * @since 4.5.0
339
-     *
340
-     */
341
-    public function get_supports_labels()
342
-    {
343
-        if (empty($this->_supports_labels->template_pack) || empty($this->_supports_labels->template_variation)) {
344
-            $this->_set_supports_labels_defaults();
345
-        }
346
-        return apply_filters('FHEE__EE_messenger__get_supports_labels', $this->_supports_labels, $this);
347
-    }
348
-
349
-
350
-    /**
351
-     * Used to retrieve a variation (typically the path/url to a css file)
352
-     *
353
-     * @param EE_Messages_Template_Pack $pack              The template pack used for retrieving the variation.
354
-     * @param string                    $message_type_name The name property of the message type that we need the
355
-     *                                                     variation for.
356
-     * @param bool                      $url               Whether to return url (true) or path (false). Default is
357
-     *                                                     false.
358
-     * @param string                    $type              What variation type to return. Default is 'main'.
359
-     * @param string                    $variation         What variation for the template pack
360
-     * @param bool                      $skip_filters      This allows messengers to add a filter for another
361
-     *                                                     messengers get_variation but call skip filters on the
362
-     *                                                     callback so there is no recursion on apply_filters.
363
-     *
364
-     * @return string                    path or url for the requested variation.
365
-     * @since 4.5.0
366
-     *
367
-     */
368
-    public function get_variation(
369
-        EE_Messages_Template_Pack $pack,
370
-        $message_type_name,
371
-        $url = false,
372
-        $type = 'main',
373
-        $variation = 'default',
374
-        $skip_filters = false
375
-    ) {
376
-        $this->_tmp_pack = $pack;
377
-        $variation_path  = apply_filters(
378
-            'EE_messenger__get_variation__variation',
379
-            false,
380
-            $pack,
381
-            $this->name,
382
-            $message_type_name,
383
-            $url,
384
-            $type,
385
-            $variation,
386
-            $skip_filters
387
-        );
388
-        return ! empty($variation_path)
389
-            ? $variation_path
390
-            : $this->_tmp_pack->get_variation(
391
-                $this->name,
392
-                $message_type_name,
393
-                $type,
394
-                $variation,
395
-                $url,
396
-                '.css',
397
-                $skip_filters
398
-            );
399
-    }
400
-
401
-
402
-    /**
403
-     * This just returns the default message types associated with this messenger when it is first activated.
404
-     *
405
-     * @access public
406
-     * @return array
407
-     */
408
-    public function get_default_message_types()
409
-    {
410
-        // messenger specific filter
411
-        $default_types = apply_filters(
412
-            "FHEE__{$this->class_name}__get_default_message_types__default_types",
413
-            $this->_default_message_types,
414
-            $this
415
-        );
416
-
417
-        // all messengers filter
418
-        return apply_filters('FHEE__EE_messenger__get_default_message_types__default_types', $default_types, $this);
419
-    }
420
-
421
-
422
-    /**
423
-     * Returns the valid message types associated with this messenger.
424
-     *
425
-     * @return array
426
-     * @since 4.5.0
427
-     *
428
-     */
429
-    public function get_valid_message_types()
430
-    {
431
-        // messenger specific filter
432
-        $valid_types = apply_filters(
433
-            "FHEE__{$this->class_name}__get_valid_message_types__valid_types",
434
-            $this->_valid_message_types,
435
-            $this
436
-        );
437
-
438
-        // all messengers filter
439
-        return apply_filters('FHEE__EE_messenger__get_valid_message_types__valid_types', $valid_types, $this);
440
-    }
441
-
442
-
443
-    /**
444
-     * this is just used by the custom validators (EE_Messages_Validator classes) to modify the _validator_config for
445
-     * certain message_type/messenger combos where a context may only use certain shortcodes etc.
446
-     *
447
-     * @access public
448
-     * @param array $new_config Whatever is put in here will reset the _validator_config property
449
-     */
450
-    public function set_validator_config($new_config)
451
-    {
452
-        $this->_validator_config = $new_config;
453
-    }
454
-
455
-
456
-    /**
457
-     * This returns the _validator_config property
458
-     *
459
-     * @access public
460
-     * @return array
461
-     */
462
-    public function get_validator_config()
463
-    {
464
-        $config = apply_filters("FHEE__{$this->class_name}__get_validator_config", $this->_validator_config, $this);
465
-        return apply_filters('FHEE__EE_messenger__get_validator_config', $config, $this);
466
-    }
467
-
468
-
469
-    /**
470
-     * this public method accepts a page slug (for an EE_admin page) and will return the response from the child class
471
-     * callback function if that page is registered via the `_admin_registered_page` property set by the child class.
472
-     *
473
-     * @param string $page          the slug of the EE admin page
474
-     * @param array  $message_types an array of active message type objects
475
-     * @param string $action        the page action (to allow for more specific handling - i.e. edit vs. add pages)
476
-     * @param array  $extra         This is just an extra argument that can be used to pass additional data for setting
477
-     *                              up page content.
478
-     * @access public
479
-     * @return string content for page
480
-     */
481
-    public function get_messenger_admin_page_content($page, $action = null, $extra = [], $message_types = [])
482
-    {
483
-        return $this->_get_admin_page_content($page, $action, $extra, $message_types);
484
-    }
485
-
486
-
487
-    /**
488
-     * @param       $message_types
489
-     * @param array $extra
490
-     * @return string
491
-     * @throws EE_Error
492
-     * @throws ReflectionException
493
-     */
494
-    protected function _get_admin_content_events_edit($message_types, $extra)
495
-    {
496
-        // defaults
497
-        $template_args = [];
498
-        $selector_rows = '';
499
-
500
-        // we don't need message types here so we're just going to ignore. we do, however, expect the event id here. The event id is needed to provide a link to setup a custom template for this event.
501
-        $event_id = $extra['event'] ?? null;
502
-
503
-        $template_wrapper_path = EE_LIBRARIES . 'messages/messenger/admin_templates/event_switcher_wrapper.template.php';
504
-        $template_row_path     = EE_LIBRARIES . 'messages/messenger/admin_templates/event_switcher_row.template.php';
505
-
506
-        // array of template objects for global and custom (non-trashed) (but remember just for this messenger!)
507
-        $global_templates    = $this->_EEM_data->get_all(
508
-            [['MTP_messenger' => $this->name, 'MTP_is_global' => true, 'MTP_is_active' => true]]
509
-        );
510
-        $templates_for_event = $this->_EEM_data->get_all_custom_templates_by_event(
511
-            $event_id,
512
-            [
513
-                'MTP_messenger' => $this->name,
514
-                'MTP_is_active' => true,
515
-            ]
516
-        );
517
-        $templates_for_event = ! empty($templates_for_event) ? $templates_for_event : [];
518
-
519
-        // so we need to set up the rows for the selectors and we use the global mtpgs (cause those will the active message template groups)
520
-        foreach ($global_templates as $mtpgID => $mtpg) {
521
-            if ($mtpg instanceof EE_Message_Template_Group) {
522
-                // verify this message type is supposed to show on this page
523
-                $mtp_obj = $mtpg->message_type_obj();
524
-                if (! $mtp_obj instanceof EE_message_type) {
525
-                    continue;
526
-                }
527
-                $mtp_obj->admin_registered_pages = (array) $mtp_obj->admin_registered_pages;
528
-                if (! in_array('events_edit', $mtp_obj->admin_registered_pages)) {
529
-                    continue;
530
-                }
531
-                $select_values            = [];
532
-                $select_values[ $mtpgID ] = esc_html__('Global', 'event_espresso');
533
-                $default_value            = array_key_exists($mtpgID, $templates_for_event)
534
-                                            && ! $mtpg->get('MTP_is_override')
535
-                    ? $mtpgID
536
-                    : null;
537
-                // if the override has been set for the global template, then that means even if there are custom templates already created we ignore them because of the set override.
538
-                if (! $mtpg->get('MTP_is_override')) {
539
-                    // any custom templates for this message type?
540
-                    $custom_templates = $this->_EEM_data->get_custom_message_template_by_m_and_mt(
541
-                        $this->name,
542
-                        $mtpg->message_type()
543
-                    );
544
-                    foreach ($custom_templates as $cmtpgID => $cmtpg) {
545
-                        $select_values[ $cmtpgID ] = $cmtpg->name();
546
-                        $default_value             = array_key_exists($cmtpgID, $templates_for_event)
547
-                            ? $cmtpgID
548
-                            : $default_value;
549
-                    }
550
-                }
551
-                // if there is no $default_value then we set it as the global
552
-                $default_value             = empty($default_value) ? $mtpgID : $default_value;
553
-                $c_config                  = $mtpg->contexts_config();
554
-                $edit_context              = key(array_slice($c_config, -1));
555
-                $edit_url_query_args       = [
556
-                    'page'    => 'espresso_messages',
557
-                    'action'  => 'edit_message_template',
558
-                    'id'      => $default_value,
559
-                    'evt_id'  => $event_id,
560
-                    'context' => $edit_context,
561
-                ];
562
-                $edit_url                  = EEH_URL::add_query_args_and_nonce(
563
-                    $edit_url_query_args,
564
-                    admin_url('admin.php')
565
-                );
566
-                $create_url_query_args     = [
567
-                    'page'         => 'espresso_messages',
568
-                    'action'       => 'add_new_message_template',
569
-                    'GRP_ID'       => $default_value,
570
-                    'message_type' => $mtpg->message_type(),
571
-                    'messenger'    => $this->name,
572
-                ];
573
-                $mt_slug                   = $mtpg->message_type();
574
-                $create_url                = EEH_URL::add_query_args_and_nonce(
575
-                    $create_url_query_args,
576
-                    admin_url('admin.php')
577
-                );
578
-                $st_args['mtpgID']         = $mtpgID;
579
-                $st_args['mt_name']        = ucwords($mtp_obj->label['singular']);
580
-                $st_args['mt_slug']        = $mt_slug;
581
-                $st_args['messenger_slug'] = $this->name;
582
-                $st_args['selector']       = EEH_Form_Fields::select_input(
583
-                    'event_message_templates_relation[' . $mtpgID . ']',
584
-                    $select_values,
585
-                    $default_value,
586
-                    'data-messenger="' . $this->name . '" data-messagetype="' . $mt_slug . '"',
587
-                    'message-template-selector ee-input-size--small'
588
-                );
589
-                $new_template_name         = sprintf(
590
-                    esc_html__('Custom %1$s', 'event_espresso'),
591
-                    $st_args['mt_name'] ?: esc_html__('Template', 'event_espresso')
592
-                );
593
-                // note that  message template group that has override_all_custom set will remove the ability to set a custom message template based off of the global (and that also in turn overrides any other custom templates).
594
-                $st_args['create_button'] = $mtpg->get('MTP_is_override')
595
-                    ? ''
596
-                    : '
18
+	/**
19
+	 * @var EEM_Message_Template_Group|EEM_Base
20
+	 * @since 5.0.46
21
+	 */
22
+	protected EEM_Message_Template_Group $_EEM_data;
23
+
24
+	private string $class_name;
25
+
26
+	/**
27
+	 * This property holds the default message types associated with this messenger when it is activated. The values of
28
+	 * the array must match a valid message type. This property gets set by the _set_default_message_types() method.
29
+	 *
30
+	 * @var array
31
+	 */
32
+	protected $_default_message_types = [];
33
+
34
+
35
+	/**
36
+	 * This property holds the message types that are valid for use with this messenger.
37
+	 * It gets set by the _set_valid_message_types() method.
38
+	 *
39
+	 * @var array
40
+	 */
41
+	protected $_valid_message_types = [];
42
+
43
+
44
+	/**
45
+	 * Holds the configuration for the EE_Messages_Validator class to know how to validated the different fields. Note
46
+	 * that the Validator will match each field here with the allowed shortcodes set in the "valid_shortcodes" array
47
+	 * for the matched message type context.  So message types don't need to set a $_validator_config property.
48
+	 *
49
+	 * Remember, ALL fields must be declared in this array.  However, an empty value for the field means that the field
50
+	 * will accept all valid shortcodes set for the given context in the message type (by default).
51
+	 *
52
+	 * Array should be in this format:
53
+	 *
54
+	 * array(
55
+	 *  'field_name(i.e.to)' => array(
56
+	 *      'shortcodes' => array('email'), //an array of shortcode groups (correspond to EE_Shortcodes library class)
57
+	 *      that are allowed in the field. Typically you can just include $this->_valid_shortcodes['field_name'] as the
58
+	 *      value here (because they will match).
59
+	 *      'specific_shortcodes' => array( array('[EVENT_AUTHOR_EMAIL]' => esc_html__('Admin Email',
60
+	 *      'event_espresso')), //if this index is present you can further restrict the field to ONLY specific
61
+	 *      shortcodes if an entire group isn't sufficient. Specific shortcodes need to be listed as an array with the
62
+	 *      index the shortcode and the value = the label.
63
+	 *      'type' => 'email' //this is the field type and should match one of the validator types (see
64
+	 *      EE_Messages_Validator::validator() for all the possible types).  If not required you can just leave empty.,
65
+	 *      'required' => array'[SHORTCODE]') //this is used to indicate the shortcodes that MUST be in the assembled
66
+	 *      array of shortcodes by the validator in order for this field to be included in validation.  Otherwise the
67
+	 *      validator will always assign shortcodes for this field (regardless of whether the field settings for the
68
+	 *      given messenger/message_type/context use the field or not.).. please note, this does NOT mean that the
69
+	 *      shortcodes listed here MUST be in the given field.
70
+	 *  )
71
+	 * )
72
+	 *
73
+	 * @var array
74
+	 */
75
+	protected $_validator_config = [];
76
+
77
+
78
+	/**
79
+	 * this property just holds an array of the various template refs.
80
+	 *
81
+	 * @var array
82
+	 */
83
+	protected $_template_fields = [];
84
+
85
+
86
+	/**
87
+	 * This holds an array of the arguments used in parsing a template for the sender.
88
+	 *
89
+	 * @var array
90
+	 */
91
+	protected $_template_args = [];
92
+
93
+
94
+	/**
95
+	 * This property will hold the configuration for any test settings fields that are required for the "test" button
96
+	 * that is used to trigger an actual test of this messenger
97
+	 *
98
+	 * @protected
99
+	 * @var array
100
+	 */
101
+	protected $_test_settings_fields = [];
102
+
103
+
104
+	/**
105
+	 * This will hold the EE_Messages_Template_Pack object when set on the messenger.  This is set via the validate and
106
+	 * setup method which grabs the template pack from the incoming messages object.
107
+	 *
108
+	 * @since 4.5.0
109
+	 *
110
+	 * @var EE_Messages_Template_Pack
111
+	 */
112
+	protected $_tmp_pack;
113
+
114
+
115
+	/**
116
+	 * This will hold the variation to use when performing a send.  It is set via the validate and setup method which
117
+	 * grabs the variation from the incoming messages object on the send method.
118
+	 *
119
+	 * @since 4.5.0
120
+	 *
121
+	 * @var string
122
+	 */
123
+	protected $_variation;
124
+
125
+
126
+	/**
127
+	 * This property is a stdClass that holds labels for all the various supporting properties for this messenger.
128
+	 * These labels are set via the _set_supports_labels() method in children classes. Initially this will include the
129
+	 * label for:
130
+	 *
131
+	 *  - template pack
132
+	 *  - template variation
133
+	 *
134
+	 * @since 4.5.0
135
+	 *
136
+	 * @var stdClass
137
+	 */
138
+	protected $_supports_labels;
139
+
140
+
141
+	/**
142
+	 * This property is set when the send_message() method is called and holds the Message Type used to generate
143
+	 * templates with this messenger for the messages.
144
+	 *
145
+	 * @var EE_message_type
146
+	 */
147
+	protected $_incoming_message_type;
148
+
149
+
150
+	/**
151
+	 * This flag sets whether a messenger is activated by default  on installation (or reactivation) of EE core or not.
152
+	 *
153
+	 * @var bool
154
+	 */
155
+	public $activate_on_install = false;
156
+
157
+
158
+	/**
159
+	 * @throws EE_Error
160
+	 * @throws ReflectionException
161
+	 */
162
+	public function __construct()
163
+	{
164
+		$this->class_name          = get_class($this);
165
+		$this->_EEM_data           = EEM_Message_Template_Group::instance();
166
+		$this->_messages_item_type = 'messenger';
167
+
168
+		parent::__construct();
169
+
170
+		$this->_set_test_settings_fields();
171
+		$this->_set_template_fields();
172
+		$this->_set_default_message_types();
173
+		$this->_set_valid_message_types();
174
+		$this->_set_validator_config();
175
+
176
+
177
+		$this->_supports_labels = new stdClass();
178
+		$this->_set_supports_labels();
179
+	}
180
+
181
+
182
+	/**
183
+	 * _set_template_fields
184
+	 * This sets up the fields that a messenger requires for the message to go out.
185
+	 *
186
+	 * @abstract
187
+	 * @access  protected
188
+	 * @return void
189
+	 */
190
+	abstract protected function _set_template_fields();
191
+
192
+
193
+	/**
194
+	 * This method sets the _default_message_type property (see definition in docs attached to property)
195
+	 *
196
+	 * @abstract
197
+	 * @access protected
198
+	 * @return void
199
+	 */
200
+	abstract protected function _set_default_message_types();
201
+
202
+
203
+	/**
204
+	 * Sets the _valid_message_types property (see definition in cods attached to property)
205
+	 *
206
+	 * @return void
207
+	 * @since 4.5.0
208
+	 *
209
+	 * @abstract
210
+	 */
211
+	abstract protected function _set_valid_message_types();
212
+
213
+
214
+	/**
215
+	 * Child classes must declare the $_validator_config property using this method.
216
+	 * See comments for $_validator_config for details on what it is used for.
217
+	 *
218
+	 * NOTE:  messengers should set an array of valid shortcodes for ALL scenarios.  The corresponding validator class
219
+	 * (validators/{messenger}) can be used to restrict only certain shortcodes per template so users cannot add
220
+	 * certain shortcodes.
221
+	 *
222
+	 * @access protected
223
+	 * @return void
224
+	 */
225
+	abstract protected function _set_validator_config();
226
+
227
+
228
+	/**
229
+	 * We just deliver the messages don't kill us!!  This method will need to be modified by child classes for whatever
230
+	 * action is taken to actually send a message.
231
+	 *
232
+	 * @return bool|WP_Error
233
+	 * @throw \Exception
234
+	 */
235
+	abstract protected function _send_message();
236
+
237
+
238
+	/**
239
+	 * We give you pretty previews of the messages!
240
+	 *
241
+	 * @return string HTML body for message content.
242
+	 */
243
+	abstract protected function _preview();
244
+
245
+
246
+	/**
247
+	 * Used by messengers (or preview) for enqueueing any scripts or styles need in message generation.
248
+	 *
249
+	 * @return void
250
+	 * @since 4.5.0
251
+	 *
252
+	 */
253
+	public function enqueue_scripts_styles()
254
+	{
255
+		do_action('AHEE__EE_messenger__enqueue_scripts_styles');
256
+	}
257
+
258
+
259
+	/**
260
+	 * This is used to indicate whether a messenger must be sent immediately or not.
261
+	 * eg. The HTML messenger will override this to return true because it should be displayed in user's browser right
262
+	 * away.  The PDF messenger is similar.
263
+	 *
264
+	 * This flag thus overrides any priorities that may be set on the message type used to generate the message.
265
+	 *
266
+	 * Default for this is false.  So children classes must override this if they want a message to be executed
267
+	 * immediately.
268
+	 *
269
+	 * @return bool
270
+	 * @since  4.9.0
271
+	 */
272
+	public function send_now()
273
+	{
274
+		return false;
275
+	}
276
+
277
+
278
+	/**
279
+	 * This is a way for a messenger to indicate whether it allows an empty to field or not.
280
+	 * Note: If the generated message is a for a preview, this value is ignored.
281
+	 *
282
+	 * @return bool
283
+	 * @since 4.9.0
284
+	 */
285
+	public function allow_empty_to_field()
286
+	{
287
+		return false;
288
+	}
289
+
290
+
291
+	/**
292
+	 * Sets the defaults for the _supports_labels property.  Can be overridden by child classes.
293
+	 *
294
+	 * @return void
295
+	 * @since 4.5.0;
296
+	 * @see   property definition for info on how its formatted.
297
+	 *
298
+	 */
299
+	protected function _set_supports_labels()
300
+	{
301
+		$this->_set_supports_labels_defaults();
302
+	}
303
+
304
+
305
+	/**
306
+	 * Sets the defaults for the _supports_labels property.
307
+	 *
308
+	 * @return void
309
+	 * @since 4.5.0
310
+	 *
311
+	 */
312
+	private function _set_supports_labels_defaults()
313
+	{
314
+		$this->_supports_labels->template_pack             = esc_html__('Template Structure', 'event_espresso');
315
+		$this->_supports_labels->template_variation        = esc_html__('Template Style', 'event_espresso');
316
+		$this->_supports_labels->template_pack_description = esc_html__(
317
+			'Template Structure options are bundled structural changes for templates.',
318
+			'event_espresso'
319
+		);
320
+
321
+		$this->_supports_labels->template_variation_description = esc_html__(
322
+			'These are different styles to choose from for the selected template structure.  Usually these affect things like font style, color, borders etc.  In some cases the styles will also make minor layout changes.',
323
+			'event_espresso'
324
+		);
325
+
326
+		$this->_supports_labels = apply_filters(
327
+			'FHEE__EE_messenger___set_supports_labels_defaults___supports_labels',
328
+			$this->_supports_labels,
329
+			$this
330
+		);
331
+	}
332
+
333
+
334
+	/**
335
+	 * This returns the _supports_labels property.
336
+	 *
337
+	 * @return stdClass
338
+	 * @since 4.5.0
339
+	 *
340
+	 */
341
+	public function get_supports_labels()
342
+	{
343
+		if (empty($this->_supports_labels->template_pack) || empty($this->_supports_labels->template_variation)) {
344
+			$this->_set_supports_labels_defaults();
345
+		}
346
+		return apply_filters('FHEE__EE_messenger__get_supports_labels', $this->_supports_labels, $this);
347
+	}
348
+
349
+
350
+	/**
351
+	 * Used to retrieve a variation (typically the path/url to a css file)
352
+	 *
353
+	 * @param EE_Messages_Template_Pack $pack              The template pack used for retrieving the variation.
354
+	 * @param string                    $message_type_name The name property of the message type that we need the
355
+	 *                                                     variation for.
356
+	 * @param bool                      $url               Whether to return url (true) or path (false). Default is
357
+	 *                                                     false.
358
+	 * @param string                    $type              What variation type to return. Default is 'main'.
359
+	 * @param string                    $variation         What variation for the template pack
360
+	 * @param bool                      $skip_filters      This allows messengers to add a filter for another
361
+	 *                                                     messengers get_variation but call skip filters on the
362
+	 *                                                     callback so there is no recursion on apply_filters.
363
+	 *
364
+	 * @return string                    path or url for the requested variation.
365
+	 * @since 4.5.0
366
+	 *
367
+	 */
368
+	public function get_variation(
369
+		EE_Messages_Template_Pack $pack,
370
+		$message_type_name,
371
+		$url = false,
372
+		$type = 'main',
373
+		$variation = 'default',
374
+		$skip_filters = false
375
+	) {
376
+		$this->_tmp_pack = $pack;
377
+		$variation_path  = apply_filters(
378
+			'EE_messenger__get_variation__variation',
379
+			false,
380
+			$pack,
381
+			$this->name,
382
+			$message_type_name,
383
+			$url,
384
+			$type,
385
+			$variation,
386
+			$skip_filters
387
+		);
388
+		return ! empty($variation_path)
389
+			? $variation_path
390
+			: $this->_tmp_pack->get_variation(
391
+				$this->name,
392
+				$message_type_name,
393
+				$type,
394
+				$variation,
395
+				$url,
396
+				'.css',
397
+				$skip_filters
398
+			);
399
+	}
400
+
401
+
402
+	/**
403
+	 * This just returns the default message types associated with this messenger when it is first activated.
404
+	 *
405
+	 * @access public
406
+	 * @return array
407
+	 */
408
+	public function get_default_message_types()
409
+	{
410
+		// messenger specific filter
411
+		$default_types = apply_filters(
412
+			"FHEE__{$this->class_name}__get_default_message_types__default_types",
413
+			$this->_default_message_types,
414
+			$this
415
+		);
416
+
417
+		// all messengers filter
418
+		return apply_filters('FHEE__EE_messenger__get_default_message_types__default_types', $default_types, $this);
419
+	}
420
+
421
+
422
+	/**
423
+	 * Returns the valid message types associated with this messenger.
424
+	 *
425
+	 * @return array
426
+	 * @since 4.5.0
427
+	 *
428
+	 */
429
+	public function get_valid_message_types()
430
+	{
431
+		// messenger specific filter
432
+		$valid_types = apply_filters(
433
+			"FHEE__{$this->class_name}__get_valid_message_types__valid_types",
434
+			$this->_valid_message_types,
435
+			$this
436
+		);
437
+
438
+		// all messengers filter
439
+		return apply_filters('FHEE__EE_messenger__get_valid_message_types__valid_types', $valid_types, $this);
440
+	}
441
+
442
+
443
+	/**
444
+	 * this is just used by the custom validators (EE_Messages_Validator classes) to modify the _validator_config for
445
+	 * certain message_type/messenger combos where a context may only use certain shortcodes etc.
446
+	 *
447
+	 * @access public
448
+	 * @param array $new_config Whatever is put in here will reset the _validator_config property
449
+	 */
450
+	public function set_validator_config($new_config)
451
+	{
452
+		$this->_validator_config = $new_config;
453
+	}
454
+
455
+
456
+	/**
457
+	 * This returns the _validator_config property
458
+	 *
459
+	 * @access public
460
+	 * @return array
461
+	 */
462
+	public function get_validator_config()
463
+	{
464
+		$config = apply_filters("FHEE__{$this->class_name}__get_validator_config", $this->_validator_config, $this);
465
+		return apply_filters('FHEE__EE_messenger__get_validator_config', $config, $this);
466
+	}
467
+
468
+
469
+	/**
470
+	 * this public method accepts a page slug (for an EE_admin page) and will return the response from the child class
471
+	 * callback function if that page is registered via the `_admin_registered_page` property set by the child class.
472
+	 *
473
+	 * @param string $page          the slug of the EE admin page
474
+	 * @param array  $message_types an array of active message type objects
475
+	 * @param string $action        the page action (to allow for more specific handling - i.e. edit vs. add pages)
476
+	 * @param array  $extra         This is just an extra argument that can be used to pass additional data for setting
477
+	 *                              up page content.
478
+	 * @access public
479
+	 * @return string content for page
480
+	 */
481
+	public function get_messenger_admin_page_content($page, $action = null, $extra = [], $message_types = [])
482
+	{
483
+		return $this->_get_admin_page_content($page, $action, $extra, $message_types);
484
+	}
485
+
486
+
487
+	/**
488
+	 * @param       $message_types
489
+	 * @param array $extra
490
+	 * @return string
491
+	 * @throws EE_Error
492
+	 * @throws ReflectionException
493
+	 */
494
+	protected function _get_admin_content_events_edit($message_types, $extra)
495
+	{
496
+		// defaults
497
+		$template_args = [];
498
+		$selector_rows = '';
499
+
500
+		// we don't need message types here so we're just going to ignore. we do, however, expect the event id here. The event id is needed to provide a link to setup a custom template for this event.
501
+		$event_id = $extra['event'] ?? null;
502
+
503
+		$template_wrapper_path = EE_LIBRARIES . 'messages/messenger/admin_templates/event_switcher_wrapper.template.php';
504
+		$template_row_path     = EE_LIBRARIES . 'messages/messenger/admin_templates/event_switcher_row.template.php';
505
+
506
+		// array of template objects for global and custom (non-trashed) (but remember just for this messenger!)
507
+		$global_templates    = $this->_EEM_data->get_all(
508
+			[['MTP_messenger' => $this->name, 'MTP_is_global' => true, 'MTP_is_active' => true]]
509
+		);
510
+		$templates_for_event = $this->_EEM_data->get_all_custom_templates_by_event(
511
+			$event_id,
512
+			[
513
+				'MTP_messenger' => $this->name,
514
+				'MTP_is_active' => true,
515
+			]
516
+		);
517
+		$templates_for_event = ! empty($templates_for_event) ? $templates_for_event : [];
518
+
519
+		// so we need to set up the rows for the selectors and we use the global mtpgs (cause those will the active message template groups)
520
+		foreach ($global_templates as $mtpgID => $mtpg) {
521
+			if ($mtpg instanceof EE_Message_Template_Group) {
522
+				// verify this message type is supposed to show on this page
523
+				$mtp_obj = $mtpg->message_type_obj();
524
+				if (! $mtp_obj instanceof EE_message_type) {
525
+					continue;
526
+				}
527
+				$mtp_obj->admin_registered_pages = (array) $mtp_obj->admin_registered_pages;
528
+				if (! in_array('events_edit', $mtp_obj->admin_registered_pages)) {
529
+					continue;
530
+				}
531
+				$select_values            = [];
532
+				$select_values[ $mtpgID ] = esc_html__('Global', 'event_espresso');
533
+				$default_value            = array_key_exists($mtpgID, $templates_for_event)
534
+											&& ! $mtpg->get('MTP_is_override')
535
+					? $mtpgID
536
+					: null;
537
+				// if the override has been set for the global template, then that means even if there are custom templates already created we ignore them because of the set override.
538
+				if (! $mtpg->get('MTP_is_override')) {
539
+					// any custom templates for this message type?
540
+					$custom_templates = $this->_EEM_data->get_custom_message_template_by_m_and_mt(
541
+						$this->name,
542
+						$mtpg->message_type()
543
+					);
544
+					foreach ($custom_templates as $cmtpgID => $cmtpg) {
545
+						$select_values[ $cmtpgID ] = $cmtpg->name();
546
+						$default_value             = array_key_exists($cmtpgID, $templates_for_event)
547
+							? $cmtpgID
548
+							: $default_value;
549
+					}
550
+				}
551
+				// if there is no $default_value then we set it as the global
552
+				$default_value             = empty($default_value) ? $mtpgID : $default_value;
553
+				$c_config                  = $mtpg->contexts_config();
554
+				$edit_context              = key(array_slice($c_config, -1));
555
+				$edit_url_query_args       = [
556
+					'page'    => 'espresso_messages',
557
+					'action'  => 'edit_message_template',
558
+					'id'      => $default_value,
559
+					'evt_id'  => $event_id,
560
+					'context' => $edit_context,
561
+				];
562
+				$edit_url                  = EEH_URL::add_query_args_and_nonce(
563
+					$edit_url_query_args,
564
+					admin_url('admin.php')
565
+				);
566
+				$create_url_query_args     = [
567
+					'page'         => 'espresso_messages',
568
+					'action'       => 'add_new_message_template',
569
+					'GRP_ID'       => $default_value,
570
+					'message_type' => $mtpg->message_type(),
571
+					'messenger'    => $this->name,
572
+				];
573
+				$mt_slug                   = $mtpg->message_type();
574
+				$create_url                = EEH_URL::add_query_args_and_nonce(
575
+					$create_url_query_args,
576
+					admin_url('admin.php')
577
+				);
578
+				$st_args['mtpgID']         = $mtpgID;
579
+				$st_args['mt_name']        = ucwords($mtp_obj->label['singular']);
580
+				$st_args['mt_slug']        = $mt_slug;
581
+				$st_args['messenger_slug'] = $this->name;
582
+				$st_args['selector']       = EEH_Form_Fields::select_input(
583
+					'event_message_templates_relation[' . $mtpgID . ']',
584
+					$select_values,
585
+					$default_value,
586
+					'data-messenger="' . $this->name . '" data-messagetype="' . $mt_slug . '"',
587
+					'message-template-selector ee-input-size--small'
588
+				);
589
+				$new_template_name         = sprintf(
590
+					esc_html__('Custom %1$s', 'event_espresso'),
591
+					$st_args['mt_name'] ?: esc_html__('Template', 'event_espresso')
592
+				);
593
+				// note that  message template group that has override_all_custom set will remove the ability to set a custom message template based off of the global (and that also in turn overrides any other custom templates).
594
+				$st_args['create_button'] = $mtpg->get('MTP_is_override')
595
+					? ''
596
+					: '
597 597
                     <a data-messenger="' . $this->name . '"
598 598
                        data-messagetype="' . $mt_slug . '"
599 599
                        data-new_template_name="' . $new_template_name . '"
@@ -604,18 +604,18 @@  discard block
 block discarded – undo
604 604
                     >
605 605
                         ' . esc_html__('Create New Custom', 'event_espresso') . '
606 606
                     </a>';
607
-                $st_args['create_button'] = EE_Registry::instance()->CAP->current_user_can(
608
-                    'ee_edit_messages',
609
-                    'espresso_messages_add_new_message_template'
610
-                )
611
-                    ? $st_args['create_button']
612
-                    : '';
613
-                $st_args['edit_button']   = EE_Registry::instance()->CAP->current_user_can(
614
-                    'ee_edit_message',
615
-                    'espresso_messages_edit_message_template',
616
-                    $mtpgID
617
-                )
618
-                    ? '
607
+				$st_args['create_button'] = EE_Registry::instance()->CAP->current_user_can(
608
+					'ee_edit_messages',
609
+					'espresso_messages_add_new_message_template'
610
+				)
611
+					? $st_args['create_button']
612
+					: '';
613
+				$st_args['edit_button']   = EE_Registry::instance()->CAP->current_user_can(
614
+					'ee_edit_message',
615
+					'espresso_messages_edit_message_template',
616
+					$mtpgID
617
+				)
618
+					? '
619 619
                     <a data-messagetype="' . $mt_slug . '"
620 620
                        data-grpid="' . $default_value . '"
621 621
                        target="_blank"
@@ -624,378 +624,378 @@  discard block
 block discarded – undo
624 624
                     >
625 625
                         ' . esc_html__('Edit', 'event_espresso') . '
626 626
                     </a>'
627
-                    : '';
628
-                $selector_rows            .= EEH_Template::display_template($template_row_path, $st_args, true);
629
-            }
630
-        }
631
-
632
-        // if no selectors present then get out.
633
-        if (empty($selector_rows)) {
634
-            return '';
635
-        }
636
-
637
-        $template_args['selector_rows'] = $selector_rows;
638
-        return EEH_Template::display_template($template_wrapper_path, $template_args, true);
639
-    }
640
-
641
-
642
-    /**
643
-     * get_template_fields
644
-     *
645
-     * @access public
646
-     * @return array $this->_template_fields
647
-     */
648
-    public function get_template_fields()
649
-    {
650
-        $template_fields = apply_filters(
651
-            "FHEE__{$this->class_name}__get_template_fields",
652
-            $this->_template_fields,
653
-            $this
654
-        );
655
-        return apply_filters('FHEE__EE_messenger__get_template_fields', $template_fields, $this);
656
-    }
657
-
658
-
659
-
660
-
661
-    /** SETUP METHODS **/
662
-    /**
663
-     * The following method doesn't NEED to be used by child classes but might be modified by the specific messenger
664
-     *
665
-     * @param string $item
666
-     * @param mixed  $value
667
-     */
668
-    protected function _set_template_value($item, $value)
669
-    {
670
-        if (array_key_exists($item, $this->_template_fields)) {
671
-            $prop          = '_' . $item;
672
-            $this->{$prop} = $value;
673
-        }
674
-    }
675
-
676
-
677
-    /**
678
-     * Sets up the message for sending.
679
-     *
680
-     * @param EE_message      $message      the message object that contains details about the message.
681
-     * @param EE_message_type $message_type The message type object used in combination with this messenger to generate
682
-     *                                      the provided message.
683
-     *
684
-     * @return bool Very important that all messengers return bool for successful send or not.  Error messages can be
685
-     *              added to EE_Error.
686
-     *              true = message sent successfully
687
-     *              false = message not sent but can be retried (i.e. the failure might be just due to communication
688
-     *              issues at the time of send). Throwing a SendMessageException means the message failed sending and
689
-     *              cannot be retried.
690
-     *
691
-     * @throws SendMessageException
692
-     */
693
-    final public function send_message($message, EE_message_type $message_type)
694
-    {
695
-        try {
696
-            $this->_validate_and_setup($message);
697
-            $this->_incoming_message_type = $message_type;
698
-            $response                     = $this->_send_message();
699
-            if ($response instanceof WP_Error) {
700
-                EE_Error::add_error($response->get_error_message(), __FILE__, __FUNCTION__, __LINE__);
701
-                $response = false;
702
-            }
703
-        } catch (Exception $e) {
704
-            // convert to an instance of SendMessageException
705
-            throw new SendMessageException($e->getMessage());
706
-        }
707
-        return $response;
708
-    }
709
-
710
-
711
-    /**
712
-     * Sets up and returns message preview
713
-     *
714
-     * @param EE_Message      $message      incoming message object
715
-     * @param EE_message_type $message_type This is whatever message type was used in combination with this messenger
716
-     *                                      to generate the message.
717
-     * @param bool            $send         true we will actually use the _send method (for test sends). FALSE we just
718
-     *                                      return preview
719
-     * @return string          return the message HTML content
720
-     * @throws EE_Error
721
-     * @throws ReflectionException
722
-     */
723
-    public function get_preview(EE_Message $message, EE_message_type $message_type, $send = false)
724
-    {
725
-        $this->_validate_and_setup($message);
726
-
727
-        $this->_incoming_message_type = $message_type;
728
-
729
-        if ($send) {
730
-            // are we overriding any existing template fields?
731
-            $settings = apply_filters(
732
-                'FHEE__EE_messenger__get_preview__messenger_test_settings',
733
-                $this->get_existing_test_settings(),
734
-                $this,
735
-                $send,
736
-                $message,
737
-                $message_type
738
-            );
739
-            if (! empty($settings)) {
740
-                foreach ($settings as $field => $value) {
741
-                    $this->_set_template_value($field, $value);
742
-                }
743
-            }
744
-        }
745
-
746
-        // enqueue preview js so that any links/buttons on the page are disabled.
747
-        if (! $send) {
748
-            // the below may seem like duplication.  However, typically if a messenger enqueues scripts/styles,
749
-            // it deregisters all existing wp scripts and styles first.  So the second hook ensures our previewer still gets setup.
750
-            add_action('admin_enqueue_scripts', [$this, 'add_preview_script'], 10);
751
-            add_action('wp_enqueue_scripts', [$this, 'add_preview_script'], 10);
752
-            add_action('AHEE__EE_messenger__enqueue_scripts_styles', [$this, 'add_preview_script'], 10);
753
-        }
754
-
755
-        return $send ? $this->_send_message() : $this->_preview();
756
-    }
757
-
758
-
759
-    /**
760
-     * Callback for enqueue_scripts so that we set up the preview script for all previews.
761
-     *
762
-     * @return void
763
-     * @since 4.5.0
764
-     *
765
-     */
766
-    public function add_preview_script()
767
-    {
768
-        // error message
769
-        EE_Registry::$i18n_js_strings['links_disabled'] = wp_strip_all_tags(
770
-            __(
771
-                'All the links on this page have been disabled because this is a generated preview message for the purpose of ensuring layout, style, and content setup.  To test generated links, you must trigger an actual message notification.',
772
-                'event_espresso'
773
-            )
774
-        );
775
-        wp_register_script(
776
-            'ee-messages-preview-js',
777
-            EE_LIBRARIES_URL . 'messages/messenger/assets/js/ee-messages-preview.js',
778
-            ['jquery'],
779
-            EVENT_ESPRESSO_VERSION,
780
-            true
781
-        );
782
-        wp_localize_script('ee-messages-preview-js', 'eei18n', EE_Registry::$i18n_js_strings);
783
-        wp_enqueue_script('ee-messages-preview-js');
784
-    }
785
-
786
-
787
-    /**
788
-     * simply validates the incoming message object and then sets up the properties for the messenger
789
-     *
790
-     * @param EE_Message $message
791
-     * @throws EE_Error
792
-     * @throws ReflectionException
793
-     */
794
-    protected function _validate_and_setup(EE_Message $message)
795
-    {
796
-        $template_pack = $message->get_template_pack();
797
-        $variation     = $message->get_template_pack_variation();
798
-
799
-        // verify we have the required template pack value on the $message object.
800
-        if (! $template_pack instanceof EE_Messages_Template_Pack) {
801
-            throw new EE_Error(
802
-                esc_html__(
803
-                    'Incoming $message object must have an EE_Messages_Template_Pack object available.',
804
-                    'event_espresso'
805
-                )
806
-            );
807
-        }
808
-
809
-        $this->_tmp_pack = $template_pack;
810
-
811
-        $this->_variation = $variation ?: 'default';
812
-
813
-        $template_fields = $this->get_template_fields();
814
-
815
-        foreach ($template_fields as $template => $value) {
816
-            if ($template !== 'extra') {
817
-                $column_value           = $message->get_field_or_extra_meta('MSG_' . $template);
818
-                $message_template_value = $column_value ?: null;
819
-                $this->_set_template_value($template, $message_template_value);
820
-            }
821
-        }
822
-    }
823
-
824
-
825
-    /**
826
-     * Utility method for child classes to get the contents of a template file and return
827
-     *
828
-     * We're assuming the child messenger class has already setup template args!
829
-     *
830
-     * @param bool $preview if true we use the preview wrapper otherwise we use main wrapper.
831
-     * @return string
832
-     * @throws EE_Error
833
-     */
834
-    protected function _get_main_template($preview = false)
835
-    {
836
-        $type = $preview ? 'preview' : 'main';
837
-
838
-        $wrapper_template = $this->_tmp_pack->get_wrapper($this->name, $type);
839
-
840
-        // check file exists and is readable
841
-        if (! is_readable($wrapper_template)) {
842
-            throw new EE_Error(
843
-                sprintf(
844
-                    esc_html__(
845
-                        'Unable to access the template file for the %s messenger main content wrapper.  The location being attempted is %s.',
846
-                        'event_espresso'
847
-                    ),
848
-                    ucwords($this->label['singular']),
849
-                    $wrapper_template
850
-                )
851
-            );
852
-        }
853
-
854
-        // add message type to template args
855
-        $this->_template_args['message_type'] = $this->_incoming_message_type;
856
-
857
-        return EEH_Template::display_template($wrapper_template, $this->_template_args, true);
858
-    }
859
-
860
-
861
-    /**
862
-     * set the _test_settings_fields property
863
-     *
864
-     * @access protected
865
-     * @return void
866
-     */
867
-    protected function _set_test_settings_fields()
868
-    {
869
-        $this->_test_settings_fields = [];
870
-    }
871
-
872
-
873
-    /**
874
-     * return the _test_settings_fields property
875
-     *
876
-     * @return array
877
-     */
878
-    public function get_test_settings_fields()
879
-    {
880
-        return $this->_test_settings_fields;
881
-    }
882
-
883
-
884
-    /**
885
-     * This just returns any existing test settings that might be saved in the database
886
-     *
887
-     * @access public
888
-     * @return array
889
-     * @throws EE_Error
890
-     * @throws ReflectionException
891
-     */
892
-    public function get_existing_test_settings()
893
-    {
894
-        /** @var EE_Message_Resource_Manager $Message_Resource_Manager */
895
-        $Message_Resource_Manager = EE_Registry::instance()->load_lib('Message_Resource_Manager');
896
-        $settings                 = $Message_Resource_Manager->get_active_messengers_option();
897
-        return $settings[ $this->name ]['test_settings'] ?? [];
898
-    }
899
-
900
-
901
-    /**
902
-     * All this does is set the existing test settings (in the db) for the messenger
903
-     *
904
-     * @access public
905
-     * @param $settings
906
-     * @return bool success/fail
907
-     * @throws EE_Error
908
-     * @throws ReflectionException
909
-     */
910
-    public function set_existing_test_settings($settings)
911
-    {
912
-        /** @var EE_Message_Resource_Manager $Message_Resource_Manager */
913
-        $Message_Resource_Manager                 = EE_Registry::instance()->load_lib('Message_Resource_Manager');
914
-        $existing                                 = $Message_Resource_Manager->get_active_messengers_option();
915
-        $existing[ $this->name ]['test_settings'] = $settings;
916
-        return $Message_Resource_Manager->update_active_messengers_option($existing);
917
-    }
918
-
919
-
920
-    /**
921
-     * This just returns the field label for a given field setup in the _template_fields property.
922
-     *
923
-     * @param string $field The field to retrieve the label for
924
-     * @return string             The label
925
-     * @since   4.3.0
926
-     *
927
-     */
928
-    public function get_field_label($field)
929
-    {
930
-        // first let's see if the field requests is in the top level array.
931
-        if (! empty($this->_template_fields[ $field ]['label'])) {
932
-            return $this->_template_fields[ $field ]['label'];
933
-        }
934
-
935
-        // nope so let's look in the extra array to see if it's there HOWEVER if the field exists as a top level index in the extra array then we know the label is in the 'main' index.
936
-        if (! empty($this->_template_fields['extra'][ $field ]['main']['label'])) {
937
-            return $this->_template_fields['extra'][ $field ]['main']['label'];
938
-        }
939
-
940
-        // now it's possible this field may just be existing in any of the extra array items.
941
-        if (! empty($this->_template_fields['extra']) && is_array($this->_template_fields['extra'])) {
942
-            foreach ($this->_template_fields['extra'] as $subfields) {
943
-                if (! empty($subfields[ $field ]['label'])) {
944
-                    return $subfields[ $field ]['label'];
945
-                }
946
-            }
947
-        }
948
-
949
-        // if we made it here then there's no label set so let's just return the $field.
950
-        return $field;
951
-    }
952
-
953
-
954
-    /**
955
-     * This is a method called from EE_messages when this messenger is a generating messenger and the sending messenger
956
-     * is a different messenger.  Child messengers can set hooks for the sending messenger to callback on if necessary
957
-     * (i.e. swap out css files or something else).
958
-     *
959
-     * @param string $sending_messenger_name the name of the sending messenger so we only set the hooks needed.
960
-     *
961
-     * @return void
962
-     * @since 4.5.0
963
-     *
964
-     */
965
-    public function do_secondary_messenger_hooks($sending_messenger_name)
966
-    {
967
-        return;
968
-    }
969
-
970
-
971
-    /**
972
-     * @return string[]
973
-     * @since 5.0.46
974
-     */
975
-    public function __sleep()
976
-    {
977
-        $vars   = (array) $this;
978
-        $remove = [
979
-            '_EEM_data',
980
-        ];
981
-        foreach ($vars as $key => $val) {
982
-            // removing null properties improves serialization performance
983
-            if (is_null($val) || in_array($val, $remove, true)) {
984
-                unset($vars[ $key ]);
985
-            }
986
-        }
987
-        return array_keys($vars);
988
-    }
989
-
990
-
991
-    /**
992
-     * @return void
993
-     * @throws EE_Error
994
-     * @throws ReflectionException
995
-     * @since 5.0.46
996
-     */
997
-    public function __wakeup()
998
-    {
999
-        $this->_EEM_data = EEM_Message_Template_Group::instance();
1000
-    }
627
+					: '';
628
+				$selector_rows            .= EEH_Template::display_template($template_row_path, $st_args, true);
629
+			}
630
+		}
631
+
632
+		// if no selectors present then get out.
633
+		if (empty($selector_rows)) {
634
+			return '';
635
+		}
636
+
637
+		$template_args['selector_rows'] = $selector_rows;
638
+		return EEH_Template::display_template($template_wrapper_path, $template_args, true);
639
+	}
640
+
641
+
642
+	/**
643
+	 * get_template_fields
644
+	 *
645
+	 * @access public
646
+	 * @return array $this->_template_fields
647
+	 */
648
+	public function get_template_fields()
649
+	{
650
+		$template_fields = apply_filters(
651
+			"FHEE__{$this->class_name}__get_template_fields",
652
+			$this->_template_fields,
653
+			$this
654
+		);
655
+		return apply_filters('FHEE__EE_messenger__get_template_fields', $template_fields, $this);
656
+	}
657
+
658
+
659
+
660
+
661
+	/** SETUP METHODS **/
662
+	/**
663
+	 * The following method doesn't NEED to be used by child classes but might be modified by the specific messenger
664
+	 *
665
+	 * @param string $item
666
+	 * @param mixed  $value
667
+	 */
668
+	protected function _set_template_value($item, $value)
669
+	{
670
+		if (array_key_exists($item, $this->_template_fields)) {
671
+			$prop          = '_' . $item;
672
+			$this->{$prop} = $value;
673
+		}
674
+	}
675
+
676
+
677
+	/**
678
+	 * Sets up the message for sending.
679
+	 *
680
+	 * @param EE_message      $message      the message object that contains details about the message.
681
+	 * @param EE_message_type $message_type The message type object used in combination with this messenger to generate
682
+	 *                                      the provided message.
683
+	 *
684
+	 * @return bool Very important that all messengers return bool for successful send or not.  Error messages can be
685
+	 *              added to EE_Error.
686
+	 *              true = message sent successfully
687
+	 *              false = message not sent but can be retried (i.e. the failure might be just due to communication
688
+	 *              issues at the time of send). Throwing a SendMessageException means the message failed sending and
689
+	 *              cannot be retried.
690
+	 *
691
+	 * @throws SendMessageException
692
+	 */
693
+	final public function send_message($message, EE_message_type $message_type)
694
+	{
695
+		try {
696
+			$this->_validate_and_setup($message);
697
+			$this->_incoming_message_type = $message_type;
698
+			$response                     = $this->_send_message();
699
+			if ($response instanceof WP_Error) {
700
+				EE_Error::add_error($response->get_error_message(), __FILE__, __FUNCTION__, __LINE__);
701
+				$response = false;
702
+			}
703
+		} catch (Exception $e) {
704
+			// convert to an instance of SendMessageException
705
+			throw new SendMessageException($e->getMessage());
706
+		}
707
+		return $response;
708
+	}
709
+
710
+
711
+	/**
712
+	 * Sets up and returns message preview
713
+	 *
714
+	 * @param EE_Message      $message      incoming message object
715
+	 * @param EE_message_type $message_type This is whatever message type was used in combination with this messenger
716
+	 *                                      to generate the message.
717
+	 * @param bool            $send         true we will actually use the _send method (for test sends). FALSE we just
718
+	 *                                      return preview
719
+	 * @return string          return the message HTML content
720
+	 * @throws EE_Error
721
+	 * @throws ReflectionException
722
+	 */
723
+	public function get_preview(EE_Message $message, EE_message_type $message_type, $send = false)
724
+	{
725
+		$this->_validate_and_setup($message);
726
+
727
+		$this->_incoming_message_type = $message_type;
728
+
729
+		if ($send) {
730
+			// are we overriding any existing template fields?
731
+			$settings = apply_filters(
732
+				'FHEE__EE_messenger__get_preview__messenger_test_settings',
733
+				$this->get_existing_test_settings(),
734
+				$this,
735
+				$send,
736
+				$message,
737
+				$message_type
738
+			);
739
+			if (! empty($settings)) {
740
+				foreach ($settings as $field => $value) {
741
+					$this->_set_template_value($field, $value);
742
+				}
743
+			}
744
+		}
745
+
746
+		// enqueue preview js so that any links/buttons on the page are disabled.
747
+		if (! $send) {
748
+			// the below may seem like duplication.  However, typically if a messenger enqueues scripts/styles,
749
+			// it deregisters all existing wp scripts and styles first.  So the second hook ensures our previewer still gets setup.
750
+			add_action('admin_enqueue_scripts', [$this, 'add_preview_script'], 10);
751
+			add_action('wp_enqueue_scripts', [$this, 'add_preview_script'], 10);
752
+			add_action('AHEE__EE_messenger__enqueue_scripts_styles', [$this, 'add_preview_script'], 10);
753
+		}
754
+
755
+		return $send ? $this->_send_message() : $this->_preview();
756
+	}
757
+
758
+
759
+	/**
760
+	 * Callback for enqueue_scripts so that we set up the preview script for all previews.
761
+	 *
762
+	 * @return void
763
+	 * @since 4.5.0
764
+	 *
765
+	 */
766
+	public function add_preview_script()
767
+	{
768
+		// error message
769
+		EE_Registry::$i18n_js_strings['links_disabled'] = wp_strip_all_tags(
770
+			__(
771
+				'All the links on this page have been disabled because this is a generated preview message for the purpose of ensuring layout, style, and content setup.  To test generated links, you must trigger an actual message notification.',
772
+				'event_espresso'
773
+			)
774
+		);
775
+		wp_register_script(
776
+			'ee-messages-preview-js',
777
+			EE_LIBRARIES_URL . 'messages/messenger/assets/js/ee-messages-preview.js',
778
+			['jquery'],
779
+			EVENT_ESPRESSO_VERSION,
780
+			true
781
+		);
782
+		wp_localize_script('ee-messages-preview-js', 'eei18n', EE_Registry::$i18n_js_strings);
783
+		wp_enqueue_script('ee-messages-preview-js');
784
+	}
785
+
786
+
787
+	/**
788
+	 * simply validates the incoming message object and then sets up the properties for the messenger
789
+	 *
790
+	 * @param EE_Message $message
791
+	 * @throws EE_Error
792
+	 * @throws ReflectionException
793
+	 */
794
+	protected function _validate_and_setup(EE_Message $message)
795
+	{
796
+		$template_pack = $message->get_template_pack();
797
+		$variation     = $message->get_template_pack_variation();
798
+
799
+		// verify we have the required template pack value on the $message object.
800
+		if (! $template_pack instanceof EE_Messages_Template_Pack) {
801
+			throw new EE_Error(
802
+				esc_html__(
803
+					'Incoming $message object must have an EE_Messages_Template_Pack object available.',
804
+					'event_espresso'
805
+				)
806
+			);
807
+		}
808
+
809
+		$this->_tmp_pack = $template_pack;
810
+
811
+		$this->_variation = $variation ?: 'default';
812
+
813
+		$template_fields = $this->get_template_fields();
814
+
815
+		foreach ($template_fields as $template => $value) {
816
+			if ($template !== 'extra') {
817
+				$column_value           = $message->get_field_or_extra_meta('MSG_' . $template);
818
+				$message_template_value = $column_value ?: null;
819
+				$this->_set_template_value($template, $message_template_value);
820
+			}
821
+		}
822
+	}
823
+
824
+
825
+	/**
826
+	 * Utility method for child classes to get the contents of a template file and return
827
+	 *
828
+	 * We're assuming the child messenger class has already setup template args!
829
+	 *
830
+	 * @param bool $preview if true we use the preview wrapper otherwise we use main wrapper.
831
+	 * @return string
832
+	 * @throws EE_Error
833
+	 */
834
+	protected function _get_main_template($preview = false)
835
+	{
836
+		$type = $preview ? 'preview' : 'main';
837
+
838
+		$wrapper_template = $this->_tmp_pack->get_wrapper($this->name, $type);
839
+
840
+		// check file exists and is readable
841
+		if (! is_readable($wrapper_template)) {
842
+			throw new EE_Error(
843
+				sprintf(
844
+					esc_html__(
845
+						'Unable to access the template file for the %s messenger main content wrapper.  The location being attempted is %s.',
846
+						'event_espresso'
847
+					),
848
+					ucwords($this->label['singular']),
849
+					$wrapper_template
850
+				)
851
+			);
852
+		}
853
+
854
+		// add message type to template args
855
+		$this->_template_args['message_type'] = $this->_incoming_message_type;
856
+
857
+		return EEH_Template::display_template($wrapper_template, $this->_template_args, true);
858
+	}
859
+
860
+
861
+	/**
862
+	 * set the _test_settings_fields property
863
+	 *
864
+	 * @access protected
865
+	 * @return void
866
+	 */
867
+	protected function _set_test_settings_fields()
868
+	{
869
+		$this->_test_settings_fields = [];
870
+	}
871
+
872
+
873
+	/**
874
+	 * return the _test_settings_fields property
875
+	 *
876
+	 * @return array
877
+	 */
878
+	public function get_test_settings_fields()
879
+	{
880
+		return $this->_test_settings_fields;
881
+	}
882
+
883
+
884
+	/**
885
+	 * This just returns any existing test settings that might be saved in the database
886
+	 *
887
+	 * @access public
888
+	 * @return array
889
+	 * @throws EE_Error
890
+	 * @throws ReflectionException
891
+	 */
892
+	public function get_existing_test_settings()
893
+	{
894
+		/** @var EE_Message_Resource_Manager $Message_Resource_Manager */
895
+		$Message_Resource_Manager = EE_Registry::instance()->load_lib('Message_Resource_Manager');
896
+		$settings                 = $Message_Resource_Manager->get_active_messengers_option();
897
+		return $settings[ $this->name ]['test_settings'] ?? [];
898
+	}
899
+
900
+
901
+	/**
902
+	 * All this does is set the existing test settings (in the db) for the messenger
903
+	 *
904
+	 * @access public
905
+	 * @param $settings
906
+	 * @return bool success/fail
907
+	 * @throws EE_Error
908
+	 * @throws ReflectionException
909
+	 */
910
+	public function set_existing_test_settings($settings)
911
+	{
912
+		/** @var EE_Message_Resource_Manager $Message_Resource_Manager */
913
+		$Message_Resource_Manager                 = EE_Registry::instance()->load_lib('Message_Resource_Manager');
914
+		$existing                                 = $Message_Resource_Manager->get_active_messengers_option();
915
+		$existing[ $this->name ]['test_settings'] = $settings;
916
+		return $Message_Resource_Manager->update_active_messengers_option($existing);
917
+	}
918
+
919
+
920
+	/**
921
+	 * This just returns the field label for a given field setup in the _template_fields property.
922
+	 *
923
+	 * @param string $field The field to retrieve the label for
924
+	 * @return string             The label
925
+	 * @since   4.3.0
926
+	 *
927
+	 */
928
+	public function get_field_label($field)
929
+	{
930
+		// first let's see if the field requests is in the top level array.
931
+		if (! empty($this->_template_fields[ $field ]['label'])) {
932
+			return $this->_template_fields[ $field ]['label'];
933
+		}
934
+
935
+		// nope so let's look in the extra array to see if it's there HOWEVER if the field exists as a top level index in the extra array then we know the label is in the 'main' index.
936
+		if (! empty($this->_template_fields['extra'][ $field ]['main']['label'])) {
937
+			return $this->_template_fields['extra'][ $field ]['main']['label'];
938
+		}
939
+
940
+		// now it's possible this field may just be existing in any of the extra array items.
941
+		if (! empty($this->_template_fields['extra']) && is_array($this->_template_fields['extra'])) {
942
+			foreach ($this->_template_fields['extra'] as $subfields) {
943
+				if (! empty($subfields[ $field ]['label'])) {
944
+					return $subfields[ $field ]['label'];
945
+				}
946
+			}
947
+		}
948
+
949
+		// if we made it here then there's no label set so let's just return the $field.
950
+		return $field;
951
+	}
952
+
953
+
954
+	/**
955
+	 * This is a method called from EE_messages when this messenger is a generating messenger and the sending messenger
956
+	 * is a different messenger.  Child messengers can set hooks for the sending messenger to callback on if necessary
957
+	 * (i.e. swap out css files or something else).
958
+	 *
959
+	 * @param string $sending_messenger_name the name of the sending messenger so we only set the hooks needed.
960
+	 *
961
+	 * @return void
962
+	 * @since 4.5.0
963
+	 *
964
+	 */
965
+	public function do_secondary_messenger_hooks($sending_messenger_name)
966
+	{
967
+		return;
968
+	}
969
+
970
+
971
+	/**
972
+	 * @return string[]
973
+	 * @since 5.0.46
974
+	 */
975
+	public function __sleep()
976
+	{
977
+		$vars   = (array) $this;
978
+		$remove = [
979
+			'_EEM_data',
980
+		];
981
+		foreach ($vars as $key => $val) {
982
+			// removing null properties improves serialization performance
983
+			if (is_null($val) || in_array($val, $remove, true)) {
984
+				unset($vars[ $key ]);
985
+			}
986
+		}
987
+		return array_keys($vars);
988
+	}
989
+
990
+
991
+	/**
992
+	 * @return void
993
+	 * @throws EE_Error
994
+	 * @throws ReflectionException
995
+	 * @since 5.0.46
996
+	 */
997
+	public function __wakeup()
998
+	{
999
+		$this->_EEM_data = EEM_Message_Template_Group::instance();
1000
+	}
1001 1001
 }
Please login to merge, or discard this patch.
Spacing   +41 added lines, -41 removed lines patch added patch discarded remove patch
@@ -500,8 +500,8 @@  discard block
 block discarded – undo
500 500
         // we don't need message types here so we're just going to ignore. we do, however, expect the event id here. The event id is needed to provide a link to setup a custom template for this event.
501 501
         $event_id = $extra['event'] ?? null;
502 502
 
503
-        $template_wrapper_path = EE_LIBRARIES . 'messages/messenger/admin_templates/event_switcher_wrapper.template.php';
504
-        $template_row_path     = EE_LIBRARIES . 'messages/messenger/admin_templates/event_switcher_row.template.php';
503
+        $template_wrapper_path = EE_LIBRARIES.'messages/messenger/admin_templates/event_switcher_wrapper.template.php';
504
+        $template_row_path     = EE_LIBRARIES.'messages/messenger/admin_templates/event_switcher_row.template.php';
505 505
 
506 506
         // array of template objects for global and custom (non-trashed) (but remember just for this messenger!)
507 507
         $global_templates    = $this->_EEM_data->get_all(
@@ -521,28 +521,28 @@  discard block
 block discarded – undo
521 521
             if ($mtpg instanceof EE_Message_Template_Group) {
522 522
                 // verify this message type is supposed to show on this page
523 523
                 $mtp_obj = $mtpg->message_type_obj();
524
-                if (! $mtp_obj instanceof EE_message_type) {
524
+                if ( ! $mtp_obj instanceof EE_message_type) {
525 525
                     continue;
526 526
                 }
527 527
                 $mtp_obj->admin_registered_pages = (array) $mtp_obj->admin_registered_pages;
528
-                if (! in_array('events_edit', $mtp_obj->admin_registered_pages)) {
528
+                if ( ! in_array('events_edit', $mtp_obj->admin_registered_pages)) {
529 529
                     continue;
530 530
                 }
531 531
                 $select_values            = [];
532
-                $select_values[ $mtpgID ] = esc_html__('Global', 'event_espresso');
532
+                $select_values[$mtpgID] = esc_html__('Global', 'event_espresso');
533 533
                 $default_value            = array_key_exists($mtpgID, $templates_for_event)
534 534
                                             && ! $mtpg->get('MTP_is_override')
535 535
                     ? $mtpgID
536 536
                     : null;
537 537
                 // if the override has been set for the global template, then that means even if there are custom templates already created we ignore them because of the set override.
538
-                if (! $mtpg->get('MTP_is_override')) {
538
+                if ( ! $mtpg->get('MTP_is_override')) {
539 539
                     // any custom templates for this message type?
540 540
                     $custom_templates = $this->_EEM_data->get_custom_message_template_by_m_and_mt(
541 541
                         $this->name,
542 542
                         $mtpg->message_type()
543 543
                     );
544 544
                     foreach ($custom_templates as $cmtpgID => $cmtpg) {
545
-                        $select_values[ $cmtpgID ] = $cmtpg->name();
545
+                        $select_values[$cmtpgID] = $cmtpg->name();
546 546
                         $default_value             = array_key_exists($cmtpgID, $templates_for_event)
547 547
                             ? $cmtpgID
548 548
                             : $default_value;
@@ -559,11 +559,11 @@  discard block
 block discarded – undo
559 559
                     'evt_id'  => $event_id,
560 560
                     'context' => $edit_context,
561 561
                 ];
562
-                $edit_url                  = EEH_URL::add_query_args_and_nonce(
562
+                $edit_url = EEH_URL::add_query_args_and_nonce(
563 563
                     $edit_url_query_args,
564 564
                     admin_url('admin.php')
565 565
                 );
566
-                $create_url_query_args     = [
566
+                $create_url_query_args = [
567 567
                     'page'         => 'espresso_messages',
568 568
                     'action'       => 'add_new_message_template',
569 569
                     'GRP_ID'       => $default_value,
@@ -580,13 +580,13 @@  discard block
 block discarded – undo
580 580
                 $st_args['mt_slug']        = $mt_slug;
581 581
                 $st_args['messenger_slug'] = $this->name;
582 582
                 $st_args['selector']       = EEH_Form_Fields::select_input(
583
-                    'event_message_templates_relation[' . $mtpgID . ']',
583
+                    'event_message_templates_relation['.$mtpgID.']',
584 584
                     $select_values,
585 585
                     $default_value,
586
-                    'data-messenger="' . $this->name . '" data-messagetype="' . $mt_slug . '"',
586
+                    'data-messenger="'.$this->name.'" data-messagetype="'.$mt_slug.'"',
587 587
                     'message-template-selector ee-input-size--small'
588 588
                 );
589
-                $new_template_name         = sprintf(
589
+                $new_template_name = sprintf(
590 590
                     esc_html__('Custom %1$s', 'event_espresso'),
591 591
                     $st_args['mt_name'] ?: esc_html__('Template', 'event_espresso')
592 592
                 );
@@ -594,15 +594,15 @@  discard block
 block discarded – undo
594 594
                 $st_args['create_button'] = $mtpg->get('MTP_is_override')
595 595
                     ? ''
596 596
                     : '
597
-                    <a data-messenger="' . $this->name . '"
598
-                       data-messagetype="' . $mt_slug . '"
599
-                       data-new_template_name="' . $new_template_name . '"
600
-                       data-grpid="' . $default_value . '"
597
+                    <a data-messenger="' . $this->name.'"
598
+                       data-messagetype="' . $mt_slug.'"
599
+                       data-new_template_name="' . $new_template_name.'"
600
+                       data-grpid="' . $default_value.'"
601 601
                        target="_blank"
602
-                       href="' . $create_url . '"
602
+                       href="' . $create_url.'"
603 603
                        class="button button--secondary button--small create-mtpg-button"
604 604
                     >
605
-                        ' . esc_html__('Create New Custom', 'event_espresso') . '
605
+                        ' . esc_html__('Create New Custom', 'event_espresso').'
606 606
                     </a>';
607 607
                 $st_args['create_button'] = EE_Registry::instance()->CAP->current_user_can(
608 608
                     'ee_edit_messages',
@@ -610,22 +610,22 @@  discard block
 block discarded – undo
610 610
                 )
611 611
                     ? $st_args['create_button']
612 612
                     : '';
613
-                $st_args['edit_button']   = EE_Registry::instance()->CAP->current_user_can(
613
+                $st_args['edit_button'] = EE_Registry::instance()->CAP->current_user_can(
614 614
                     'ee_edit_message',
615 615
                     'espresso_messages_edit_message_template',
616 616
                     $mtpgID
617 617
                 )
618 618
                     ? '
619
-                    <a data-messagetype="' . $mt_slug . '"
620
-                       data-grpid="' . $default_value . '"
619
+                    <a data-messagetype="' . $mt_slug.'"
620
+                       data-grpid="' . $default_value.'"
621 621
                        target="_blank"
622
-                       href="' . $edit_url . '"
622
+                       href="' . $edit_url.'"
623 623
                        class="button button--secondary button--small edit-mtpg-button"
624 624
                     >
625
-                        ' . esc_html__('Edit', 'event_espresso') . '
625
+                        ' . esc_html__('Edit', 'event_espresso').'
626 626
                     </a>'
627 627
                     : '';
628
-                $selector_rows            .= EEH_Template::display_template($template_row_path, $st_args, true);
628
+                $selector_rows .= EEH_Template::display_template($template_row_path, $st_args, true);
629 629
             }
630 630
         }
631 631
 
@@ -668,7 +668,7 @@  discard block
 block discarded – undo
668 668
     protected function _set_template_value($item, $value)
669 669
     {
670 670
         if (array_key_exists($item, $this->_template_fields)) {
671
-            $prop          = '_' . $item;
671
+            $prop          = '_'.$item;
672 672
             $this->{$prop} = $value;
673 673
         }
674 674
     }
@@ -736,7 +736,7 @@  discard block
 block discarded – undo
736 736
                 $message,
737 737
                 $message_type
738 738
             );
739
-            if (! empty($settings)) {
739
+            if ( ! empty($settings)) {
740 740
                 foreach ($settings as $field => $value) {
741 741
                     $this->_set_template_value($field, $value);
742 742
                 }
@@ -744,7 +744,7 @@  discard block
 block discarded – undo
744 744
         }
745 745
 
746 746
         // enqueue preview js so that any links/buttons on the page are disabled.
747
-        if (! $send) {
747
+        if ( ! $send) {
748 748
             // the below may seem like duplication.  However, typically if a messenger enqueues scripts/styles,
749 749
             // it deregisters all existing wp scripts and styles first.  So the second hook ensures our previewer still gets setup.
750 750
             add_action('admin_enqueue_scripts', [$this, 'add_preview_script'], 10);
@@ -774,7 +774,7 @@  discard block
 block discarded – undo
774 774
         );
775 775
         wp_register_script(
776 776
             'ee-messages-preview-js',
777
-            EE_LIBRARIES_URL . 'messages/messenger/assets/js/ee-messages-preview.js',
777
+            EE_LIBRARIES_URL.'messages/messenger/assets/js/ee-messages-preview.js',
778 778
             ['jquery'],
779 779
             EVENT_ESPRESSO_VERSION,
780 780
             true
@@ -797,7 +797,7 @@  discard block
 block discarded – undo
797 797
         $variation     = $message->get_template_pack_variation();
798 798
 
799 799
         // verify we have the required template pack value on the $message object.
800
-        if (! $template_pack instanceof EE_Messages_Template_Pack) {
800
+        if ( ! $template_pack instanceof EE_Messages_Template_Pack) {
801 801
             throw new EE_Error(
802 802
                 esc_html__(
803 803
                     'Incoming $message object must have an EE_Messages_Template_Pack object available.',
@@ -814,7 +814,7 @@  discard block
 block discarded – undo
814 814
 
815 815
         foreach ($template_fields as $template => $value) {
816 816
             if ($template !== 'extra') {
817
-                $column_value           = $message->get_field_or_extra_meta('MSG_' . $template);
817
+                $column_value           = $message->get_field_or_extra_meta('MSG_'.$template);
818 818
                 $message_template_value = $column_value ?: null;
819 819
                 $this->_set_template_value($template, $message_template_value);
820 820
             }
@@ -838,7 +838,7 @@  discard block
 block discarded – undo
838 838
         $wrapper_template = $this->_tmp_pack->get_wrapper($this->name, $type);
839 839
 
840 840
         // check file exists and is readable
841
-        if (! is_readable($wrapper_template)) {
841
+        if ( ! is_readable($wrapper_template)) {
842 842
             throw new EE_Error(
843 843
                 sprintf(
844 844
                     esc_html__(
@@ -894,7 +894,7 @@  discard block
 block discarded – undo
894 894
         /** @var EE_Message_Resource_Manager $Message_Resource_Manager */
895 895
         $Message_Resource_Manager = EE_Registry::instance()->load_lib('Message_Resource_Manager');
896 896
         $settings                 = $Message_Resource_Manager->get_active_messengers_option();
897
-        return $settings[ $this->name ]['test_settings'] ?? [];
897
+        return $settings[$this->name]['test_settings'] ?? [];
898 898
     }
899 899
 
900 900
 
@@ -912,7 +912,7 @@  discard block
 block discarded – undo
912 912
         /** @var EE_Message_Resource_Manager $Message_Resource_Manager */
913 913
         $Message_Resource_Manager                 = EE_Registry::instance()->load_lib('Message_Resource_Manager');
914 914
         $existing                                 = $Message_Resource_Manager->get_active_messengers_option();
915
-        $existing[ $this->name ]['test_settings'] = $settings;
915
+        $existing[$this->name]['test_settings'] = $settings;
916 916
         return $Message_Resource_Manager->update_active_messengers_option($existing);
917 917
     }
918 918
 
@@ -928,20 +928,20 @@  discard block
 block discarded – undo
928 928
     public function get_field_label($field)
929 929
     {
930 930
         // first let's see if the field requests is in the top level array.
931
-        if (! empty($this->_template_fields[ $field ]['label'])) {
932
-            return $this->_template_fields[ $field ]['label'];
931
+        if ( ! empty($this->_template_fields[$field]['label'])) {
932
+            return $this->_template_fields[$field]['label'];
933 933
         }
934 934
 
935 935
         // nope so let's look in the extra array to see if it's there HOWEVER if the field exists as a top level index in the extra array then we know the label is in the 'main' index.
936
-        if (! empty($this->_template_fields['extra'][ $field ]['main']['label'])) {
937
-            return $this->_template_fields['extra'][ $field ]['main']['label'];
936
+        if ( ! empty($this->_template_fields['extra'][$field]['main']['label'])) {
937
+            return $this->_template_fields['extra'][$field]['main']['label'];
938 938
         }
939 939
 
940 940
         // now it's possible this field may just be existing in any of the extra array items.
941
-        if (! empty($this->_template_fields['extra']) && is_array($this->_template_fields['extra'])) {
941
+        if ( ! empty($this->_template_fields['extra']) && is_array($this->_template_fields['extra'])) {
942 942
             foreach ($this->_template_fields['extra'] as $subfields) {
943
-                if (! empty($subfields[ $field ]['label'])) {
944
-                    return $subfields[ $field ]['label'];
943
+                if ( ! empty($subfields[$field]['label'])) {
944
+                    return $subfields[$field]['label'];
945 945
                 }
946 946
             }
947 947
         }
@@ -981,7 +981,7 @@  discard block
 block discarded – undo
981 981
         foreach ($vars as $key => $val) {
982 982
             // removing null properties improves serialization performance
983 983
             if (is_null($val) || in_array($val, $remove, true)) {
984
-                unset($vars[ $key ]);
984
+                unset($vars[$key]);
985 985
             }
986 986
         }
987 987
         return array_keys($vars);
Please login to merge, or discard this patch.
core/libraries/messages/EE_Messages_Base.lib.php 2 patches
Indentation   +275 added lines, -275 removed lines patch added patch discarded remove patch
@@ -11,279 +11,279 @@
 block discarded – undo
11 11
  */
12 12
 abstract class EE_Messages_Base extends EE_Base
13 13
 {
14
-    /** DETAILS PROPERTIES **/
15
-    /**
16
-     * The following are used to hold details on the type for reference (i.e. on admin screens)
17
-     * and also used by the EE_message_type object to figure out where to get template data.
18
-     */
19
-    public $name;
20
-
21
-    public $description;
22
-
23
-    protected $_messages_item_type; // messenger OR message_type?
24
-
25
-
26
-    /**
27
-     * This is an array describing the ui facing labels
28
-     * that will be used whenever the messenger is referenced in the ui
29
-     *
30
-     * array(
31
-     *  'singular' => esc_html__('something'),
32
-     *  'plural' => esc_html__('somethings')
33
-     * )
34
-     *
35
-     * @var array
36
-     */
37
-    public $label;
38
-
39
-
40
-    /**
41
-     * This property when set will hold the slugs of all EE admin pages that we will need to retrieve fields for
42
-     * (and used to determine which callback method to call from the child class)
43
-     *
44
-     * structure should be
45
-     * array(
46
-     * 'page_action' => true
47
-     * )
48
-     *
49
-     * @var array
50
-     */
51
-    public $admin_registered_pages = [];
52
-
53
-
54
-    /**
55
-     * this property holds any specific fields for holding any settings related to a messenger (if any needed)
56
-     *
57
-     * @var array
58
-     */
59
-    protected $_admin_settings_fields = [];
60
-
61
-
62
-    /**
63
-     * this property will hold any existing settings that may have been set in the admin.
64
-     *
65
-     * @var array
66
-     */
67
-    protected $_existing_admin_settings = [];
68
-
69
-
70
-    /**
71
-     * this property will hold an array of valid shortcodes for this message type and messengers.
72
-     * #For Message Types:
73
-     * This is an array of strings that correspond to defined EE_Shortcode libraries and per context.
74
-     * For example:
75
-     * array( 'admin' => array('transaction', 'event', 'attendee') )
76
-     * corresponds to 'EE_Transaction_Shortcodes.lib.php, EE_Event_Shortcodes.lib.php, EE_Attendee_Shortcodes.lib.php'
77
-     * for the admin context;
78
-     *
79
-     *
80
-     * #For Messengers:
81
-     * For example:
82
-     * array('subject' => array('transaction', 'event', 'attendee'))
83
-     * corresponds to 'EE_Transaction_Shortcodes.lib.php, EE_Event_Shortcodes.lib.php, EE_Attendee_Shortcodes.lib.php'
84
-     * for the 'subject' field;
85
-     * NOTE:  by default, with messengers, if the valid shortcodes for a field is left blank,
86
-     * that field will inherit whatever are set as valid shortcodes by message_type.
87
-     * This is so messenger can set specific valid codes for fields and leave other
88
-     * valid shortcodes up to the message type matched with the messenger.
89
-     *
90
-     * @access protected
91
-     * @var array
92
-     */
93
-    protected $_valid_shortcodes = [];
94
-
95
-
96
-    public function __construct()
97
-    {
98
-        $this->_set_admin_settings_fields();
99
-        $this->_set_valid_shortcodes();
100
-        $this->_set_admin_pages();
101
-    }
102
-
103
-
104
-    /**
105
-     * sets the _admin_settings_fields property which needs to be defined by child classes.
106
-     * You will want to set the _admin_settings_fields properties as a multi-dimensional array with the following format
107
-     * array(
108
-     *      {field_name - also used for setting index} => array(
109
-     *          'field_type' => {type of field: 'text', 'textarea', 'checkbox'},
110
-     *          'value_type' => {type of value: 'string', 'int', 'array', 'bool'},
111
-     *          'required' => {bool, required or not},
112
-     *          'validation' => {bool, true if we want validation, false if not},
113
-     *          'format' => {%d, or %s},
114
-     *          'label' => {label for the field, make sure it's localized},
115
-     *          'default' => {default value for the setting}
116
-     *      ),
117
-     * );
118
-     *
119
-     * @abstract
120
-     * @access protected
121
-     * @return void
122
-     */
123
-    abstract protected function _set_admin_settings_fields();
124
-
125
-
126
-    /**
127
-     * sets any properties on whether a message type or messenger interface shows up on a ee administration page.
128
-     * Child classes have to define this method but don't necessarily have to set the flags
129
-     * as they will be set to false by default.
130
-     *
131
-     * Child classes use this method to set the `_admin_registered_page` property.
132
-     * That property is to indicate what EE admin pages we have a corresponding callback for in the child class
133
-     * so Message Type/messenger fields/content is included on that admin page.
134
-     *
135
-     * @abstract
136
-     * @access protected
137
-     * @return void
138
-     */
139
-    abstract protected function _set_admin_pages();
140
-
141
-
142
-    /**
143
-     * Child classes must declare the $_valid_shortcodes property using this method.
144
-     * See comments for $_valid_shortcodes property for details on what it is used for.
145
-     *
146
-     * @access protected
147
-     * @return void
148
-     */
149
-    abstract protected function _set_valid_shortcodes();
150
-
151
-
152
-    /**
153
-     * sets the _existing_admin_settings property can be overridden by child classes.
154
-     * We do this so we only do database calls if needed.
155
-     *
156
-     * @access protected
157
-     * @param string $messenger
158
-     * @throws EE_Error
159
-     * @throws ReflectionException
160
-     */
161
-    protected function _set_existing_admin_settings($messenger = '')
162
-    {
163
-        /** @var EE_Message_Resource_Manager $Message_Resource_Manager */
164
-        $Message_Resource_Manager = EE_Registry::instance()->load_lib('Message_Resource_Manager');
165
-        $active_messengers        = $Message_Resource_Manager->get_active_messengers_option();
166
-        $settings_to_use          = $active_messengers;
167
-
168
-        /**
169
-         * This determines what will be used for the getting the settings.
170
-         */
171
-        if (
172
-            ! empty($messenger)
173
-            && $Message_Resource_Manager->is_message_type_active_for_messenger($messenger, $this->name)
174
-        ) {
175
-            $settings_to_use = $active_messengers[ $messenger ]['settings'][ $messenger . '-message_types' ];
176
-        }
177
-
178
-        $this->_existing_admin_settings = $settings_to_use[ $this->name ]['settings'] ?? null;
179
-    }
180
-
181
-
182
-    /**
183
-     * get_existing_admin_settings
184
-     * (if needed) sets and returns the _existing_admin_settings property.
185
-     *
186
-     * @access public
187
-     * @param string $messenger
188
-     * @return array          settings
189
-     * @throws EE_Error
190
-     * @throws ReflectionException
191
-     */
192
-    public function get_existing_admin_settings($messenger = '')
193
-    {
194
-        // if admin_settings property empty lets try setting it.
195
-        if (method_exists($this, '_set_existing_admin_settings') && empty($this->_existing_admin_settings)) {
196
-            $this->_set_existing_admin_settings($messenger);
197
-        }
198
-        return property_exists($this, '_existing_admin_settings')
199
-            ? $this->_existing_admin_settings
200
-            : null;
201
-    }
202
-
203
-
204
-    /**
205
-     * This returns the array of valid shortcodes for a message type or messenger as set by the child in the
206
-     * $_valid_shortcode property.
207
-     *
208
-     * @return array   an array of valid shortcodes.
209
-     */
210
-    public function get_valid_shortcodes()
211
-    {
212
-        $valid_shortcodes = apply_filters(
213
-            'FHEE__' . get_class($this) . '__get_valid_shortcodes',
214
-            $this->_valid_shortcodes,
215
-            $this
216
-        );
217
-        // The below filter applies to ALL messengers and message types so use with care!
218
-        return apply_filters('FHEE__EE_Messages_Base__get_valid_shortcodes', $valid_shortcodes, $this);
219
-    }
220
-
221
-
222
-    /**
223
-     * getter that returns the protected admin_settings_fields property
224
-     *
225
-     * @access public
226
-     * @return array admin settings fields
227
-     */
228
-    public function get_admin_settings_fields()
229
-    {
230
-        return $this->_admin_settings_fields;
231
-    }
232
-
233
-
234
-    /**
235
-     * this public method accepts a page slug (for an EE_admin page)
236
-     * and will return the response from the child class callback function
237
-     * if that page is registered via the `_admin_registered_page` property set by the child class.
238
-     *
239
-     * @param string $page    the slug of the EE admin page
240
-     * @param array  $actives an array of active message type (or messenger) objects.
241
-     * @param string $action  the page action (to allow for more specific handling - i.e. edit vs. add pages)
242
-     * @param array  $extra   This is just an extra argument that can be used
243
-     *                        to pass additional data for setting up page content.
244
-     * @access protected
245
-     * @return string $content for page.
246
-     */
247
-    protected function _get_admin_page_content($page, $action, $extra, $actives)
248
-    {
249
-        // we can also further refine the context by action (if present).
250
-        if (! empty($action)) {
251
-            $page = $page . '_' . $action;
252
-        }
253
-
254
-        if (! isset($this->admin_registered_pages[ $page ])) {
255
-            // todo: a place to throw an exception?
256
-            // We need to indicate there is no registered page so this function is not being called correctly.
257
-            return false;
258
-        }
259
-        // k made it here so let's call the method
260
-        $content = call_user_func_array(
261
-            [$this, '_get_admin_content_' . $page],
262
-            [$actives, $extra]
263
-        );
264
-        if ($content === false) {
265
-            // todo this needs to be an exception once we've got exceptions in place.
266
-            return false;
267
-        }
268
-        return $content;
269
-    }
270
-
271
-
272
-    /**
273
-     * Allows a message type to specifically exclude template fields for the provided messenger.
274
-     * Filtered so this can be programmatically altered as well.
275
-     *
276
-     * @param string $messenger_name name of messenger
277
-     * @return array
278
-     */
279
-    public function excludedFieldsForMessenger($messenger_name)
280
-    {
281
-        return apply_filters(
282
-            'FHEE__EE_Messages_Base__excludedFieldForMessenger',
283
-            [],
284
-            $messenger_name,
285
-            $this->name,
286
-            $this
287
-        );
288
-    }
14
+	/** DETAILS PROPERTIES **/
15
+	/**
16
+	 * The following are used to hold details on the type for reference (i.e. on admin screens)
17
+	 * and also used by the EE_message_type object to figure out where to get template data.
18
+	 */
19
+	public $name;
20
+
21
+	public $description;
22
+
23
+	protected $_messages_item_type; // messenger OR message_type?
24
+
25
+
26
+	/**
27
+	 * This is an array describing the ui facing labels
28
+	 * that will be used whenever the messenger is referenced in the ui
29
+	 *
30
+	 * array(
31
+	 *  'singular' => esc_html__('something'),
32
+	 *  'plural' => esc_html__('somethings')
33
+	 * )
34
+	 *
35
+	 * @var array
36
+	 */
37
+	public $label;
38
+
39
+
40
+	/**
41
+	 * This property when set will hold the slugs of all EE admin pages that we will need to retrieve fields for
42
+	 * (and used to determine which callback method to call from the child class)
43
+	 *
44
+	 * structure should be
45
+	 * array(
46
+	 * 'page_action' => true
47
+	 * )
48
+	 *
49
+	 * @var array
50
+	 */
51
+	public $admin_registered_pages = [];
52
+
53
+
54
+	/**
55
+	 * this property holds any specific fields for holding any settings related to a messenger (if any needed)
56
+	 *
57
+	 * @var array
58
+	 */
59
+	protected $_admin_settings_fields = [];
60
+
61
+
62
+	/**
63
+	 * this property will hold any existing settings that may have been set in the admin.
64
+	 *
65
+	 * @var array
66
+	 */
67
+	protected $_existing_admin_settings = [];
68
+
69
+
70
+	/**
71
+	 * this property will hold an array of valid shortcodes for this message type and messengers.
72
+	 * #For Message Types:
73
+	 * This is an array of strings that correspond to defined EE_Shortcode libraries and per context.
74
+	 * For example:
75
+	 * array( 'admin' => array('transaction', 'event', 'attendee') )
76
+	 * corresponds to 'EE_Transaction_Shortcodes.lib.php, EE_Event_Shortcodes.lib.php, EE_Attendee_Shortcodes.lib.php'
77
+	 * for the admin context;
78
+	 *
79
+	 *
80
+	 * #For Messengers:
81
+	 * For example:
82
+	 * array('subject' => array('transaction', 'event', 'attendee'))
83
+	 * corresponds to 'EE_Transaction_Shortcodes.lib.php, EE_Event_Shortcodes.lib.php, EE_Attendee_Shortcodes.lib.php'
84
+	 * for the 'subject' field;
85
+	 * NOTE:  by default, with messengers, if the valid shortcodes for a field is left blank,
86
+	 * that field will inherit whatever are set as valid shortcodes by message_type.
87
+	 * This is so messenger can set specific valid codes for fields and leave other
88
+	 * valid shortcodes up to the message type matched with the messenger.
89
+	 *
90
+	 * @access protected
91
+	 * @var array
92
+	 */
93
+	protected $_valid_shortcodes = [];
94
+
95
+
96
+	public function __construct()
97
+	{
98
+		$this->_set_admin_settings_fields();
99
+		$this->_set_valid_shortcodes();
100
+		$this->_set_admin_pages();
101
+	}
102
+
103
+
104
+	/**
105
+	 * sets the _admin_settings_fields property which needs to be defined by child classes.
106
+	 * You will want to set the _admin_settings_fields properties as a multi-dimensional array with the following format
107
+	 * array(
108
+	 *      {field_name - also used for setting index} => array(
109
+	 *          'field_type' => {type of field: 'text', 'textarea', 'checkbox'},
110
+	 *          'value_type' => {type of value: 'string', 'int', 'array', 'bool'},
111
+	 *          'required' => {bool, required or not},
112
+	 *          'validation' => {bool, true if we want validation, false if not},
113
+	 *          'format' => {%d, or %s},
114
+	 *          'label' => {label for the field, make sure it's localized},
115
+	 *          'default' => {default value for the setting}
116
+	 *      ),
117
+	 * );
118
+	 *
119
+	 * @abstract
120
+	 * @access protected
121
+	 * @return void
122
+	 */
123
+	abstract protected function _set_admin_settings_fields();
124
+
125
+
126
+	/**
127
+	 * sets any properties on whether a message type or messenger interface shows up on a ee administration page.
128
+	 * Child classes have to define this method but don't necessarily have to set the flags
129
+	 * as they will be set to false by default.
130
+	 *
131
+	 * Child classes use this method to set the `_admin_registered_page` property.
132
+	 * That property is to indicate what EE admin pages we have a corresponding callback for in the child class
133
+	 * so Message Type/messenger fields/content is included on that admin page.
134
+	 *
135
+	 * @abstract
136
+	 * @access protected
137
+	 * @return void
138
+	 */
139
+	abstract protected function _set_admin_pages();
140
+
141
+
142
+	/**
143
+	 * Child classes must declare the $_valid_shortcodes property using this method.
144
+	 * See comments for $_valid_shortcodes property for details on what it is used for.
145
+	 *
146
+	 * @access protected
147
+	 * @return void
148
+	 */
149
+	abstract protected function _set_valid_shortcodes();
150
+
151
+
152
+	/**
153
+	 * sets the _existing_admin_settings property can be overridden by child classes.
154
+	 * We do this so we only do database calls if needed.
155
+	 *
156
+	 * @access protected
157
+	 * @param string $messenger
158
+	 * @throws EE_Error
159
+	 * @throws ReflectionException
160
+	 */
161
+	protected function _set_existing_admin_settings($messenger = '')
162
+	{
163
+		/** @var EE_Message_Resource_Manager $Message_Resource_Manager */
164
+		$Message_Resource_Manager = EE_Registry::instance()->load_lib('Message_Resource_Manager');
165
+		$active_messengers        = $Message_Resource_Manager->get_active_messengers_option();
166
+		$settings_to_use          = $active_messengers;
167
+
168
+		/**
169
+		 * This determines what will be used for the getting the settings.
170
+		 */
171
+		if (
172
+			! empty($messenger)
173
+			&& $Message_Resource_Manager->is_message_type_active_for_messenger($messenger, $this->name)
174
+		) {
175
+			$settings_to_use = $active_messengers[ $messenger ]['settings'][ $messenger . '-message_types' ];
176
+		}
177
+
178
+		$this->_existing_admin_settings = $settings_to_use[ $this->name ]['settings'] ?? null;
179
+	}
180
+
181
+
182
+	/**
183
+	 * get_existing_admin_settings
184
+	 * (if needed) sets and returns the _existing_admin_settings property.
185
+	 *
186
+	 * @access public
187
+	 * @param string $messenger
188
+	 * @return array          settings
189
+	 * @throws EE_Error
190
+	 * @throws ReflectionException
191
+	 */
192
+	public function get_existing_admin_settings($messenger = '')
193
+	{
194
+		// if admin_settings property empty lets try setting it.
195
+		if (method_exists($this, '_set_existing_admin_settings') && empty($this->_existing_admin_settings)) {
196
+			$this->_set_existing_admin_settings($messenger);
197
+		}
198
+		return property_exists($this, '_existing_admin_settings')
199
+			? $this->_existing_admin_settings
200
+			: null;
201
+	}
202
+
203
+
204
+	/**
205
+	 * This returns the array of valid shortcodes for a message type or messenger as set by the child in the
206
+	 * $_valid_shortcode property.
207
+	 *
208
+	 * @return array   an array of valid shortcodes.
209
+	 */
210
+	public function get_valid_shortcodes()
211
+	{
212
+		$valid_shortcodes = apply_filters(
213
+			'FHEE__' . get_class($this) . '__get_valid_shortcodes',
214
+			$this->_valid_shortcodes,
215
+			$this
216
+		);
217
+		// The below filter applies to ALL messengers and message types so use with care!
218
+		return apply_filters('FHEE__EE_Messages_Base__get_valid_shortcodes', $valid_shortcodes, $this);
219
+	}
220
+
221
+
222
+	/**
223
+	 * getter that returns the protected admin_settings_fields property
224
+	 *
225
+	 * @access public
226
+	 * @return array admin settings fields
227
+	 */
228
+	public function get_admin_settings_fields()
229
+	{
230
+		return $this->_admin_settings_fields;
231
+	}
232
+
233
+
234
+	/**
235
+	 * this public method accepts a page slug (for an EE_admin page)
236
+	 * and will return the response from the child class callback function
237
+	 * if that page is registered via the `_admin_registered_page` property set by the child class.
238
+	 *
239
+	 * @param string $page    the slug of the EE admin page
240
+	 * @param array  $actives an array of active message type (or messenger) objects.
241
+	 * @param string $action  the page action (to allow for more specific handling - i.e. edit vs. add pages)
242
+	 * @param array  $extra   This is just an extra argument that can be used
243
+	 *                        to pass additional data for setting up page content.
244
+	 * @access protected
245
+	 * @return string $content for page.
246
+	 */
247
+	protected function _get_admin_page_content($page, $action, $extra, $actives)
248
+	{
249
+		// we can also further refine the context by action (if present).
250
+		if (! empty($action)) {
251
+			$page = $page . '_' . $action;
252
+		}
253
+
254
+		if (! isset($this->admin_registered_pages[ $page ])) {
255
+			// todo: a place to throw an exception?
256
+			// We need to indicate there is no registered page so this function is not being called correctly.
257
+			return false;
258
+		}
259
+		// k made it here so let's call the method
260
+		$content = call_user_func_array(
261
+			[$this, '_get_admin_content_' . $page],
262
+			[$actives, $extra]
263
+		);
264
+		if ($content === false) {
265
+			// todo this needs to be an exception once we've got exceptions in place.
266
+			return false;
267
+		}
268
+		return $content;
269
+	}
270
+
271
+
272
+	/**
273
+	 * Allows a message type to specifically exclude template fields for the provided messenger.
274
+	 * Filtered so this can be programmatically altered as well.
275
+	 *
276
+	 * @param string $messenger_name name of messenger
277
+	 * @return array
278
+	 */
279
+	public function excludedFieldsForMessenger($messenger_name)
280
+	{
281
+		return apply_filters(
282
+			'FHEE__EE_Messages_Base__excludedFieldForMessenger',
283
+			[],
284
+			$messenger_name,
285
+			$this->name,
286
+			$this
287
+		);
288
+	}
289 289
 }
Please login to merge, or discard this patch.
Spacing   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -172,10 +172,10 @@  discard block
 block discarded – undo
172 172
             ! empty($messenger)
173 173
             && $Message_Resource_Manager->is_message_type_active_for_messenger($messenger, $this->name)
174 174
         ) {
175
-            $settings_to_use = $active_messengers[ $messenger ]['settings'][ $messenger . '-message_types' ];
175
+            $settings_to_use = $active_messengers[$messenger]['settings'][$messenger.'-message_types'];
176 176
         }
177 177
 
178
-        $this->_existing_admin_settings = $settings_to_use[ $this->name ]['settings'] ?? null;
178
+        $this->_existing_admin_settings = $settings_to_use[$this->name]['settings'] ?? null;
179 179
     }
180 180
 
181 181
 
@@ -210,7 +210,7 @@  discard block
 block discarded – undo
210 210
     public function get_valid_shortcodes()
211 211
     {
212 212
         $valid_shortcodes = apply_filters(
213
-            'FHEE__' . get_class($this) . '__get_valid_shortcodes',
213
+            'FHEE__'.get_class($this).'__get_valid_shortcodes',
214 214
             $this->_valid_shortcodes,
215 215
             $this
216 216
         );
@@ -247,18 +247,18 @@  discard block
 block discarded – undo
247 247
     protected function _get_admin_page_content($page, $action, $extra, $actives)
248 248
     {
249 249
         // we can also further refine the context by action (if present).
250
-        if (! empty($action)) {
251
-            $page = $page . '_' . $action;
250
+        if ( ! empty($action)) {
251
+            $page = $page.'_'.$action;
252 252
         }
253 253
 
254
-        if (! isset($this->admin_registered_pages[ $page ])) {
254
+        if ( ! isset($this->admin_registered_pages[$page])) {
255 255
             // todo: a place to throw an exception?
256 256
             // We need to indicate there is no registered page so this function is not being called correctly.
257 257
             return false;
258 258
         }
259 259
         // k made it here so let's call the method
260 260
         $content = call_user_func_array(
261
-            [$this, '_get_admin_content_' . $page],
261
+            [$this, '_get_admin_content_'.$page],
262 262
             [$actives, $extra]
263 263
         );
264 264
         if ($content === false) {
Please login to merge, or discard this patch.
core/libraries/messages/messenger/EE_Html_messenger.class.php 2 patches
Indentation   +547 added lines, -547 removed lines patch added patch discarded remove patch
@@ -10,551 +10,551 @@
 block discarded – undo
10 10
  */
11 11
 class EE_Html_messenger extends EE_messenger
12 12
 {
13
-    /**
14
-     * The following are the properties that this messenger requires for displaying the html
15
-     */
16
-    /**
17
-     * This is the html body generated by the template via the message type.
18
-     *
19
-     * @var string
20
-     */
21
-    protected $_content = '';
22
-
23
-    /**
24
-     * This is for the page title that gets displayed.  (Why use "subject"?  Because the "title" tag in html is
25
-     * equivalent to the "subject" of the page.
26
-     *
27
-     * @var string
28
-     */
29
-    protected $_subject = '';
30
-
31
-
32
-    /**
33
-     * @throws EE_Error
34
-     * @throws ReflectionException
35
-     */
36
-    public function __construct()
37
-    {
38
-        // set properties
39
-        $this->name                = 'html';
40
-        $this->description         = esc_html__(
41
-            'This messenger outputs a message to a browser for display.',
42
-            'event_espresso'
43
-        );
44
-        $this->label               = [
45
-            'singular' => esc_html__('html', 'event_espresso'),
46
-            'plural'   => esc_html__('html', 'event_espresso'),
47
-        ];
48
-        $this->activate_on_install = true;
49
-        // add the "powered by EE" credit link to the HTML receipt and invoice
50
-        add_filter(
51
-            'FHEE__EE_Html_messenger___send_message__main_body',
52
-            [$this, 'add_powered_by_credit_link_to_receipt_and_invoice'],
53
-            10,
54
-            3
55
-        );
56
-        parent::__construct();
57
-    }
58
-
59
-
60
-    /**
61
-     * HTML Messenger desires execution immediately.
62
-     *
63
-     * @return bool
64
-     * @since  4.9.0
65
-     * @see    parent::send_now() for documentation.
66
-     */
67
-    public function send_now(): bool
68
-    {
69
-        return true;
70
-    }
71
-
72
-
73
-    /**
74
-     * HTML Messenger allows an empty to field.
75
-     *
76
-     * @return bool
77
-     * @since  4.9.0
78
-     * @see    parent::allow_empty_to_field() for documentation
79
-     */
80
-    public function allow_empty_to_field(): bool
81
-    {
82
-        return true;
83
-    }
84
-
85
-
86
-    /**
87
-     * @see abstract declaration in EE_messenger for details.
88
-     */
89
-    protected function _set_admin_pages()
90
-    {
91
-        $this->admin_registered_pages = ['events_edit' => true];
92
-    }
93
-
94
-
95
-    /**
96
-     * @see abstract declaration in EE_messenger for details.
97
-     */
98
-    protected function _set_valid_shortcodes()
99
-    {
100
-        $this->_valid_shortcodes = [];
101
-    }
102
-
103
-
104
-    /**
105
-     * @see abstract declaration in EE_messenger for details.
106
-     */
107
-    protected function _set_validator_config()
108
-    {
109
-        $this->_validator_config = [
110
-            'subject'                       => [
111
-                'shortcodes' => ['organization', 'primary_registration_details', 'email', 'transaction'],
112
-            ],
113
-            'content'                       => [
114
-                'shortcodes' => [
115
-                    'organization',
116
-                    'primary_registration_list',
117
-                    'primary_registration_details',
118
-                    'email',
119
-                    'transaction',
120
-                    'event_list',
121
-                    'payment_list',
122
-                    'venue',
123
-                    'line_item_list',
124
-                    'messenger',
125
-                    'ticket_list',
126
-                ],
127
-            ],
128
-            'event_list'                    => [
129
-                'shortcodes' => [
130
-                    'event',
131
-                    'ticket_list',
132
-                    'venue',
133
-                    'primary_registration_details',
134
-                    'primary_registration_list',
135
-                    'event_author',
136
-                ],
137
-                'required'   => ['[EVENT_LIST]'],
138
-            ],
139
-            'ticket_list'                   => [
140
-                'shortcodes' => [
141
-                    'attendee_list',
142
-                    'ticket',
143
-                    'datetime_list',
144
-                    'primary_registration_details',
145
-                    'line_item_list',
146
-                    'venue',
147
-                ],
148
-                'required'   => ['[TICKET_LIST]'],
149
-            ],
150
-            'ticket_line_item_no_pms'       => [
151
-                'shortcodes' => ['line_item', 'ticket'],
152
-                'required'   => ['[TICKET_LINE_ITEM_LIST]'],
153
-            ],
154
-            'ticket_line_item_pms'          => [
155
-                'shortcodes' => ['line_item', 'ticket', 'line_item_list'],
156
-                'required'   => ['[TICKET_LINE_ITEM_LIST]'],
157
-            ],
158
-            'price_modifier_line_item_list' => [
159
-                'shortcodes' => ['line_item'],
160
-                'required'   => ['[PRICE_MODIFIER_LINE_ITEM_LIST]'],
161
-            ],
162
-            'datetime_list'                 => [
163
-                'shortcodes' => ['datetime'],
164
-                'required'   => ['[DATETIME_LIST]'],
165
-            ],
166
-            'attendee_list'                 => [
167
-                'shortcodes' => ['attendee'],
168
-                'required'   => ['[ATTENDEE_LIST]'],
169
-            ],
170
-            'tax_line_item_list'            => [
171
-                'shortcodes' => ['line_item'],
172
-                'required'   => ['[TAX_LINE_ITEM_LIST]'],
173
-            ],
174
-            'additional_line_item_list'     => [
175
-                'shortcodes' => ['line_item'],
176
-                'required'   => ['[ADDITIONAL_LINE_ITEM_LIST]'],
177
-            ],
178
-            'payment_list'                  => [
179
-                'shortcodes' => ['payment'],
180
-                'required'   => ['[PAYMENT_LIST_*]'],
181
-            ],
182
-        ];
183
-    }
184
-
185
-
186
-    /**
187
-     * This is a method called from EE_messages when this messenger is a generating messenger and the sending messenger
188
-     * is a different messenger.  Child messengers can set hooks for the sending messenger to callback on if necessary
189
-     * (i.e. swap out css files or something else).
190
-     *
191
-     * @param string $sending_messenger_name the name of the sending messenger so we only set the hooks needed.
192
-     * @return void
193
-     * @since 4.5.0
194
-     */
195
-    public function do_secondary_messenger_hooks($sending_messenger_name)
196
-    {
197
-        if ($sending_messenger_name === 'pdf') {
198
-            add_filter('EE_messenger__get_variation__variation', [$this, 'add_html_css'], 10, 8);
199
-        }
200
-    }
201
-
202
-
203
-    /**
204
-     * @param                            $variation_path
205
-     * @param EE_Messages_Template_Pack  $template_pack
206
-     * @param                            $messenger_name
207
-     * @param                            $message_type_name
208
-     * @param                            $url
209
-     * @param                            $type
210
-     * @param                            $variation
211
-     * @param                            $skip_filters
212
-     * @return string
213
-     */
214
-    public function add_html_css(
215
-        $variation_path,
216
-        EE_Messages_Template_Pack $template_pack,
217
-        $messenger_name,
218
-        $message_type_name,
219
-        $url,
220
-        $type,
221
-        $variation,
222
-        $skip_filters
223
-    ): string {
224
-        return $template_pack->get_variation(
225
-            $this->name,
226
-            $message_type_name,
227
-            $type,
228
-            $variation,
229
-            $url,
230
-            '.css',
231
-            $skip_filters
232
-        );
233
-    }
234
-
235
-
236
-    /**
237
-     * Takes care of enqueuing any necessary scripts or styles for the page.  A do_action() so message types using this
238
-     * messenger can add their own js.
239
-     *
240
-     * @return void.
241
-     */
242
-    public function enqueue_scripts_styles()
243
-    {
244
-        parent::enqueue_scripts_styles();
245
-        do_action('AHEE__EE_Html_messenger__enqueue_scripts_styles');
246
-    }
247
-
248
-
249
-    /**
250
-     * _set_template_fields
251
-     * This sets up the fields that a messenger requires for the message to go out.
252
-     *
253
-     * @access  protected
254
-     * @return void
255
-     */
256
-    protected function _set_template_fields()
257
-    {
258
-        // any extra template fields that are NOT used by the messenger
259
-        // but will get used by a messenger field for shortcode replacement
260
-        // get added to the 'extra' key in an associated array
261
-        // indexed by the messenger field they relate to.
262
-        // This is important for the Messages_admin to know what fields to display to the user.
263
-        // Also, notice that the "values" are equal to the field type
264
-        // that messages admin will use to know what kind of field to display.
265
-        // The values ALSO have one index labeled "shortcode".
266
-        // The values in that array indicate which ACTUAL SHORTCODE (i.e. [SHORTCODE])
267
-        // is required in order for this extra field to be displayed.
268
-        //  If the required shortcode isn't part of the shortcodes array
269
-        // then the field is not needed and will not be displayed/parsed.
270
-        $this->_template_fields = [
271
-            'subject' => [
272
-                'input'      => 'text',
273
-                'label'      => esc_html__('Page Title', 'event_espresso'),
274
-                'type'       => 'string',
275
-                'required'   => true,
276
-                'validation' => true,
277
-                'css_class'  => 'large-text',
278
-                'format'     => '%s',
279
-            ],
280
-            'content' => '',
281
-            // left empty b/c it is in the "extra array" but messenger still needs to know this is a field.
282
-            'extra'   => [
283
-                'content' => [
284
-                    'main'                          => [
285
-                        'input'      => 'wp_editor',
286
-                        'label'      => esc_html__('Main Content', 'event_espresso'),
287
-                        'type'       => 'string',
288
-                        'required'   => false,
289
-                        'validation' => true,
290
-                        'format'     => '%s',
291
-                        'rows'       => '15',
292
-                    ],
293
-                    'event_list'                    => [
294
-                        'input'               => 'wp_editor',
295
-                        'label'               => '[EVENT_LIST]',
296
-                        'type'                => 'string',
297
-                        'required'            => false,
298
-                        'validation'          => true,
299
-                        'format'              => '%s',
300
-                        'rows'                => '15',
301
-                        'shortcodes_required' => ['[EVENT_LIST]'],
302
-                    ],
303
-                    'ticket_list'                   => [
304
-                        'input'               => 'textarea',
305
-                        'label'               => '[TICKET_LIST]',
306
-                        'type'                => 'string',
307
-                        'required'            => false,
308
-                        'validation'          => true,
309
-                        'format'              => '%s',
310
-                        'css_class'           => 'large-text',
311
-                        'rows'                => '10',
312
-                        'shortcodes_required' => ['[TICKET_LIST]'],
313
-                    ],
314
-                    'ticket_line_item_no_pms'       => [
315
-                        'input'               => 'textarea',
316
-                        'label'               => '[TICKET_LINE_ITEM_LIST] <br>' . esc_html__(
317
-                                'Ticket Line Item List with no Price Modifiers',
318
-                                'event_espresso'
319
-                            ),
320
-                        'type'                => 'string',
321
-                        'required'            => false,
322
-                        'validation'          => true,
323
-                        'format'              => '%s',
324
-                        'css_class'           => 'large-text',
325
-                        'rows'                => '5',
326
-                        'shortcodes_required' => ['[TICKET_LINE_ITEM_LIST]'],
327
-                    ],
328
-                    'ticket_line_item_pms'          => [
329
-                        'input'               => 'textarea',
330
-                        'label'               => '[TICKET_LINE_ITEM_LIST] <br>' . esc_html__(
331
-                                'Ticket Line Item List with Price Modifiers',
332
-                                'event_espresso'
333
-                            ),
334
-                        'type'                => 'string',
335
-                        'required'            => false,
336
-                        'validation'          => true,
337
-                        'format'              => '%s',
338
-                        'css_class'           => 'large-text',
339
-                        'rows'                => '5',
340
-                        'shortcodes_required' => ['[TICKET_LINE_ITEM_LIST]'],
341
-                    ],
342
-                    'price_modifier_line_item_list' => [
343
-                        'input'               => 'textarea',
344
-                        'label'               => '[PRICE_MODIFIER_LINE_ITEM_LIST]',
345
-                        'type'                => 'string',
346
-                        'required'            => false,
347
-                        'validation'          => true,
348
-                        'format'              => '%s',
349
-                        'css_class'           => 'large-text',
350
-                        'rows'                => '5',
351
-                        'shortcodes_required' => ['[PRICE_MODIFIER_LINE_ITEM_LIST]'],
352
-                    ],
353
-                    'datetime_list'                 => [
354
-                        'input'               => 'textarea',
355
-                        'label'               => '[DATETIME_LIST]',
356
-                        'type'                => 'string',
357
-                        'required'            => false,
358
-                        'validation'          => true,
359
-                        'format'              => '%s',
360
-                        'css_class'           => 'large-text',
361
-                        'rows'                => '5',
362
-                        'shortcodes_required' => ['[DATETIME_LIST]'],
363
-                    ],
364
-                    'attendee_list'                 => [
365
-                        'input'               => 'textarea',
366
-                        'label'               => '[ATTENDEE_LIST]',
367
-                        'type'                => 'string',
368
-                        'required'            => false,
369
-                        'validation'          => true,
370
-                        'format'              => '%s',
371
-                        'css_class'           => 'large-text',
372
-                        'rows'                => '5',
373
-                        'shortcodes_required' => ['[ATTENDEE_LIST]'],
374
-                    ],
375
-                    'tax_line_item_list'            => [
376
-                        'input'               => 'textarea',
377
-                        'label'               => '[TAX_LINE_ITEM_LIST]',
378
-                        'type'                => 'string',
379
-                        'required'            => false,
380
-                        'validation'          => true,
381
-                        'format'              => '%s',
382
-                        'css_class'           => 'large-text',
383
-                        'rows'                => '5',
384
-                        'shortcodes_required' => ['[TAX_LINE_ITEM_LIST]'],
385
-                    ],
386
-                    'additional_line_item_list'     => [
387
-                        'input'               => 'textarea',
388
-                        'label'               => '[ADDITIONAL_LINE_ITEM_LIST]',
389
-                        'type'                => 'string',
390
-                        'required'            => false,
391
-                        'validation'          => true,
392
-                        'format'              => '%s',
393
-                        'css_class'           => 'large-text',
394
-                        'rows'                => '5',
395
-                        'shortcodes_required' => ['[ADDITIONAL_LINE_ITEM_LIST]'],
396
-                    ],
397
-                    'payment_list'                  => [
398
-                        'input'               => 'textarea',
399
-                        'label'               => '[PAYMENT_LIST]',
400
-                        'type'                => 'string',
401
-                        'required'            => false,
402
-                        'validation'          => true,
403
-                        'format'              => '%s',
404
-                        'css_class'           => 'large-text',
405
-                        'rows'                => '5',
406
-                        'shortcodes_required' => ['[PAYMENT_LIST_*]'],
407
-                    ],
408
-                ],
409
-            ],
410
-        ];
411
-    }
412
-
413
-
414
-    /**
415
-     * @see   definition of this method in parent
416
-     * @since 4.5.0
417
-     */
418
-    protected function _set_default_message_types()
419
-    {
420
-        $this->_default_message_types = ['receipt', 'invoice'];
421
-    }
422
-
423
-
424
-    /**
425
-     * @see   definition of this method in parent
426
-     * @since 4.5.0
427
-     */
428
-    protected function _set_valid_message_types()
429
-    {
430
-        $this->_valid_message_types = ['receipt', 'invoice'];
431
-    }
432
-
433
-
434
-    /**
435
-     * Displays the message in the browser.
436
-     *
437
-     * @return void.
438
-     * @since 4.5.0
439
-     */
440
-    protected function _send_message()
441
-    {
442
-        $this->_template_args = [
443
-            'page_title' => $this->_subject,
444
-            'base_css'   => $this->get_variation(
445
-                $this->_tmp_pack,
446
-                $this->_incoming_message_type->name,
447
-                true,
448
-                'base',
449
-                $this->_variation
450
-            ),
451
-            'print_css'  => $this->get_variation(
452
-                $this->_tmp_pack,
453
-                $this->_incoming_message_type->name,
454
-                true,
455
-                'print',
456
-                $this->_variation
457
-            ),
458
-            'main_css'   => $this->get_variation(
459
-                $this->_tmp_pack,
460
-                $this->_incoming_message_type->name,
461
-                true,
462
-                'main',
463
-                $this->_variation
464
-            ),
465
-            'main_body'  => wpautop(
466
-                apply_filters(
467
-                    'FHEE__EE_Html_messenger___send_message__main_body',
468
-                    (string) $this->_content,
469
-                    (string) $this->_content,
470
-                    $this->_incoming_message_type
471
-                ),
472
-                false
473
-            ),
474
-        ];
475
-        $this->_deregister_wp_hooks();
476
-        add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts_styles']);
477
-        echo($this->_get_main_template());
478
-        exit();
479
-    }
480
-
481
-
482
-    /**
483
-     * The purpose of this function is to de register all actions hooked into wp_head and wp_footer so that it doesn't
484
-     * interfere with our templates.  If users want to add any custom styles or scripts they must use the
485
-     * AHEE__EE_Html_messenger__enqueue_scripts_styles hook.
486
-     *
487
-     * @return void
488
-     * @since 4.5.0
489
-     */
490
-    protected function _deregister_wp_hooks()
491
-    {
492
-        remove_all_actions('wp_head');
493
-        remove_all_actions('wp_footer');
494
-        remove_all_actions('wp_print_footer_scripts');
495
-        remove_all_actions('wp_enqueue_scripts');
496
-        global $wp_scripts, $wp_styles;
497
-        $wp_scripts = $wp_styles = [];
498
-        // just add back in wp_enqueue_scripts and wp_print_footer_scripts cause that's all we want to load.
499
-        add_action('wp_footer', 'wp_print_footer_scripts');
500
-        add_action('wp_print_footer_scripts', '_wp_footer_scripts');
501
-        add_action('wp_head', 'wp_enqueue_scripts');
502
-    }
503
-
504
-
505
-    /**
506
-     * Overwrite parent _get_main_template for display_html purposes.
507
-     *
508
-     * @param bool $preview
509
-     * @return string
510
-     * @since  4.5.0
511
-     */
512
-    protected function _get_main_template($preview = false): string
513
-    {
514
-        $wrapper_template = $this->_tmp_pack->get_wrapper($this->name);
515
-        // include message type as a template arg
516
-        $this->_template_args['message_type'] = $this->_incoming_message_type;
517
-        return EEH_Template::display_template($wrapper_template, $this->_template_args, true);
518
-    }
519
-
520
-
521
-    /**
522
-     * @return void
523
-     */
524
-    protected function _preview()
525
-    {
526
-        $this->_send_message();
527
-    }
528
-
529
-
530
-    protected function _set_admin_settings_fields()
531
-    {
532
-    }
533
-
534
-
535
-    /**
536
-     * add the "powered by EE" credit link to the HTML receipt and invoice
537
-     *
538
-     * @param string|null     $content
539
-     * @param string|null     $content_again
540
-     * @param EE_message_type $incoming_message_type
541
-     * @return string
542
-     */
543
-    public function add_powered_by_credit_link_to_receipt_and_invoice(
544
-        ?string $content,
545
-        ?string $content_again,
546
-        EE_message_type $incoming_message_type
547
-    ): string {
548
-        if (
549
-            ($incoming_message_type->name === 'invoice' || $incoming_message_type->name === 'receipt')
550
-            && apply_filters('FHEE_EE_Html_messenger__add_powered_by_credit_link_to_receipt_and_invoice', true)
551
-        ) {
552
-            $content .= EEH_Template::powered_by_event_espresso(
553
-                    'aln-cntr',
554
-                    '',
555
-                    ['utm_content' => 'messages_system']
556
-                ) . EEH_HTML::div(EEH_HTML::p('&nbsp;'));
557
-        }
558
-        return $content;
559
-    }
13
+	/**
14
+	 * The following are the properties that this messenger requires for displaying the html
15
+	 */
16
+	/**
17
+	 * This is the html body generated by the template via the message type.
18
+	 *
19
+	 * @var string
20
+	 */
21
+	protected $_content = '';
22
+
23
+	/**
24
+	 * This is for the page title that gets displayed.  (Why use "subject"?  Because the "title" tag in html is
25
+	 * equivalent to the "subject" of the page.
26
+	 *
27
+	 * @var string
28
+	 */
29
+	protected $_subject = '';
30
+
31
+
32
+	/**
33
+	 * @throws EE_Error
34
+	 * @throws ReflectionException
35
+	 */
36
+	public function __construct()
37
+	{
38
+		// set properties
39
+		$this->name                = 'html';
40
+		$this->description         = esc_html__(
41
+			'This messenger outputs a message to a browser for display.',
42
+			'event_espresso'
43
+		);
44
+		$this->label               = [
45
+			'singular' => esc_html__('html', 'event_espresso'),
46
+			'plural'   => esc_html__('html', 'event_espresso'),
47
+		];
48
+		$this->activate_on_install = true;
49
+		// add the "powered by EE" credit link to the HTML receipt and invoice
50
+		add_filter(
51
+			'FHEE__EE_Html_messenger___send_message__main_body',
52
+			[$this, 'add_powered_by_credit_link_to_receipt_and_invoice'],
53
+			10,
54
+			3
55
+		);
56
+		parent::__construct();
57
+	}
58
+
59
+
60
+	/**
61
+	 * HTML Messenger desires execution immediately.
62
+	 *
63
+	 * @return bool
64
+	 * @since  4.9.0
65
+	 * @see    parent::send_now() for documentation.
66
+	 */
67
+	public function send_now(): bool
68
+	{
69
+		return true;
70
+	}
71
+
72
+
73
+	/**
74
+	 * HTML Messenger allows an empty to field.
75
+	 *
76
+	 * @return bool
77
+	 * @since  4.9.0
78
+	 * @see    parent::allow_empty_to_field() for documentation
79
+	 */
80
+	public function allow_empty_to_field(): bool
81
+	{
82
+		return true;
83
+	}
84
+
85
+
86
+	/**
87
+	 * @see abstract declaration in EE_messenger for details.
88
+	 */
89
+	protected function _set_admin_pages()
90
+	{
91
+		$this->admin_registered_pages = ['events_edit' => true];
92
+	}
93
+
94
+
95
+	/**
96
+	 * @see abstract declaration in EE_messenger for details.
97
+	 */
98
+	protected function _set_valid_shortcodes()
99
+	{
100
+		$this->_valid_shortcodes = [];
101
+	}
102
+
103
+
104
+	/**
105
+	 * @see abstract declaration in EE_messenger for details.
106
+	 */
107
+	protected function _set_validator_config()
108
+	{
109
+		$this->_validator_config = [
110
+			'subject'                       => [
111
+				'shortcodes' => ['organization', 'primary_registration_details', 'email', 'transaction'],
112
+			],
113
+			'content'                       => [
114
+				'shortcodes' => [
115
+					'organization',
116
+					'primary_registration_list',
117
+					'primary_registration_details',
118
+					'email',
119
+					'transaction',
120
+					'event_list',
121
+					'payment_list',
122
+					'venue',
123
+					'line_item_list',
124
+					'messenger',
125
+					'ticket_list',
126
+				],
127
+			],
128
+			'event_list'                    => [
129
+				'shortcodes' => [
130
+					'event',
131
+					'ticket_list',
132
+					'venue',
133
+					'primary_registration_details',
134
+					'primary_registration_list',
135
+					'event_author',
136
+				],
137
+				'required'   => ['[EVENT_LIST]'],
138
+			],
139
+			'ticket_list'                   => [
140
+				'shortcodes' => [
141
+					'attendee_list',
142
+					'ticket',
143
+					'datetime_list',
144
+					'primary_registration_details',
145
+					'line_item_list',
146
+					'venue',
147
+				],
148
+				'required'   => ['[TICKET_LIST]'],
149
+			],
150
+			'ticket_line_item_no_pms'       => [
151
+				'shortcodes' => ['line_item', 'ticket'],
152
+				'required'   => ['[TICKET_LINE_ITEM_LIST]'],
153
+			],
154
+			'ticket_line_item_pms'          => [
155
+				'shortcodes' => ['line_item', 'ticket', 'line_item_list'],
156
+				'required'   => ['[TICKET_LINE_ITEM_LIST]'],
157
+			],
158
+			'price_modifier_line_item_list' => [
159
+				'shortcodes' => ['line_item'],
160
+				'required'   => ['[PRICE_MODIFIER_LINE_ITEM_LIST]'],
161
+			],
162
+			'datetime_list'                 => [
163
+				'shortcodes' => ['datetime'],
164
+				'required'   => ['[DATETIME_LIST]'],
165
+			],
166
+			'attendee_list'                 => [
167
+				'shortcodes' => ['attendee'],
168
+				'required'   => ['[ATTENDEE_LIST]'],
169
+			],
170
+			'tax_line_item_list'            => [
171
+				'shortcodes' => ['line_item'],
172
+				'required'   => ['[TAX_LINE_ITEM_LIST]'],
173
+			],
174
+			'additional_line_item_list'     => [
175
+				'shortcodes' => ['line_item'],
176
+				'required'   => ['[ADDITIONAL_LINE_ITEM_LIST]'],
177
+			],
178
+			'payment_list'                  => [
179
+				'shortcodes' => ['payment'],
180
+				'required'   => ['[PAYMENT_LIST_*]'],
181
+			],
182
+		];
183
+	}
184
+
185
+
186
+	/**
187
+	 * This is a method called from EE_messages when this messenger is a generating messenger and the sending messenger
188
+	 * is a different messenger.  Child messengers can set hooks for the sending messenger to callback on if necessary
189
+	 * (i.e. swap out css files or something else).
190
+	 *
191
+	 * @param string $sending_messenger_name the name of the sending messenger so we only set the hooks needed.
192
+	 * @return void
193
+	 * @since 4.5.0
194
+	 */
195
+	public function do_secondary_messenger_hooks($sending_messenger_name)
196
+	{
197
+		if ($sending_messenger_name === 'pdf') {
198
+			add_filter('EE_messenger__get_variation__variation', [$this, 'add_html_css'], 10, 8);
199
+		}
200
+	}
201
+
202
+
203
+	/**
204
+	 * @param                            $variation_path
205
+	 * @param EE_Messages_Template_Pack  $template_pack
206
+	 * @param                            $messenger_name
207
+	 * @param                            $message_type_name
208
+	 * @param                            $url
209
+	 * @param                            $type
210
+	 * @param                            $variation
211
+	 * @param                            $skip_filters
212
+	 * @return string
213
+	 */
214
+	public function add_html_css(
215
+		$variation_path,
216
+		EE_Messages_Template_Pack $template_pack,
217
+		$messenger_name,
218
+		$message_type_name,
219
+		$url,
220
+		$type,
221
+		$variation,
222
+		$skip_filters
223
+	): string {
224
+		return $template_pack->get_variation(
225
+			$this->name,
226
+			$message_type_name,
227
+			$type,
228
+			$variation,
229
+			$url,
230
+			'.css',
231
+			$skip_filters
232
+		);
233
+	}
234
+
235
+
236
+	/**
237
+	 * Takes care of enqueuing any necessary scripts or styles for the page.  A do_action() so message types using this
238
+	 * messenger can add their own js.
239
+	 *
240
+	 * @return void.
241
+	 */
242
+	public function enqueue_scripts_styles()
243
+	{
244
+		parent::enqueue_scripts_styles();
245
+		do_action('AHEE__EE_Html_messenger__enqueue_scripts_styles');
246
+	}
247
+
248
+
249
+	/**
250
+	 * _set_template_fields
251
+	 * This sets up the fields that a messenger requires for the message to go out.
252
+	 *
253
+	 * @access  protected
254
+	 * @return void
255
+	 */
256
+	protected function _set_template_fields()
257
+	{
258
+		// any extra template fields that are NOT used by the messenger
259
+		// but will get used by a messenger field for shortcode replacement
260
+		// get added to the 'extra' key in an associated array
261
+		// indexed by the messenger field they relate to.
262
+		// This is important for the Messages_admin to know what fields to display to the user.
263
+		// Also, notice that the "values" are equal to the field type
264
+		// that messages admin will use to know what kind of field to display.
265
+		// The values ALSO have one index labeled "shortcode".
266
+		// The values in that array indicate which ACTUAL SHORTCODE (i.e. [SHORTCODE])
267
+		// is required in order for this extra field to be displayed.
268
+		//  If the required shortcode isn't part of the shortcodes array
269
+		// then the field is not needed and will not be displayed/parsed.
270
+		$this->_template_fields = [
271
+			'subject' => [
272
+				'input'      => 'text',
273
+				'label'      => esc_html__('Page Title', 'event_espresso'),
274
+				'type'       => 'string',
275
+				'required'   => true,
276
+				'validation' => true,
277
+				'css_class'  => 'large-text',
278
+				'format'     => '%s',
279
+			],
280
+			'content' => '',
281
+			// left empty b/c it is in the "extra array" but messenger still needs to know this is a field.
282
+			'extra'   => [
283
+				'content' => [
284
+					'main'                          => [
285
+						'input'      => 'wp_editor',
286
+						'label'      => esc_html__('Main Content', 'event_espresso'),
287
+						'type'       => 'string',
288
+						'required'   => false,
289
+						'validation' => true,
290
+						'format'     => '%s',
291
+						'rows'       => '15',
292
+					],
293
+					'event_list'                    => [
294
+						'input'               => 'wp_editor',
295
+						'label'               => '[EVENT_LIST]',
296
+						'type'                => 'string',
297
+						'required'            => false,
298
+						'validation'          => true,
299
+						'format'              => '%s',
300
+						'rows'                => '15',
301
+						'shortcodes_required' => ['[EVENT_LIST]'],
302
+					],
303
+					'ticket_list'                   => [
304
+						'input'               => 'textarea',
305
+						'label'               => '[TICKET_LIST]',
306
+						'type'                => 'string',
307
+						'required'            => false,
308
+						'validation'          => true,
309
+						'format'              => '%s',
310
+						'css_class'           => 'large-text',
311
+						'rows'                => '10',
312
+						'shortcodes_required' => ['[TICKET_LIST]'],
313
+					],
314
+					'ticket_line_item_no_pms'       => [
315
+						'input'               => 'textarea',
316
+						'label'               => '[TICKET_LINE_ITEM_LIST] <br>' . esc_html__(
317
+								'Ticket Line Item List with no Price Modifiers',
318
+								'event_espresso'
319
+							),
320
+						'type'                => 'string',
321
+						'required'            => false,
322
+						'validation'          => true,
323
+						'format'              => '%s',
324
+						'css_class'           => 'large-text',
325
+						'rows'                => '5',
326
+						'shortcodes_required' => ['[TICKET_LINE_ITEM_LIST]'],
327
+					],
328
+					'ticket_line_item_pms'          => [
329
+						'input'               => 'textarea',
330
+						'label'               => '[TICKET_LINE_ITEM_LIST] <br>' . esc_html__(
331
+								'Ticket Line Item List with Price Modifiers',
332
+								'event_espresso'
333
+							),
334
+						'type'                => 'string',
335
+						'required'            => false,
336
+						'validation'          => true,
337
+						'format'              => '%s',
338
+						'css_class'           => 'large-text',
339
+						'rows'                => '5',
340
+						'shortcodes_required' => ['[TICKET_LINE_ITEM_LIST]'],
341
+					],
342
+					'price_modifier_line_item_list' => [
343
+						'input'               => 'textarea',
344
+						'label'               => '[PRICE_MODIFIER_LINE_ITEM_LIST]',
345
+						'type'                => 'string',
346
+						'required'            => false,
347
+						'validation'          => true,
348
+						'format'              => '%s',
349
+						'css_class'           => 'large-text',
350
+						'rows'                => '5',
351
+						'shortcodes_required' => ['[PRICE_MODIFIER_LINE_ITEM_LIST]'],
352
+					],
353
+					'datetime_list'                 => [
354
+						'input'               => 'textarea',
355
+						'label'               => '[DATETIME_LIST]',
356
+						'type'                => 'string',
357
+						'required'            => false,
358
+						'validation'          => true,
359
+						'format'              => '%s',
360
+						'css_class'           => 'large-text',
361
+						'rows'                => '5',
362
+						'shortcodes_required' => ['[DATETIME_LIST]'],
363
+					],
364
+					'attendee_list'                 => [
365
+						'input'               => 'textarea',
366
+						'label'               => '[ATTENDEE_LIST]',
367
+						'type'                => 'string',
368
+						'required'            => false,
369
+						'validation'          => true,
370
+						'format'              => '%s',
371
+						'css_class'           => 'large-text',
372
+						'rows'                => '5',
373
+						'shortcodes_required' => ['[ATTENDEE_LIST]'],
374
+					],
375
+					'tax_line_item_list'            => [
376
+						'input'               => 'textarea',
377
+						'label'               => '[TAX_LINE_ITEM_LIST]',
378
+						'type'                => 'string',
379
+						'required'            => false,
380
+						'validation'          => true,
381
+						'format'              => '%s',
382
+						'css_class'           => 'large-text',
383
+						'rows'                => '5',
384
+						'shortcodes_required' => ['[TAX_LINE_ITEM_LIST]'],
385
+					],
386
+					'additional_line_item_list'     => [
387
+						'input'               => 'textarea',
388
+						'label'               => '[ADDITIONAL_LINE_ITEM_LIST]',
389
+						'type'                => 'string',
390
+						'required'            => false,
391
+						'validation'          => true,
392
+						'format'              => '%s',
393
+						'css_class'           => 'large-text',
394
+						'rows'                => '5',
395
+						'shortcodes_required' => ['[ADDITIONAL_LINE_ITEM_LIST]'],
396
+					],
397
+					'payment_list'                  => [
398
+						'input'               => 'textarea',
399
+						'label'               => '[PAYMENT_LIST]',
400
+						'type'                => 'string',
401
+						'required'            => false,
402
+						'validation'          => true,
403
+						'format'              => '%s',
404
+						'css_class'           => 'large-text',
405
+						'rows'                => '5',
406
+						'shortcodes_required' => ['[PAYMENT_LIST_*]'],
407
+					],
408
+				],
409
+			],
410
+		];
411
+	}
412
+
413
+
414
+	/**
415
+	 * @see   definition of this method in parent
416
+	 * @since 4.5.0
417
+	 */
418
+	protected function _set_default_message_types()
419
+	{
420
+		$this->_default_message_types = ['receipt', 'invoice'];
421
+	}
422
+
423
+
424
+	/**
425
+	 * @see   definition of this method in parent
426
+	 * @since 4.5.0
427
+	 */
428
+	protected function _set_valid_message_types()
429
+	{
430
+		$this->_valid_message_types = ['receipt', 'invoice'];
431
+	}
432
+
433
+
434
+	/**
435
+	 * Displays the message in the browser.
436
+	 *
437
+	 * @return void.
438
+	 * @since 4.5.0
439
+	 */
440
+	protected function _send_message()
441
+	{
442
+		$this->_template_args = [
443
+			'page_title' => $this->_subject,
444
+			'base_css'   => $this->get_variation(
445
+				$this->_tmp_pack,
446
+				$this->_incoming_message_type->name,
447
+				true,
448
+				'base',
449
+				$this->_variation
450
+			),
451
+			'print_css'  => $this->get_variation(
452
+				$this->_tmp_pack,
453
+				$this->_incoming_message_type->name,
454
+				true,
455
+				'print',
456
+				$this->_variation
457
+			),
458
+			'main_css'   => $this->get_variation(
459
+				$this->_tmp_pack,
460
+				$this->_incoming_message_type->name,
461
+				true,
462
+				'main',
463
+				$this->_variation
464
+			),
465
+			'main_body'  => wpautop(
466
+				apply_filters(
467
+					'FHEE__EE_Html_messenger___send_message__main_body',
468
+					(string) $this->_content,
469
+					(string) $this->_content,
470
+					$this->_incoming_message_type
471
+				),
472
+				false
473
+			),
474
+		];
475
+		$this->_deregister_wp_hooks();
476
+		add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts_styles']);
477
+		echo($this->_get_main_template());
478
+		exit();
479
+	}
480
+
481
+
482
+	/**
483
+	 * The purpose of this function is to de register all actions hooked into wp_head and wp_footer so that it doesn't
484
+	 * interfere with our templates.  If users want to add any custom styles or scripts they must use the
485
+	 * AHEE__EE_Html_messenger__enqueue_scripts_styles hook.
486
+	 *
487
+	 * @return void
488
+	 * @since 4.5.0
489
+	 */
490
+	protected function _deregister_wp_hooks()
491
+	{
492
+		remove_all_actions('wp_head');
493
+		remove_all_actions('wp_footer');
494
+		remove_all_actions('wp_print_footer_scripts');
495
+		remove_all_actions('wp_enqueue_scripts');
496
+		global $wp_scripts, $wp_styles;
497
+		$wp_scripts = $wp_styles = [];
498
+		// just add back in wp_enqueue_scripts and wp_print_footer_scripts cause that's all we want to load.
499
+		add_action('wp_footer', 'wp_print_footer_scripts');
500
+		add_action('wp_print_footer_scripts', '_wp_footer_scripts');
501
+		add_action('wp_head', 'wp_enqueue_scripts');
502
+	}
503
+
504
+
505
+	/**
506
+	 * Overwrite parent _get_main_template for display_html purposes.
507
+	 *
508
+	 * @param bool $preview
509
+	 * @return string
510
+	 * @since  4.5.0
511
+	 */
512
+	protected function _get_main_template($preview = false): string
513
+	{
514
+		$wrapper_template = $this->_tmp_pack->get_wrapper($this->name);
515
+		// include message type as a template arg
516
+		$this->_template_args['message_type'] = $this->_incoming_message_type;
517
+		return EEH_Template::display_template($wrapper_template, $this->_template_args, true);
518
+	}
519
+
520
+
521
+	/**
522
+	 * @return void
523
+	 */
524
+	protected function _preview()
525
+	{
526
+		$this->_send_message();
527
+	}
528
+
529
+
530
+	protected function _set_admin_settings_fields()
531
+	{
532
+	}
533
+
534
+
535
+	/**
536
+	 * add the "powered by EE" credit link to the HTML receipt and invoice
537
+	 *
538
+	 * @param string|null     $content
539
+	 * @param string|null     $content_again
540
+	 * @param EE_message_type $incoming_message_type
541
+	 * @return string
542
+	 */
543
+	public function add_powered_by_credit_link_to_receipt_and_invoice(
544
+		?string $content,
545
+		?string $content_again,
546
+		EE_message_type $incoming_message_type
547
+	): string {
548
+		if (
549
+			($incoming_message_type->name === 'invoice' || $incoming_message_type->name === 'receipt')
550
+			&& apply_filters('FHEE_EE_Html_messenger__add_powered_by_credit_link_to_receipt_and_invoice', true)
551
+		) {
552
+			$content .= EEH_Template::powered_by_event_espresso(
553
+					'aln-cntr',
554
+					'',
555
+					['utm_content' => 'messages_system']
556
+				) . EEH_HTML::div(EEH_HTML::p('&nbsp;'));
557
+		}
558
+		return $content;
559
+	}
560 560
 }
Please login to merge, or discard this patch.
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -41,7 +41,7 @@  discard block
 block discarded – undo
41 41
             'This messenger outputs a message to a browser for display.',
42 42
             'event_espresso'
43 43
         );
44
-        $this->label               = [
44
+        $this->label = [
45 45
             'singular' => esc_html__('html', 'event_espresso'),
46 46
             'plural'   => esc_html__('html', 'event_espresso'),
47 47
         ];
@@ -313,7 +313,7 @@  discard block
 block discarded – undo
313 313
                     ],
314 314
                     'ticket_line_item_no_pms'       => [
315 315
                         'input'               => 'textarea',
316
-                        'label'               => '[TICKET_LINE_ITEM_LIST] <br>' . esc_html__(
316
+                        'label'               => '[TICKET_LINE_ITEM_LIST] <br>'.esc_html__(
317 317
                                 'Ticket Line Item List with no Price Modifiers',
318 318
                                 'event_espresso'
319 319
                             ),
@@ -327,7 +327,7 @@  discard block
 block discarded – undo
327 327
                     ],
328 328
                     'ticket_line_item_pms'          => [
329 329
                         'input'               => 'textarea',
330
-                        'label'               => '[TICKET_LINE_ITEM_LIST] <br>' . esc_html__(
330
+                        'label'               => '[TICKET_LINE_ITEM_LIST] <br>'.esc_html__(
331 331
                                 'Ticket Line Item List with Price Modifiers',
332 332
                                 'event_espresso'
333 333
                             ),
@@ -553,7 +553,7 @@  discard block
 block discarded – undo
553 553
                     'aln-cntr',
554 554
                     '',
555 555
                     ['utm_content' => 'messages_system']
556
-                ) . EEH_HTML::div(EEH_HTML::p('&nbsp;'));
556
+                ).EEH_HTML::div(EEH_HTML::p('&nbsp;'));
557 557
         }
558 558
         return $content;
559 559
     }
Please login to merge, or discard this patch.
core/libraries/messages/messenger/EE_Email_messenger.class.php 2 patches
Indentation   +643 added lines, -643 removed lines patch added patch discarded remove patch
@@ -5,647 +5,647 @@
 block discarded – undo
5 5
  */
6 6
 class EE_Email_messenger extends EE_messenger
7 7
 {
8
-    /**
9
-     * To field for email
10
-     *
11
-     * @var string
12
-     */
13
-    protected $_to = '';
14
-
15
-
16
-    /**
17
-     * CC field for email.
18
-     *
19
-     * @var string
20
-     */
21
-    protected $_cc = '';
22
-
23
-    /**
24
-     * From field for email
25
-     *
26
-     * @var string
27
-     */
28
-    protected $_from = '';
29
-
30
-
31
-    /**
32
-     * Subject field for email
33
-     *
34
-     * @var string
35
-     */
36
-    protected $_subject = '';
37
-
38
-
39
-    /**
40
-     * Content field for email
41
-     *
42
-     * @var string
43
-     */
44
-    protected $_content = '';
45
-
46
-
47
-    /**
48
-     * @throws EE_Error
49
-     * @throws ReflectionException
50
-     */
51
-    public function __construct()
52
-    {
53
-        // set name and description properties
54
-        $this->name                = 'email';
55
-        $this->description         = sprintf(
56
-            esc_html__(
57
-                'This messenger delivers messages via email using the built-in %s function included with WordPress',
58
-                'event_espresso'
59
-            ),
60
-            '<code>wp_mail</code>'
61
-        );
62
-        $this->label               = [
63
-            'singular' => esc_html__('email', 'event_espresso'),
64
-            'plural'   => esc_html__('emails', 'event_espresso'),
65
-        ];
66
-        $this->activate_on_install = true;
67
-
68
-        // we're using defaults so let's call parent constructor that will take care of setting up all the other
69
-        // properties
70
-        parent::__construct();
71
-    }
72
-
73
-
74
-    /**
75
-     * see abstract declaration in parent class for details.
76
-     */
77
-    protected function _set_admin_pages()
78
-    {
79
-        $this->admin_registered_pages = [
80
-            'events_edit' => true,
81
-        ];
82
-    }
83
-
84
-
85
-    /**
86
-     * see abstract declaration in parent class for details
87
-     */
88
-    protected function _set_valid_shortcodes()
89
-    {
90
-        // remember by leaving the other fields not set, those fields will inherit the valid shortcodes from the
91
-        // message type.
92
-        $this->_valid_shortcodes = [
93
-            'to'   => ['email', 'event_author', 'primary_registration_details', 'recipient_details'],
94
-            'cc'   => ['email', 'event_author', 'primary_registration_details', 'recipient_details'],
95
-            'from' => ['email', 'event_author', 'primary_registration_details', 'recipient_details'],
96
-        ];
97
-    }
98
-
99
-
100
-    /**
101
-     * see abstract declaration in parent class for details
102
-     *
103
-     * @access protected
104
-     * @return void
105
-     */
106
-    protected function _set_validator_config()
107
-    {
108
-        $valid_shortcodes = $this->get_valid_shortcodes();
109
-
110
-        $this->_validator_config = [
111
-            'to'            => [
112
-                'shortcodes' => $valid_shortcodes['to'],
113
-                'type'       => 'email',
114
-            ],
115
-            'cc'            => [
116
-                'shortcodes' => $valid_shortcodes['to'],
117
-                'type'       => 'email',
118
-            ],
119
-            'from'          => [
120
-                'shortcodes' => $valid_shortcodes['from'],
121
-                'type'       => 'email',
122
-            ],
123
-            'subject'       => [
124
-                'shortcodes' => [
125
-                    'organization',
126
-                    'primary_registration_details',
127
-                    'event_author',
128
-                    'primary_registration_details',
129
-                    'recipient_details',
130
-                ],
131
-            ],
132
-            'content'       => [
133
-                'shortcodes' => [
134
-                    'event_list',
135
-                    'attendee_list',
136
-                    'ticket_list',
137
-                    'organization',
138
-                    'primary_registration_details',
139
-                    'primary_registration_list',
140
-                    'event_author',
141
-                    'recipient_details',
142
-                    'recipient_list',
143
-                    'transaction',
144
-                    'messenger',
145
-                ],
146
-            ],
147
-            'attendee_list' => [
148
-                'shortcodes' => ['attendee', 'event_list', 'ticket_list'],
149
-                'required'   => ['[ATTENDEE_LIST]'],
150
-            ],
151
-            'event_list'    => [
152
-                'shortcodes' => [
153
-                    'event',
154
-                    'attendee_list',
155
-                    'ticket_list',
156
-                    'venue',
157
-                    'datetime_list',
158
-                    'attendee',
159
-                    'primary_registration_details',
160
-                    'primary_registration_list',
161
-                    'event_author',
162
-                    'recipient_details',
163
-                    'recipient_list',
164
-                ],
165
-                'required'   => ['[EVENT_LIST]'],
166
-            ],
167
-            'ticket_list'   => [
168
-                'shortcodes' => [
169
-                    'event_list',
170
-                    'attendee_list',
171
-                    'ticket',
172
-                    'datetime_list',
173
-                    'primary_registration_details',
174
-                    'recipient_details',
175
-                ],
176
-                'required'   => ['[TICKET_LIST]'],
177
-            ],
178
-            'datetime_list' => [
179
-                'shortcodes' => ['datetime'],
180
-                'required'   => ['[DATETIME_LIST]'],
181
-            ],
182
-        ];
183
-    }
184
-
185
-
186
-    /**
187
-     * @see   parent EE_messenger class for docs
188
-     * @since 4.5.0
189
-     */
190
-    public function do_secondary_messenger_hooks($sending_messenger_name)
191
-    {
192
-        if ($sending_messenger_name === 'html') {
193
-            add_filter('FHEE__EE_Messages_Template_Pack__get_variation', [$this, 'add_email_css'], 10, 8);
194
-        }
195
-    }
196
-
197
-
198
-    public function add_email_css(
199
-        $variation_path,
200
-        $messenger,
201
-        $message_type,
202
-        $type,
203
-        $variation,
204
-        $file_extension,
205
-        $url,
206
-        EE_Messages_Template_Pack $template_pack
207
-    ) {
208
-        // prevent recursion on this callback.
209
-        remove_filter('FHEE__EE_Messages_Template_Pack__get_variation', [$this, 'add_email_css'], 10);
210
-        $variation = $this->get_variation($template_pack, $message_type, $url, 'main', $variation, false);
211
-
212
-        add_filter('FHEE__EE_Messages_Template_Pack__get_variation', [$this, 'add_email_css'], 10, 8);
213
-        return $variation;
214
-    }
215
-
216
-
217
-    /**
218
-     * See parent for details
219
-     *
220
-     * @access protected
221
-     * @return void
222
-     */
223
-    protected function _set_test_settings_fields()
224
-    {
225
-        $this->_test_settings_fields = [
226
-            'to'      => [
227
-                'input'      => 'text',
228
-                'label'      => esc_html__('Send a test email to', 'event_espresso'),
229
-                'type'       => 'email',
230
-                'required'   => false,
231
-                'validation' => true,
232
-                'css_class'  => 'ee-input-width--big',
233
-                'format'     => '%s',
234
-                'default'    => get_bloginfo('admin_email'),
235
-            ],
236
-            'subject' => [
237
-                'input'      => 'hidden',
238
-                'label'      => '',
239
-                'type'       => 'string',
240
-                'required'   => false,
241
-                'validation' => false,
242
-                'format'     => '%s',
243
-                'value'      => sprintf(esc_html__('Test email sent from %s', 'event_espresso'), get_bloginfo('name')),
244
-                'default'    => '',
245
-                'css_class'  => '',
246
-            ],
247
-        ];
248
-    }
249
-
250
-
251
-    /**
252
-     * _set_template_fields
253
-     * This sets up the fields that a messenger requires for the message to go out.
254
-     *
255
-     * @access  protected
256
-     * @return void
257
-     */
258
-    protected function _set_template_fields()
259
-    {
260
-        // any extra template fields that are NOT used by the messenger but will get used by a messenger field for
261
-        // shortcode replacement get added to the 'extra' key in an associated array indexed by the messenger field
262
-        // they relate to.  This is important for the Messages_admin to know what fields to display to the user.
263
-        //  Also, notice that the "values" are equal to the field type that messages admin will use to know what
264
-        // kind of field to display. The values ALSO have one index labeled "shortcode".  the values in that array
265
-        // indicate which ACTUAL SHORTCODE (i.e. [SHORTCODE]) is required in order for this extra field to be
266
-        // displayed.  If the required shortcode isn't part of the shortcodes array then the field is not needed and
267
-        // will not be displayed/parsed.
268
-        $this->_template_fields = [
269
-            'to'      => [
270
-                'input'      => 'text',
271
-                'label'      => esc_html_x(
272
-                    'To',
273
-                    'Label for the "To" field for email addresses',
274
-                    'event_espresso'
275
-                ),
276
-                'type'       => 'string',
277
-                'required'   => false,
278
-                'validation' => true,
279
-                'css_class'  => 'large-text',
280
-                'format'     => '%s',
281
-            ],
282
-            'cc'      => [
283
-                'input'      => 'text',
284
-                'label'      => esc_html_x(
285
-                    'CC',
286
-                    'Label for the "Carbon Copy" field used for additional email addresses',
287
-                    'event_espresso'
288
-                ),
289
-                'type'       => 'string',
290
-                'required'   => false,
291
-                'validation' => true,
292
-                'css_class'  => 'large-text',
293
-                'format'     => '%s',
294
-            ],
295
-            'from'    => [
296
-                'input'      => 'text',
297
-                'label'      => esc_html_x(
298
-                    'From',
299
-                    'Label for the "From" field for email addresses.',
300
-                    'event_espresso'
301
-                ),
302
-                'type'       => 'string',
303
-                'required'   => false,
304
-                'validation' => true,
305
-                'css_class'  => 'large-text',
306
-                'format'     => '%s',
307
-            ],
308
-            'subject' => [
309
-                'input'      => 'text',
310
-                'label'      => esc_html_x(
311
-                    'Subject',
312
-                    'Label for the "Subject" field (short description of contents) for emails.',
313
-                    'event_espresso'
314
-                ),
315
-                'type'       => 'string',
316
-                'required'   => true,
317
-                'validation' => true,
318
-                'css_class'  => 'large-text',
319
-                'format'     => '%s',
320
-            ],
321
-            'content' => '',
322
-            // left empty b/c it is in the "extra array" but messenger still needs needs to know this is a field.
323
-            'extra'   => [
324
-                'content' => [
325
-                    'main'          => [
326
-                        'input'      => 'wp_editor',
327
-                        'label'      => esc_html__('Main Content', 'event_espresso'),
328
-                        'type'       => 'string',
329
-                        'required'   => false,
330
-                        'validation' => true,
331
-                        'format'     => '%s',
332
-                        'rows'       => '15',
333
-                    ],
334
-                    'event_list'    => [
335
-                        'input'               => 'wp_editor',
336
-                        'label'               => '[EVENT_LIST]',
337
-                        'type'                => 'string',
338
-                        'required'            => false,
339
-                        'validation'          => true,
340
-                        'format'              => '%s',
341
-                        'rows'                => '15',
342
-                        'shortcodes_required' => ['[EVENT_LIST]'],
343
-                    ],
344
-                    'attendee_list' => [
345
-                        'input'               => 'textarea',
346
-                        'label'               => '[ATTENDEE_LIST]',
347
-                        'type'                => 'string',
348
-                        'required'            => false,
349
-                        'validation'          => true,
350
-                        'format'              => '%s',
351
-                        'css_class'           => 'large-text',
352
-                        'rows'                => '5',
353
-                        'shortcodes_required' => ['[ATTENDEE_LIST]'],
354
-                    ],
355
-                    'ticket_list'   => [
356
-                        'input'               => 'textarea',
357
-                        'label'               => '[TICKET_LIST]',
358
-                        'type'                => 'string',
359
-                        'required'            => false,
360
-                        'validation'          => true,
361
-                        'format'              => '%s',
362
-                        'css_class'           => 'large-text',
363
-                        'rows'                => '10',
364
-                        'shortcodes_required' => ['[TICKET_LIST]'],
365
-                    ],
366
-                    'datetime_list' => [
367
-                        'input'               => 'textarea',
368
-                        'label'               => '[DATETIME_LIST]',
369
-                        'type'                => 'string',
370
-                        'required'            => false,
371
-                        'validation'          => true,
372
-                        'format'              => '%s',
373
-                        'css_class'           => 'large-text',
374
-                        'rows'                => '10',
375
-                        'shortcodes_required' => ['[DATETIME_LIST]'],
376
-                    ],
377
-                ],
378
-            ],
379
-        ];
380
-    }
381
-
382
-
383
-    /**
384
-     * See definition of this class in parent
385
-     */
386
-    protected function _set_default_message_types()
387
-    {
388
-        $this->_default_message_types = [
389
-            'payment',
390
-            'payment_refund',
391
-            'registration',
392
-            'not_approved_registration',
393
-            'pending_approval',
394
-        ];
395
-    }
396
-
397
-
398
-    /**
399
-     * @see   definition of this class in parent
400
-     * @since 4.5.0
401
-     */
402
-    protected function _set_valid_message_types()
403
-    {
404
-        $this->_valid_message_types = [
405
-            'payment',
406
-            'registration',
407
-            'not_approved_registration',
408
-            'declined_registration',
409
-            'cancelled_registration',
410
-            'pending_approval',
411
-            'registration_summary',
412
-            'payment_reminder',
413
-            'payment_declined',
414
-            'payment_refund',
415
-        ];
416
-    }
417
-
418
-
419
-    /**
420
-     * setting up admin_settings_fields for messenger.
421
-     */
422
-    protected function _set_admin_settings_fields()
423
-    {
424
-    }
425
-
426
-
427
-    /**
428
-     * We just deliver the messages don't kill us!!
429
-     *
430
-     * @return bool|WP_Error true if message delivered, false if it didn't deliver OR bubble up any error object if
431
-     *              present.
432
-     * @throws EE_Error
433
-     * @throws Exception
434
-     */
435
-    protected function _send_message()
436
-    {
437
-        $success = wp_mail(
438
-            $this->_to,
439
-            // some old values for subject may be expecting HTML entities to be decoded in the subject
440
-            // and subjects aren't interpreted as HTML, so there should be no HTML in them
441
-            wp_strip_all_tags(wp_specialchars_decode($this->_subject, ENT_QUOTES)),
442
-            $this->_body(),
443
-            $this->_headers()
444
-        );
445
-        if (! $success) {
446
-            EE_Error::add_error(
447
-                sprintf(
448
-                    esc_html__(
449
-                        'The email did not send successfully.%3$sThe WordPress wp_mail function is used for sending mails but does not give any useful information when an email fails to send.%3$sIt is possible the "to" address (%1$s) or "from" address (%2$s) is invalid.%3$s',
450
-                        'event_espresso'
451
-                    ),
452
-                    $this->_to,
453
-                    $this->_from,
454
-                    '<br />'
455
-                ),
456
-                __FILE__,
457
-                __FUNCTION__,
458
-                __LINE__
459
-            );
460
-        }
461
-        return $success;
462
-    }
463
-
464
-
465
-    /**
466
-     * see parent for definition
467
-     *
468
-     * @return string html body of the message content and the related css.
469
-     * @throws EE_Error
470
-     * @throws Exception
471
-     */
472
-    protected function _preview()
473
-    {
474
-        return $this->_body(true);
475
-    }
476
-
477
-
478
-    /**
479
-     * Setup headers for email
480
-     *
481
-     * @access protected
482
-     * @return string formatted header for email
483
-     */
484
-    protected function _headers()
485
-    {
486
-        $this->_ensure_has_from_email_address();
487
-        $from    = $this->_from;
488
-        $headers = [
489
-            'From:' . $from,
490
-            'Reply-To:' . $from,
491
-            'Content-Type:text/html; charset=utf-8',
492
-        ];
493
-
494
-        /**
495
-         * Second condition added as a result of https://events.codebasehq.com/projects/event-espresso/tickets/11416 to
496
-         * cover back compat where there may be users who have saved cc values in their db for the newsletter message
497
-         * type which they are no longer able to change.
498
-         */
499
-        if (! empty($this->_cc) && ! $this->_incoming_message_type instanceof EE_Newsletter_message_type) {
500
-            $headers[] = 'cc: ' . $this->_cc;
501
-        }
502
-
503
-        // but wait!  Header's for the from is NOT reliable because some plugins don't respect From: as set in the
504
-        // header.
505
-        add_filter('wp_mail_from', [$this, 'set_from_address'], 100);
506
-        add_filter('wp_mail_from_name', [$this, 'set_from_name'], 100);
507
-        return apply_filters('FHEE__EE_Email_messenger___headers', $headers, $this->_incoming_message_type, $this);
508
-    }
509
-
510
-
511
-    /**
512
-     * This simply ensures that the from address is not empty.  If it is, then we use whatever is set as the site email
513
-     * address for the from address to avoid problems with sending emails.
514
-     */
515
-    protected function _ensure_has_from_email_address()
516
-    {
517
-        if (empty($this->_from)) {
518
-            $this->_from = get_bloginfo('admin_email');
519
-        }
520
-    }
521
-
522
-
523
-    /**
524
-     * This simply parses whatever is set as the $_from address and determines if it is in the format {name} <{email}>
525
-     * or just {email} and returns an array with the "from_name" and "from_email" as the values. Note from_name *MAY*
526
-     * be empty
527
-     *
528
-     * @return array
529
-     * @since 4.3.1
530
-     */
531
-    private function _parse_from()
532
-    {
533
-        if (strpos($this->_from, '<') !== false) {
534
-            $from_name = substr($this->_from, 0, strpos($this->_from, '<') - 1);
535
-            $from_name = str_replace('"', '', $from_name);
536
-            $from_name = trim($from_name);
537
-
538
-            $from_email = substr($this->_from, strpos($this->_from, '<') + 1);
539
-            $from_email = str_replace('>', '', $from_email);
540
-            $from_email = trim($from_email);
541
-        } elseif (trim($this->_from) !== '') {
542
-            $from_name  = '';
543
-            $from_email = trim($this->_from);
544
-        } else {
545
-            $from_name = $from_email = '';
546
-        }
547
-        return [$from_name, $from_email];
548
-    }
549
-
550
-
551
-    /**
552
-     * Callback for the wp_mail_from filter.
553
-     *
554
-     * @param string $from_email What the original from_email is.
555
-     * @return string
556
-     * @since 4.3.1
557
-     */
558
-    public function set_from_address($from_email)
559
-    {
560
-        $parsed_from = $this->_parse_from();
561
-        // includes fallback if the parsing failed.
562
-        return is_array($parsed_from) && ! empty($parsed_from[1])
563
-            ? $parsed_from[1]
564
-            : get_bloginfo('admin_email');
565
-    }
566
-
567
-
568
-    /**
569
-     * Callback for the wp_mail_from_name filter.
570
-     *
571
-     * @param string $from_name The original from_name.
572
-     * @return string
573
-     * @since 4.3.1
574
-     */
575
-    public function set_from_name($from_name)
576
-    {
577
-        $parsed_from = $this->_parse_from();
578
-        if (is_array($parsed_from) && ! empty($parsed_from[0])) {
579
-            $from_name = $parsed_from[0];
580
-        }
581
-
582
-        // if from name is "WordPress" let's sub in the site name instead (more friendly!)
583
-        // but realize the default name is HTML entity-encoded
584
-        return $from_name == 'WordPress' ? wp_specialchars_decode(get_bloginfo(), ENT_QUOTES) : $from_name;
585
-    }
586
-
587
-
588
-    /**
589
-     * setup body for email
590
-     *
591
-     * @param bool $preview will determine whether this is preview template or not.
592
-     * @return string formatted body for email.
593
-     * @throws EE_Error
594
-     * @throws Exception
595
-     */
596
-    protected function _body($preview = false)
597
-    {
598
-        // setup template args!
599
-        $this->_template_args = [
600
-            'subject'   => $this->_subject,
601
-            'from'      => $this->_from,
602
-            'main_body' => wpautop($this->_content),
603
-        ];
604
-        $body                 = $this->_get_main_template($preview);
605
-
606
-        /**
607
-         * This filter allows one to bypass the CSSToInlineStyles tool and leave the body untouched.
608
-         *
609
-         * @type    bool $preview Indicates whether a preview is being generated or not.
610
-         * @return  bool    true  indicates to use the inliner, false bypasses it.
611
-         */
612
-        if (apply_filters('FHEE__EE_Email_messenger__apply_CSSInliner ', true, $preview)) {
613
-            // now if this isn't a preview, let's set up the body so it has inline styles
614
-            if (! $preview || (defined('DOING_AJAX') && DOING_AJAX)) {
615
-                $style = file_get_contents(
616
-                    $this->get_variation(
617
-                        $this->_tmp_pack,
618
-                        $this->_incoming_message_type->name,
619
-                        false,
620
-                        'main',
621
-                        $this->_variation
622
-                    ),
623
-                    true
624
-                );
625
-                $CSS   = new TijsVerkoyen\CssToInlineStyles\CssToInlineStyles();
626
-                $body  = $CSS->convert($body, $style);
627
-            }
628
-        }
629
-        return $body;
630
-    }
631
-
632
-
633
-    /**
634
-     * This just returns any existing test settings that might be saved in the database
635
-     *
636
-     * @access public
637
-     * @return array
638
-     * @throws EE_Error
639
-     * @throws ReflectionException
640
-     */
641
-    public function get_existing_test_settings()
642
-    {
643
-        $settings = parent::get_existing_test_settings();
644
-        // override subject if present because we always want it to be fresh.
645
-        if (is_array($settings) && ! empty($settings['subject'])) {
646
-            $settings['subject'] =
647
-                sprintf(esc_html__('Test email sent from %s', 'event_espresso'), get_bloginfo('name'));
648
-        }
649
-        return $settings;
650
-    }
8
+	/**
9
+	 * To field for email
10
+	 *
11
+	 * @var string
12
+	 */
13
+	protected $_to = '';
14
+
15
+
16
+	/**
17
+	 * CC field for email.
18
+	 *
19
+	 * @var string
20
+	 */
21
+	protected $_cc = '';
22
+
23
+	/**
24
+	 * From field for email
25
+	 *
26
+	 * @var string
27
+	 */
28
+	protected $_from = '';
29
+
30
+
31
+	/**
32
+	 * Subject field for email
33
+	 *
34
+	 * @var string
35
+	 */
36
+	protected $_subject = '';
37
+
38
+
39
+	/**
40
+	 * Content field for email
41
+	 *
42
+	 * @var string
43
+	 */
44
+	protected $_content = '';
45
+
46
+
47
+	/**
48
+	 * @throws EE_Error
49
+	 * @throws ReflectionException
50
+	 */
51
+	public function __construct()
52
+	{
53
+		// set name and description properties
54
+		$this->name                = 'email';
55
+		$this->description         = sprintf(
56
+			esc_html__(
57
+				'This messenger delivers messages via email using the built-in %s function included with WordPress',
58
+				'event_espresso'
59
+			),
60
+			'<code>wp_mail</code>'
61
+		);
62
+		$this->label               = [
63
+			'singular' => esc_html__('email', 'event_espresso'),
64
+			'plural'   => esc_html__('emails', 'event_espresso'),
65
+		];
66
+		$this->activate_on_install = true;
67
+
68
+		// we're using defaults so let's call parent constructor that will take care of setting up all the other
69
+		// properties
70
+		parent::__construct();
71
+	}
72
+
73
+
74
+	/**
75
+	 * see abstract declaration in parent class for details.
76
+	 */
77
+	protected function _set_admin_pages()
78
+	{
79
+		$this->admin_registered_pages = [
80
+			'events_edit' => true,
81
+		];
82
+	}
83
+
84
+
85
+	/**
86
+	 * see abstract declaration in parent class for details
87
+	 */
88
+	protected function _set_valid_shortcodes()
89
+	{
90
+		// remember by leaving the other fields not set, those fields will inherit the valid shortcodes from the
91
+		// message type.
92
+		$this->_valid_shortcodes = [
93
+			'to'   => ['email', 'event_author', 'primary_registration_details', 'recipient_details'],
94
+			'cc'   => ['email', 'event_author', 'primary_registration_details', 'recipient_details'],
95
+			'from' => ['email', 'event_author', 'primary_registration_details', 'recipient_details'],
96
+		];
97
+	}
98
+
99
+
100
+	/**
101
+	 * see abstract declaration in parent class for details
102
+	 *
103
+	 * @access protected
104
+	 * @return void
105
+	 */
106
+	protected function _set_validator_config()
107
+	{
108
+		$valid_shortcodes = $this->get_valid_shortcodes();
109
+
110
+		$this->_validator_config = [
111
+			'to'            => [
112
+				'shortcodes' => $valid_shortcodes['to'],
113
+				'type'       => 'email',
114
+			],
115
+			'cc'            => [
116
+				'shortcodes' => $valid_shortcodes['to'],
117
+				'type'       => 'email',
118
+			],
119
+			'from'          => [
120
+				'shortcodes' => $valid_shortcodes['from'],
121
+				'type'       => 'email',
122
+			],
123
+			'subject'       => [
124
+				'shortcodes' => [
125
+					'organization',
126
+					'primary_registration_details',
127
+					'event_author',
128
+					'primary_registration_details',
129
+					'recipient_details',
130
+				],
131
+			],
132
+			'content'       => [
133
+				'shortcodes' => [
134
+					'event_list',
135
+					'attendee_list',
136
+					'ticket_list',
137
+					'organization',
138
+					'primary_registration_details',
139
+					'primary_registration_list',
140
+					'event_author',
141
+					'recipient_details',
142
+					'recipient_list',
143
+					'transaction',
144
+					'messenger',
145
+				],
146
+			],
147
+			'attendee_list' => [
148
+				'shortcodes' => ['attendee', 'event_list', 'ticket_list'],
149
+				'required'   => ['[ATTENDEE_LIST]'],
150
+			],
151
+			'event_list'    => [
152
+				'shortcodes' => [
153
+					'event',
154
+					'attendee_list',
155
+					'ticket_list',
156
+					'venue',
157
+					'datetime_list',
158
+					'attendee',
159
+					'primary_registration_details',
160
+					'primary_registration_list',
161
+					'event_author',
162
+					'recipient_details',
163
+					'recipient_list',
164
+				],
165
+				'required'   => ['[EVENT_LIST]'],
166
+			],
167
+			'ticket_list'   => [
168
+				'shortcodes' => [
169
+					'event_list',
170
+					'attendee_list',
171
+					'ticket',
172
+					'datetime_list',
173
+					'primary_registration_details',
174
+					'recipient_details',
175
+				],
176
+				'required'   => ['[TICKET_LIST]'],
177
+			],
178
+			'datetime_list' => [
179
+				'shortcodes' => ['datetime'],
180
+				'required'   => ['[DATETIME_LIST]'],
181
+			],
182
+		];
183
+	}
184
+
185
+
186
+	/**
187
+	 * @see   parent EE_messenger class for docs
188
+	 * @since 4.5.0
189
+	 */
190
+	public function do_secondary_messenger_hooks($sending_messenger_name)
191
+	{
192
+		if ($sending_messenger_name === 'html') {
193
+			add_filter('FHEE__EE_Messages_Template_Pack__get_variation', [$this, 'add_email_css'], 10, 8);
194
+		}
195
+	}
196
+
197
+
198
+	public function add_email_css(
199
+		$variation_path,
200
+		$messenger,
201
+		$message_type,
202
+		$type,
203
+		$variation,
204
+		$file_extension,
205
+		$url,
206
+		EE_Messages_Template_Pack $template_pack
207
+	) {
208
+		// prevent recursion on this callback.
209
+		remove_filter('FHEE__EE_Messages_Template_Pack__get_variation', [$this, 'add_email_css'], 10);
210
+		$variation = $this->get_variation($template_pack, $message_type, $url, 'main', $variation, false);
211
+
212
+		add_filter('FHEE__EE_Messages_Template_Pack__get_variation', [$this, 'add_email_css'], 10, 8);
213
+		return $variation;
214
+	}
215
+
216
+
217
+	/**
218
+	 * See parent for details
219
+	 *
220
+	 * @access protected
221
+	 * @return void
222
+	 */
223
+	protected function _set_test_settings_fields()
224
+	{
225
+		$this->_test_settings_fields = [
226
+			'to'      => [
227
+				'input'      => 'text',
228
+				'label'      => esc_html__('Send a test email to', 'event_espresso'),
229
+				'type'       => 'email',
230
+				'required'   => false,
231
+				'validation' => true,
232
+				'css_class'  => 'ee-input-width--big',
233
+				'format'     => '%s',
234
+				'default'    => get_bloginfo('admin_email'),
235
+			],
236
+			'subject' => [
237
+				'input'      => 'hidden',
238
+				'label'      => '',
239
+				'type'       => 'string',
240
+				'required'   => false,
241
+				'validation' => false,
242
+				'format'     => '%s',
243
+				'value'      => sprintf(esc_html__('Test email sent from %s', 'event_espresso'), get_bloginfo('name')),
244
+				'default'    => '',
245
+				'css_class'  => '',
246
+			],
247
+		];
248
+	}
249
+
250
+
251
+	/**
252
+	 * _set_template_fields
253
+	 * This sets up the fields that a messenger requires for the message to go out.
254
+	 *
255
+	 * @access  protected
256
+	 * @return void
257
+	 */
258
+	protected function _set_template_fields()
259
+	{
260
+		// any extra template fields that are NOT used by the messenger but will get used by a messenger field for
261
+		// shortcode replacement get added to the 'extra' key in an associated array indexed by the messenger field
262
+		// they relate to.  This is important for the Messages_admin to know what fields to display to the user.
263
+		//  Also, notice that the "values" are equal to the field type that messages admin will use to know what
264
+		// kind of field to display. The values ALSO have one index labeled "shortcode".  the values in that array
265
+		// indicate which ACTUAL SHORTCODE (i.e. [SHORTCODE]) is required in order for this extra field to be
266
+		// displayed.  If the required shortcode isn't part of the shortcodes array then the field is not needed and
267
+		// will not be displayed/parsed.
268
+		$this->_template_fields = [
269
+			'to'      => [
270
+				'input'      => 'text',
271
+				'label'      => esc_html_x(
272
+					'To',
273
+					'Label for the "To" field for email addresses',
274
+					'event_espresso'
275
+				),
276
+				'type'       => 'string',
277
+				'required'   => false,
278
+				'validation' => true,
279
+				'css_class'  => 'large-text',
280
+				'format'     => '%s',
281
+			],
282
+			'cc'      => [
283
+				'input'      => 'text',
284
+				'label'      => esc_html_x(
285
+					'CC',
286
+					'Label for the "Carbon Copy" field used for additional email addresses',
287
+					'event_espresso'
288
+				),
289
+				'type'       => 'string',
290
+				'required'   => false,
291
+				'validation' => true,
292
+				'css_class'  => 'large-text',
293
+				'format'     => '%s',
294
+			],
295
+			'from'    => [
296
+				'input'      => 'text',
297
+				'label'      => esc_html_x(
298
+					'From',
299
+					'Label for the "From" field for email addresses.',
300
+					'event_espresso'
301
+				),
302
+				'type'       => 'string',
303
+				'required'   => false,
304
+				'validation' => true,
305
+				'css_class'  => 'large-text',
306
+				'format'     => '%s',
307
+			],
308
+			'subject' => [
309
+				'input'      => 'text',
310
+				'label'      => esc_html_x(
311
+					'Subject',
312
+					'Label for the "Subject" field (short description of contents) for emails.',
313
+					'event_espresso'
314
+				),
315
+				'type'       => 'string',
316
+				'required'   => true,
317
+				'validation' => true,
318
+				'css_class'  => 'large-text',
319
+				'format'     => '%s',
320
+			],
321
+			'content' => '',
322
+			// left empty b/c it is in the "extra array" but messenger still needs needs to know this is a field.
323
+			'extra'   => [
324
+				'content' => [
325
+					'main'          => [
326
+						'input'      => 'wp_editor',
327
+						'label'      => esc_html__('Main Content', 'event_espresso'),
328
+						'type'       => 'string',
329
+						'required'   => false,
330
+						'validation' => true,
331
+						'format'     => '%s',
332
+						'rows'       => '15',
333
+					],
334
+					'event_list'    => [
335
+						'input'               => 'wp_editor',
336
+						'label'               => '[EVENT_LIST]',
337
+						'type'                => 'string',
338
+						'required'            => false,
339
+						'validation'          => true,
340
+						'format'              => '%s',
341
+						'rows'                => '15',
342
+						'shortcodes_required' => ['[EVENT_LIST]'],
343
+					],
344
+					'attendee_list' => [
345
+						'input'               => 'textarea',
346
+						'label'               => '[ATTENDEE_LIST]',
347
+						'type'                => 'string',
348
+						'required'            => false,
349
+						'validation'          => true,
350
+						'format'              => '%s',
351
+						'css_class'           => 'large-text',
352
+						'rows'                => '5',
353
+						'shortcodes_required' => ['[ATTENDEE_LIST]'],
354
+					],
355
+					'ticket_list'   => [
356
+						'input'               => 'textarea',
357
+						'label'               => '[TICKET_LIST]',
358
+						'type'                => 'string',
359
+						'required'            => false,
360
+						'validation'          => true,
361
+						'format'              => '%s',
362
+						'css_class'           => 'large-text',
363
+						'rows'                => '10',
364
+						'shortcodes_required' => ['[TICKET_LIST]'],
365
+					],
366
+					'datetime_list' => [
367
+						'input'               => 'textarea',
368
+						'label'               => '[DATETIME_LIST]',
369
+						'type'                => 'string',
370
+						'required'            => false,
371
+						'validation'          => true,
372
+						'format'              => '%s',
373
+						'css_class'           => 'large-text',
374
+						'rows'                => '10',
375
+						'shortcodes_required' => ['[DATETIME_LIST]'],
376
+					],
377
+				],
378
+			],
379
+		];
380
+	}
381
+
382
+
383
+	/**
384
+	 * See definition of this class in parent
385
+	 */
386
+	protected function _set_default_message_types()
387
+	{
388
+		$this->_default_message_types = [
389
+			'payment',
390
+			'payment_refund',
391
+			'registration',
392
+			'not_approved_registration',
393
+			'pending_approval',
394
+		];
395
+	}
396
+
397
+
398
+	/**
399
+	 * @see   definition of this class in parent
400
+	 * @since 4.5.0
401
+	 */
402
+	protected function _set_valid_message_types()
403
+	{
404
+		$this->_valid_message_types = [
405
+			'payment',
406
+			'registration',
407
+			'not_approved_registration',
408
+			'declined_registration',
409
+			'cancelled_registration',
410
+			'pending_approval',
411
+			'registration_summary',
412
+			'payment_reminder',
413
+			'payment_declined',
414
+			'payment_refund',
415
+		];
416
+	}
417
+
418
+
419
+	/**
420
+	 * setting up admin_settings_fields for messenger.
421
+	 */
422
+	protected function _set_admin_settings_fields()
423
+	{
424
+	}
425
+
426
+
427
+	/**
428
+	 * We just deliver the messages don't kill us!!
429
+	 *
430
+	 * @return bool|WP_Error true if message delivered, false if it didn't deliver OR bubble up any error object if
431
+	 *              present.
432
+	 * @throws EE_Error
433
+	 * @throws Exception
434
+	 */
435
+	protected function _send_message()
436
+	{
437
+		$success = wp_mail(
438
+			$this->_to,
439
+			// some old values for subject may be expecting HTML entities to be decoded in the subject
440
+			// and subjects aren't interpreted as HTML, so there should be no HTML in them
441
+			wp_strip_all_tags(wp_specialchars_decode($this->_subject, ENT_QUOTES)),
442
+			$this->_body(),
443
+			$this->_headers()
444
+		);
445
+		if (! $success) {
446
+			EE_Error::add_error(
447
+				sprintf(
448
+					esc_html__(
449
+						'The email did not send successfully.%3$sThe WordPress wp_mail function is used for sending mails but does not give any useful information when an email fails to send.%3$sIt is possible the "to" address (%1$s) or "from" address (%2$s) is invalid.%3$s',
450
+						'event_espresso'
451
+					),
452
+					$this->_to,
453
+					$this->_from,
454
+					'<br />'
455
+				),
456
+				__FILE__,
457
+				__FUNCTION__,
458
+				__LINE__
459
+			);
460
+		}
461
+		return $success;
462
+	}
463
+
464
+
465
+	/**
466
+	 * see parent for definition
467
+	 *
468
+	 * @return string html body of the message content and the related css.
469
+	 * @throws EE_Error
470
+	 * @throws Exception
471
+	 */
472
+	protected function _preview()
473
+	{
474
+		return $this->_body(true);
475
+	}
476
+
477
+
478
+	/**
479
+	 * Setup headers for email
480
+	 *
481
+	 * @access protected
482
+	 * @return string formatted header for email
483
+	 */
484
+	protected function _headers()
485
+	{
486
+		$this->_ensure_has_from_email_address();
487
+		$from    = $this->_from;
488
+		$headers = [
489
+			'From:' . $from,
490
+			'Reply-To:' . $from,
491
+			'Content-Type:text/html; charset=utf-8',
492
+		];
493
+
494
+		/**
495
+		 * Second condition added as a result of https://events.codebasehq.com/projects/event-espresso/tickets/11416 to
496
+		 * cover back compat where there may be users who have saved cc values in their db for the newsletter message
497
+		 * type which they are no longer able to change.
498
+		 */
499
+		if (! empty($this->_cc) && ! $this->_incoming_message_type instanceof EE_Newsletter_message_type) {
500
+			$headers[] = 'cc: ' . $this->_cc;
501
+		}
502
+
503
+		// but wait!  Header's for the from is NOT reliable because some plugins don't respect From: as set in the
504
+		// header.
505
+		add_filter('wp_mail_from', [$this, 'set_from_address'], 100);
506
+		add_filter('wp_mail_from_name', [$this, 'set_from_name'], 100);
507
+		return apply_filters('FHEE__EE_Email_messenger___headers', $headers, $this->_incoming_message_type, $this);
508
+	}
509
+
510
+
511
+	/**
512
+	 * This simply ensures that the from address is not empty.  If it is, then we use whatever is set as the site email
513
+	 * address for the from address to avoid problems with sending emails.
514
+	 */
515
+	protected function _ensure_has_from_email_address()
516
+	{
517
+		if (empty($this->_from)) {
518
+			$this->_from = get_bloginfo('admin_email');
519
+		}
520
+	}
521
+
522
+
523
+	/**
524
+	 * This simply parses whatever is set as the $_from address and determines if it is in the format {name} <{email}>
525
+	 * or just {email} and returns an array with the "from_name" and "from_email" as the values. Note from_name *MAY*
526
+	 * be empty
527
+	 *
528
+	 * @return array
529
+	 * @since 4.3.1
530
+	 */
531
+	private function _parse_from()
532
+	{
533
+		if (strpos($this->_from, '<') !== false) {
534
+			$from_name = substr($this->_from, 0, strpos($this->_from, '<') - 1);
535
+			$from_name = str_replace('"', '', $from_name);
536
+			$from_name = trim($from_name);
537
+
538
+			$from_email = substr($this->_from, strpos($this->_from, '<') + 1);
539
+			$from_email = str_replace('>', '', $from_email);
540
+			$from_email = trim($from_email);
541
+		} elseif (trim($this->_from) !== '') {
542
+			$from_name  = '';
543
+			$from_email = trim($this->_from);
544
+		} else {
545
+			$from_name = $from_email = '';
546
+		}
547
+		return [$from_name, $from_email];
548
+	}
549
+
550
+
551
+	/**
552
+	 * Callback for the wp_mail_from filter.
553
+	 *
554
+	 * @param string $from_email What the original from_email is.
555
+	 * @return string
556
+	 * @since 4.3.1
557
+	 */
558
+	public function set_from_address($from_email)
559
+	{
560
+		$parsed_from = $this->_parse_from();
561
+		// includes fallback if the parsing failed.
562
+		return is_array($parsed_from) && ! empty($parsed_from[1])
563
+			? $parsed_from[1]
564
+			: get_bloginfo('admin_email');
565
+	}
566
+
567
+
568
+	/**
569
+	 * Callback for the wp_mail_from_name filter.
570
+	 *
571
+	 * @param string $from_name The original from_name.
572
+	 * @return string
573
+	 * @since 4.3.1
574
+	 */
575
+	public function set_from_name($from_name)
576
+	{
577
+		$parsed_from = $this->_parse_from();
578
+		if (is_array($parsed_from) && ! empty($parsed_from[0])) {
579
+			$from_name = $parsed_from[0];
580
+		}
581
+
582
+		// if from name is "WordPress" let's sub in the site name instead (more friendly!)
583
+		// but realize the default name is HTML entity-encoded
584
+		return $from_name == 'WordPress' ? wp_specialchars_decode(get_bloginfo(), ENT_QUOTES) : $from_name;
585
+	}
586
+
587
+
588
+	/**
589
+	 * setup body for email
590
+	 *
591
+	 * @param bool $preview will determine whether this is preview template or not.
592
+	 * @return string formatted body for email.
593
+	 * @throws EE_Error
594
+	 * @throws Exception
595
+	 */
596
+	protected function _body($preview = false)
597
+	{
598
+		// setup template args!
599
+		$this->_template_args = [
600
+			'subject'   => $this->_subject,
601
+			'from'      => $this->_from,
602
+			'main_body' => wpautop($this->_content),
603
+		];
604
+		$body                 = $this->_get_main_template($preview);
605
+
606
+		/**
607
+		 * This filter allows one to bypass the CSSToInlineStyles tool and leave the body untouched.
608
+		 *
609
+		 * @type    bool $preview Indicates whether a preview is being generated or not.
610
+		 * @return  bool    true  indicates to use the inliner, false bypasses it.
611
+		 */
612
+		if (apply_filters('FHEE__EE_Email_messenger__apply_CSSInliner ', true, $preview)) {
613
+			// now if this isn't a preview, let's set up the body so it has inline styles
614
+			if (! $preview || (defined('DOING_AJAX') && DOING_AJAX)) {
615
+				$style = file_get_contents(
616
+					$this->get_variation(
617
+						$this->_tmp_pack,
618
+						$this->_incoming_message_type->name,
619
+						false,
620
+						'main',
621
+						$this->_variation
622
+					),
623
+					true
624
+				);
625
+				$CSS   = new TijsVerkoyen\CssToInlineStyles\CssToInlineStyles();
626
+				$body  = $CSS->convert($body, $style);
627
+			}
628
+		}
629
+		return $body;
630
+	}
631
+
632
+
633
+	/**
634
+	 * This just returns any existing test settings that might be saved in the database
635
+	 *
636
+	 * @access public
637
+	 * @return array
638
+	 * @throws EE_Error
639
+	 * @throws ReflectionException
640
+	 */
641
+	public function get_existing_test_settings()
642
+	{
643
+		$settings = parent::get_existing_test_settings();
644
+		// override subject if present because we always want it to be fresh.
645
+		if (is_array($settings) && ! empty($settings['subject'])) {
646
+			$settings['subject'] =
647
+				sprintf(esc_html__('Test email sent from %s', 'event_espresso'), get_bloginfo('name'));
648
+		}
649
+		return $settings;
650
+	}
651 651
 }
Please login to merge, or discard this patch.
Spacing   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -59,7 +59,7 @@  discard block
 block discarded – undo
59 59
             ),
60 60
             '<code>wp_mail</code>'
61 61
         );
62
-        $this->label               = [
62
+        $this->label = [
63 63
             'singular' => esc_html__('email', 'event_espresso'),
64 64
             'plural'   => esc_html__('emails', 'event_espresso'),
65 65
         ];
@@ -442,7 +442,7 @@  discard block
 block discarded – undo
442 442
             $this->_body(),
443 443
             $this->_headers()
444 444
         );
445
-        if (! $success) {
445
+        if ( ! $success) {
446 446
             EE_Error::add_error(
447 447
                 sprintf(
448 448
                     esc_html__(
@@ -486,8 +486,8 @@  discard block
 block discarded – undo
486 486
         $this->_ensure_has_from_email_address();
487 487
         $from    = $this->_from;
488 488
         $headers = [
489
-            'From:' . $from,
490
-            'Reply-To:' . $from,
489
+            'From:'.$from,
490
+            'Reply-To:'.$from,
491 491
             'Content-Type:text/html; charset=utf-8',
492 492
         ];
493 493
 
@@ -496,8 +496,8 @@  discard block
 block discarded – undo
496 496
          * cover back compat where there may be users who have saved cc values in their db for the newsletter message
497 497
          * type which they are no longer able to change.
498 498
          */
499
-        if (! empty($this->_cc) && ! $this->_incoming_message_type instanceof EE_Newsletter_message_type) {
500
-            $headers[] = 'cc: ' . $this->_cc;
499
+        if ( ! empty($this->_cc) && ! $this->_incoming_message_type instanceof EE_Newsletter_message_type) {
500
+            $headers[] = 'cc: '.$this->_cc;
501 501
         }
502 502
 
503 503
         // but wait!  Header's for the from is NOT reliable because some plugins don't respect From: as set in the
@@ -601,7 +601,7 @@  discard block
 block discarded – undo
601 601
             'from'      => $this->_from,
602 602
             'main_body' => wpautop($this->_content),
603 603
         ];
604
-        $body                 = $this->_get_main_template($preview);
604
+        $body = $this->_get_main_template($preview);
605 605
 
606 606
         /**
607 607
          * This filter allows one to bypass the CSSToInlineStyles tool and leave the body untouched.
@@ -611,7 +611,7 @@  discard block
 block discarded – undo
611 611
          */
612 612
         if (apply_filters('FHEE__EE_Email_messenger__apply_CSSInliner ', true, $preview)) {
613 613
             // now if this isn't a preview, let's set up the body so it has inline styles
614
-            if (! $preview || (defined('DOING_AJAX') && DOING_AJAX)) {
614
+            if ( ! $preview || (defined('DOING_AJAX') && DOING_AJAX)) {
615 615
                 $style = file_get_contents(
616 616
                     $this->get_variation(
617 617
                         $this->_tmp_pack,
Please login to merge, or discard this patch.