Completed
Branch master (5e2734)
by
unknown
14:08 queued 10:23
created
espresso.php 1 patch
Indentation   +97 added lines, -97 removed lines patch added patch discarded remove patch
@@ -37,124 +37,124 @@
 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="http://php.net/downloads.php">http://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="http://php.net/downloads.php">http://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';
98
-        require_once __DIR__ . '/vendor/wp-graphql/wp-graphql/wp-graphql.php';
97
+		require_once __DIR__ . '/vendor/autoload.php';
98
+		require_once __DIR__ . '/vendor/wp-graphql/wp-graphql/wp-graphql.php';
99 99
 
100
-        /**
101
-         * espresso_version
102
-         * Returns the plugin version
103
-         *
104
-         * @return string
105
-         */
106
-        function espresso_version()
107
-        {
108
-            return apply_filters('FHEE__espresso__espresso_version', '5.0.7.rc.000');
109
-        }
100
+		/**
101
+		 * espresso_version
102
+		 * Returns the plugin version
103
+		 *
104
+		 * @return string
105
+		 */
106
+		function espresso_version()
107
+		{
108
+			return apply_filters('FHEE__espresso__espresso_version', '5.0.7.rc.000');
109
+		}
110 110
 
111
-        /**
112
-         * espresso_plugin_activation
113
-         * adds a wp-option to indicate that EE has been activated via the WP admin plugins page
114
-         */
115
-        function espresso_plugin_activation()
116
-        {
117
-            update_option('ee_espresso_activation', true);
118
-            update_option('event-espresso-core_allow_tracking', 'no');
119
-            update_option('event-espresso-core_tracking_notice', 'hide');
120
-            // Run WP GraphQL activation callback
121
-            graphql_activation_callback();
122
-        }
111
+		/**
112
+		 * espresso_plugin_activation
113
+		 * adds a wp-option to indicate that EE has been activated via the WP admin plugins page
114
+		 */
115
+		function espresso_plugin_activation()
116
+		{
117
+			update_option('ee_espresso_activation', true);
118
+			update_option('event-espresso-core_allow_tracking', 'no');
119
+			update_option('event-espresso-core_tracking_notice', 'hide');
120
+			// Run WP GraphQL activation callback
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
-            graphql_deactivation_callback();
133
-            delete_option('event-espresso-core_allow_tracking');
134
-            delete_option('event-espresso-core_tracking_notice');
135
-        }
136
-        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
+			graphql_deactivation_callback();
133
+			delete_option('event-espresso-core_allow_tracking');
134
+			delete_option('event-espresso-core_tracking_notice');
135
+		}
136
+		register_deactivation_hook(EVENT_ESPRESSO_MAIN_FILE, 'espresso_plugin_deactivation');
137 137
 
138
-        require_once __DIR__ . '/core/bootstrap_espresso.php';
139
-        bootstrap_espresso();
140
-    }
138
+		require_once __DIR__ . '/core/bootstrap_espresso.php';
139
+		bootstrap_espresso();
140
+	}
141 141
 }
142 142
 
143 143
 if (! function_exists('espresso_deactivate_plugin')) {
144
-    /**
145
-     *    deactivate_plugin
146
-     * usage:  espresso_deactivate_plugin( plugin_basename( __FILE__ ));
147
-     *
148
-     * @access public
149
-     * @param string $plugin_basename - the results of plugin_basename( __FILE__ ) for the plugin's main file
150
-     * @return    void
151
-     */
152
-    function espresso_deactivate_plugin($plugin_basename = '')
153
-    {
154
-        if (! function_exists('deactivate_plugins')) {
155
-            require_once ABSPATH . 'wp-admin/includes/plugin.php';
156
-        }
157
-        unset($_GET['activate'], $_REQUEST['activate']);
158
-        deactivate_plugins($plugin_basename);
159
-    }
144
+	/**
145
+	 *    deactivate_plugin
146
+	 * usage:  espresso_deactivate_plugin( plugin_basename( __FILE__ ));
147
+	 *
148
+	 * @access public
149
+	 * @param string $plugin_basename - the results of plugin_basename( __FILE__ ) for the plugin's main file
150
+	 * @return    void
151
+	 */
152
+	function espresso_deactivate_plugin($plugin_basename = '')
153
+	{
154
+		if (! function_exists('deactivate_plugins')) {
155
+			require_once ABSPATH . 'wp-admin/includes/plugin.php';
156
+		}
157
+		unset($_GET['activate'], $_REQUEST['activate']);
158
+		deactivate_plugins($plugin_basename);
159
+	}
160 160
 }
Please login to merge, or discard this patch.
core/helpers/EEH_DTT_Helper.helper.php 1 patch
Indentation   +990 added lines, -990 removed lines patch added patch discarded remove patch
@@ -15,1054 +15,1054 @@
 block discarded – undo
15 15
  */
16 16
 class EEH_DTT_Helper
17 17
 {
18
-    /**
19
-     * @since 5.0.6.p
20
-     *
21
-     * @var HelperInterface|null
22
-     */
23
-    protected static ?HelperInterface $dtt_helper = null;
18
+	/**
19
+	 * @since 5.0.6.p
20
+	 *
21
+	 * @var HelperInterface|null
22
+	 */
23
+	protected static ?HelperInterface $dtt_helper = null;
24 24
 
25
-    /**
26
-     * return the timezone set for the WP install
27
-     *
28
-     * @return string valid timezone string for PHP DateTimeZone() class
29
-     * @throws InvalidArgumentException
30
-     * @throws InvalidDataTypeException
31
-     * @throws InvalidInterfaceException
32
-     */
33
-    public static function get_timezone()
34
-    {
35
-        return EEH_DTT_Helper::get_valid_timezone_string();
36
-    }
25
+	/**
26
+	 * return the timezone set for the WP install
27
+	 *
28
+	 * @return string valid timezone string for PHP DateTimeZone() class
29
+	 * @throws InvalidArgumentException
30
+	 * @throws InvalidDataTypeException
31
+	 * @throws InvalidInterfaceException
32
+	 */
33
+	public static function get_timezone()
34
+	{
35
+		return EEH_DTT_Helper::get_valid_timezone_string();
36
+	}
37 37
 
38 38
 
39
-    /**
40
-     * get_valid_timezone_string
41
-     *    ensures that a valid timezone string is returned
42
-     *
43
-     * @param string $timezone_string
44
-     * @return string
45
-     * @throws InvalidArgumentException
46
-     * @throws InvalidDataTypeException
47
-     * @throws InvalidInterfaceException
48
-     */
49
-    public static function get_valid_timezone_string($timezone_string = '')
50
-    {
51
-        return self::getHelperAdapter()->getValidTimezoneString($timezone_string);
52
-    }
39
+	/**
40
+	 * get_valid_timezone_string
41
+	 *    ensures that a valid timezone string is returned
42
+	 *
43
+	 * @param string $timezone_string
44
+	 * @return string
45
+	 * @throws InvalidArgumentException
46
+	 * @throws InvalidDataTypeException
47
+	 * @throws InvalidInterfaceException
48
+	 */
49
+	public static function get_valid_timezone_string($timezone_string = '')
50
+	{
51
+		return self::getHelperAdapter()->getValidTimezoneString($timezone_string);
52
+	}
53 53
 
54 54
 
55
-    /**
56
-     * This only purpose for this static method is to validate that the incoming timezone is a valid php timezone.
57
-     *
58
-     * @static
59
-     * @param  string $timezone_string Timezone string to check
60
-     * @param bool    $throw_error
61
-     * @return bool
62
-     * @throws InvalidArgumentException
63
-     * @throws InvalidDataTypeException
64
-     * @throws InvalidInterfaceException
65
-     */
66
-    public static function validate_timezone($timezone_string, $throw_error = true)
67
-    {
68
-        return self::getHelperAdapter()->validateTimezone($timezone_string, $throw_error);
69
-    }
55
+	/**
56
+	 * This only purpose for this static method is to validate that the incoming timezone is a valid php timezone.
57
+	 *
58
+	 * @static
59
+	 * @param  string $timezone_string Timezone string to check
60
+	 * @param bool    $throw_error
61
+	 * @return bool
62
+	 * @throws InvalidArgumentException
63
+	 * @throws InvalidDataTypeException
64
+	 * @throws InvalidInterfaceException
65
+	 */
66
+	public static function validate_timezone($timezone_string, $throw_error = true)
67
+	{
68
+		return self::getHelperAdapter()->validateTimezone($timezone_string, $throw_error);
69
+	}
70 70
 
71 71
 
72
-    /**
73
-     * This returns a string that can represent the provided gmt offset in format that can be passed into
74
-     * DateTimeZone.  This is NOT a string that can be passed as a value on the WordPress timezone_string option.
75
-     *
76
-     * @param float|string $gmt_offset
77
-     * @return string
78
-     * @throws InvalidArgumentException
79
-     * @throws InvalidDataTypeException
80
-     * @throws InvalidInterfaceException
81
-     */
82
-    public static function get_timezone_string_from_gmt_offset($gmt_offset = '')
83
-    {
84
-        return self::getHelperAdapter()->getTimezoneStringFromGmtOffset($gmt_offset);
85
-    }
72
+	/**
73
+	 * This returns a string that can represent the provided gmt offset in format that can be passed into
74
+	 * DateTimeZone.  This is NOT a string that can be passed as a value on the WordPress timezone_string option.
75
+	 *
76
+	 * @param float|string $gmt_offset
77
+	 * @return string
78
+	 * @throws InvalidArgumentException
79
+	 * @throws InvalidDataTypeException
80
+	 * @throws InvalidInterfaceException
81
+	 */
82
+	public static function get_timezone_string_from_gmt_offset($gmt_offset = '')
83
+	{
84
+		return self::getHelperAdapter()->getTimezoneStringFromGmtOffset($gmt_offset);
85
+	}
86 86
 
87 87
 
88
-    /**
89
-     * Gets the site's GMT offset based on either the timezone string
90
-     * (in which case teh gmt offset will vary depending on the location's
91
-     * observance of daylight savings time) or the gmt_offset wp option
92
-     *
93
-     * @return int seconds offset
94
-     * @throws InvalidArgumentException
95
-     * @throws InvalidDataTypeException
96
-     * @throws InvalidInterfaceException
97
-     */
98
-    public static function get_site_timezone_gmt_offset()
99
-    {
100
-        return self::getHelperAdapter()->getSiteTimezoneGmtOffset();
101
-    }
88
+	/**
89
+	 * Gets the site's GMT offset based on either the timezone string
90
+	 * (in which case teh gmt offset will vary depending on the location's
91
+	 * observance of daylight savings time) or the gmt_offset wp option
92
+	 *
93
+	 * @return int seconds offset
94
+	 * @throws InvalidArgumentException
95
+	 * @throws InvalidDataTypeException
96
+	 * @throws InvalidInterfaceException
97
+	 */
98
+	public static function get_site_timezone_gmt_offset()
99
+	{
100
+		return self::getHelperAdapter()->getSiteTimezoneGmtOffset();
101
+	}
102 102
 
103 103
 
104
-    /**
105
-     * Depending on PHP version,
106
-     * there might not be valid current timezone strings to match these gmt_offsets in its timezone tables.
107
-     * To get around that, for these fringe timezones we bump them to a known valid offset.
108
-     * This method should ONLY be called after first verifying an timezone_string cannot be retrieved for the offset.
109
-     *
110
-     * @deprecated 4.9.54.rc    Developers this was always meant to only be an internally used method.  This will be
111
-     *                          removed in a future version of EE.
112
-     * @param int $gmt_offset
113
-     * @return int
114
-     * @throws InvalidArgumentException
115
-     * @throws InvalidDataTypeException
116
-     * @throws InvalidInterfaceException
117
-     */
118
-    public static function adjust_invalid_gmt_offsets($gmt_offset = 0)
119
-    {
120
-        return self::getHelperAdapter()->adjustInvalidGmtOffsets($gmt_offset);
121
-    }
104
+	/**
105
+	 * Depending on PHP version,
106
+	 * there might not be valid current timezone strings to match these gmt_offsets in its timezone tables.
107
+	 * To get around that, for these fringe timezones we bump them to a known valid offset.
108
+	 * This method should ONLY be called after first verifying an timezone_string cannot be retrieved for the offset.
109
+	 *
110
+	 * @deprecated 4.9.54.rc    Developers this was always meant to only be an internally used method.  This will be
111
+	 *                          removed in a future version of EE.
112
+	 * @param int $gmt_offset
113
+	 * @return int
114
+	 * @throws InvalidArgumentException
115
+	 * @throws InvalidDataTypeException
116
+	 * @throws InvalidInterfaceException
117
+	 */
118
+	public static function adjust_invalid_gmt_offsets($gmt_offset = 0)
119
+	{
120
+		return self::getHelperAdapter()->adjustInvalidGmtOffsets($gmt_offset);
121
+	}
122 122
 
123 123
 
124
-    /**
125
-     * get_timezone_string_from_abbreviations_list
126
-     *
127
-     * @deprecated 4.9.54.rc  Developers, this was never intended to be public.  This is a soft deprecation for now.
128
-     *                        If you are using this, you'll want to work out an alternate way of getting the value.
129
-     * @param int  $gmt_offset
130
-     * @param bool $coerce If true, we attempt to coerce with our adjustment table @see self::adjust_invalid_gmt_offset.
131
-     * @return string
132
-     * @throws EE_Error
133
-     * @throws InvalidArgumentException
134
-     * @throws InvalidDataTypeException
135
-     * @throws InvalidInterfaceException
136
-     */
137
-    public static function get_timezone_string_from_abbreviations_list($gmt_offset = 0, $coerce = true)
138
-    {
139
-        $gmt_offset =  (int) $gmt_offset;
140
-        /** @var array[] $abbreviations */
141
-        $abbreviations = DateTimeZone::listAbbreviations();
142
-        foreach ($abbreviations as $abbreviation) {
143
-            foreach ($abbreviation as $timezone) {
144
-                if ((int) $timezone['offset'] === $gmt_offset && (bool) $timezone['dst'] === false) {
145
-                    try {
146
-                        $offset = self::get_timezone_offset(new DateTimeZone($timezone['timezone_id']));
147
-                        if ($offset !== $gmt_offset) {
148
-                            continue;
149
-                        }
150
-                        return $timezone['timezone_id'];
151
-                    } catch (Exception $e) {
152
-                        continue;
153
-                    }
154
-                }
155
-            }
156
-        }
157
-        // if $coerce is true, let's see if we can get a timezone string after the offset is adjusted
158
-        if ($coerce === true) {
159
-            $timezone_string = self::get_timezone_string_from_abbreviations_list(
160
-                self::adjust_invalid_gmt_offsets($gmt_offset),
161
-                false
162
-            );
163
-            if ($timezone_string) {
164
-                return $timezone_string;
165
-            }
166
-        }
167
-        throw new EE_Error(
168
-            sprintf(
169
-                esc_html__(
170
-                    'The provided GMT offset (%1$s), is invalid, please check with %2$sthis list%3$s for what valid timezones can be used',
171
-                    'event_espresso'
172
-                ),
173
-                $gmt_offset / HOUR_IN_SECONDS,
174
-                '<a href="http://www.php.net/manual/en/timezones.php">',
175
-                '</a>'
176
-            )
177
-        );
178
-    }
124
+	/**
125
+	 * get_timezone_string_from_abbreviations_list
126
+	 *
127
+	 * @deprecated 4.9.54.rc  Developers, this was never intended to be public.  This is a soft deprecation for now.
128
+	 *                        If you are using this, you'll want to work out an alternate way of getting the value.
129
+	 * @param int  $gmt_offset
130
+	 * @param bool $coerce If true, we attempt to coerce with our adjustment table @see self::adjust_invalid_gmt_offset.
131
+	 * @return string
132
+	 * @throws EE_Error
133
+	 * @throws InvalidArgumentException
134
+	 * @throws InvalidDataTypeException
135
+	 * @throws InvalidInterfaceException
136
+	 */
137
+	public static function get_timezone_string_from_abbreviations_list($gmt_offset = 0, $coerce = true)
138
+	{
139
+		$gmt_offset =  (int) $gmt_offset;
140
+		/** @var array[] $abbreviations */
141
+		$abbreviations = DateTimeZone::listAbbreviations();
142
+		foreach ($abbreviations as $abbreviation) {
143
+			foreach ($abbreviation as $timezone) {
144
+				if ((int) $timezone['offset'] === $gmt_offset && (bool) $timezone['dst'] === false) {
145
+					try {
146
+						$offset = self::get_timezone_offset(new DateTimeZone($timezone['timezone_id']));
147
+						if ($offset !== $gmt_offset) {
148
+							continue;
149
+						}
150
+						return $timezone['timezone_id'];
151
+					} catch (Exception $e) {
152
+						continue;
153
+					}
154
+				}
155
+			}
156
+		}
157
+		// if $coerce is true, let's see if we can get a timezone string after the offset is adjusted
158
+		if ($coerce === true) {
159
+			$timezone_string = self::get_timezone_string_from_abbreviations_list(
160
+				self::adjust_invalid_gmt_offsets($gmt_offset),
161
+				false
162
+			);
163
+			if ($timezone_string) {
164
+				return $timezone_string;
165
+			}
166
+		}
167
+		throw new EE_Error(
168
+			sprintf(
169
+				esc_html__(
170
+					'The provided GMT offset (%1$s), is invalid, please check with %2$sthis list%3$s for what valid timezones can be used',
171
+					'event_espresso'
172
+				),
173
+				$gmt_offset / HOUR_IN_SECONDS,
174
+				'<a href="http://www.php.net/manual/en/timezones.php">',
175
+				'</a>'
176
+			)
177
+		);
178
+	}
179 179
 
180 180
 
181
-    /**
182
-     * Get Timezone Transitions
183
-     *
184
-     * @param DateTimeZone $date_time_zone
185
-     * @param int|null     $time
186
-     * @param bool         $first_only
187
-     * @return array
188
-     * @throws InvalidArgumentException
189
-     * @throws InvalidDataTypeException
190
-     * @throws InvalidInterfaceException
191
-     */
192
-    public static function get_timezone_transitions(DateTimeZone $date_time_zone, $time = null, $first_only = true)
193
-    {
194
-        return self::getHelperAdapter()->getTimezoneTransitions($date_time_zone, $time, $first_only);
195
-    }
181
+	/**
182
+	 * Get Timezone Transitions
183
+	 *
184
+	 * @param DateTimeZone $date_time_zone
185
+	 * @param int|null     $time
186
+	 * @param bool         $first_only
187
+	 * @return array
188
+	 * @throws InvalidArgumentException
189
+	 * @throws InvalidDataTypeException
190
+	 * @throws InvalidInterfaceException
191
+	 */
192
+	public static function get_timezone_transitions(DateTimeZone $date_time_zone, $time = null, $first_only = true)
193
+	{
194
+		return self::getHelperAdapter()->getTimezoneTransitions($date_time_zone, $time, $first_only);
195
+	}
196 196
 
197 197
 
198
-    /**
199
-     * Get Timezone Offset for given timezone object.
200
-     *
201
-     * @param DateTimeZone      $date_time_zone
202
-     * @param int|string|null   $time
203
-     * @return mixed
204
-     * @throws InvalidArgumentException
205
-     * @throws InvalidDataTypeException
206
-     * @throws InvalidInterfaceException
207
-     */
208
-    public static function get_timezone_offset(DateTimeZone $date_time_zone, $time = null)
209
-    {
210
-        return self::getHelperAdapter()->getTimezoneOffset($date_time_zone, $time);
211
-    }
198
+	/**
199
+	 * Get Timezone Offset for given timezone object.
200
+	 *
201
+	 * @param DateTimeZone      $date_time_zone
202
+	 * @param int|string|null   $time
203
+	 * @return mixed
204
+	 * @throws InvalidArgumentException
205
+	 * @throws InvalidDataTypeException
206
+	 * @throws InvalidInterfaceException
207
+	 */
208
+	public static function get_timezone_offset(DateTimeZone $date_time_zone, $time = null)
209
+	{
210
+		return self::getHelperAdapter()->getTimezoneOffset($date_time_zone, $time);
211
+	}
212 212
 
213 213
 
214
-    /**
215
-     * Prints a select input for the given timezone string.
216
-     * @param string $timezone_string
217
-     * @deprecatd 4.9.54.rc   Soft deprecation.  Consider using \EEH_DTT_Helper::wp_timezone_choice instead.
218
-     * @throws InvalidArgumentException
219
-     * @throws InvalidDataTypeException
220
-     * @throws InvalidInterfaceException
221
-     */
222
-    public static function timezone_select_input($timezone_string = '')
223
-    {
224
-        self::getHelperAdapter()->timezoneSelectInput($timezone_string);
225
-    }
214
+	/**
215
+	 * Prints a select input for the given timezone string.
216
+	 * @param string $timezone_string
217
+	 * @deprecatd 4.9.54.rc   Soft deprecation.  Consider using \EEH_DTT_Helper::wp_timezone_choice instead.
218
+	 * @throws InvalidArgumentException
219
+	 * @throws InvalidDataTypeException
220
+	 * @throws InvalidInterfaceException
221
+	 */
222
+	public static function timezone_select_input($timezone_string = '')
223
+	{
224
+		self::getHelperAdapter()->timezoneSelectInput($timezone_string);
225
+	}
226 226
 
227 227
 
228
-    /**
229
-     * This method will take an incoming unix timestamp and add the offset to it for the given timezone_string.
230
-     * If no unix timestamp is given then time() is used.  If no timezone is given then the set timezone string for
231
-     * the site is used.
232
-     * This is used typically when using a Unix timestamp any core WP functions that expect their specially
233
-     * computed timestamp (i.e. date_i18n() )
234
-     *
235
-     * @param int    $unix_timestamp                  if 0, then time() will be used.
236
-     * @param string $timezone_string                 timezone_string. If empty, then the current set timezone for the
237
-     *                                                site will be used.
238
-     * @return int $unix_timestamp with the offset applied for the given timezone.
239
-     * @throws InvalidArgumentException
240
-     * @throws InvalidDataTypeException
241
-     * @throws InvalidInterfaceException
242
-     */
243
-    public static function get_timestamp_with_offset($unix_timestamp = 0, $timezone_string = '')
244
-    {
245
-        return self::getHelperAdapter()->getTimestampWithOffset($unix_timestamp, $timezone_string);
246
-    }
228
+	/**
229
+	 * This method will take an incoming unix timestamp and add the offset to it for the given timezone_string.
230
+	 * If no unix timestamp is given then time() is used.  If no timezone is given then the set timezone string for
231
+	 * the site is used.
232
+	 * This is used typically when using a Unix timestamp any core WP functions that expect their specially
233
+	 * computed timestamp (i.e. date_i18n() )
234
+	 *
235
+	 * @param int    $unix_timestamp                  if 0, then time() will be used.
236
+	 * @param string $timezone_string                 timezone_string. If empty, then the current set timezone for the
237
+	 *                                                site will be used.
238
+	 * @return int $unix_timestamp with the offset applied for the given timezone.
239
+	 * @throws InvalidArgumentException
240
+	 * @throws InvalidDataTypeException
241
+	 * @throws InvalidInterfaceException
242
+	 */
243
+	public static function get_timestamp_with_offset($unix_timestamp = 0, $timezone_string = '')
244
+	{
245
+		return self::getHelperAdapter()->getTimestampWithOffset($unix_timestamp, $timezone_string);
246
+	}
247 247
 
248 248
 
249
-    /**
250
-     *    _set_date_time_field
251
-     *    modifies EE_Base_Class EE_Datetime_Field objects
252
-     *
253
-     * @param  EE_Base_Class $obj                 EE_Base_Class object
254
-     * @param    DateTime    $DateTime            PHP DateTime object
255
-     * @param  string        $datetime_field_name the datetime fieldname to be manipulated
256
-     * @return EE_Base_Class
257
-     * @throws EE_Error
258
-     */
259
-    protected static function _set_date_time_field(EE_Base_Class $obj, DateTime $DateTime, $datetime_field_name)
260
-    {
261
-        // grab current datetime format
262
-        $current_format = $obj->get_format();
263
-        // set new full timestamp format
264
-        $obj->set_date_format(EE_Datetime_Field::mysql_date_format);
265
-        $obj->set_time_format(EE_Datetime_Field::mysql_time_format);
266
-        // set the new date value using a full timestamp format so that no data is lost
267
-        $obj->set($datetime_field_name, $DateTime->format(EE_Datetime_Field::mysql_timestamp_format));
268
-        // reset datetime formats
269
-        $obj->set_date_format($current_format[0]);
270
-        $obj->set_time_format($current_format[1]);
271
-        return $obj;
272
-    }
249
+	/**
250
+	 *    _set_date_time_field
251
+	 *    modifies EE_Base_Class EE_Datetime_Field objects
252
+	 *
253
+	 * @param  EE_Base_Class $obj                 EE_Base_Class object
254
+	 * @param    DateTime    $DateTime            PHP DateTime object
255
+	 * @param  string        $datetime_field_name the datetime fieldname to be manipulated
256
+	 * @return EE_Base_Class
257
+	 * @throws EE_Error
258
+	 */
259
+	protected static function _set_date_time_field(EE_Base_Class $obj, DateTime $DateTime, $datetime_field_name)
260
+	{
261
+		// grab current datetime format
262
+		$current_format = $obj->get_format();
263
+		// set new full timestamp format
264
+		$obj->set_date_format(EE_Datetime_Field::mysql_date_format);
265
+		$obj->set_time_format(EE_Datetime_Field::mysql_time_format);
266
+		// set the new date value using a full timestamp format so that no data is lost
267
+		$obj->set($datetime_field_name, $DateTime->format(EE_Datetime_Field::mysql_timestamp_format));
268
+		// reset datetime formats
269
+		$obj->set_date_format($current_format[0]);
270
+		$obj->set_time_format($current_format[1]);
271
+		return $obj;
272
+	}
273 273
 
274 274
 
275
-    /**
276
-     *    date_time_add
277
-     *    helper for doing simple datetime calculations on a given datetime from EE_Base_Class
278
-     *    and modifying it IN the EE_Base_Class so you don't have to do anything else.
279
-     *
280
-     * @param  EE_Base_Class $obj                 EE_Base_Class object
281
-     * @param  string        $datetime_field_name name of the EE_Datetime_Filed datatype db column to be manipulated
282
-     * @param  string        $period              what you are adding. The options are (years, months, days, hours,
283
-     *                                            minutes, seconds) defaults to years
284
-     * @param  integer       $value               what you want to increment the time by
285
-     * @return EE_Base_Class return the EE_Base_Class object so right away you can do something with it
286
-     *                                            (chaining)
287
-     * @throws EE_Error
288
-     * @throws Exception
289
-     */
290
-    public static function date_time_add(EE_Base_Class $obj, $datetime_field_name, $period = 'years', $value = 1)
291
-    {
292
-        // get the raw UTC date.
293
-        $DateTime = $obj->get_DateTime_object($datetime_field_name);
294
-        $DateTime = EEH_DTT_Helper::calc_date($DateTime, $period, $value);
295
-        return EEH_DTT_Helper::_set_date_time_field($obj, $DateTime, $datetime_field_name);
296
-    }
275
+	/**
276
+	 *    date_time_add
277
+	 *    helper for doing simple datetime calculations on a given datetime from EE_Base_Class
278
+	 *    and modifying it IN the EE_Base_Class so you don't have to do anything else.
279
+	 *
280
+	 * @param  EE_Base_Class $obj                 EE_Base_Class object
281
+	 * @param  string        $datetime_field_name name of the EE_Datetime_Filed datatype db column to be manipulated
282
+	 * @param  string        $period              what you are adding. The options are (years, months, days, hours,
283
+	 *                                            minutes, seconds) defaults to years
284
+	 * @param  integer       $value               what you want to increment the time by
285
+	 * @return EE_Base_Class return the EE_Base_Class object so right away you can do something with it
286
+	 *                                            (chaining)
287
+	 * @throws EE_Error
288
+	 * @throws Exception
289
+	 */
290
+	public static function date_time_add(EE_Base_Class $obj, $datetime_field_name, $period = 'years', $value = 1)
291
+	{
292
+		// get the raw UTC date.
293
+		$DateTime = $obj->get_DateTime_object($datetime_field_name);
294
+		$DateTime = EEH_DTT_Helper::calc_date($DateTime, $period, $value);
295
+		return EEH_DTT_Helper::_set_date_time_field($obj, $DateTime, $datetime_field_name);
296
+	}
297 297
 
298 298
 
299
-    /**
300
-     *    date_time_subtract
301
-     *    same as date_time_add except subtracting value instead of adding.
302
-     *
303
-     * @param EE_Base_Class $obj
304
-     * @param  string       $datetime_field_name name of the EE_Datetime_Filed datatype db column to be manipulated
305
-     * @param string        $period
306
-     * @param int           $value
307
-     * @return EE_Base_Class
308
-     * @throws EE_Error
309
-     * @throws Exception
310
-     */
311
-    public static function date_time_subtract(EE_Base_Class $obj, $datetime_field_name, $period = 'years', $value = 1)
312
-    {
313
-        // get the raw UTC date
314
-        $DateTime = $obj->get_DateTime_object($datetime_field_name);
315
-        $DateTime = EEH_DTT_Helper::calc_date($DateTime, $period, $value, '-');
316
-        return EEH_DTT_Helper::_set_date_time_field($obj, $DateTime, $datetime_field_name);
317
-    }
299
+	/**
300
+	 *    date_time_subtract
301
+	 *    same as date_time_add except subtracting value instead of adding.
302
+	 *
303
+	 * @param EE_Base_Class $obj
304
+	 * @param  string       $datetime_field_name name of the EE_Datetime_Filed datatype db column to be manipulated
305
+	 * @param string        $period
306
+	 * @param int           $value
307
+	 * @return EE_Base_Class
308
+	 * @throws EE_Error
309
+	 * @throws Exception
310
+	 */
311
+	public static function date_time_subtract(EE_Base_Class $obj, $datetime_field_name, $period = 'years', $value = 1)
312
+	{
313
+		// get the raw UTC date
314
+		$DateTime = $obj->get_DateTime_object($datetime_field_name);
315
+		$DateTime = EEH_DTT_Helper::calc_date($DateTime, $period, $value, '-');
316
+		return EEH_DTT_Helper::_set_date_time_field($obj, $DateTime, $datetime_field_name);
317
+	}
318 318
 
319 319
 
320
-    /**
321
-     * Simply takes an incoming DateTime object and does calculations on it based on the incoming parameters
322
-     *
323
-     * @param  DateTime   $DateTime DateTime object
324
-     * @param  string     $period   a value to indicate what interval is being used in the calculation. The options are
325
-     *                              'years', 'months', 'days', 'hours', 'minutes', 'seconds'. Defaults to years.
326
-     * @param  int|string $value    What you want to increment the date by
327
-     * @param  string     $operand  What operand you wish to use for the calculation
328
-     * @return DateTime return whatever type came in.
329
-     * @throws Exception
330
-     * @throws EE_Error
331
-     */
332
-    protected static function _modify_datetime_object(DateTime $DateTime, $period = 'years', $value = 1, $operand = '+')
333
-    {
334
-        if (! $DateTime instanceof DateTime) {
335
-            throw new EE_Error(
336
-                sprintf(
337
-                    esc_html__('Expected a PHP DateTime object, but instead received %1$s', 'event_espresso'),
338
-                    print_r($DateTime, true)
339
-                )
340
-            );
341
-        }
342
-        switch ($period) {
343
-            case 'years':
344
-                $value = 'P' . $value . 'Y';
345
-                break;
346
-            case 'months':
347
-                $value = 'P' . $value . 'M';
348
-                break;
349
-            case 'weeks':
350
-                $value = 'P' . $value . 'W';
351
-                break;
352
-            case 'days':
353
-                $value = 'P' . $value . 'D';
354
-                break;
355
-            case 'hours':
356
-                $value = 'PT' . $value . 'H';
357
-                break;
358
-            case 'minutes':
359
-                $value = 'PT' . $value . 'M';
360
-                break;
361
-            case 'seconds':
362
-                $value = 'PT' . $value . 'S';
363
-                break;
364
-        }
365
-        switch ($operand) {
366
-            case '+':
367
-                $DateTime->add(new DateInterval($value));
368
-                break;
369
-            case '-':
370
-                $DateTime->sub(new DateInterval($value));
371
-                break;
372
-        }
373
-        return $DateTime;
374
-    }
320
+	/**
321
+	 * Simply takes an incoming DateTime object and does calculations on it based on the incoming parameters
322
+	 *
323
+	 * @param  DateTime   $DateTime DateTime object
324
+	 * @param  string     $period   a value to indicate what interval is being used in the calculation. The options are
325
+	 *                              'years', 'months', 'days', 'hours', 'minutes', 'seconds'. Defaults to years.
326
+	 * @param  int|string $value    What you want to increment the date by
327
+	 * @param  string     $operand  What operand you wish to use for the calculation
328
+	 * @return DateTime return whatever type came in.
329
+	 * @throws Exception
330
+	 * @throws EE_Error
331
+	 */
332
+	protected static function _modify_datetime_object(DateTime $DateTime, $period = 'years', $value = 1, $operand = '+')
333
+	{
334
+		if (! $DateTime instanceof DateTime) {
335
+			throw new EE_Error(
336
+				sprintf(
337
+					esc_html__('Expected a PHP DateTime object, but instead received %1$s', 'event_espresso'),
338
+					print_r($DateTime, true)
339
+				)
340
+			);
341
+		}
342
+		switch ($period) {
343
+			case 'years':
344
+				$value = 'P' . $value . 'Y';
345
+				break;
346
+			case 'months':
347
+				$value = 'P' . $value . 'M';
348
+				break;
349
+			case 'weeks':
350
+				$value = 'P' . $value . 'W';
351
+				break;
352
+			case 'days':
353
+				$value = 'P' . $value . 'D';
354
+				break;
355
+			case 'hours':
356
+				$value = 'PT' . $value . 'H';
357
+				break;
358
+			case 'minutes':
359
+				$value = 'PT' . $value . 'M';
360
+				break;
361
+			case 'seconds':
362
+				$value = 'PT' . $value . 'S';
363
+				break;
364
+		}
365
+		switch ($operand) {
366
+			case '+':
367
+				$DateTime->add(new DateInterval($value));
368
+				break;
369
+			case '-':
370
+				$DateTime->sub(new DateInterval($value));
371
+				break;
372
+		}
373
+		return $DateTime;
374
+	}
375 375
 
376 376
 
377
-    /**
378
-     * Simply takes an incoming Unix timestamp and does calculations on it based on the incoming parameters
379
-     *
380
-     * @param  int     $timestamp Unix timestamp
381
-     * @param  string  $period    a value to indicate what interval is being used in the calculation. The options are
382
-     *                            'years', 'months', 'days', 'hours', 'minutes', 'seconds'. Defaults to years.
383
-     * @param  integer $value     What you want to increment the date by
384
-     * @param  string  $operand   What operand you wish to use for the calculation
385
-     * @return int
386
-     * @throws EE_Error
387
-     */
388
-    protected static function _modify_timestamp($timestamp, $period = 'years', $value = 1, $operand = '+')
389
-    {
390
-        if (! preg_match(EE_Datetime_Field::unix_timestamp_regex, $timestamp)) {
391
-            throw new EE_Error(
392
-                sprintf(
393
-                    esc_html__('Expected a Unix timestamp, but instead received %1$s', 'event_espresso'),
394
-                    print_r($timestamp, true)
395
-                )
396
-            );
397
-        }
398
-        switch ($period) {
399
-            case 'years':
400
-                $value = YEAR_IN_SECONDS * $value;
401
-                break;
402
-            case 'months':
403
-                $value = YEAR_IN_SECONDS / 12 * $value;
404
-                break;
405
-            case 'weeks':
406
-                $value = WEEK_IN_SECONDS * $value;
407
-                break;
408
-            case 'days':
409
-                $value = DAY_IN_SECONDS * $value;
410
-                break;
411
-            case 'hours':
412
-                $value = HOUR_IN_SECONDS * $value;
413
-                break;
414
-            case 'minutes':
415
-                $value = MINUTE_IN_SECONDS * $value;
416
-                break;
417
-        }
418
-        switch ($operand) {
419
-            case '+':
420
-                $timestamp += $value;
421
-                break;
422
-            case '-':
423
-                $timestamp -= $value;
424
-                break;
425
-        }
426
-        return $timestamp;
427
-    }
377
+	/**
378
+	 * Simply takes an incoming Unix timestamp and does calculations on it based on the incoming parameters
379
+	 *
380
+	 * @param  int     $timestamp Unix timestamp
381
+	 * @param  string  $period    a value to indicate what interval is being used in the calculation. The options are
382
+	 *                            'years', 'months', 'days', 'hours', 'minutes', 'seconds'. Defaults to years.
383
+	 * @param  integer $value     What you want to increment the date by
384
+	 * @param  string  $operand   What operand you wish to use for the calculation
385
+	 * @return int
386
+	 * @throws EE_Error
387
+	 */
388
+	protected static function _modify_timestamp($timestamp, $period = 'years', $value = 1, $operand = '+')
389
+	{
390
+		if (! preg_match(EE_Datetime_Field::unix_timestamp_regex, $timestamp)) {
391
+			throw new EE_Error(
392
+				sprintf(
393
+					esc_html__('Expected a Unix timestamp, but instead received %1$s', 'event_espresso'),
394
+					print_r($timestamp, true)
395
+				)
396
+			);
397
+		}
398
+		switch ($period) {
399
+			case 'years':
400
+				$value = YEAR_IN_SECONDS * $value;
401
+				break;
402
+			case 'months':
403
+				$value = YEAR_IN_SECONDS / 12 * $value;
404
+				break;
405
+			case 'weeks':
406
+				$value = WEEK_IN_SECONDS * $value;
407
+				break;
408
+			case 'days':
409
+				$value = DAY_IN_SECONDS * $value;
410
+				break;
411
+			case 'hours':
412
+				$value = HOUR_IN_SECONDS * $value;
413
+				break;
414
+			case 'minutes':
415
+				$value = MINUTE_IN_SECONDS * $value;
416
+				break;
417
+		}
418
+		switch ($operand) {
419
+			case '+':
420
+				$timestamp += $value;
421
+				break;
422
+			case '-':
423
+				$timestamp -= $value;
424
+				break;
425
+		}
426
+		return $timestamp;
427
+	}
428 428
 
429 429
 
430
-    /**
431
-     * Simply takes an incoming UTC timestamp or DateTime object and does calculations on it based on the incoming
432
-     * parameters and returns the new timestamp or DateTime.
433
-     *
434
-     * @param  int | DateTime $DateTime_or_timestamp DateTime object or Unix timestamp
435
-     * @param  string         $period                a value to indicate what interval is being used in the
436
-     *                                               calculation. The options are 'years', 'months', 'days', 'hours',
437
-     *                                               'minutes', 'seconds'. Defaults to years.
438
-     * @param  integer        $value                 What you want to increment the date by
439
-     * @param  string         $operand               What operand you wish to use for the calculation
440
-     * @return mixed string|DateTime          return whatever type came in.
441
-     * @throws Exception
442
-     * @throws EE_Error
443
-     */
444
-    public static function calc_date($DateTime_or_timestamp, $period = 'years', $value = 1, $operand = '+')
445
-    {
446
-        if ($DateTime_or_timestamp instanceof DateTime) {
447
-            return EEH_DTT_Helper::_modify_datetime_object(
448
-                $DateTime_or_timestamp,
449
-                $period,
450
-                $value,
451
-                $operand
452
-            );
453
-        }
454
-        if (preg_match(EE_Datetime_Field::unix_timestamp_regex, $DateTime_or_timestamp)) {
455
-            return EEH_DTT_Helper::_modify_timestamp(
456
-                $DateTime_or_timestamp,
457
-                $period,
458
-                $value,
459
-                $operand
460
-            );
461
-        }
462
-        // error
463
-        return $DateTime_or_timestamp;
464
-    }
430
+	/**
431
+	 * Simply takes an incoming UTC timestamp or DateTime object and does calculations on it based on the incoming
432
+	 * parameters and returns the new timestamp or DateTime.
433
+	 *
434
+	 * @param  int | DateTime $DateTime_or_timestamp DateTime object or Unix timestamp
435
+	 * @param  string         $period                a value to indicate what interval is being used in the
436
+	 *                                               calculation. The options are 'years', 'months', 'days', 'hours',
437
+	 *                                               'minutes', 'seconds'. Defaults to years.
438
+	 * @param  integer        $value                 What you want to increment the date by
439
+	 * @param  string         $operand               What operand you wish to use for the calculation
440
+	 * @return mixed string|DateTime          return whatever type came in.
441
+	 * @throws Exception
442
+	 * @throws EE_Error
443
+	 */
444
+	public static function calc_date($DateTime_or_timestamp, $period = 'years', $value = 1, $operand = '+')
445
+	{
446
+		if ($DateTime_or_timestamp instanceof DateTime) {
447
+			return EEH_DTT_Helper::_modify_datetime_object(
448
+				$DateTime_or_timestamp,
449
+				$period,
450
+				$value,
451
+				$operand
452
+			);
453
+		}
454
+		if (preg_match(EE_Datetime_Field::unix_timestamp_regex, $DateTime_or_timestamp)) {
455
+			return EEH_DTT_Helper::_modify_timestamp(
456
+				$DateTime_or_timestamp,
457
+				$period,
458
+				$value,
459
+				$operand
460
+			);
461
+		}
462
+		// error
463
+		return $DateTime_or_timestamp;
464
+	}
465 465
 
466 466
 
467
-    /**
468
-     * The purpose of this helper method is to receive an incoming format string in php date/time format
469
-     * and spit out the js and moment.js equivalent formats.
470
-     * Note, if no format string is given, then it is assumed the user wants what is set for WP.
471
-     * Note, js date and time formats are those used by the jquery-ui datepicker and the jquery-ui date-
472
-     * time picker.
473
-     *
474
-     * @see http://stackoverflow.com/posts/16725290/ for the code inspiration.
475
-     * @param string $date_format_string
476
-     * @param string $time_format_string
477
-     * @return array
478
-     *              array(
479
-     *              'js' => array (
480
-     *              'date' => //date format
481
-     *              'time' => //time format
482
-     *              ),
483
-     *              'moment' => //date and time format.
484
-     *              )
485
-     */
486
-    public static function convert_php_to_js_and_moment_date_formats(
487
-        $date_format_string = null,
488
-        $time_format_string = null
489
-    ) {
490
-        if ($date_format_string === null) {
491
-            $date_format_string = (string) get_option('date_format');
492
-        }
493
-        if ($time_format_string === null) {
494
-            $time_format_string = (string) get_option('time_format');
495
-        }
496
-        $date_format = self::_php_to_js_moment_converter($date_format_string);
497
-        $time_format = self::_php_to_js_moment_converter($time_format_string);
498
-        return array(
499
-            'js'     => array(
500
-                'date' => $date_format['js'],
501
-                'time' => $time_format['js'],
502
-            ),
503
-            'moment' => $date_format['moment'] . ' ' . $time_format['moment'],
504
-            'moment_split' => array(
505
-                'date' => $date_format['moment'],
506
-                'time' => $time_format['moment']
507
-            )
508
-        );
509
-    }
467
+	/**
468
+	 * The purpose of this helper method is to receive an incoming format string in php date/time format
469
+	 * and spit out the js and moment.js equivalent formats.
470
+	 * Note, if no format string is given, then it is assumed the user wants what is set for WP.
471
+	 * Note, js date and time formats are those used by the jquery-ui datepicker and the jquery-ui date-
472
+	 * time picker.
473
+	 *
474
+	 * @see http://stackoverflow.com/posts/16725290/ for the code inspiration.
475
+	 * @param string $date_format_string
476
+	 * @param string $time_format_string
477
+	 * @return array
478
+	 *              array(
479
+	 *              'js' => array (
480
+	 *              'date' => //date format
481
+	 *              'time' => //time format
482
+	 *              ),
483
+	 *              'moment' => //date and time format.
484
+	 *              )
485
+	 */
486
+	public static function convert_php_to_js_and_moment_date_formats(
487
+		$date_format_string = null,
488
+		$time_format_string = null
489
+	) {
490
+		if ($date_format_string === null) {
491
+			$date_format_string = (string) get_option('date_format');
492
+		}
493
+		if ($time_format_string === null) {
494
+			$time_format_string = (string) get_option('time_format');
495
+		}
496
+		$date_format = self::_php_to_js_moment_converter($date_format_string);
497
+		$time_format = self::_php_to_js_moment_converter($time_format_string);
498
+		return array(
499
+			'js'     => array(
500
+				'date' => $date_format['js'],
501
+				'time' => $time_format['js'],
502
+			),
503
+			'moment' => $date_format['moment'] . ' ' . $time_format['moment'],
504
+			'moment_split' => array(
505
+				'date' => $date_format['moment'],
506
+				'time' => $time_format['moment']
507
+			)
508
+		);
509
+	}
510 510
 
511 511
 
512
-    /**
513
-     * This converts incoming format string into js and moment variations.
514
-     *
515
-     * @param string $format_string incoming php format string
516
-     * @return array js and moment formats.
517
-     */
518
-    protected static function _php_to_js_moment_converter($format_string)
519
-    {
520
-        /**
521
-         * This is a map of symbols for formats.
522
-         * The index is the php symbol, the equivalent values are in the array.
523
-         *
524
-         * @var array
525
-         */
526
-        $symbols_map          = array(
527
-            // Day
528
-            // 01
529
-            'd' => array(
530
-                'js'     => 'dd',
531
-                'moment' => 'DD',
532
-            ),
533
-            // Mon
534
-            'D' => array(
535
-                'js'     => 'D',
536
-                'moment' => 'ddd',
537
-            ),
538
-            // 1,2,...31
539
-            'j' => array(
540
-                'js'     => 'd',
541
-                'moment' => 'D',
542
-            ),
543
-            // Monday
544
-            'l' => array(
545
-                'js'     => 'DD',
546
-                'moment' => 'dddd',
547
-            ),
548
-            // ISO numeric representation of the day of the week (1-6)
549
-            'N' => array(
550
-                'js'     => '',
551
-                'moment' => 'E',
552
-            ),
553
-            // st,nd.rd
554
-            'S' => array(
555
-                'js'     => '',
556
-                'moment' => 'o',
557
-            ),
558
-            // numeric representation of day of week (0-6)
559
-            'w' => array(
560
-                'js'     => '',
561
-                'moment' => 'd',
562
-            ),
563
-            // day of year starting from 0 (0-365)
564
-            'z' => array(
565
-                'js'     => 'o',
566
-                'moment' => 'DDD' // note moment does not start with 0 so will need to modify by subtracting 1
567
-            ),
568
-            // Week
569
-            // ISO-8601 week number of year (weeks starting on monday)
570
-            'W' => array(
571
-                'js'     => '',
572
-                'moment' => 'w',
573
-            ),
574
-            // Month
575
-            // January...December
576
-            'F' => array(
577
-                'js'     => 'MM',
578
-                'moment' => 'MMMM',
579
-            ),
580
-            // 01...12
581
-            'm' => array(
582
-                'js'     => 'mm',
583
-                'moment' => 'MM',
584
-            ),
585
-            // Jan...Dec
586
-            'M' => array(
587
-                'js'     => 'M',
588
-                'moment' => 'MMM',
589
-            ),
590
-            // 1-12
591
-            'n' => array(
592
-                'js'     => 'm',
593
-                'moment' => 'M',
594
-            ),
595
-            // number of days in given month
596
-            't' => array(
597
-                'js'     => '',
598
-                'moment' => '',
599
-            ),
600
-            // Year
601
-            // whether leap year or not 1/0
602
-            'L' => array(
603
-                'js'     => '',
604
-                'moment' => '',
605
-            ),
606
-            // ISO-8601 year number
607
-            'o' => array(
608
-                'js'     => '',
609
-                'moment' => 'GGGG',
610
-            ),
611
-            // 1999...2003
612
-            'Y' => array(
613
-                'js'     => 'yy',
614
-                'moment' => 'YYYY',
615
-            ),
616
-            // 99...03
617
-            'y' => array(
618
-                'js'     => 'y',
619
-                'moment' => 'YY',
620
-            ),
621
-            // Time
622
-            // am/pm
623
-            'a' => array(
624
-                'js'     => 'tt',
625
-                'moment' => 'a',
626
-            ),
627
-            // AM/PM
628
-            'A' => array(
629
-                'js'     => 'TT',
630
-                'moment' => 'A',
631
-            ),
632
-            // Swatch Internet Time?!?
633
-            'B' => array(
634
-                'js'     => '',
635
-                'moment' => '',
636
-            ),
637
-            // 1...12
638
-            'g' => array(
639
-                'js'     => 'h',
640
-                'moment' => 'h',
641
-            ),
642
-            // 0...23
643
-            'G' => array(
644
-                'js'     => 'H',
645
-                'moment' => 'H',
646
-            ),
647
-            // 01...12
648
-            'h' => array(
649
-                'js'     => 'hh',
650
-                'moment' => 'hh',
651
-            ),
652
-            // 00...23
653
-            'H' => array(
654
-                'js'     => 'HH',
655
-                'moment' => 'HH',
656
-            ),
657
-            // 00..59
658
-            'i' => array(
659
-                'js'     => 'mm',
660
-                'moment' => 'mm',
661
-            ),
662
-            // seconds... 00...59
663
-            's' => array(
664
-                'js'     => 'ss',
665
-                'moment' => 'ss',
666
-            ),
667
-            // microseconds
668
-            'u' => array(
669
-                'js'     => '',
670
-                'moment' => '',
671
-            ),
672
-        );
673
-        $jquery_ui_format     = '';
674
-        $moment_format        = '';
675
-        $escaping             = false;
676
-        $format_string_length = strlen($format_string);
677
-        for ($i = 0; $i < $format_string_length; $i++) {
678
-            $char = $format_string[ $i ];
679
-            if ($char === '\\') { // PHP date format escaping character
680
-                $i++;
681
-                if ($escaping) {
682
-                    $jquery_ui_format .= $format_string[ $i ];
683
-                    $moment_format    .= $format_string[ $i ];
684
-                } else {
685
-                    $jquery_ui_format .= '\'' . $format_string[ $i ];
686
-                    $moment_format    .= $format_string[ $i ];
687
-                }
688
-                $escaping = true;
689
-            } else {
690
-                if ($escaping) {
691
-                    $jquery_ui_format .= "'";
692
-                    $moment_format    .= "'";
693
-                    $escaping         = false;
694
-                }
695
-                if (isset($symbols_map[ $char ])) {
696
-                    $jquery_ui_format .= $symbols_map[ $char ]['js'];
697
-                    $moment_format    .= $symbols_map[ $char ]['moment'];
698
-                } else {
699
-                    $jquery_ui_format .= $char;
700
-                    $moment_format    .= $char;
701
-                }
702
-            }
703
-        }
704
-        return array('js' => $jquery_ui_format, 'moment' => $moment_format);
705
-    }
512
+	/**
513
+	 * This converts incoming format string into js and moment variations.
514
+	 *
515
+	 * @param string $format_string incoming php format string
516
+	 * @return array js and moment formats.
517
+	 */
518
+	protected static function _php_to_js_moment_converter($format_string)
519
+	{
520
+		/**
521
+		 * This is a map of symbols for formats.
522
+		 * The index is the php symbol, the equivalent values are in the array.
523
+		 *
524
+		 * @var array
525
+		 */
526
+		$symbols_map          = array(
527
+			// Day
528
+			// 01
529
+			'd' => array(
530
+				'js'     => 'dd',
531
+				'moment' => 'DD',
532
+			),
533
+			// Mon
534
+			'D' => array(
535
+				'js'     => 'D',
536
+				'moment' => 'ddd',
537
+			),
538
+			// 1,2,...31
539
+			'j' => array(
540
+				'js'     => 'd',
541
+				'moment' => 'D',
542
+			),
543
+			// Monday
544
+			'l' => array(
545
+				'js'     => 'DD',
546
+				'moment' => 'dddd',
547
+			),
548
+			// ISO numeric representation of the day of the week (1-6)
549
+			'N' => array(
550
+				'js'     => '',
551
+				'moment' => 'E',
552
+			),
553
+			// st,nd.rd
554
+			'S' => array(
555
+				'js'     => '',
556
+				'moment' => 'o',
557
+			),
558
+			// numeric representation of day of week (0-6)
559
+			'w' => array(
560
+				'js'     => '',
561
+				'moment' => 'd',
562
+			),
563
+			// day of year starting from 0 (0-365)
564
+			'z' => array(
565
+				'js'     => 'o',
566
+				'moment' => 'DDD' // note moment does not start with 0 so will need to modify by subtracting 1
567
+			),
568
+			// Week
569
+			// ISO-8601 week number of year (weeks starting on monday)
570
+			'W' => array(
571
+				'js'     => '',
572
+				'moment' => 'w',
573
+			),
574
+			// Month
575
+			// January...December
576
+			'F' => array(
577
+				'js'     => 'MM',
578
+				'moment' => 'MMMM',
579
+			),
580
+			// 01...12
581
+			'm' => array(
582
+				'js'     => 'mm',
583
+				'moment' => 'MM',
584
+			),
585
+			// Jan...Dec
586
+			'M' => array(
587
+				'js'     => 'M',
588
+				'moment' => 'MMM',
589
+			),
590
+			// 1-12
591
+			'n' => array(
592
+				'js'     => 'm',
593
+				'moment' => 'M',
594
+			),
595
+			// number of days in given month
596
+			't' => array(
597
+				'js'     => '',
598
+				'moment' => '',
599
+			),
600
+			// Year
601
+			// whether leap year or not 1/0
602
+			'L' => array(
603
+				'js'     => '',
604
+				'moment' => '',
605
+			),
606
+			// ISO-8601 year number
607
+			'o' => array(
608
+				'js'     => '',
609
+				'moment' => 'GGGG',
610
+			),
611
+			// 1999...2003
612
+			'Y' => array(
613
+				'js'     => 'yy',
614
+				'moment' => 'YYYY',
615
+			),
616
+			// 99...03
617
+			'y' => array(
618
+				'js'     => 'y',
619
+				'moment' => 'YY',
620
+			),
621
+			// Time
622
+			// am/pm
623
+			'a' => array(
624
+				'js'     => 'tt',
625
+				'moment' => 'a',
626
+			),
627
+			// AM/PM
628
+			'A' => array(
629
+				'js'     => 'TT',
630
+				'moment' => 'A',
631
+			),
632
+			// Swatch Internet Time?!?
633
+			'B' => array(
634
+				'js'     => '',
635
+				'moment' => '',
636
+			),
637
+			// 1...12
638
+			'g' => array(
639
+				'js'     => 'h',
640
+				'moment' => 'h',
641
+			),
642
+			// 0...23
643
+			'G' => array(
644
+				'js'     => 'H',
645
+				'moment' => 'H',
646
+			),
647
+			// 01...12
648
+			'h' => array(
649
+				'js'     => 'hh',
650
+				'moment' => 'hh',
651
+			),
652
+			// 00...23
653
+			'H' => array(
654
+				'js'     => 'HH',
655
+				'moment' => 'HH',
656
+			),
657
+			// 00..59
658
+			'i' => array(
659
+				'js'     => 'mm',
660
+				'moment' => 'mm',
661
+			),
662
+			// seconds... 00...59
663
+			's' => array(
664
+				'js'     => 'ss',
665
+				'moment' => 'ss',
666
+			),
667
+			// microseconds
668
+			'u' => array(
669
+				'js'     => '',
670
+				'moment' => '',
671
+			),
672
+		);
673
+		$jquery_ui_format     = '';
674
+		$moment_format        = '';
675
+		$escaping             = false;
676
+		$format_string_length = strlen($format_string);
677
+		for ($i = 0; $i < $format_string_length; $i++) {
678
+			$char = $format_string[ $i ];
679
+			if ($char === '\\') { // PHP date format escaping character
680
+				$i++;
681
+				if ($escaping) {
682
+					$jquery_ui_format .= $format_string[ $i ];
683
+					$moment_format    .= $format_string[ $i ];
684
+				} else {
685
+					$jquery_ui_format .= '\'' . $format_string[ $i ];
686
+					$moment_format    .= $format_string[ $i ];
687
+				}
688
+				$escaping = true;
689
+			} else {
690
+				if ($escaping) {
691
+					$jquery_ui_format .= "'";
692
+					$moment_format    .= "'";
693
+					$escaping         = false;
694
+				}
695
+				if (isset($symbols_map[ $char ])) {
696
+					$jquery_ui_format .= $symbols_map[ $char ]['js'];
697
+					$moment_format    .= $symbols_map[ $char ]['moment'];
698
+				} else {
699
+					$jquery_ui_format .= $char;
700
+					$moment_format    .= $char;
701
+				}
702
+			}
703
+		}
704
+		return array('js' => $jquery_ui_format, 'moment' => $moment_format);
705
+	}
706 706
 
707 707
 
708
-    /**
709
-     * This takes an incoming format string and validates it to ensure it will work fine with PHP.
710
-     *
711
-     * @param string $format_string   Incoming format string for php date().
712
-     * @return mixed bool|array  If all is okay then TRUE is returned.  Otherwise an array of validation
713
-     *                                errors is returned.  So for client code calling, check for is_array() to
714
-     *                                indicate failed validations.
715
-     */
716
-    public static function validate_format_string($format_string)
717
-    {
718
-        $error_msg = array();
719
-        // time format checks
720
-        switch (true) {
721
-            case strpos($format_string, 'h') !== false:
722
-            case strpos($format_string, 'g') !== false:
723
-                /**
724
-                 * if the time string has a lowercase 'h' which == 12 hour time format and there
725
-                 * is not any ante meridiem format ('a' or 'A').  Then throw an error because its
726
-                 * too ambiguous and PHP won't be able to figure out whether 1 = 1pm or 1am.
727
-                 */
728
-                if (stripos($format_string, 'A') === false) {
729
-                    $error_msg[] = esc_html__(
730
-                        'There is a  time format for 12 hour time but no  "a" or "A" to indicate am/pm.  Without this distinction, PHP is unable to determine if a "1" for the hour value equals "1pm" or "1am".',
731
-                        'event_espresso'
732
-                    );
733
-                }
734
-                break;
735
-        }
736
-        return empty($error_msg) ? true : $error_msg;
737
-    }
708
+	/**
709
+	 * This takes an incoming format string and validates it to ensure it will work fine with PHP.
710
+	 *
711
+	 * @param string $format_string   Incoming format string for php date().
712
+	 * @return mixed bool|array  If all is okay then TRUE is returned.  Otherwise an array of validation
713
+	 *                                errors is returned.  So for client code calling, check for is_array() to
714
+	 *                                indicate failed validations.
715
+	 */
716
+	public static function validate_format_string($format_string)
717
+	{
718
+		$error_msg = array();
719
+		// time format checks
720
+		switch (true) {
721
+			case strpos($format_string, 'h') !== false:
722
+			case strpos($format_string, 'g') !== false:
723
+				/**
724
+				 * if the time string has a lowercase 'h' which == 12 hour time format and there
725
+				 * is not any ante meridiem format ('a' or 'A').  Then throw an error because its
726
+				 * too ambiguous and PHP won't be able to figure out whether 1 = 1pm or 1am.
727
+				 */
728
+				if (stripos($format_string, 'A') === false) {
729
+					$error_msg[] = esc_html__(
730
+						'There is a  time format for 12 hour time but no  "a" or "A" to indicate am/pm.  Without this distinction, PHP is unable to determine if a "1" for the hour value equals "1pm" or "1am".',
731
+						'event_espresso'
732
+					);
733
+				}
734
+				break;
735
+		}
736
+		return empty($error_msg) ? true : $error_msg;
737
+	}
738 738
 
739 739
 
740
-    /**
741
-     *     If the the first date starts at midnight on one day, and the next date ends at midnight on the
742
-     *     very next day then this method will return true.
743
-     *    If $date_1 = 2015-12-15 00:00:00 and $date_2 = 2015-12-16 00:00:00 then this function will return true.
744
-     *    If $date_1 = 2015-12-15 03:00:00 and $date_2 = 2015-12_16 03:00:00 then this function will return false.
745
-     *    If $date_1 = 2015-12-15 00:00:00 and $date_2 = 2015-12-15 00:00:00 then this function will return true.
746
-     *
747
-     * @param mixed $date_1
748
-     * @param mixed $date_2
749
-     * @return bool
750
-     */
751
-    public static function dates_represent_one_24_hour_date($date_1, $date_2)
752
-    {
740
+	/**
741
+	 *     If the the first date starts at midnight on one day, and the next date ends at midnight on the
742
+	 *     very next day then this method will return true.
743
+	 *    If $date_1 = 2015-12-15 00:00:00 and $date_2 = 2015-12-16 00:00:00 then this function will return true.
744
+	 *    If $date_1 = 2015-12-15 03:00:00 and $date_2 = 2015-12_16 03:00:00 then this function will return false.
745
+	 *    If $date_1 = 2015-12-15 00:00:00 and $date_2 = 2015-12-15 00:00:00 then this function will return true.
746
+	 *
747
+	 * @param mixed $date_1
748
+	 * @param mixed $date_2
749
+	 * @return bool
750
+	 */
751
+	public static function dates_represent_one_24_hour_date($date_1, $date_2)
752
+	{
753 753
 
754
-        if (
755
-            (! $date_1 instanceof DateTime || ! $date_2 instanceof DateTime)
756
-            || ($date_1->format(EE_Datetime_Field::mysql_time_format) !== '00:00:00'
757
-                || $date_2->format(
758
-                    EE_Datetime_Field::mysql_time_format
759
-                ) !== '00:00:00')
760
-        ) {
761
-            return false;
762
-        }
763
-        return $date_2->format('U') - $date_1->format('U') === 86400;
764
-    }
754
+		if (
755
+			(! $date_1 instanceof DateTime || ! $date_2 instanceof DateTime)
756
+			|| ($date_1->format(EE_Datetime_Field::mysql_time_format) !== '00:00:00'
757
+				|| $date_2->format(
758
+					EE_Datetime_Field::mysql_time_format
759
+				) !== '00:00:00')
760
+		) {
761
+			return false;
762
+		}
763
+		return $date_2->format('U') - $date_1->format('U') === 86400;
764
+	}
765 765
 
766 766
 
767
-    /**
768
-     * This returns the appropriate query interval string that can be used in sql queries involving mysql Date
769
-     * Functions.
770
-     *
771
-     * @param string $timezone_string    A timezone string in a valid format to instantiate a DateTimeZone object.
772
-     * @param string $field_for_interval The Database field that is the interval is applied to in the query.
773
-     * @return string
774
-     */
775
-    public static function get_sql_query_interval_for_offset($timezone_string, $field_for_interval)
776
-    {
777
-        try {
778
-            /** need to account for timezone offset on the selects */
779
-            $DateTimeZone = new DateTimeZone($timezone_string);
780
-        } catch (Exception $e) {
781
-            $DateTimeZone = null;
782
-        }
783
-        /**
784
-         * Note get_option( 'gmt_offset') returns a value in hours, whereas DateTimeZone::getOffset returns values in seconds.
785
-         * Hence we do the calc for DateTimeZone::getOffset.
786
-         */
787
-        $offset         = $DateTimeZone instanceof DateTimeZone
788
-            ? $DateTimeZone->getOffset(new DateTime('now')) / HOUR_IN_SECONDS
789
-            : (float) get_option('gmt_offset');
790
-        $query_interval = $offset < 0
791
-            ? 'DATE_SUB(' . $field_for_interval . ', INTERVAL ' . $offset * -1 . ' HOUR)'
792
-            : 'DATE_ADD(' . $field_for_interval . ', INTERVAL ' . $offset . ' HOUR)';
793
-        return $query_interval;
794
-    }
767
+	/**
768
+	 * This returns the appropriate query interval string that can be used in sql queries involving mysql Date
769
+	 * Functions.
770
+	 *
771
+	 * @param string $timezone_string    A timezone string in a valid format to instantiate a DateTimeZone object.
772
+	 * @param string $field_for_interval The Database field that is the interval is applied to in the query.
773
+	 * @return string
774
+	 */
775
+	public static function get_sql_query_interval_for_offset($timezone_string, $field_for_interval)
776
+	{
777
+		try {
778
+			/** need to account for timezone offset on the selects */
779
+			$DateTimeZone = new DateTimeZone($timezone_string);
780
+		} catch (Exception $e) {
781
+			$DateTimeZone = null;
782
+		}
783
+		/**
784
+		 * Note get_option( 'gmt_offset') returns a value in hours, whereas DateTimeZone::getOffset returns values in seconds.
785
+		 * Hence we do the calc for DateTimeZone::getOffset.
786
+		 */
787
+		$offset         = $DateTimeZone instanceof DateTimeZone
788
+			? $DateTimeZone->getOffset(new DateTime('now')) / HOUR_IN_SECONDS
789
+			: (float) get_option('gmt_offset');
790
+		$query_interval = $offset < 0
791
+			? 'DATE_SUB(' . $field_for_interval . ', INTERVAL ' . $offset * -1 . ' HOUR)'
792
+			: 'DATE_ADD(' . $field_for_interval . ', INTERVAL ' . $offset . ' HOUR)';
793
+		return $query_interval;
794
+	}
795 795
 
796 796
 
797
-    /**
798
-     * Retrieves the site's default timezone and returns it formatted so it's ready for display
799
-     * to users. If you want to customize how its displayed feel free to fetch the 'timezone_string'
800
-     * and 'gmt_offset' WordPress options directly; or use the filter
801
-     * FHEE__EEH_DTT_Helper__get_timezone_string_for_display
802
-     * (although note that we remove any HTML that may be added)
803
-     *
804
-     * @return string
805
-     */
806
-    public static function get_timezone_string_for_display()
807
-    {
808
-        $pretty_timezone = apply_filters('FHEE__EEH_DTT_Helper__get_timezone_string_for_display', '');
809
-        if (! empty($pretty_timezone)) {
810
-            return esc_html($pretty_timezone);
811
-        }
812
-        $timezone_string = get_option('timezone_string');
813
-        if ($timezone_string) {
814
-            static $mo_loaded = false;
815
-            // Load translations for continents and cities just like wp_timezone_choice does
816
-            if (! $mo_loaded) {
817
-                $locale = get_locale();
818
-                $mofile = WP_LANG_DIR . '/continents-cities-' . $locale . '.mo';
819
-                load_textdomain('continents-cities', $mofile);
820
-                $mo_loaded = true;
821
-            }
822
-            // well that was easy.
823
-            $parts = explode('/', $timezone_string);
824
-            // remove the continent
825
-            unset($parts[0]);
826
-            $t_parts = array();
827
-            // phpcs:disable WordPress.WP.I18n.NonSingularStringLiteralText
828
-            // phpcs:disable WordPress.WP.I18n.TextDomainMismatch
829
-            // disabled because this code is copied from WordPress and is a WordPress domain
830
-            foreach ($parts as $part) {
831
-                $t_parts[] = translate(str_replace('_', ' ', $part), 'continents-cities');
832
-            }
833
-            return implode(' - ', $t_parts);
834
-            // phpcs:enable
835
-        }
836
-        // they haven't set the timezone string, so let's return a string like "UTC+1"
837
-        $gmt_offset = get_option('gmt_offset');
838
-        $prefix     = (int) $gmt_offset >= 0 ? '+' : '';
839
-        $parts      = explode('.', (string) $gmt_offset);
840
-        if (count($parts) === 1) {
841
-            $parts[1] = '00';
842
-        } else {
843
-            // convert the part after the decimal, eg "5" (from x.5) or "25" (from x.25)
844
-            // to minutes, eg 30 or 15, respectively
845
-            $hour_fraction = (float) ('0.' . $parts[1]);
846
-            $parts[1]      = (string) $hour_fraction * 60;
847
-        }
848
-        return sprintf(esc_html__('UTC%1$s', 'event_espresso'), $prefix . implode(':', $parts));
849
-    }
797
+	/**
798
+	 * Retrieves the site's default timezone and returns it formatted so it's ready for display
799
+	 * to users. If you want to customize how its displayed feel free to fetch the 'timezone_string'
800
+	 * and 'gmt_offset' WordPress options directly; or use the filter
801
+	 * FHEE__EEH_DTT_Helper__get_timezone_string_for_display
802
+	 * (although note that we remove any HTML that may be added)
803
+	 *
804
+	 * @return string
805
+	 */
806
+	public static function get_timezone_string_for_display()
807
+	{
808
+		$pretty_timezone = apply_filters('FHEE__EEH_DTT_Helper__get_timezone_string_for_display', '');
809
+		if (! empty($pretty_timezone)) {
810
+			return esc_html($pretty_timezone);
811
+		}
812
+		$timezone_string = get_option('timezone_string');
813
+		if ($timezone_string) {
814
+			static $mo_loaded = false;
815
+			// Load translations for continents and cities just like wp_timezone_choice does
816
+			if (! $mo_loaded) {
817
+				$locale = get_locale();
818
+				$mofile = WP_LANG_DIR . '/continents-cities-' . $locale . '.mo';
819
+				load_textdomain('continents-cities', $mofile);
820
+				$mo_loaded = true;
821
+			}
822
+			// well that was easy.
823
+			$parts = explode('/', $timezone_string);
824
+			// remove the continent
825
+			unset($parts[0]);
826
+			$t_parts = array();
827
+			// phpcs:disable WordPress.WP.I18n.NonSingularStringLiteralText
828
+			// phpcs:disable WordPress.WP.I18n.TextDomainMismatch
829
+			// disabled because this code is copied from WordPress and is a WordPress domain
830
+			foreach ($parts as $part) {
831
+				$t_parts[] = translate(str_replace('_', ' ', $part), 'continents-cities');
832
+			}
833
+			return implode(' - ', $t_parts);
834
+			// phpcs:enable
835
+		}
836
+		// they haven't set the timezone string, so let's return a string like "UTC+1"
837
+		$gmt_offset = get_option('gmt_offset');
838
+		$prefix     = (int) $gmt_offset >= 0 ? '+' : '';
839
+		$parts      = explode('.', (string) $gmt_offset);
840
+		if (count($parts) === 1) {
841
+			$parts[1] = '00';
842
+		} else {
843
+			// convert the part after the decimal, eg "5" (from x.5) or "25" (from x.25)
844
+			// to minutes, eg 30 or 15, respectively
845
+			$hour_fraction = (float) ('0.' . $parts[1]);
846
+			$parts[1]      = (string) $hour_fraction * 60;
847
+		}
848
+		return sprintf(esc_html__('UTC%1$s', 'event_espresso'), $prefix . implode(':', $parts));
849
+	}
850 850
 
851 851
 
852 852
 
853
-    /**
854
-     * So PHP does this awesome thing where if you are trying to get a timestamp
855
-     * for a month using a string like "February" or "February 2017",
856
-     * and you don't specify a day as part of your string,
857
-     * then PHP will use whatever the current day of the month is.
858
-     * IF the current day of the month happens to be the 30th or 31st,
859
-     * then PHP gets really confused by a date like February 30,
860
-     * so instead of saying
861
-     *      "Hey February only has 28 days (this year)...
862
-     *      ...you must have meant the last day of the month!"
863
-     * PHP does the next most logical thing, and bumps the date up to March 2nd,
864
-     * because someone requesting February 30th obviously meant March 1st!
865
-     * The way around this is to always set the day to the first,
866
-     * so that the month will stay on the month you wanted.
867
-     * this method will add that "1" into your date regardless of the format.
868
-     *
869
-     * @param string $month
870
-     * @return string
871
-     */
872
-    public static function first_of_month_timestamp($month = '')
873
-    {
874
-        $month = (string) $month;
875
-        $year  = '';
876
-        // check if the incoming string has a year in it or not
877
-        if (preg_match('/\b\d{4}\b/', $month, $matches)) {
878
-            $year = $matches[0];
879
-            // ten remove that from the month string as well as any spaces
880
-            $month = trim(str_replace($year, '', $month));
881
-            // add a space before the year
882
-            $year = " {$year}";
883
-        }
884
-        // return timestamp for something like "February 1 2017"
885
-        return strtotime("{$month} 1{$year}");
886
-    }
853
+	/**
854
+	 * So PHP does this awesome thing where if you are trying to get a timestamp
855
+	 * for a month using a string like "February" or "February 2017",
856
+	 * and you don't specify a day as part of your string,
857
+	 * then PHP will use whatever the current day of the month is.
858
+	 * IF the current day of the month happens to be the 30th or 31st,
859
+	 * then PHP gets really confused by a date like February 30,
860
+	 * so instead of saying
861
+	 *      "Hey February only has 28 days (this year)...
862
+	 *      ...you must have meant the last day of the month!"
863
+	 * PHP does the next most logical thing, and bumps the date up to March 2nd,
864
+	 * because someone requesting February 30th obviously meant March 1st!
865
+	 * The way around this is to always set the day to the first,
866
+	 * so that the month will stay on the month you wanted.
867
+	 * this method will add that "1" into your date regardless of the format.
868
+	 *
869
+	 * @param string $month
870
+	 * @return string
871
+	 */
872
+	public static function first_of_month_timestamp($month = '')
873
+	{
874
+		$month = (string) $month;
875
+		$year  = '';
876
+		// check if the incoming string has a year in it or not
877
+		if (preg_match('/\b\d{4}\b/', $month, $matches)) {
878
+			$year = $matches[0];
879
+			// ten remove that from the month string as well as any spaces
880
+			$month = trim(str_replace($year, '', $month));
881
+			// add a space before the year
882
+			$year = " {$year}";
883
+		}
884
+		// return timestamp for something like "February 1 2017"
885
+		return strtotime("{$month} 1{$year}");
886
+	}
887 887
 
888 888
 
889
-    /**
890
-     * This simply returns the timestamp for tomorrow (midnight next day) in this sites timezone.  So it may be midnight
891
-     * for this sites timezone, but the timestamp could be some other time GMT.
892
-     */
893
-    public static function tomorrow()
894
-    {
895
-        // The multiplication of -1 ensures that we switch positive offsets to negative and negative offsets to positive
896
-        // before adding to the timestamp.  Why? Because we want tomorrow to be for midnight the next day in THIS timezone
897
-        // not an offset from midnight in UTC.  So if we're starting with UTC 00:00:00, then we want to make sure the
898
-        // final timestamp is equivalent to midnight in this timezone as represented in GMT.
899
-        return strtotime('tomorrow') + (self::get_site_timezone_gmt_offset() * -1);
900
-    }
889
+	/**
890
+	 * This simply returns the timestamp for tomorrow (midnight next day) in this sites timezone.  So it may be midnight
891
+	 * for this sites timezone, but the timestamp could be some other time GMT.
892
+	 */
893
+	public static function tomorrow()
894
+	{
895
+		// The multiplication of -1 ensures that we switch positive offsets to negative and negative offsets to positive
896
+		// before adding to the timestamp.  Why? Because we want tomorrow to be for midnight the next day in THIS timezone
897
+		// not an offset from midnight in UTC.  So if we're starting with UTC 00:00:00, then we want to make sure the
898
+		// final timestamp is equivalent to midnight in this timezone as represented in GMT.
899
+		return strtotime('tomorrow') + (self::get_site_timezone_gmt_offset() * -1);
900
+	}
901 901
 
902 902
 
903
-    /**
904
-     * **
905
-     * Gives a nicely-formatted list of timezone strings.
906
-     * Copied from the core wp function by the same name so we could customize to remove UTC offsets.
907
-     *
908
-     * @since     4.9.40.rc.008
909
-     * @staticvar bool $mo_loaded
910
-     * @staticvar string $locale_loaded
911
-     * @param string $selected_zone Selected timezone.
912
-     * @param string $locale        Optional. Locale to load the timezones in. Default current site locale.
913
-     * @return string
914
-     */
915
-    public static function wp_timezone_choice($selected_zone, $locale = null)
916
-    {
917
-        static $mo_loaded = false, $locale_loaded = null;
918
-        $continents = array(
919
-            'Africa',
920
-            'America',
921
-            'Antarctica',
922
-            'Arctic',
923
-            'Asia',
924
-            'Atlantic',
925
-            'Australia',
926
-            'Europe',
927
-            'Indian',
928
-            'Pacific',
929
-        );
930
-        // Load translations for continents and cities.
931
-        if (! $mo_loaded || $locale !== $locale_loaded) {
932
-            $locale_loaded = $locale ? $locale : get_locale();
933
-            $mofile        = WP_LANG_DIR . '/continents-cities-' . $locale_loaded . '.mo';
934
-            unload_textdomain('continents-cities');
935
-            load_textdomain('continents-cities', $mofile);
936
-            $mo_loaded = true;
937
-        }
938
-        $zone_data = array();
939
-        foreach (timezone_identifiers_list() as $zone) {
940
-            $zone = explode('/', $zone);
941
-            if (! in_array($zone[0], $continents, true)) {
942
-                continue;
943
-            }
944
-            // This determines what gets set and translated - we don't translate Etc/* strings here, they are done later
945
-            $exists      = array(
946
-                0 => isset($zone[0]) && $zone[0],
947
-                1 => isset($zone[1]) && $zone[1],
948
-                2 => isset($zone[2]) && $zone[2],
949
-            );
950
-            $exists[3]   = $exists[0] && $zone[0] !== 'Etc';
951
-            $exists[4]   = $exists[1] && $exists[3];
952
-            $exists[5]   = $exists[2] && $exists[3];
953
-            // phpcs:disable WordPress.WP.I18n.NonSingularStringLiteralText
954
-            // phpcs:disable WordPress.WP.I18n.TextDomainMismatch
955
-            // disabled because this code is copied from WordPress and is a WordPress domain
956
-            $zone_data[] = array(
957
-                'continent'   => $exists[0] ? $zone[0] : '',
958
-                'city'        => $exists[1] ? $zone[1] : '',
959
-                'subcity'     => $exists[2] ? $zone[2] : '',
960
-                't_continent' => $exists[3]
961
-                    ? translate(str_replace('_', ' ', $zone[0]), 'continents-cities')
962
-                    : '',
963
-                't_city'      => $exists[4]
964
-                    ? translate(str_replace('_', ' ', $zone[1]), 'continents-cities')
965
-                    : '',
966
-                't_subcity'   => $exists[5]
967
-                    ? translate(str_replace('_', ' ', $zone[2]), 'continents-cities')
968
-                    : '',
969
-            );
970
-            // phpcs:enable
971
-        }
972
-        usort($zone_data, '_wp_timezone_choice_usort_callback');
973
-        $structure = array();
974
-        if (empty($selected_zone)) {
975
-            $structure[] = '<option selected value="">' . esc_html__('Select a city', 'event_espresso') . '</option>';
976
-        }
977
-        foreach ($zone_data as $key => $zone) {
978
-            // Build value in an array to join later
979
-            $value = array($zone['continent']);
980
-            if (empty($zone['city'])) {
981
-                // It's at the continent level (generally won't happen)
982
-                $display = $zone['t_continent'];
983
-            } else {
984
-                // It's inside a continent group
985
-                // Continent optgroup
986
-                if (! isset($zone_data[ $key - 1 ]) || $zone_data[ $key - 1 ]['continent'] !== $zone['continent']) {
987
-                    $label       = $zone['t_continent'];
988
-                    $structure[] = '<optgroup label="' . esc_attr($label) . '">';
989
-                }
990
-                // Add the city to the value
991
-                $value[] = $zone['city'];
992
-                $display = $zone['t_city'];
993
-                if (! empty($zone['subcity'])) {
994
-                    // Add the subcity to the value
995
-                    $value[] = $zone['subcity'];
996
-                    $display .= ' - ' . $zone['t_subcity'];
997
-                }
998
-            }
999
-            // Build the value
1000
-            $value       = implode('/', $value);
1001
-            $selected    = $value === $selected_zone ? ' selected' : '';
1002
-            $structure[] = '<option value="' . esc_attr($value) . '"' . $selected . '>'
1003
-                           . esc_html($display)
1004
-                           . '</option>';
1005
-            // Close continent optgroup
1006
-            if (
1007
-                ! empty($zone['city'])
1008
-                && (
1009
-                    ! isset($zone_data[ $key + 1 ])
1010
-                    || (isset($zone_data[ $key + 1 ]) && $zone_data[ $key + 1 ]['continent'] !== $zone['continent'])
1011
-                )
1012
-            ) {
1013
-                $structure[] = '</optgroup>';
1014
-            }
1015
-        }
1016
-        return implode("\n", $structure);
1017
-    }
903
+	/**
904
+	 * **
905
+	 * Gives a nicely-formatted list of timezone strings.
906
+	 * Copied from the core wp function by the same name so we could customize to remove UTC offsets.
907
+	 *
908
+	 * @since     4.9.40.rc.008
909
+	 * @staticvar bool $mo_loaded
910
+	 * @staticvar string $locale_loaded
911
+	 * @param string $selected_zone Selected timezone.
912
+	 * @param string $locale        Optional. Locale to load the timezones in. Default current site locale.
913
+	 * @return string
914
+	 */
915
+	public static function wp_timezone_choice($selected_zone, $locale = null)
916
+	{
917
+		static $mo_loaded = false, $locale_loaded = null;
918
+		$continents = array(
919
+			'Africa',
920
+			'America',
921
+			'Antarctica',
922
+			'Arctic',
923
+			'Asia',
924
+			'Atlantic',
925
+			'Australia',
926
+			'Europe',
927
+			'Indian',
928
+			'Pacific',
929
+		);
930
+		// Load translations for continents and cities.
931
+		if (! $mo_loaded || $locale !== $locale_loaded) {
932
+			$locale_loaded = $locale ? $locale : get_locale();
933
+			$mofile        = WP_LANG_DIR . '/continents-cities-' . $locale_loaded . '.mo';
934
+			unload_textdomain('continents-cities');
935
+			load_textdomain('continents-cities', $mofile);
936
+			$mo_loaded = true;
937
+		}
938
+		$zone_data = array();
939
+		foreach (timezone_identifiers_list() as $zone) {
940
+			$zone = explode('/', $zone);
941
+			if (! in_array($zone[0], $continents, true)) {
942
+				continue;
943
+			}
944
+			// This determines what gets set and translated - we don't translate Etc/* strings here, they are done later
945
+			$exists      = array(
946
+				0 => isset($zone[0]) && $zone[0],
947
+				1 => isset($zone[1]) && $zone[1],
948
+				2 => isset($zone[2]) && $zone[2],
949
+			);
950
+			$exists[3]   = $exists[0] && $zone[0] !== 'Etc';
951
+			$exists[4]   = $exists[1] && $exists[3];
952
+			$exists[5]   = $exists[2] && $exists[3];
953
+			// phpcs:disable WordPress.WP.I18n.NonSingularStringLiteralText
954
+			// phpcs:disable WordPress.WP.I18n.TextDomainMismatch
955
+			// disabled because this code is copied from WordPress and is a WordPress domain
956
+			$zone_data[] = array(
957
+				'continent'   => $exists[0] ? $zone[0] : '',
958
+				'city'        => $exists[1] ? $zone[1] : '',
959
+				'subcity'     => $exists[2] ? $zone[2] : '',
960
+				't_continent' => $exists[3]
961
+					? translate(str_replace('_', ' ', $zone[0]), 'continents-cities')
962
+					: '',
963
+				't_city'      => $exists[4]
964
+					? translate(str_replace('_', ' ', $zone[1]), 'continents-cities')
965
+					: '',
966
+				't_subcity'   => $exists[5]
967
+					? translate(str_replace('_', ' ', $zone[2]), 'continents-cities')
968
+					: '',
969
+			);
970
+			// phpcs:enable
971
+		}
972
+		usort($zone_data, '_wp_timezone_choice_usort_callback');
973
+		$structure = array();
974
+		if (empty($selected_zone)) {
975
+			$structure[] = '<option selected value="">' . esc_html__('Select a city', 'event_espresso') . '</option>';
976
+		}
977
+		foreach ($zone_data as $key => $zone) {
978
+			// Build value in an array to join later
979
+			$value = array($zone['continent']);
980
+			if (empty($zone['city'])) {
981
+				// It's at the continent level (generally won't happen)
982
+				$display = $zone['t_continent'];
983
+			} else {
984
+				// It's inside a continent group
985
+				// Continent optgroup
986
+				if (! isset($zone_data[ $key - 1 ]) || $zone_data[ $key - 1 ]['continent'] !== $zone['continent']) {
987
+					$label       = $zone['t_continent'];
988
+					$structure[] = '<optgroup label="' . esc_attr($label) . '">';
989
+				}
990
+				// Add the city to the value
991
+				$value[] = $zone['city'];
992
+				$display = $zone['t_city'];
993
+				if (! empty($zone['subcity'])) {
994
+					// Add the subcity to the value
995
+					$value[] = $zone['subcity'];
996
+					$display .= ' - ' . $zone['t_subcity'];
997
+				}
998
+			}
999
+			// Build the value
1000
+			$value       = implode('/', $value);
1001
+			$selected    = $value === $selected_zone ? ' selected' : '';
1002
+			$structure[] = '<option value="' . esc_attr($value) . '"' . $selected . '>'
1003
+						   . esc_html($display)
1004
+						   . '</option>';
1005
+			// Close continent optgroup
1006
+			if (
1007
+				! empty($zone['city'])
1008
+				&& (
1009
+					! isset($zone_data[ $key + 1 ])
1010
+					|| (isset($zone_data[ $key + 1 ]) && $zone_data[ $key + 1 ]['continent'] !== $zone['continent'])
1011
+				)
1012
+			) {
1013
+				$structure[] = '</optgroup>';
1014
+			}
1015
+		}
1016
+		return implode("\n", $structure);
1017
+	}
1018 1018
 
1019 1019
 
1020
-    /**
1021
-     * Shim for the WP function `get_user_locale` that was added in WordPress 4.7.0
1022
-     *
1023
-     * @param int|WP_User $user_id
1024
-     * @return string
1025
-     */
1026
-    public static function get_user_locale($user_id = 0)
1027
-    {
1028
-        if (function_exists('get_user_locale')) {
1029
-            return get_user_locale($user_id);
1030
-        }
1031
-        return get_locale();
1032
-    }
1020
+	/**
1021
+	 * Shim for the WP function `get_user_locale` that was added in WordPress 4.7.0
1022
+	 *
1023
+	 * @param int|WP_User $user_id
1024
+	 * @return string
1025
+	 */
1026
+	public static function get_user_locale($user_id = 0)
1027
+	{
1028
+		if (function_exists('get_user_locale')) {
1029
+			return get_user_locale($user_id);
1030
+		}
1031
+		return get_locale();
1032
+	}
1033 1033
 
1034 1034
 
1035
-    /**
1036
-     * Return the appropriate helper adapter for DTT related things.
1037
-     *
1038
-     * @return HelperInterface
1039
-     * @throws InvalidArgumentException
1040
-     * @throws InvalidDataTypeException
1041
-     * @throws InvalidInterfaceException
1042
-     */
1043
-    private static function getHelperAdapter()
1044
-    {
1045
-        if (is_null(self::$dtt_helper)) {
1046
-            self::$dtt_helper = LoaderFactory::getLoader()
1047
-                ->getShared('EventEspresso\core\services\helpers\datetime\PhpCompatGreaterFiveSixHelper');
1048
-        }
1035
+	/**
1036
+	 * Return the appropriate helper adapter for DTT related things.
1037
+	 *
1038
+	 * @return HelperInterface
1039
+	 * @throws InvalidArgumentException
1040
+	 * @throws InvalidDataTypeException
1041
+	 * @throws InvalidInterfaceException
1042
+	 */
1043
+	private static function getHelperAdapter()
1044
+	{
1045
+		if (is_null(self::$dtt_helper)) {
1046
+			self::$dtt_helper = LoaderFactory::getLoader()
1047
+				->getShared('EventEspresso\core\services\helpers\datetime\PhpCompatGreaterFiveSixHelper');
1048
+		}
1049 1049
 
1050
-        return self::$dtt_helper;
1051
-    }
1050
+		return self::$dtt_helper;
1051
+	}
1052 1052
 
1053 1053
 
1054
-    /**
1055
-     * Helper function for setting the timezone on a DateTime object.
1056
-     * This is implemented to standardize a workaround for a PHP bug outlined in
1057
-     * https://events.codebasehq.com/projects/event-espresso/tickets/11407 and
1058
-     * https://events.codebasehq.com/projects/event-espresso/tickets/11233
1059
-     *
1060
-     * @param DateTime     $datetime
1061
-     * @param DateTimeZone $timezone
1062
-     */
1063
-    public static function setTimezone(DateTime $datetime, DateTimeZone $timezone)
1064
-    {
1065
-        $datetime->setTimezone($timezone);
1066
-        $datetime->getTimestamp();
1067
-    }
1054
+	/**
1055
+	 * Helper function for setting the timezone on a DateTime object.
1056
+	 * This is implemented to standardize a workaround for a PHP bug outlined in
1057
+	 * https://events.codebasehq.com/projects/event-espresso/tickets/11407 and
1058
+	 * https://events.codebasehq.com/projects/event-espresso/tickets/11233
1059
+	 *
1060
+	 * @param DateTime     $datetime
1061
+	 * @param DateTimeZone $timezone
1062
+	 */
1063
+	public static function setTimezone(DateTime $datetime, DateTimeZone $timezone)
1064
+	{
1065
+		$datetime->setTimezone($timezone);
1066
+		$datetime->getTimestamp();
1067
+	}
1068 1068
 }
Please login to merge, or discard this patch.
core/helpers/EEH_Line_Item.helper.php 1 patch
Indentation   +2128 added lines, -2128 removed lines patch added patch discarded remove patch
@@ -21,2132 +21,2132 @@
 block discarded – undo
21 21
  */
22 22
 class EEH_Line_Item
23 23
 {
24
-    /**
25
-     * @var EE_Line_Item[]
26
-    */
27
-    private static $global_taxes;
28
-
29
-
30
-    /**
31
-     * Adds a simple item (unrelated to any other model object) to the provided PARENT line item.
32
-     * Does NOT automatically re-calculate the line item totals or update the related transaction.
33
-     * You should call recalculate_total_including_taxes() on the grant total line item after this
34
-     * to update the subtotals, and EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
35
-     * to keep the registration final prices in-sync with the transaction's total.
36
-     *
37
-     * @param EE_Line_Item $parent_line_item
38
-     * @param string       $name
39
-     * @param float        $unit_price
40
-     * @param string       $description
41
-     * @param int          $quantity
42
-     * @param boolean      $taxable
43
-     * @param string|null  $code if set to a value, ensures there is only one line item with that code
44
-     * @param bool         $return_item
45
-     * @param bool         $recalculate_totals
46
-     * @return boolean|EE_Line_Item success
47
-     * @throws EE_Error
48
-     * @throws ReflectionException
49
-     */
50
-    public static function add_unrelated_item(
51
-        EE_Line_Item $parent_line_item,
52
-        string $name,
53
-        float $unit_price,
54
-        string $description = '',
55
-        int $quantity = 1,
56
-        bool $taxable = false,
57
-        ?string $code = null,
58
-        bool $return_item = false,
59
-        bool $recalculate_totals = true
60
-    ) {
61
-        $items_subtotal = self::get_pre_tax_subtotal($parent_line_item);
62
-        $line_item      = EE_Line_Item::new_instance(
63
-            [
64
-                'LIN_name'       => $name,
65
-                'LIN_desc'       => $description,
66
-                'LIN_unit_price' => $unit_price,
67
-                'LIN_quantity'   => $quantity,
68
-                'LIN_percent'    => null,
69
-                'LIN_is_taxable' => $taxable,
70
-                'LIN_order'      => $items_subtotal instanceof EE_Line_Item
71
-                    ? count($items_subtotal->children())
72
-                    : 0,
73
-                'LIN_total'      => (float) $unit_price * (int) $quantity,
74
-                'LIN_type'       => EEM_Line_Item::type_line_item,
75
-                'LIN_code'       => $code,
76
-            ]
77
-        );
78
-        $line_item      = apply_filters(
79
-            'FHEE__EEH_Line_Item__add_unrelated_item__line_item',
80
-            $line_item,
81
-            $parent_line_item
82
-        );
83
-        $added          = self::add_item($parent_line_item, $line_item, $recalculate_totals);
84
-        return $return_item ? $line_item : $added;
85
-    }
86
-
87
-
88
-    /**
89
-     * Adds a simple item ( unrelated to any other model object) to the total line item,
90
-     * in the correct spot in the line item tree. Does not automatically
91
-     * re-calculate the line item totals, nor update the related transaction, nor upgrade the transaction's
92
-     * registrations' final prices (which should probably change because of this).
93
-     * You should call recalculate_total_including_taxes() on the grand total line item, then
94
-     * update the transaction's total, and EE_Registration_Processor::update_registration_final_prices()
95
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
96
-     *
97
-     * @param EE_Line_Item $parent_line_item
98
-     * @param string       $name
99
-     * @param float        $percentage_amount
100
-     * @param string       $description
101
-     * @param boolean      $taxable
102
-     * @param string|null  $code
103
-     * @param bool         $return_item
104
-     * @return boolean|EE_Line_Item success
105
-     * @throws EE_Error
106
-     * @throws ReflectionException
107
-     */
108
-    public static function add_percentage_based_item(
109
-        EE_Line_Item $parent_line_item,
110
-        string $name,
111
-        float $percentage_amount,
112
-        string $description = '',
113
-        bool $taxable = false,
114
-        ?string $code = null,
115
-        bool $return_item = false
116
-    ) {
117
-        $total = $percentage_amount * $parent_line_item->total() / 100;
118
-        $line_item = EE_Line_Item::new_instance(
119
-            [
120
-                'LIN_name'       => $name,
121
-                'LIN_desc'       => $description,
122
-                'LIN_unit_price' => 0,
123
-                'LIN_percent'    => $percentage_amount,
124
-                'LIN_quantity'   => 1,
125
-                'LIN_is_taxable' => $taxable,
126
-                'LIN_total'      => (float) $total,
127
-                'LIN_type'       => EEM_Line_Item::type_line_item,
128
-                'LIN_parent'     => $parent_line_item->ID(),
129
-                'LIN_code'       => $code,
130
-            ]
131
-        );
132
-        $line_item = apply_filters(
133
-            'FHEE__EEH_Line_Item__add_percentage_based_item__line_item',
134
-            $line_item
135
-        );
136
-        $added     = $parent_line_item->add_child_line_item($line_item, false);
137
-        return $return_item ? $line_item : $added;
138
-    }
139
-
140
-
141
-    /**
142
-     * Returns the new line item created by adding a purchase of the ticket
143
-     * ensures that ticket line item is saved, and that cart total has been recalculated.
144
-     * If this ticket has already been purchased, just increments its count.
145
-     * Automatically re-calculates the line item totals and updates the related transaction. But
146
-     * DOES NOT automatically upgrade the transaction's registrations' final prices (which
147
-     * should probably change because of this).
148
-     * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
149
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
150
-     *
151
-     * @param EE_Line_Item|null $total_line_item grand total line item of type EEM_Line_Item::type_total
152
-     * @param EE_Ticket         $ticket
153
-     * @param int               $qty
154
-     * @return EE_Line_Item
155
-     * @throws EE_Error
156
-     * @throws ReflectionException
157
-     */
158
-    public static function add_ticket_purchase(
159
-        ?EE_Line_Item $total_line_item,
160
-        EE_Ticket $ticket,
161
-        int $qty = 1
162
-    ): ?EE_Line_Item {
163
-        if (! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
164
-            throw new EE_Error(
165
-                sprintf(
166
-                    esc_html__(
167
-                        'A valid line item total is required in order to add tickets. A line item of type "%s" was passed.',
168
-                        'event_espresso'
169
-                    ),
170
-                    $ticket->ID(),
171
-                    $total_line_item->ID()
172
-                )
173
-            );
174
-        }
175
-        // either increment the qty for an existing ticket
176
-        $line_item = self::increment_ticket_qty_if_already_in_cart($total_line_item, $ticket, $qty);
177
-        // or add a new one
178
-        if (! $line_item instanceof EE_Line_Item) {
179
-            $line_item = self::create_ticket_line_item($total_line_item, $ticket, $qty);
180
-        }
181
-        $total_line_item->recalculate_total_including_taxes();
182
-        return $line_item;
183
-    }
184
-
185
-
186
-    /**
187
-     * Returns the new line item created by adding a purchase of the ticket
188
-     *
189
-     * @param EE_Line_Item $total_line_item
190
-     * @param EE_Ticket    $ticket
191
-     * @param int          $qty
192
-     * @return EE_Line_Item
193
-     * @throws EE_Error
194
-     * @throws InvalidArgumentException
195
-     * @throws InvalidDataTypeException
196
-     * @throws InvalidInterfaceException
197
-     * @throws ReflectionException
198
-     */
199
-    public static function increment_ticket_qty_if_already_in_cart(
200
-        EE_Line_Item $total_line_item,
201
-        EE_Ticket $ticket,
202
-        $qty = 1
203
-    ) {
204
-        $line_item = null;
205
-        if ($total_line_item instanceof EE_Line_Item && $total_line_item->is_total()) {
206
-            $ticket_line_items = EEH_Line_Item::get_ticket_line_items($total_line_item);
207
-            foreach ($ticket_line_items as $ticket_line_item) {
208
-                if (
209
-                    $ticket_line_item instanceof EE_Line_Item
210
-                    && (int) $ticket_line_item->OBJ_ID() === (int) $ticket->ID()
211
-                ) {
212
-                    $line_item = $ticket_line_item;
213
-                    break;
214
-                }
215
-            }
216
-        }
217
-        if ($line_item instanceof EE_Line_Item) {
218
-            EEH_Line_Item::increment_quantity($line_item, $qty);
219
-            return $line_item;
220
-        }
221
-        return null;
222
-    }
223
-
224
-
225
-    /**
226
-     * Increments the line item and all its children's quantity by $qty (but percent line items are unaffected).
227
-     * Does NOT save or recalculate other line items totals
228
-     *
229
-     * @param EE_Line_Item $line_item
230
-     * @param int          $qty
231
-     * @return void
232
-     * @throws EE_Error
233
-     * @throws InvalidArgumentException
234
-     * @throws InvalidDataTypeException
235
-     * @throws InvalidInterfaceException
236
-     * @throws ReflectionException
237
-     */
238
-    public static function increment_quantity(EE_Line_Item $line_item, $qty = 1)
239
-    {
240
-        if (! $line_item->is_percent()) {
241
-            $qty += $line_item->quantity();
242
-            $line_item->set_quantity($qty);
243
-            $line_item->set_total($line_item->unit_price() * $qty);
244
-            $line_item->save();
245
-        }
246
-        foreach ($line_item->children() as $child) {
247
-            if ($child->is_sub_line_item()) {
248
-                EEH_Line_Item::update_quantity($child, $qty);
249
-            }
250
-        }
251
-    }
252
-
253
-
254
-    /**
255
-     * Decrements the line item and all its children's quantity by $qty (but percent line items are unaffected).
256
-     * Does NOT save or recalculate other line items totals
257
-     *
258
-     * @param EE_Line_Item $line_item
259
-     * @param int          $qty
260
-     * @return void
261
-     * @throws EE_Error
262
-     * @throws InvalidArgumentException
263
-     * @throws InvalidDataTypeException
264
-     * @throws InvalidInterfaceException
265
-     * @throws ReflectionException
266
-     */
267
-    public static function decrement_quantity(EE_Line_Item $line_item, $qty = 1)
268
-    {
269
-        if (! $line_item->is_percent()) {
270
-            $qty = $line_item->quantity() - $qty;
271
-            $qty = max($qty, 0);
272
-            $line_item->set_quantity($qty);
273
-            $line_item->set_total($line_item->unit_price() * $qty);
274
-            $line_item->save();
275
-        }
276
-        foreach ($line_item->children() as $child) {
277
-            if ($child->is_sub_line_item()) {
278
-                EEH_Line_Item::update_quantity($child, $qty);
279
-            }
280
-        }
281
-    }
282
-
283
-
284
-    /**
285
-     * Updates the line item and its children's quantities to the specified number.
286
-     * Does NOT save them or recalculate totals.
287
-     *
288
-     * @param EE_Line_Item $line_item
289
-     * @param int          $new_quantity
290
-     * @throws EE_Error
291
-     * @throws InvalidArgumentException
292
-     * @throws InvalidDataTypeException
293
-     * @throws InvalidInterfaceException
294
-     * @throws ReflectionException
295
-     */
296
-    public static function update_quantity(EE_Line_Item $line_item, $new_quantity)
297
-    {
298
-        if (! $line_item->is_percent()) {
299
-            $line_item->set_quantity($new_quantity);
300
-            $line_item->set_total($line_item->unit_price() * $new_quantity);
301
-            $line_item->save();
302
-        }
303
-        foreach ($line_item->children() as $child) {
304
-            if ($child->is_sub_line_item()) {
305
-                EEH_Line_Item::update_quantity($child, $new_quantity);
306
-            }
307
-        }
308
-    }
309
-
310
-
311
-    /**
312
-     * Returns the new line item created by adding a purchase of the ticket
313
-     *
314
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
315
-     * @param EE_Ticket    $ticket
316
-     * @param int          $qty
317
-     * @return EE_Line_Item
318
-     * @throws EE_Error
319
-     * @throws InvalidArgumentException
320
-     * @throws InvalidDataTypeException
321
-     * @throws InvalidInterfaceException
322
-     * @throws ReflectionException
323
-     */
324
-    public static function create_ticket_line_item(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
325
-    {
326
-        $datetimes = $ticket->datetimes();
327
-        $first_datetime = reset($datetimes);
328
-        $first_datetime_name = esc_html__('Event', 'event_espresso');
329
-        if ($first_datetime instanceof EE_Datetime && $first_datetime->event() instanceof EE_Event) {
330
-            $first_datetime_name = $first_datetime->event()->name();
331
-        }
332
-        $event = sprintf(_x('(For %1$s)', '(For Event Name)', 'event_espresso'), $first_datetime_name);
333
-        // get event subtotal line
334
-        $events_sub_total = self::get_event_line_item_for_ticket($total_line_item, $ticket);
335
-        $taxes = $ticket->tax_price_modifiers();
336
-        // add $ticket to cart
337
-        $line_item = EE_Line_Item::new_instance(array(
338
-            'LIN_name'       => $ticket->name(),
339
-            'LIN_desc'       => $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
340
-            'LIN_unit_price' => $ticket->price(),
341
-            'LIN_quantity'   => $qty,
342
-            'LIN_is_taxable' => empty($taxes) && $ticket->taxable(),
343
-            'LIN_order'      => count($events_sub_total->children()),
344
-            'LIN_total'      => $ticket->price() * $qty,
345
-            'LIN_type'       => EEM_Line_Item::type_line_item,
346
-            'OBJ_ID'         => $ticket->ID(),
347
-            'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_TICKET,
348
-        ));
349
-        $line_item = apply_filters(
350
-            'FHEE__EEH_Line_Item__create_ticket_line_item__line_item',
351
-            $line_item
352
-        );
353
-        if (!$line_item instanceof EE_Line_Item) {
354
-            throw new DomainException(
355
-                esc_html__('Invalid EE_Line_Item received.', 'event_espresso')
356
-            );
357
-        }
358
-        $events_sub_total->add_child_line_item($line_item);
359
-        // now add the sub-line items
360
-        $running_total = 0;
361
-        $running_pre_tax_total = 0;
362
-        foreach ($ticket->prices() as $price) {
363
-            $sign = $price->is_discount() ? -1 : 1;
364
-            $price_total = $price->is_percent()
365
-                ? $running_pre_tax_total * $price->amount() / 100
366
-                : $price->amount() * $qty;
367
-            if ($price->is_percent()) {
368
-                $percent = $sign * $price->amount();
369
-                $unit_price = 0;
370
-            } else {
371
-                $percent    = 0;
372
-                $unit_price = $sign * $price->amount();
373
-            }
374
-            $sub_line_item = EE_Line_Item::new_instance(array(
375
-                'LIN_name'       => $price->name(),
376
-                'LIN_desc'       => $price->desc(),
377
-                'LIN_quantity'   => $price->is_percent() ? null : $qty,
378
-                'LIN_is_taxable' => false,
379
-                'LIN_order'      => $price->order(),
380
-                'LIN_total'      => $price_total,
381
-                'LIN_pretax'     => 0,
382
-                'LIN_unit_price' => $unit_price,
383
-                'LIN_percent'    => $percent,
384
-                'LIN_type'       => $price->is_tax() ? EEM_Line_Item::type_sub_tax : EEM_Line_Item::type_sub_line_item,
385
-                'OBJ_ID'         => $price->ID(),
386
-                'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
387
-            ));
388
-            $sub_line_item = apply_filters(
389
-                'FHEE__EEH_Line_Item__create_ticket_line_item__sub_line_item',
390
-                $sub_line_item
391
-            );
392
-            $running_total += $sign * $price_total;
393
-            $running_pre_tax_total += ! $price->is_tax() ? $sign * $price_total : 0;
394
-            $line_item->add_child_line_item($sub_line_item);
395
-        }
396
-        $line_item->setPretaxTotal($running_pre_tax_total);
397
-        return $line_item;
398
-    }
399
-
400
-
401
-    /**
402
-     * Adds the specified item under the pre-tax-sub-total line item. Automatically
403
-     * re-calculates the line item totals and updates the related transaction. But
404
-     * DOES NOT automatically upgrade the transaction's registrations' final prices (which
405
-     * should probably change because of this).
406
-     * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
407
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
408
-     *
409
-     * @param EE_Line_Item $total_line_item
410
-     * @param EE_Line_Item $item to be added
411
-     * @return boolean
412
-     * @throws EE_Error
413
-     * @throws InvalidArgumentException
414
-     * @throws InvalidDataTypeException
415
-     * @throws InvalidInterfaceException
416
-     * @throws ReflectionException
417
-     */
418
-    public static function add_item(EE_Line_Item $total_line_item, EE_Line_Item $item, $recalculate_totals = true)
419
-    {
420
-        $pre_tax_subtotal = self::get_pre_tax_subtotal($total_line_item);
421
-        if ($pre_tax_subtotal instanceof EE_Line_Item) {
422
-            $success = $pre_tax_subtotal->add_child_line_item($item);
423
-        } else {
424
-            return false;
425
-        }
426
-        if ($recalculate_totals) {
427
-            $total_line_item->recalculate_total_including_taxes();
428
-        }
429
-        return $success;
430
-    }
431
-
432
-
433
-    /**
434
-     * cancels an existing ticket line item,
435
-     * by decrementing it's quantity by 1 and adding a new "type_cancellation" sub-line-item.
436
-     * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
437
-     *
438
-     * @param EE_Line_Item $ticket_line_item
439
-     * @param int          $qty
440
-     * @return bool success
441
-     * @throws EE_Error
442
-     * @throws InvalidArgumentException
443
-     * @throws InvalidDataTypeException
444
-     * @throws InvalidInterfaceException
445
-     * @throws ReflectionException
446
-     */
447
-    public static function cancel_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
448
-    {
449
-        // validate incoming line_item
450
-        if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
451
-            throw new EE_Error(
452
-                sprintf(
453
-                    esc_html__(
454
-                        'The supplied line item must have an Object Type of "Ticket", not %1$s.',
455
-                        'event_espresso'
456
-                    ),
457
-                    $ticket_line_item->type()
458
-                )
459
-            );
460
-        }
461
-        if ($ticket_line_item->quantity() < $qty) {
462
-            throw new EE_Error(
463
-                sprintf(
464
-                    esc_html__(
465
-                        'Can not cancel %1$d ticket(s) because the supplied line item has a quantity of %2$d.',
466
-                        'event_espresso'
467
-                    ),
468
-                    $qty,
469
-                    $ticket_line_item->quantity()
470
-                )
471
-            );
472
-        }
473
-        // decrement ticket quantity; don't rely on auto-fixing when recalculating totals to do this
474
-        $ticket_line_item->set_quantity($ticket_line_item->quantity() - $qty);
475
-        foreach ($ticket_line_item->children() as $child_line_item) {
476
-            if (
477
-                $child_line_item->is_sub_line_item()
478
-                && ! $child_line_item->is_percent()
479
-                && ! $child_line_item->is_cancellation()
480
-            ) {
481
-                $child_line_item->set_quantity($child_line_item->quantity() - $qty);
482
-            }
483
-        }
484
-        // get cancellation sub line item
485
-        $cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
486
-            $ticket_line_item,
487
-            EEM_Line_Item::type_cancellation
488
-        );
489
-        $cancellation_line_item = reset($cancellation_line_item);
490
-        // verify that this ticket was indeed previously cancelled
491
-        if ($cancellation_line_item instanceof EE_Line_Item) {
492
-            // increment cancelled quantity
493
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() + $qty);
494
-        } else {
495
-            // create cancellation sub line item
496
-            $cancellation_line_item = EE_Line_Item::new_instance(array(
497
-                'LIN_name'       => esc_html__('Cancellation', 'event_espresso'),
498
-                'LIN_desc'       => sprintf(
499
-                    esc_html_x(
500
-                        'Cancelled %1$s : %2$s',
501
-                        'Cancelled Ticket Name : 2015-01-01 11:11',
502
-                        'event_espresso'
503
-                    ),
504
-                    $ticket_line_item->name(),
505
-                    current_time(get_option('date_format') . ' ' . get_option('time_format'))
506
-                ),
507
-                'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
508
-                'LIN_quantity'   => $qty,
509
-                'LIN_is_taxable' => $ticket_line_item->is_taxable(),
510
-                'LIN_order'      => count($ticket_line_item->children()),
511
-                'LIN_total'      => 0, // $ticket_line_item->unit_price()
512
-                'LIN_type'       => EEM_Line_Item::type_cancellation,
513
-            ));
514
-            $ticket_line_item->add_child_line_item($cancellation_line_item);
515
-        }
516
-        if ($ticket_line_item->save_this_and_descendants() > 0) {
517
-            // decrement parent line item quantity
518
-            $event_line_item = $ticket_line_item->parent();
519
-            if (
520
-                $event_line_item instanceof EE_Line_Item
521
-                && $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
522
-            ) {
523
-                $event_line_item->set_quantity($event_line_item->quantity() - $qty);
524
-                $event_line_item->save();
525
-            }
526
-            EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
527
-            return true;
528
-        }
529
-        return false;
530
-    }
531
-
532
-
533
-    /**
534
-     * reinstates (un-cancels?) a previously canceled ticket line item,
535
-     * by incrementing it's quantity by 1, and decrementing it's "type_cancellation" sub-line-item.
536
-     * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
537
-     *
538
-     * @param EE_Line_Item $ticket_line_item
539
-     * @param int          $qty
540
-     * @return bool success
541
-     * @throws EE_Error
542
-     * @throws InvalidArgumentException
543
-     * @throws InvalidDataTypeException
544
-     * @throws InvalidInterfaceException
545
-     * @throws ReflectionException
546
-     */
547
-    public static function reinstate_canceled_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
548
-    {
549
-        // validate incoming line_item
550
-        if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
551
-            throw new EE_Error(
552
-                sprintf(
553
-                    esc_html__(
554
-                        'The supplied line item must have an Object Type of "Ticket", not %1$s.',
555
-                        'event_espresso'
556
-                    ),
557
-                    $ticket_line_item->type()
558
-                )
559
-            );
560
-        }
561
-        // get cancellation sub line item
562
-        $cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
563
-            $ticket_line_item,
564
-            EEM_Line_Item::type_cancellation
565
-        );
566
-        $cancellation_line_item = reset($cancellation_line_item);
567
-        // verify that this ticket was indeed previously cancelled
568
-        if (! $cancellation_line_item instanceof EE_Line_Item) {
569
-            return false;
570
-        }
571
-        if ($cancellation_line_item->quantity() > $qty) {
572
-            // decrement cancelled quantity
573
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
574
-        } elseif ($cancellation_line_item->quantity() === $qty) {
575
-            // decrement cancelled quantity in case anyone still has the object kicking around
576
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
577
-            // delete because quantity will end up as 0
578
-            $cancellation_line_item->delete();
579
-            // and attempt to destroy the object,
580
-            // even though PHP won't actually destroy it until it needs the memory
581
-            unset($cancellation_line_item);
582
-        } else {
583
-            // what ?!?! negative quantity ?!?!
584
-            throw new EE_Error(
585
-                sprintf(
586
-                    esc_html__(
587
-                        'Can not reinstate %1$d cancelled ticket(s) because the cancelled ticket quantity is only %2$d.',
588
-                        'event_espresso'
589
-                    ),
590
-                    $qty,
591
-                    $cancellation_line_item->quantity()
592
-                )
593
-            );
594
-        }
595
-        // increment ticket quantity
596
-        $ticket_line_item->set_quantity($ticket_line_item->quantity() + $qty);
597
-        if ($ticket_line_item->save_this_and_descendants() > 0) {
598
-            // increment parent line item quantity
599
-            $event_line_item = $ticket_line_item->parent();
600
-            if (
601
-                $event_line_item instanceof EE_Line_Item
602
-                && $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
603
-            ) {
604
-                $event_line_item->set_quantity($event_line_item->quantity() + $qty);
605
-            }
606
-            EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
607
-            return true;
608
-        }
609
-        return false;
610
-    }
611
-
612
-
613
-    /**
614
-     * calls EEH_Line_Item::find_transaction_grand_total_for_line_item()
615
-     * then EE_Line_Item::recalculate_total_including_taxes() on the result
616
-     *
617
-     * @param EE_Line_Item $line_item
618
-     * @return float
619
-     * @throws EE_Error
620
-     * @throws InvalidArgumentException
621
-     * @throws InvalidDataTypeException
622
-     * @throws InvalidInterfaceException
623
-     * @throws ReflectionException
624
-     */
625
-    public static function get_grand_total_and_recalculate_everything(EE_Line_Item $line_item)
626
-    {
627
-        $grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item);
628
-        return $grand_total_line_item->recalculate_total_including_taxes();
629
-    }
630
-
631
-
632
-    /**
633
-     * Gets the line item which contains the subtotal of all the items
634
-     *
635
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
636
-     * @return EE_Line_Item
637
-     * @throws EE_Error
638
-     * @throws InvalidArgumentException
639
-     * @throws InvalidDataTypeException
640
-     * @throws InvalidInterfaceException
641
-     * @throws ReflectionException
642
-     */
643
-    public static function get_pre_tax_subtotal(EE_Line_Item $total_line_item)
644
-    {
645
-        $pre_tax_subtotal = $total_line_item->get_child_line_item('pre-tax-subtotal');
646
-        return $pre_tax_subtotal instanceof EE_Line_Item
647
-            ? $pre_tax_subtotal
648
-            : self::create_pre_tax_subtotal($total_line_item);
649
-    }
650
-
651
-
652
-    /**
653
-     * Gets the line item for the taxes subtotal
654
-     *
655
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
656
-     * @return EE_Line_Item
657
-     * @throws EE_Error
658
-     * @throws InvalidArgumentException
659
-     * @throws InvalidDataTypeException
660
-     * @throws InvalidInterfaceException
661
-     * @throws ReflectionException
662
-     */
663
-    public static function get_taxes_subtotal(EE_Line_Item $total_line_item)
664
-    {
665
-        $taxes = $total_line_item->get_child_line_item('taxes');
666
-        return $taxes ? $taxes : self::create_taxes_subtotal($total_line_item);
667
-    }
668
-
669
-
670
-    /**
671
-     * sets the TXN ID on an EE_Line_Item if passed a valid EE_Transaction object
672
-     *
673
-     * @param EE_Line_Item   $line_item
674
-     * @param EE_Transaction $transaction
675
-     * @return void
676
-     * @throws EE_Error
677
-     * @throws InvalidArgumentException
678
-     * @throws InvalidDataTypeException
679
-     * @throws InvalidInterfaceException
680
-     * @throws ReflectionException
681
-     */
682
-    public static function set_TXN_ID(EE_Line_Item $line_item, $transaction = null)
683
-    {
684
-        if ($transaction) {
685
-            /** @type EEM_Transaction $EEM_Transaction */
686
-            $EEM_Transaction = EE_Registry::instance()->load_model('Transaction');
687
-            $TXN_ID = $EEM_Transaction->ensure_is_ID($transaction);
688
-            $line_item->set_TXN_ID($TXN_ID);
689
-        }
690
-    }
691
-
692
-
693
-    /**
694
-     * Creates a new default total line item for the transaction,
695
-     * and its tickets subtotal and taxes subtotal line items (and adds the
696
-     * existing taxes as children of the taxes subtotal line item)
697
-     *
698
-     * @param EE_Transaction $transaction
699
-     * @return EE_Line_Item of type total
700
-     * @throws EE_Error
701
-     * @throws InvalidArgumentException
702
-     * @throws InvalidDataTypeException
703
-     * @throws InvalidInterfaceException
704
-     * @throws ReflectionException
705
-     */
706
-    public static function create_total_line_item($transaction = null)
707
-    {
708
-        $total_line_item = EE_Line_Item::new_instance(array(
709
-            'LIN_code' => 'total',
710
-            'LIN_name' => esc_html__('Grand Total', 'event_espresso'),
711
-            'LIN_type' => EEM_Line_Item::type_total,
712
-            'OBJ_type' => EEM_Line_Item::OBJ_TYPE_TRANSACTION,
713
-        ));
714
-        $total_line_item = apply_filters(
715
-            'FHEE__EEH_Line_Item__create_total_line_item__total_line_item',
716
-            $total_line_item
717
-        );
718
-        self::set_TXN_ID($total_line_item, $transaction);
719
-        self::create_pre_tax_subtotal($total_line_item, $transaction);
720
-        self::create_taxes_subtotal($total_line_item, $transaction);
721
-        return $total_line_item;
722
-    }
723
-
724
-
725
-    /**
726
-     * Creates a default items subtotal line item
727
-     *
728
-     * @param EE_Line_Item   $total_line_item
729
-     * @param EE_Transaction $transaction
730
-     * @return EE_Line_Item
731
-     * @throws EE_Error
732
-     * @throws InvalidArgumentException
733
-     * @throws InvalidDataTypeException
734
-     * @throws InvalidInterfaceException
735
-     * @throws ReflectionException
736
-     */
737
-    protected static function create_pre_tax_subtotal(EE_Line_Item $total_line_item, $transaction = null)
738
-    {
739
-        $pre_tax_line_item = EE_Line_Item::new_instance(array(
740
-            'LIN_code' => 'pre-tax-subtotal',
741
-            'LIN_name' => esc_html__('Pre-Tax Subtotal', 'event_espresso'),
742
-            'LIN_type' => EEM_Line_Item::type_sub_total,
743
-        ));
744
-        $pre_tax_line_item = apply_filters(
745
-            'FHEE__EEH_Line_Item__create_pre_tax_subtotal__pre_tax_line_item',
746
-            $pre_tax_line_item
747
-        );
748
-        self::set_TXN_ID($pre_tax_line_item, $transaction);
749
-        $total_line_item->add_child_line_item($pre_tax_line_item);
750
-        self::create_event_subtotal($pre_tax_line_item, $transaction);
751
-        return $pre_tax_line_item;
752
-    }
753
-
754
-
755
-    /**
756
-     * Creates a line item for the taxes subtotal and finds all the tax prices
757
-     * and applies taxes to it
758
-     *
759
-     * @param EE_Line_Item   $total_line_item of type EEM_Line_Item::type_total
760
-     * @param EE_Transaction $transaction
761
-     * @return EE_Line_Item
762
-     * @throws EE_Error
763
-     * @throws InvalidArgumentException
764
-     * @throws InvalidDataTypeException
765
-     * @throws InvalidInterfaceException
766
-     * @throws ReflectionException
767
-     */
768
-    protected static function create_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
769
-    {
770
-        $tax_line_item = EE_Line_Item::new_instance(array(
771
-            'LIN_code'  => 'taxes',
772
-            'LIN_name'  => esc_html__('Taxes', 'event_espresso'),
773
-            'LIN_type'  => EEM_Line_Item::type_tax_sub_total,
774
-            'LIN_order' => 1000,// this should always come last
775
-        ));
776
-        $tax_line_item = apply_filters(
777
-            'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
778
-            $tax_line_item
779
-        );
780
-        self::set_TXN_ID($tax_line_item, $transaction);
781
-        $total_line_item->add_child_line_item($tax_line_item);
782
-        // and lastly, add the actual taxes
783
-        self::apply_taxes($total_line_item);
784
-        return $tax_line_item;
785
-    }
786
-
787
-
788
-    /**
789
-     * Creates a default items subtotal line item
790
-     *
791
-     * @param EE_Line_Item   $pre_tax_line_item
792
-     * @param EE_Transaction $transaction
793
-     * @param EE_Event       $event
794
-     * @return EE_Line_Item
795
-     * @throws EE_Error
796
-     * @throws InvalidArgumentException
797
-     * @throws InvalidDataTypeException
798
-     * @throws InvalidInterfaceException
799
-     * @throws ReflectionException
800
-     */
801
-    public static function create_event_subtotal(EE_Line_Item $pre_tax_line_item, $transaction = null, $event = null)
802
-    {
803
-        $event_line_item = EE_Line_Item::new_instance(array(
804
-            'LIN_code' => self::get_event_code($event),
805
-            'LIN_name' => self::get_event_name($event),
806
-            'LIN_desc' => self::get_event_desc($event),
807
-            'LIN_type' => EEM_Line_Item::type_sub_total,
808
-            'OBJ_type' => EEM_Line_Item::OBJ_TYPE_EVENT,
809
-            'OBJ_ID'   => $event instanceof EE_Event ? $event->ID() : 0,
810
-        ));
811
-        $event_line_item = apply_filters(
812
-            'FHEE__EEH_Line_Item__create_event_subtotal__event_line_item',
813
-            $event_line_item
814
-        );
815
-        self::set_TXN_ID($event_line_item, $transaction);
816
-        $pre_tax_line_item->add_child_line_item($event_line_item);
817
-        return $event_line_item;
818
-    }
819
-
820
-
821
-    /**
822
-     * Gets what the event ticket's code SHOULD be
823
-     *
824
-     * @param EE_Event $event
825
-     * @return string
826
-     * @throws EE_Error
827
-     */
828
-    public static function get_event_code($event)
829
-    {
830
-        return 'event-' . ($event instanceof EE_Event ? $event->ID() : '0');
831
-    }
832
-
833
-
834
-    /**
835
-     * Gets the event name
836
-     *
837
-     * @param EE_Event $event
838
-     * @return string
839
-     * @throws EE_Error
840
-     */
841
-    public static function get_event_name($event)
842
-    {
843
-        return $event instanceof EE_Event
844
-            ? mb_substr($event->name(), 0, 245)
845
-            : esc_html__('Event', 'event_espresso');
846
-    }
847
-
848
-
849
-    /**
850
-     * Gets the event excerpt
851
-     *
852
-     * @param EE_Event $event
853
-     * @return string
854
-     * @throws EE_Error
855
-     */
856
-    public static function get_event_desc($event)
857
-    {
858
-        return $event instanceof EE_Event ? $event->short_description() : '';
859
-    }
860
-
861
-
862
-    /**
863
-     * Given the grand total line item and a ticket, finds the event sub-total
864
-     * line item the ticket's purchase should be added onto
865
-     *
866
-     * @access public
867
-     * @param EE_Line_Item $grand_total the grand total line item
868
-     * @param EE_Ticket    $ticket
869
-     * @return EE_Line_Item
870
-     * @throws EE_Error
871
-     * @throws InvalidArgumentException
872
-     * @throws InvalidDataTypeException
873
-     * @throws InvalidInterfaceException
874
-     * @throws ReflectionException
875
-     */
876
-    public static function get_event_line_item_for_ticket(EE_Line_Item $grand_total, EE_Ticket $ticket)
877
-    {
878
-        $first_datetime = $ticket->first_datetime();
879
-        if (! $first_datetime instanceof EE_Datetime) {
880
-            throw new EE_Error(
881
-                sprintf(
882
-                    esc_html__('The supplied ticket (ID %d) has no datetimes', 'event_espresso'),
883
-                    $ticket->ID()
884
-                )
885
-            );
886
-        }
887
-        $event = $first_datetime->event();
888
-        if (! $event instanceof EE_Event) {
889
-            throw new EE_Error(
890
-                sprintf(
891
-                    esc_html__(
892
-                        'The supplied ticket (ID %d) has no event data associated with it.',
893
-                        'event_espresso'
894
-                    ),
895
-                    $ticket->ID()
896
-                )
897
-            );
898
-        }
899
-        $events_sub_total = EEH_Line_Item::get_event_line_item($grand_total, $event);
900
-        if (! $events_sub_total instanceof EE_Line_Item) {
901
-            throw new EE_Error(
902
-                sprintf(
903
-                    esc_html__(
904
-                        'There is no events sub-total for ticket %s on total line item %d',
905
-                        'event_espresso'
906
-                    ),
907
-                    $ticket->ID(),
908
-                    $grand_total->ID()
909
-                )
910
-            );
911
-        }
912
-        return $events_sub_total;
913
-    }
914
-
915
-
916
-    /**
917
-     * Gets the event line item
918
-     *
919
-     * @param EE_Line_Item $grand_total
920
-     * @param EE_Event     $event
921
-     * @return EE_Line_Item for the event subtotal which is a child of $grand_total
922
-     * @throws EE_Error
923
-     * @throws InvalidArgumentException
924
-     * @throws InvalidDataTypeException
925
-     * @throws InvalidInterfaceException
926
-     * @throws ReflectionException
927
-     */
928
-    public static function get_event_line_item(EE_Line_Item $grand_total, $event)
929
-    {
930
-        /** @type EE_Event $event */
931
-        $event = EEM_Event::instance()->ensure_is_obj($event, true);
932
-        $event_line_item = null;
933
-        $found = false;
934
-        foreach (EEH_Line_Item::get_event_subtotals($grand_total) as $event_line_item) {
935
-            // default event subtotal, we should only ever find this the first time this method is called
936
-            if (! $event_line_item->OBJ_ID()) {
937
-                // let's use this! but first... set the event details
938
-                EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
939
-                $found = true;
940
-                break;
941
-            }
942
-            if ($event_line_item->OBJ_ID() === $event->ID()) {
943
-                // found existing line item for this event in the cart, so break out of loop and use this one
944
-                $found = true;
945
-                break;
946
-            }
947
-        }
948
-        if (! $found) {
949
-            // there is no event sub-total yet, so add it
950
-            $pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal($grand_total);
951
-            // create a new "event" subtotal below that
952
-            $event_line_item = EEH_Line_Item::create_event_subtotal($pre_tax_subtotal, null, $event);
953
-            // and set the event details
954
-            EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
955
-        }
956
-        return $event_line_item;
957
-    }
958
-
959
-
960
-    /**
961
-     * Creates a default items subtotal line item
962
-     *
963
-     * @param EE_Line_Item   $event_line_item
964
-     * @param EE_Event       $event
965
-     * @param EE_Transaction $transaction
966
-     * @return void
967
-     * @throws EE_Error
968
-     * @throws InvalidArgumentException
969
-     * @throws InvalidDataTypeException
970
-     * @throws InvalidInterfaceException
971
-     * @throws ReflectionException
972
-     */
973
-    public static function set_event_subtotal_details(
974
-        EE_Line_Item $event_line_item,
975
-        EE_Event $event,
976
-        $transaction = null
977
-    ) {
978
-        if ($event instanceof EE_Event) {
979
-            $event_line_item->set_code(self::get_event_code($event));
980
-            $event_line_item->set_name(self::get_event_name($event));
981
-            $event_line_item->set_desc(self::get_event_desc($event));
982
-            $event_line_item->set_OBJ_ID($event->ID());
983
-        }
984
-        self::set_TXN_ID($event_line_item, $transaction);
985
-    }
986
-
987
-
988
-    /**
989
-     * Finds what taxes should apply, adds them as tax line items under the taxes sub-total,
990
-     * and recalculates the taxes sub-total and the grand total. Resets the taxes, so
991
-     * any old taxes are removed
992
-     *
993
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
994
-     * @param bool         $update_txn_status
995
-     * @return bool
996
-     * @throws EE_Error
997
-     * @throws InvalidArgumentException
998
-     * @throws InvalidDataTypeException
999
-     * @throws InvalidInterfaceException
1000
-     * @throws ReflectionException
1001
-     * @throws RuntimeException
1002
-     */
1003
-    public static function apply_taxes(EE_Line_Item $total_line_item, $update_txn_status = false)
1004
-    {
1005
-        $total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($total_line_item);
1006
-        $taxes_line_item = self::get_taxes_subtotal($total_line_item);
1007
-        $existing_global_taxes = $taxes_line_item->tax_descendants();
1008
-        $updates = false;
1009
-        // loop thru taxes
1010
-        $global_taxes = EEH_Line_Item::getGlobalTaxes();
1011
-        foreach ($global_taxes as $order => $taxes) {
1012
-            foreach ($taxes as $tax) {
1013
-                if ($tax instanceof EE_Price) {
1014
-                    $found = false;
1015
-                    // check if this is already an existing tax
1016
-                    foreach ($existing_global_taxes as $existing_global_tax) {
1017
-                        if ($tax->ID() === $existing_global_tax->OBJ_ID()) {
1018
-                            // maybe update the tax rate in case it has changed
1019
-                            if ($existing_global_tax->percent() !== $tax->amount()) {
1020
-                                $existing_global_tax->set_percent($tax->amount());
1021
-                                $existing_global_tax->save();
1022
-                                $updates = true;
1023
-                            }
1024
-                            $found = true;
1025
-                            break;
1026
-                        }
1027
-                    }
1028
-                    if (! $found) {
1029
-                        // add a new line item for this global tax
1030
-                        $tax_line_item = apply_filters(
1031
-                            'FHEE__EEH_Line_Item__apply_taxes__tax_line_item',
1032
-                            EE_Line_Item::new_instance(
1033
-                                [
1034
-                                    'LIN_name'       => $tax->name(),
1035
-                                    'LIN_desc'       => $tax->desc(),
1036
-                                    'LIN_percent'    => $tax->amount(),
1037
-                                    'LIN_is_taxable' => false,
1038
-                                    'LIN_order'      => $order,
1039
-                                    'LIN_total'      => 0,
1040
-                                    'LIN_type'       => EEM_Line_Item::type_tax,
1041
-                                    'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
1042
-                                    'OBJ_ID'         => $tax->ID(),
1043
-                                ]
1044
-                            )
1045
-                        );
1046
-                        $updates = $taxes_line_item->add_child_line_item($tax_line_item) ? true : $updates;
1047
-                    }
1048
-                }
1049
-            }
1050
-        }
1051
-        // only recalculate totals if something changed
1052
-        if ($updates || $update_txn_status) {
1053
-            $total_line_item->recalculate_total_including_taxes($update_txn_status);
1054
-            return true;
1055
-        }
1056
-        return false;
1057
-    }
1058
-
1059
-
1060
-    /**
1061
-     * Ensures that taxes have been applied to the order, if not applies them.
1062
-     * Returns the total amount of tax
1063
-     *
1064
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
1065
-     * @return float
1066
-     * @throws EE_Error
1067
-     * @throws InvalidArgumentException
1068
-     * @throws InvalidDataTypeException
1069
-     * @throws InvalidInterfaceException
1070
-     * @throws ReflectionException
1071
-     */
1072
-    public static function ensure_taxes_applied($total_line_item)
1073
-    {
1074
-        $taxes_subtotal = self::get_taxes_subtotal($total_line_item);
1075
-        if (! $taxes_subtotal->children()) {
1076
-            self::apply_taxes($total_line_item);
1077
-        }
1078
-        return $taxes_subtotal->total();
1079
-    }
1080
-
1081
-
1082
-    /**
1083
-     * Deletes ALL children of the passed line item
1084
-     *
1085
-     * @param EE_Line_Item $parent_line_item
1086
-     * @return bool
1087
-     * @throws EE_Error
1088
-     * @throws InvalidArgumentException
1089
-     * @throws InvalidDataTypeException
1090
-     * @throws InvalidInterfaceException
1091
-     * @throws ReflectionException
1092
-     */
1093
-    public static function delete_all_child_items(EE_Line_Item $parent_line_item)
1094
-    {
1095
-        $deleted = 0;
1096
-        foreach ($parent_line_item->children() as $child_line_item) {
1097
-            if ($child_line_item instanceof EE_Line_Item) {
1098
-                $deleted += EEH_Line_Item::delete_all_child_items($child_line_item);
1099
-                if ($child_line_item->ID()) {
1100
-                    $child_line_item->delete();
1101
-                    unset($child_line_item);
1102
-                } else {
1103
-                    $parent_line_item->delete_child_line_item($child_line_item->code());
1104
-                }
1105
-                $deleted++;
1106
-            }
1107
-        }
1108
-        return $deleted;
1109
-    }
1110
-
1111
-
1112
-    /**
1113
-     * Deletes the line items as indicated by the line item code(s) provided,
1114
-     * regardless of where they're found in the line item tree. Automatically
1115
-     * re-calculates the line item totals and updates the related transaction. But
1116
-     * DOES NOT automatically upgrade the transaction's registrations' final prices (which
1117
-     * should probably change because of this).
1118
-     * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
1119
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
1120
-     *
1121
-     * @param EE_Line_Item      $total_line_item of type EEM_Line_Item::type_total
1122
-     * @param array|bool|string $line_item_codes
1123
-     * @return int number of items successfully removed
1124
-     * @throws EE_Error
1125
-     */
1126
-    public static function delete_items(EE_Line_Item $total_line_item, $line_item_codes = false)
1127
-    {
1128
-
1129
-        if ($total_line_item->type() !== EEM_Line_Item::type_total) {
1130
-            EE_Error::doing_it_wrong(
1131
-                'EEH_Line_Item::delete_items',
1132
-                esc_html__(
1133
-                    'This static method should only be called with a TOTAL line item, otherwise we won\'t recalculate the totals correctly',
1134
-                    'event_espresso'
1135
-                ),
1136
-                '4.6.18'
1137
-            );
1138
-        }
1139
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1140
-
1141
-        // check if only a single line_item_id was passed
1142
-        if (! empty($line_item_codes) && ! is_array($line_item_codes)) {
1143
-            // place single line_item_id in an array to appear as multiple line_item_ids
1144
-            $line_item_codes = array($line_item_codes);
1145
-        }
1146
-        $removals = 0;
1147
-        // cycle thru line_item_ids
1148
-        foreach ($line_item_codes as $line_item_id) {
1149
-            $removals += $total_line_item->delete_child_line_item($line_item_id);
1150
-        }
1151
-
1152
-        if ($removals > 0) {
1153
-            $total_line_item->recalculate_taxes_and_tax_total();
1154
-            return $removals;
1155
-        } else {
1156
-            return false;
1157
-        }
1158
-    }
1159
-
1160
-
1161
-    /**
1162
-     * Overwrites the previous tax by clearing out the old taxes, and creates a new
1163
-     * tax and updates the total line item accordingly
1164
-     *
1165
-     * @param EE_Line_Item $total_line_item
1166
-     * @param float        $amount
1167
-     * @param string       $name
1168
-     * @param string       $description
1169
-     * @param string       $code
1170
-     * @param boolean      $add_to_existing_line_item
1171
-     *                          if true, and a duplicate line item with the same code is found,
1172
-     *                          $amount will be added onto it; otherwise will simply set the taxes to match $amount
1173
-     * @return EE_Line_Item the new tax line item created
1174
-     * @throws EE_Error
1175
-     * @throws InvalidArgumentException
1176
-     * @throws InvalidDataTypeException
1177
-     * @throws InvalidInterfaceException
1178
-     * @throws ReflectionException
1179
-     */
1180
-    public static function set_total_tax_to(
1181
-        EE_Line_Item $total_line_item,
1182
-        $amount,
1183
-        $name = null,
1184
-        $description = null,
1185
-        $code = null,
1186
-        $add_to_existing_line_item = false
1187
-    ) {
1188
-        $tax_subtotal = self::get_taxes_subtotal($total_line_item);
1189
-        $taxable_total = $total_line_item->taxable_total();
1190
-
1191
-        if ($add_to_existing_line_item) {
1192
-            $new_tax = $tax_subtotal->get_child_line_item($code);
1193
-            EEM_Line_Item::instance()->delete(
1194
-                array(array('LIN_code' => array('!=', $code), 'LIN_parent' => $tax_subtotal->ID()))
1195
-            );
1196
-        } else {
1197
-            $new_tax = null;
1198
-            $tax_subtotal->delete_children_line_items();
1199
-        }
1200
-        if ($new_tax) {
1201
-            $new_tax->set_total($new_tax->total() + $amount);
1202
-            $new_tax->set_percent($taxable_total ? $new_tax->total() / $taxable_total * 100 : 0);
1203
-        } else {
1204
-            // no existing tax item. Create it
1205
-            $new_tax = EE_Line_Item::new_instance(array(
1206
-                'TXN_ID'      => $total_line_item->TXN_ID(),
1207
-                'LIN_name'    => $name ?: esc_html__('Tax', 'event_espresso'),
1208
-                'LIN_desc'    => $description ?: '',
1209
-                'LIN_percent' => $taxable_total ? ($amount / $taxable_total * 100) : 0,
1210
-                'LIN_total'   => $amount,
1211
-                'LIN_parent'  => $tax_subtotal->ID(),
1212
-                'LIN_type'    => EEM_Line_Item::type_tax,
1213
-                'LIN_code'    => $code,
1214
-            ));
1215
-        }
1216
-
1217
-        $new_tax = apply_filters(
1218
-            'FHEE__EEH_Line_Item__set_total_tax_to__new_tax_subtotal',
1219
-            $new_tax,
1220
-            $total_line_item
1221
-        );
1222
-        $new_tax->save();
1223
-        $tax_subtotal->set_total($new_tax->total());
1224
-        $tax_subtotal->save();
1225
-        $total_line_item->recalculate_total_including_taxes();
1226
-        return $new_tax;
1227
-    }
1228
-
1229
-
1230
-    /**
1231
-     * Makes all the line items which are children of $line_item taxable (or not).
1232
-     * Does NOT save the line items
1233
-     *
1234
-     * @param EE_Line_Item $line_item
1235
-     * @param boolean      $taxable
1236
-     * @param string       $code_substring_for_whitelist if this string is part of the line item's code
1237
-     *                                                   it will be whitelisted (ie, except from becoming taxable)
1238
-     * @throws EE_Error
1239
-     */
1240
-    public static function set_line_items_taxable(
1241
-        EE_Line_Item $line_item,
1242
-        $taxable = true,
1243
-        $code_substring_for_whitelist = null
1244
-    ) {
1245
-        $whitelisted = false;
1246
-        if ($code_substring_for_whitelist !== null) {
1247
-            $whitelisted = strpos($line_item->code(), $code_substring_for_whitelist) !== false;
1248
-        }
1249
-        if (! $whitelisted && $line_item->is_line_item()) {
1250
-            $line_item->set_is_taxable($taxable);
1251
-        }
1252
-        foreach ($line_item->children() as $child_line_item) {
1253
-            EEH_Line_Item::set_line_items_taxable(
1254
-                $child_line_item,
1255
-                $taxable,
1256
-                $code_substring_for_whitelist
1257
-            );
1258
-        }
1259
-    }
1260
-
1261
-
1262
-    /**
1263
-     * Gets all descendants that are event subtotals
1264
-     *
1265
-     * @uses  EEH_Line_Item::get_subtotals_of_object_type()
1266
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1267
-     * @return EE_Line_Item[]
1268
-     * @throws EE_Error
1269
-     */
1270
-    public static function get_event_subtotals(EE_Line_Item $parent_line_item)
1271
-    {
1272
-        return self::get_subtotals_of_object_type($parent_line_item, EEM_Line_Item::OBJ_TYPE_EVENT);
1273
-    }
1274
-
1275
-
1276
-    /**
1277
-     * Gets all descendants subtotals that match the supplied object type
1278
-     *
1279
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1280
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1281
-     * @param string       $obj_type
1282
-     * @return EE_Line_Item[]
1283
-     * @throws EE_Error
1284
-     */
1285
-    public static function get_subtotals_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1286
-    {
1287
-        return self::_get_descendants_by_type_and_object_type(
1288
-            $parent_line_item,
1289
-            EEM_Line_Item::type_sub_total,
1290
-            $obj_type
1291
-        );
1292
-    }
1293
-
1294
-
1295
-    /**
1296
-     * Gets all descendants that are tickets
1297
-     *
1298
-     * @uses  EEH_Line_Item::get_line_items_of_object_type()
1299
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1300
-     * @return EE_Line_Item[]
1301
-     * @throws EE_Error
1302
-     */
1303
-    public static function get_ticket_line_items(EE_Line_Item $parent_line_item)
1304
-    {
1305
-        return self::get_line_items_of_object_type(
1306
-            $parent_line_item,
1307
-            EEM_Line_Item::OBJ_TYPE_TICKET
1308
-        );
1309
-    }
1310
-
1311
-
1312
-    /**
1313
-     * Gets all descendants subtotals that match the supplied object type
1314
-     *
1315
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1316
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1317
-     * @param string       $obj_type
1318
-     * @return EE_Line_Item[]
1319
-     * @throws EE_Error
1320
-     */
1321
-    public static function get_line_items_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1322
-    {
1323
-        return self::_get_descendants_by_type_and_object_type(
1324
-            $parent_line_item,
1325
-            EEM_Line_Item::type_line_item,
1326
-            $obj_type
1327
-        );
1328
-    }
1329
-
1330
-
1331
-    /**
1332
-     * Gets all the descendants (ie, children or children of children etc) that are of the type 'tax'
1333
-     *
1334
-     * @uses  EEH_Line_Item::get_descendants_of_type()
1335
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1336
-     * @return EE_Line_Item[]
1337
-     * @throws EE_Error
1338
-     */
1339
-    public static function get_tax_descendants(EE_Line_Item $parent_line_item)
1340
-    {
1341
-        return EEH_Line_Item::get_descendants_of_type(
1342
-            $parent_line_item,
1343
-            EEM_Line_Item::type_tax
1344
-        );
1345
-    }
1346
-
1347
-
1348
-    /**
1349
-     * Gets all the real items purchased which are children of this item
1350
-     *
1351
-     * @uses  EEH_Line_Item::get_descendants_of_type()
1352
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1353
-     * @return EE_Line_Item[]
1354
-     * @throws EE_Error
1355
-     */
1356
-    public static function get_line_item_descendants(EE_Line_Item $parent_line_item)
1357
-    {
1358
-        return EEH_Line_Item::get_descendants_of_type(
1359
-            $parent_line_item,
1360
-            EEM_Line_Item::type_line_item
1361
-        );
1362
-    }
1363
-
1364
-
1365
-    /**
1366
-     * Gets all descendants of supplied line item that match the supplied line item type
1367
-     *
1368
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1369
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1370
-     * @param string       $line_item_type   one of the EEM_Line_Item constants
1371
-     * @return EE_Line_Item[]
1372
-     * @throws EE_Error
1373
-     */
1374
-    public static function get_descendants_of_type(EE_Line_Item $parent_line_item, $line_item_type)
1375
-    {
1376
-        return self::_get_descendants_by_type_and_object_type(
1377
-            $parent_line_item,
1378
-            $line_item_type,
1379
-            null
1380
-        );
1381
-    }
1382
-
1383
-
1384
-    /**
1385
-     * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1386
-     * as well
1387
-     *
1388
-     * @param EE_Line_Item  $parent_line_item - the line item to find descendants of
1389
-     * @param string        $line_item_type   one of the EEM_Line_Item constants
1390
-     * @param string | NULL $obj_type         object model class name (minus prefix) or NULL to ignore object type when
1391
-     *                                        searching
1392
-     * @return EE_Line_Item[]
1393
-     * @throws EE_Error
1394
-     */
1395
-    protected static function _get_descendants_by_type_and_object_type(
1396
-        EE_Line_Item $parent_line_item,
1397
-        $line_item_type,
1398
-        $obj_type = null
1399
-    ) {
1400
-        $objects = array();
1401
-        foreach ($parent_line_item->children() as $child_line_item) {
1402
-            if ($child_line_item instanceof EE_Line_Item) {
1403
-                if (
1404
-                    $child_line_item->type() === $line_item_type
1405
-                    && (
1406
-                        $child_line_item->OBJ_type() === $obj_type || $obj_type === null
1407
-                    )
1408
-                ) {
1409
-                    $objects[] = $child_line_item;
1410
-                } else {
1411
-                    // go-through-all-its children looking for more matches
1412
-                    $objects = array_merge(
1413
-                        $objects,
1414
-                        self::_get_descendants_by_type_and_object_type(
1415
-                            $child_line_item,
1416
-                            $line_item_type,
1417
-                            $obj_type
1418
-                        )
1419
-                    );
1420
-                }
1421
-            }
1422
-        }
1423
-        return $objects;
1424
-    }
1425
-
1426
-
1427
-    /**
1428
-     * Gets all descendants subtotals that match the supplied object type
1429
-     *
1430
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1431
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1432
-     * @param string       $OBJ_type         object type (like Event)
1433
-     * @param array        $OBJ_IDs          array of OBJ_IDs
1434
-     * @return EE_Line_Item[]
1435
-     * @throws EE_Error
1436
-     */
1437
-    public static function get_line_items_by_object_type_and_IDs(
1438
-        EE_Line_Item $parent_line_item,
1439
-        $OBJ_type = '',
1440
-        $OBJ_IDs = array()
1441
-    ) {
1442
-        return self::_get_descendants_by_object_type_and_object_ID(
1443
-            $parent_line_item,
1444
-            $OBJ_type,
1445
-            $OBJ_IDs
1446
-        );
1447
-    }
1448
-
1449
-
1450
-    /**
1451
-     * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1452
-     * as well
1453
-     *
1454
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1455
-     * @param string       $OBJ_type         object type (like Event)
1456
-     * @param array        $OBJ_IDs          array of OBJ_IDs
1457
-     * @return EE_Line_Item[]
1458
-     * @throws EE_Error
1459
-     */
1460
-    protected static function _get_descendants_by_object_type_and_object_ID(
1461
-        EE_Line_Item $parent_line_item,
1462
-        $OBJ_type,
1463
-        $OBJ_IDs
1464
-    ) {
1465
-        $objects = array();
1466
-        foreach ($parent_line_item->children() as $child_line_item) {
1467
-            if ($child_line_item instanceof EE_Line_Item) {
1468
-                if (
1469
-                    $child_line_item->OBJ_type() === $OBJ_type
1470
-                    && is_array($OBJ_IDs)
1471
-                    && in_array($child_line_item->OBJ_ID(), $OBJ_IDs)
1472
-                ) {
1473
-                    $objects[] = $child_line_item;
1474
-                } else {
1475
-                    // go-through-all-its children looking for more matches
1476
-                    $objects = array_merge(
1477
-                        $objects,
1478
-                        self::_get_descendants_by_object_type_and_object_ID(
1479
-                            $child_line_item,
1480
-                            $OBJ_type,
1481
-                            $OBJ_IDs
1482
-                        )
1483
-                    );
1484
-                }
1485
-            }
1486
-        }
1487
-        return $objects;
1488
-    }
1489
-
1490
-
1491
-    /**
1492
-     * Uses a breadth-first-search in order to find the nearest descendant of
1493
-     * the specified type and returns it, else NULL
1494
-     *
1495
-     * @uses  EEH_Line_Item::_get_nearest_descendant()
1496
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1497
-     * @param string       $type             like one of the EEM_Line_Item::type_*
1498
-     * @return EE_Line_Item
1499
-     * @throws EE_Error
1500
-     * @throws InvalidArgumentException
1501
-     * @throws InvalidDataTypeException
1502
-     * @throws InvalidInterfaceException
1503
-     * @throws ReflectionException
1504
-     */
1505
-    public static function get_nearest_descendant_of_type(EE_Line_Item $parent_line_item, $type)
1506
-    {
1507
-        return self::_get_nearest_descendant($parent_line_item, 'LIN_type', $type);
1508
-    }
1509
-
1510
-
1511
-    /**
1512
-     * Uses a breadth-first-search in order to find the nearest descendant
1513
-     * having the specified LIN_code and returns it, else NULL
1514
-     *
1515
-     * @uses  EEH_Line_Item::_get_nearest_descendant()
1516
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1517
-     * @param string       $code             any value used for LIN_code
1518
-     * @return EE_Line_Item
1519
-     * @throws EE_Error
1520
-     * @throws InvalidArgumentException
1521
-     * @throws InvalidDataTypeException
1522
-     * @throws InvalidInterfaceException
1523
-     * @throws ReflectionException
1524
-     */
1525
-    public static function get_nearest_descendant_having_code(EE_Line_Item $parent_line_item, $code)
1526
-    {
1527
-        return self::_get_nearest_descendant($parent_line_item, 'LIN_code', $code);
1528
-    }
1529
-
1530
-
1531
-    /**
1532
-     * Uses a breadth-first-search in order to find the nearest descendant
1533
-     * having the specified LIN_code and returns it, else NULL
1534
-     *
1535
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1536
-     * @param string       $search_field     name of EE_Line_Item property
1537
-     * @param string       $value            any value stored in $search_field
1538
-     * @return EE_Line_Item
1539
-     * @throws EE_Error
1540
-     * @throws InvalidArgumentException
1541
-     * @throws InvalidDataTypeException
1542
-     * @throws InvalidInterfaceException
1543
-     * @throws ReflectionException
1544
-     */
1545
-    protected static function _get_nearest_descendant(EE_Line_Item $parent_line_item, $search_field, $value)
1546
-    {
1547
-        foreach ($parent_line_item->children() as $child) {
1548
-            if ($child->get($search_field) == $value) {
1549
-                return $child;
1550
-            }
1551
-        }
1552
-        foreach ($parent_line_item->children() as $child) {
1553
-            $descendant_found = self::_get_nearest_descendant(
1554
-                $child,
1555
-                $search_field,
1556
-                $value
1557
-            );
1558
-            if ($descendant_found) {
1559
-                return $descendant_found;
1560
-            }
1561
-        }
1562
-        return null;
1563
-    }
1564
-
1565
-
1566
-    /**
1567
-     * if passed line item has a TXN ID, uses that to jump directly to the grand total line item for the transaction,
1568
-     * else recursively walks up the line item tree until a parent of type total is found,
1569
-     *
1570
-     * @param EE_Line_Item $line_item
1571
-     * @return EE_Line_Item
1572
-     * @throws EE_Error
1573
-     * @throws ReflectionException
1574
-     */
1575
-    public static function find_transaction_grand_total_for_line_item(EE_Line_Item $line_item): EE_Line_Item
1576
-    {
1577
-        if ($line_item->is_total()) {
1578
-            return $line_item;
1579
-        }
1580
-        if ($line_item->TXN_ID()) {
1581
-            $total_line_item = $line_item->transaction()->total_line_item(false);
1582
-            if ($total_line_item instanceof EE_Line_Item) {
1583
-                return $total_line_item;
1584
-            }
1585
-        } else {
1586
-            $line_item_parent = $line_item->parent();
1587
-            if ($line_item_parent instanceof EE_Line_Item) {
1588
-                if ($line_item_parent->is_total()) {
1589
-                    return $line_item_parent;
1590
-                }
1591
-                return EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item_parent);
1592
-            }
1593
-        }
1594
-        throw new EE_Error(
1595
-            sprintf(
1596
-                esc_html__(
1597
-                    'A valid grand total for line item %1$d was not found.',
1598
-                    'event_espresso'
1599
-                ),
1600
-                $line_item->ID()
1601
-            )
1602
-        );
1603
-    }
1604
-
1605
-
1606
-    /**
1607
-     * Prints out a representation of the line item tree
1608
-     *
1609
-     * @param EE_Line_Item $line_item
1610
-     * @param int          $indentation
1611
-     * @return void
1612
-     * @throws EE_Error
1613
-     */
1614
-    public static function visualize(EE_Line_Item $line_item, $indentation = 0)
1615
-    {
1616
-        $new_line = defined('EE_TESTS_DIR') ? "\n" : '<br />';
1617
-        echo $new_line;
1618
-        if (! $indentation) {
1619
-            echo $new_line;
1620
-        }
1621
-        echo str_repeat('. ', $indentation);
1622
-        $breakdown = '';
1623
-        if ($line_item->is_line_item() || $line_item->is_sub_line_item() || $line_item->isSubTax()) {
1624
-            if ($line_item->is_percent()) {
1625
-                $breakdown = "{$line_item->percent()}%";
1626
-            } else {
1627
-                $breakdown = "\${$line_item->unit_price()} x {$line_item->quantity()}";
1628
-            }
1629
-        }
1630
-        echo wp_kses($line_item->name(), AllowedTags::getAllowedTags());
1631
-        echo " [ ID:{$line_item->ID()} | qty:{$line_item->quantity()} ] {$line_item->type()} : ";
1632
-        echo "\${$line_item->total()}";
1633
-        if ($breakdown) {
1634
-            echo " ( {$breakdown} )";
1635
-        }
1636
-        if ($line_item->is_taxable()) {
1637
-            echo '  * taxable';
1638
-        }
1639
-        if ($line_item->children()) {
1640
-            foreach ($line_item->children() as $child) {
1641
-                self::visualize($child, $indentation + 1);
1642
-            }
1643
-        }
1644
-        if (! $indentation) {
1645
-            echo $new_line . $new_line;
1646
-        }
1647
-    }
1648
-
1649
-
1650
-    /**
1651
-     * Calculates the registration's final price, taking into account that they
1652
-     * need to not only help pay for their OWN ticket, but also any transaction-wide surcharges and taxes,
1653
-     * and receive a portion of any transaction-wide discounts.
1654
-     * eg1, if I buy a $1 ticket and brent buys a $9 ticket, and we receive a $5 discount
1655
-     * then I'll get 1/10 of that $5 discount, which is $0.50, and brent will get
1656
-     * 9/10ths of that $5 discount, which is $4.50. So my final price should be $0.50
1657
-     * and brent's final price should be $5.50.
1658
-     * In order to do this, we basically need to traverse the line item tree calculating
1659
-     * the running totals (just as if we were recalculating the total), but when we identify
1660
-     * regular line items, we need to keep track of their share of the grand total.
1661
-     * Also, we need to keep track of the TAXABLE total for each ticket purchase, so
1662
-     * we can know how to apply taxes to it. (Note: "taxable total" does not equal the "pretax total"
1663
-     * when there are non-taxable items; otherwise they would be the same)
1664
-     *
1665
-     * @param EE_Line_Item $line_item
1666
-     * @param array        $billable_ticket_quantities  array of EE_Ticket IDs and their corresponding quantity that
1667
-     *                                                  can be included in price calculations at this moment
1668
-     * @return array        keys are line items for tickets IDs and values are their share of the running total,
1669
-     *                                                  plus the key 'total', and 'taxable' which also has keys of all
1670
-     *                                                  the ticket IDs.
1671
-     *                                                  Eg array(
1672
-     *                                                      12 => 4.3
1673
-     *                                                      23 => 8.0
1674
-     *                                                      'total' => 16.6,
1675
-     *                                                      'taxable' => array(
1676
-     *                                                          12 => 10,
1677
-     *                                                          23 => 4
1678
-     *                                                      ).
1679
-     *                                                  So to find which registrations have which final price, we need
1680
-     *                                                  to find which line item is theirs, which can be done with
1681
-     *                                                  `EEM_Line_Item::instance()->get_line_item_for_registration(
1682
-     *                                                  $registration );`
1683
-     * @throws EE_Error
1684
-     * @throws InvalidArgumentException
1685
-     * @throws InvalidDataTypeException
1686
-     * @throws InvalidInterfaceException
1687
-     * @throws ReflectionException
1688
-     */
1689
-    public static function calculate_reg_final_prices_per_line_item(
1690
-        EE_Line_Item $line_item,
1691
-        $billable_ticket_quantities = array()
1692
-    ) {
1693
-        $running_totals = [
1694
-            'total'   => 0,
1695
-            'taxable' => ['total' => 0]
1696
-        ];
1697
-        foreach ($line_item->children() as $child_line_item) {
1698
-            switch ($child_line_item->type()) {
1699
-                case EEM_Line_Item::type_sub_total:
1700
-                    $running_totals_from_subtotal = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1701
-                        $child_line_item,
1702
-                        $billable_ticket_quantities
1703
-                    );
1704
-                    // combine arrays but preserve numeric keys
1705
-                    $running_totals = array_replace_recursive($running_totals_from_subtotal, $running_totals);
1706
-                    $running_totals['total'] += $running_totals_from_subtotal['total'];
1707
-                    $running_totals['taxable']['total'] += $running_totals_from_subtotal['taxable']['total'];
1708
-                    break;
1709
-
1710
-                case EEM_Line_Item::type_tax_sub_total:
1711
-                    // find how much the taxes percentage is
1712
-                    if ($child_line_item->percent() !== 0) {
1713
-                        $tax_percent_decimal = $child_line_item->percent() / 100;
1714
-                    } else {
1715
-                        $tax_percent_decimal = EE_Taxes::get_total_taxes_percentage() / 100;
1716
-                    }
1717
-                    // and apply to all the taxable totals, and add to the pretax totals
1718
-                    foreach ($running_totals as $line_item_id => $this_running_total) {
1719
-                        // "total" and "taxable" array key is an exception
1720
-                        if ($line_item_id === 'taxable') {
1721
-                            continue;
1722
-                        }
1723
-                        $taxable_total = $running_totals['taxable'][ $line_item_id ];
1724
-                        $running_totals[ $line_item_id ] += ($taxable_total * $tax_percent_decimal);
1725
-                    }
1726
-                    break;
1727
-
1728
-                case EEM_Line_Item::type_line_item:
1729
-                    // ticket line items or ????
1730
-                    if ($child_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
1731
-                        // kk it's a ticket
1732
-                        if (isset($running_totals[ $child_line_item->ID() ])) {
1733
-                            // huh? that shouldn't happen.
1734
-                            $running_totals['total'] += $child_line_item->total();
1735
-                        } else {
1736
-                            // its not in our running totals yet. great.
1737
-                            if ($child_line_item->is_taxable()) {
1738
-                                $taxable_amount = $child_line_item->unit_price();
1739
-                            } else {
1740
-                                $taxable_amount = 0;
1741
-                            }
1742
-                            // are we only calculating totals for some tickets?
1743
-                            if (isset($billable_ticket_quantities[ $child_line_item->OBJ_ID() ])) {
1744
-                                $quantity = $billable_ticket_quantities[ $child_line_item->OBJ_ID() ];
1745
-                                $running_totals[ $child_line_item->ID() ] = $quantity
1746
-                                    ? $child_line_item->unit_price()
1747
-                                    : 0;
1748
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $quantity
1749
-                                    ? $taxable_amount
1750
-                                    : 0;
1751
-                            } else {
1752
-                                $quantity = $child_line_item->quantity();
1753
-                                $running_totals[ $child_line_item->ID() ] = $child_line_item->unit_price();
1754
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $taxable_amount;
1755
-                            }
1756
-                            $running_totals['taxable']['total'] += $taxable_amount * $quantity;
1757
-                            $running_totals['total'] += $child_line_item->unit_price() * $quantity;
1758
-                        }
1759
-                    } else {
1760
-                        // it's some other type of item added to the cart
1761
-                        // it should affect the running totals
1762
-                        // basically we want to convert it into a PERCENT modifier. Because
1763
-                        // more clearly affect all registration's final price equally
1764
-                        $line_items_percent_of_running_total = $running_totals['total'] > 0
1765
-                            ? ($child_line_item->total() / $running_totals['total']) + 1
1766
-                            : 1;
1767
-                        foreach ($running_totals as $line_item_id => $this_running_total) {
1768
-                            // the "taxable" array key is an exception
1769
-                            if ($line_item_id === 'taxable') {
1770
-                                continue;
1771
-                            }
1772
-                            // update the running totals
1773
-                            // yes this actually even works for the running grand total!
1774
-                            $running_totals[ $line_item_id ] =
1775
-                                $line_items_percent_of_running_total * $this_running_total;
1776
-
1777
-                            if ($child_line_item->is_taxable()) {
1778
-                                $running_totals['taxable'][ $line_item_id ] =
1779
-                                    $line_items_percent_of_running_total * $running_totals['taxable'][ $line_item_id ];
1780
-                            }
1781
-                        }
1782
-                    }
1783
-                    break;
1784
-            }
1785
-        }
1786
-        return $running_totals;
1787
-    }
1788
-
1789
-
1790
-    /**
1791
-     * @param EE_Line_Item $total_line_item
1792
-     * @param EE_Line_Item $ticket_line_item
1793
-     * @return float | null
1794
-     * @throws EE_Error
1795
-     * @throws InvalidArgumentException
1796
-     * @throws InvalidDataTypeException
1797
-     * @throws InvalidInterfaceException
1798
-     * @throws OutOfRangeException
1799
-     * @throws ReflectionException
1800
-     */
1801
-    public static function calculate_final_price_for_ticket_line_item(
1802
-        EE_Line_Item $total_line_item,
1803
-        EE_Line_Item $ticket_line_item
1804
-    ) {
1805
-        static $final_prices_per_ticket_line_item = array();
1806
-        if (empty($final_prices_per_ticket_line_item) || empty($final_prices_per_ticket_line_item[ $total_line_item->ID() ])) {
1807
-            $final_prices_per_ticket_line_item[ $total_line_item->ID() ] = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1808
-                $total_line_item
1809
-            );
1810
-        }
1811
-        // ok now find this new registration's final price
1812
-        if (isset($final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ])) {
1813
-            return $final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ];
1814
-        }
1815
-        $message = sprintf(
1816
-            esc_html__(
1817
-                'The final price for the ticket line item (ID:%1$d) on the total line item (ID:%2$d) could not be calculated.',
1818
-                'event_espresso'
1819
-            ),
1820
-            $ticket_line_item->ID(),
1821
-            $total_line_item->ID()
1822
-        );
1823
-        if (WP_DEBUG) {
1824
-            $message .= '<br>' . print_r($final_prices_per_ticket_line_item, true);
1825
-            throw new OutOfRangeException($message);
1826
-        }
1827
-        EE_Log::instance()->log(__CLASS__, __FUNCTION__, $message);
1828
-        return null;
1829
-    }
1830
-
1831
-
1832
-    /**
1833
-     * Creates a duplicate of the line item tree, except only includes billable items
1834
-     * and the portion of line items attributed to billable things
1835
-     *
1836
-     * @param EE_Line_Item      $line_item
1837
-     * @param EE_Registration[] $registrations
1838
-     * @return EE_Line_Item
1839
-     * @throws EE_Error
1840
-     * @throws InvalidArgumentException
1841
-     * @throws InvalidDataTypeException
1842
-     * @throws InvalidInterfaceException
1843
-     * @throws ReflectionException
1844
-     */
1845
-    public static function billable_line_item_tree(EE_Line_Item $line_item, $registrations)
1846
-    {
1847
-        $copy_li = EEH_Line_Item::billable_line_item($line_item, $registrations);
1848
-        foreach ($line_item->children() as $child_li) {
1849
-            $copy_li->add_child_line_item(
1850
-                EEH_Line_Item::billable_line_item_tree($child_li, $registrations)
1851
-            );
1852
-        }
1853
-        // if this is the grand total line item, make sure the totals all add up
1854
-        // (we could have duplicated this logic AS we copied the line items, but
1855
-        // it seems DRYer this way)
1856
-        if ($copy_li->type() === EEM_Line_Item::type_total) {
1857
-            $copy_li->recalculate_total_including_taxes();
1858
-        }
1859
-        return $copy_li;
1860
-    }
1861
-
1862
-
1863
-    /**
1864
-     * Creates a new, unsaved line item from $line_item that factors in the
1865
-     * number of billable registrations on $registrations.
1866
-     *
1867
-     * @param EE_Line_Item      $line_item
1868
-     * @param EE_Registration[] $registrations
1869
-     * @return EE_Line_Item
1870
-     * @throws EE_Error
1871
-     * @throws InvalidArgumentException
1872
-     * @throws InvalidDataTypeException
1873
-     * @throws InvalidInterfaceException
1874
-     * @throws ReflectionException
1875
-     */
1876
-    public static function billable_line_item(EE_Line_Item $line_item, $registrations)
1877
-    {
1878
-        $new_li_fields = $line_item->model_field_array();
1879
-        if (
1880
-            $line_item->type() === EEM_Line_Item::type_line_item &&
1881
-            $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1882
-        ) {
1883
-            $count = 0;
1884
-            foreach ($registrations as $registration) {
1885
-                if (
1886
-                    $line_item->OBJ_ID() === $registration->ticket_ID() &&
1887
-                    in_array(
1888
-                        $registration->status_ID(),
1889
-                        EEM_Registration::reg_statuses_that_allow_payment(),
1890
-                        true
1891
-                    )
1892
-                ) {
1893
-                    $count++;
1894
-                }
1895
-            }
1896
-            $new_li_fields['LIN_quantity'] = $count;
1897
-        }
1898
-        // don't set the total. We'll leave that up to the code that calculates it
1899
-        unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent'], $new_li_fields['LIN_total']);
1900
-        return EE_Line_Item::new_instance($new_li_fields);
1901
-    }
1902
-
1903
-
1904
-    /**
1905
-     * Returns a modified line item tree where all the subtotals which have a total of 0
1906
-     * are removed, and line items with a quantity of 0
1907
-     *
1908
-     * @param EE_Line_Item $line_item |null
1909
-     * @return EE_Line_Item|null
1910
-     * @throws EE_Error
1911
-     * @throws InvalidArgumentException
1912
-     * @throws InvalidDataTypeException
1913
-     * @throws InvalidInterfaceException
1914
-     * @throws ReflectionException
1915
-     */
1916
-    public static function non_empty_line_items(EE_Line_Item $line_item)
1917
-    {
1918
-        $copied_li = EEH_Line_Item::non_empty_line_item($line_item);
1919
-        if ($copied_li === null) {
1920
-            return null;
1921
-        }
1922
-        // if this is an event subtotal, we want to only include it if it
1923
-        // has a non-zero total and at least one ticket line item child
1924
-        $ticket_children = 0;
1925
-        foreach ($line_item->children() as $child_li) {
1926
-            $child_li_copy = EEH_Line_Item::non_empty_line_items($child_li);
1927
-            if ($child_li_copy !== null) {
1928
-                $copied_li->add_child_line_item($child_li_copy);
1929
-                if (
1930
-                    $child_li_copy->type() === EEM_Line_Item::type_line_item &&
1931
-                    $child_li_copy->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1932
-                ) {
1933
-                    $ticket_children++;
1934
-                }
1935
-            }
1936
-        }
1937
-        // if this is an event subtotal with NO ticket children
1938
-        // we basically want to ignore it
1939
-        if (
1940
-            $ticket_children === 0
1941
-            && $line_item->type() === EEM_Line_Item::type_sub_total
1942
-            && $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
1943
-            && $line_item->total() === 0
1944
-        ) {
1945
-            return null;
1946
-        }
1947
-        return $copied_li;
1948
-    }
1949
-
1950
-
1951
-    /**
1952
-     * Creates a new, unsaved line item, but if it's a ticket line item
1953
-     * with a total of 0, or a subtotal of 0, returns null instead
1954
-     *
1955
-     * @param EE_Line_Item $line_item
1956
-     * @return EE_Line_Item
1957
-     * @throws EE_Error
1958
-     * @throws InvalidArgumentException
1959
-     * @throws InvalidDataTypeException
1960
-     * @throws InvalidInterfaceException
1961
-     * @throws ReflectionException
1962
-     */
1963
-    public static function non_empty_line_item(EE_Line_Item $line_item)
1964
-    {
1965
-        if (
1966
-            $line_item->type() === EEM_Line_Item::type_line_item
1967
-            && $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1968
-            && $line_item->quantity() === 0
1969
-        ) {
1970
-            return null;
1971
-        }
1972
-        $new_li_fields = $line_item->model_field_array();
1973
-        // don't set the total. We'll leave that up to the code that calculates it
1974
-        unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent']);
1975
-        return EE_Line_Item::new_instance($new_li_fields);
1976
-    }
1977
-
1978
-
1979
-    /**
1980
-     * Cycles through all of the ticket line items for the supplied total line item
1981
-     * and ensures that the line item's "is_taxable" field matches that of its corresponding ticket
1982
-     *
1983
-     * @param EE_Line_Item $total_line_item
1984
-     * @since 4.9.79.p
1985
-     * @throws EE_Error
1986
-     * @throws InvalidArgumentException
1987
-     * @throws InvalidDataTypeException
1988
-     * @throws InvalidInterfaceException
1989
-     * @throws ReflectionException
1990
-     */
1991
-    public static function resetIsTaxableForTickets(EE_Line_Item $total_line_item)
1992
-    {
1993
-        $ticket_line_items = self::get_ticket_line_items($total_line_item);
1994
-        foreach ($ticket_line_items as $ticket_line_item) {
1995
-            if (
1996
-                $ticket_line_item instanceof EE_Line_Item
1997
-                && $ticket_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1998
-            ) {
1999
-                $ticket = $ticket_line_item->ticket();
2000
-                if ($ticket instanceof EE_Ticket && $ticket->taxable() !== $ticket_line_item->is_taxable()) {
2001
-                    $ticket_line_item->set_is_taxable($ticket->taxable());
2002
-                    $ticket_line_item->save();
2003
-                }
2004
-            }
2005
-        }
2006
-    }
2007
-
2008
-
2009
-    /**
2010
-     * @return EE_Line_Item[]
2011
-     * @throws EE_Error
2012
-     * @throws ReflectionException
2013
-     * @since   5.0.0.p
2014
-     */
2015
-    private static function getGlobalTaxes(): array
2016
-    {
2017
-        if (EEH_Line_Item::$global_taxes === null) {
2018
-
2019
-            /** @type EEM_Price $EEM_Price */
2020
-            $EEM_Price = EE_Registry::instance()->load_model('Price');
2021
-            // get array of taxes via Price Model
2022
-            EEH_Line_Item::$global_taxes = $EEM_Price->get_all_prices_that_are_taxes();
2023
-            ksort(EEH_Line_Item::$global_taxes);
2024
-        }
2025
-        return EEH_Line_Item::$global_taxes;
2026
-    }
2027
-
2028
-
2029
-
2030
-    /**************************************** @DEPRECATED METHODS *************************************** */
2031
-    /**
2032
-     * @deprecated
2033
-     * @param EE_Line_Item $total_line_item
2034
-     * @return EE_Line_Item
2035
-     * @throws EE_Error
2036
-     * @throws InvalidArgumentException
2037
-     * @throws InvalidDataTypeException
2038
-     * @throws InvalidInterfaceException
2039
-     * @throws ReflectionException
2040
-     */
2041
-    public static function get_items_subtotal(EE_Line_Item $total_line_item)
2042
-    {
2043
-        EE_Error::doing_it_wrong(
2044
-            'EEH_Line_Item::get_items_subtotal()',
2045
-            sprintf(
2046
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2047
-                'EEH_Line_Item::get_pre_tax_subtotal()'
2048
-            ),
2049
-            '4.6.0'
2050
-        );
2051
-        return self::get_pre_tax_subtotal($total_line_item);
2052
-    }
2053
-
2054
-
2055
-    /**
2056
-     * @deprecated
2057
-     * @param EE_Transaction $transaction
2058
-     * @return EE_Line_Item
2059
-     * @throws EE_Error
2060
-     * @throws InvalidArgumentException
2061
-     * @throws InvalidDataTypeException
2062
-     * @throws InvalidInterfaceException
2063
-     * @throws ReflectionException
2064
-     */
2065
-    public static function create_default_total_line_item($transaction = null)
2066
-    {
2067
-        EE_Error::doing_it_wrong(
2068
-            'EEH_Line_Item::create_default_total_line_item()',
2069
-            sprintf(
2070
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2071
-                'EEH_Line_Item::create_total_line_item()'
2072
-            ),
2073
-            '4.6.0'
2074
-        );
2075
-        return self::create_total_line_item($transaction);
2076
-    }
2077
-
2078
-
2079
-    /**
2080
-     * @deprecated
2081
-     * @param EE_Line_Item   $total_line_item
2082
-     * @param EE_Transaction $transaction
2083
-     * @return EE_Line_Item
2084
-     * @throws EE_Error
2085
-     * @throws InvalidArgumentException
2086
-     * @throws InvalidDataTypeException
2087
-     * @throws InvalidInterfaceException
2088
-     * @throws ReflectionException
2089
-     */
2090
-    public static function create_default_tickets_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2091
-    {
2092
-        EE_Error::doing_it_wrong(
2093
-            'EEH_Line_Item::create_default_tickets_subtotal()',
2094
-            sprintf(
2095
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2096
-                'EEH_Line_Item::create_pre_tax_subtotal()'
2097
-            ),
2098
-            '4.6.0'
2099
-        );
2100
-        return self::create_pre_tax_subtotal($total_line_item, $transaction);
2101
-    }
2102
-
2103
-
2104
-    /**
2105
-     * @deprecated
2106
-     * @param EE_Line_Item   $total_line_item
2107
-     * @param EE_Transaction $transaction
2108
-     * @return EE_Line_Item
2109
-     * @throws EE_Error
2110
-     * @throws InvalidArgumentException
2111
-     * @throws InvalidDataTypeException
2112
-     * @throws InvalidInterfaceException
2113
-     * @throws ReflectionException
2114
-     */
2115
-    public static function create_default_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2116
-    {
2117
-        EE_Error::doing_it_wrong(
2118
-            'EEH_Line_Item::create_default_taxes_subtotal()',
2119
-            sprintf(
2120
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2121
-                'EEH_Line_Item::create_taxes_subtotal()'
2122
-            ),
2123
-            '4.6.0'
2124
-        );
2125
-        return self::create_taxes_subtotal($total_line_item, $transaction);
2126
-    }
2127
-
2128
-
2129
-    /**
2130
-     * @deprecated
2131
-     * @param EE_Line_Item   $total_line_item
2132
-     * @param EE_Transaction $transaction
2133
-     * @return EE_Line_Item
2134
-     * @throws EE_Error
2135
-     * @throws InvalidArgumentException
2136
-     * @throws InvalidDataTypeException
2137
-     * @throws InvalidInterfaceException
2138
-     * @throws ReflectionException
2139
-     */
2140
-    public static function create_default_event_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2141
-    {
2142
-        EE_Error::doing_it_wrong(
2143
-            'EEH_Line_Item::create_default_event_subtotal()',
2144
-            sprintf(
2145
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2146
-                'EEH_Line_Item::create_event_subtotal()'
2147
-            ),
2148
-            '4.6.0'
2149
-        );
2150
-        return self::create_event_subtotal($total_line_item, $transaction);
2151
-    }
24
+	/**
25
+	 * @var EE_Line_Item[]
26
+	 */
27
+	private static $global_taxes;
28
+
29
+
30
+	/**
31
+	 * Adds a simple item (unrelated to any other model object) to the provided PARENT line item.
32
+	 * Does NOT automatically re-calculate the line item totals or update the related transaction.
33
+	 * You should call recalculate_total_including_taxes() on the grant total line item after this
34
+	 * to update the subtotals, and EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
35
+	 * to keep the registration final prices in-sync with the transaction's total.
36
+	 *
37
+	 * @param EE_Line_Item $parent_line_item
38
+	 * @param string       $name
39
+	 * @param float        $unit_price
40
+	 * @param string       $description
41
+	 * @param int          $quantity
42
+	 * @param boolean      $taxable
43
+	 * @param string|null  $code if set to a value, ensures there is only one line item with that code
44
+	 * @param bool         $return_item
45
+	 * @param bool         $recalculate_totals
46
+	 * @return boolean|EE_Line_Item success
47
+	 * @throws EE_Error
48
+	 * @throws ReflectionException
49
+	 */
50
+	public static function add_unrelated_item(
51
+		EE_Line_Item $parent_line_item,
52
+		string $name,
53
+		float $unit_price,
54
+		string $description = '',
55
+		int $quantity = 1,
56
+		bool $taxable = false,
57
+		?string $code = null,
58
+		bool $return_item = false,
59
+		bool $recalculate_totals = true
60
+	) {
61
+		$items_subtotal = self::get_pre_tax_subtotal($parent_line_item);
62
+		$line_item      = EE_Line_Item::new_instance(
63
+			[
64
+				'LIN_name'       => $name,
65
+				'LIN_desc'       => $description,
66
+				'LIN_unit_price' => $unit_price,
67
+				'LIN_quantity'   => $quantity,
68
+				'LIN_percent'    => null,
69
+				'LIN_is_taxable' => $taxable,
70
+				'LIN_order'      => $items_subtotal instanceof EE_Line_Item
71
+					? count($items_subtotal->children())
72
+					: 0,
73
+				'LIN_total'      => (float) $unit_price * (int) $quantity,
74
+				'LIN_type'       => EEM_Line_Item::type_line_item,
75
+				'LIN_code'       => $code,
76
+			]
77
+		);
78
+		$line_item      = apply_filters(
79
+			'FHEE__EEH_Line_Item__add_unrelated_item__line_item',
80
+			$line_item,
81
+			$parent_line_item
82
+		);
83
+		$added          = self::add_item($parent_line_item, $line_item, $recalculate_totals);
84
+		return $return_item ? $line_item : $added;
85
+	}
86
+
87
+
88
+	/**
89
+	 * Adds a simple item ( unrelated to any other model object) to the total line item,
90
+	 * in the correct spot in the line item tree. Does not automatically
91
+	 * re-calculate the line item totals, nor update the related transaction, nor upgrade the transaction's
92
+	 * registrations' final prices (which should probably change because of this).
93
+	 * You should call recalculate_total_including_taxes() on the grand total line item, then
94
+	 * update the transaction's total, and EE_Registration_Processor::update_registration_final_prices()
95
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
96
+	 *
97
+	 * @param EE_Line_Item $parent_line_item
98
+	 * @param string       $name
99
+	 * @param float        $percentage_amount
100
+	 * @param string       $description
101
+	 * @param boolean      $taxable
102
+	 * @param string|null  $code
103
+	 * @param bool         $return_item
104
+	 * @return boolean|EE_Line_Item success
105
+	 * @throws EE_Error
106
+	 * @throws ReflectionException
107
+	 */
108
+	public static function add_percentage_based_item(
109
+		EE_Line_Item $parent_line_item,
110
+		string $name,
111
+		float $percentage_amount,
112
+		string $description = '',
113
+		bool $taxable = false,
114
+		?string $code = null,
115
+		bool $return_item = false
116
+	) {
117
+		$total = $percentage_amount * $parent_line_item->total() / 100;
118
+		$line_item = EE_Line_Item::new_instance(
119
+			[
120
+				'LIN_name'       => $name,
121
+				'LIN_desc'       => $description,
122
+				'LIN_unit_price' => 0,
123
+				'LIN_percent'    => $percentage_amount,
124
+				'LIN_quantity'   => 1,
125
+				'LIN_is_taxable' => $taxable,
126
+				'LIN_total'      => (float) $total,
127
+				'LIN_type'       => EEM_Line_Item::type_line_item,
128
+				'LIN_parent'     => $parent_line_item->ID(),
129
+				'LIN_code'       => $code,
130
+			]
131
+		);
132
+		$line_item = apply_filters(
133
+			'FHEE__EEH_Line_Item__add_percentage_based_item__line_item',
134
+			$line_item
135
+		);
136
+		$added     = $parent_line_item->add_child_line_item($line_item, false);
137
+		return $return_item ? $line_item : $added;
138
+	}
139
+
140
+
141
+	/**
142
+	 * Returns the new line item created by adding a purchase of the ticket
143
+	 * ensures that ticket line item is saved, and that cart total has been recalculated.
144
+	 * If this ticket has already been purchased, just increments its count.
145
+	 * Automatically re-calculates the line item totals and updates the related transaction. But
146
+	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
147
+	 * should probably change because of this).
148
+	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
149
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
150
+	 *
151
+	 * @param EE_Line_Item|null $total_line_item grand total line item of type EEM_Line_Item::type_total
152
+	 * @param EE_Ticket         $ticket
153
+	 * @param int               $qty
154
+	 * @return EE_Line_Item
155
+	 * @throws EE_Error
156
+	 * @throws ReflectionException
157
+	 */
158
+	public static function add_ticket_purchase(
159
+		?EE_Line_Item $total_line_item,
160
+		EE_Ticket $ticket,
161
+		int $qty = 1
162
+	): ?EE_Line_Item {
163
+		if (! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
164
+			throw new EE_Error(
165
+				sprintf(
166
+					esc_html__(
167
+						'A valid line item total is required in order to add tickets. A line item of type "%s" was passed.',
168
+						'event_espresso'
169
+					),
170
+					$ticket->ID(),
171
+					$total_line_item->ID()
172
+				)
173
+			);
174
+		}
175
+		// either increment the qty for an existing ticket
176
+		$line_item = self::increment_ticket_qty_if_already_in_cart($total_line_item, $ticket, $qty);
177
+		// or add a new one
178
+		if (! $line_item instanceof EE_Line_Item) {
179
+			$line_item = self::create_ticket_line_item($total_line_item, $ticket, $qty);
180
+		}
181
+		$total_line_item->recalculate_total_including_taxes();
182
+		return $line_item;
183
+	}
184
+
185
+
186
+	/**
187
+	 * Returns the new line item created by adding a purchase of the ticket
188
+	 *
189
+	 * @param EE_Line_Item $total_line_item
190
+	 * @param EE_Ticket    $ticket
191
+	 * @param int          $qty
192
+	 * @return EE_Line_Item
193
+	 * @throws EE_Error
194
+	 * @throws InvalidArgumentException
195
+	 * @throws InvalidDataTypeException
196
+	 * @throws InvalidInterfaceException
197
+	 * @throws ReflectionException
198
+	 */
199
+	public static function increment_ticket_qty_if_already_in_cart(
200
+		EE_Line_Item $total_line_item,
201
+		EE_Ticket $ticket,
202
+		$qty = 1
203
+	) {
204
+		$line_item = null;
205
+		if ($total_line_item instanceof EE_Line_Item && $total_line_item->is_total()) {
206
+			$ticket_line_items = EEH_Line_Item::get_ticket_line_items($total_line_item);
207
+			foreach ($ticket_line_items as $ticket_line_item) {
208
+				if (
209
+					$ticket_line_item instanceof EE_Line_Item
210
+					&& (int) $ticket_line_item->OBJ_ID() === (int) $ticket->ID()
211
+				) {
212
+					$line_item = $ticket_line_item;
213
+					break;
214
+				}
215
+			}
216
+		}
217
+		if ($line_item instanceof EE_Line_Item) {
218
+			EEH_Line_Item::increment_quantity($line_item, $qty);
219
+			return $line_item;
220
+		}
221
+		return null;
222
+	}
223
+
224
+
225
+	/**
226
+	 * Increments the line item and all its children's quantity by $qty (but percent line items are unaffected).
227
+	 * Does NOT save or recalculate other line items totals
228
+	 *
229
+	 * @param EE_Line_Item $line_item
230
+	 * @param int          $qty
231
+	 * @return void
232
+	 * @throws EE_Error
233
+	 * @throws InvalidArgumentException
234
+	 * @throws InvalidDataTypeException
235
+	 * @throws InvalidInterfaceException
236
+	 * @throws ReflectionException
237
+	 */
238
+	public static function increment_quantity(EE_Line_Item $line_item, $qty = 1)
239
+	{
240
+		if (! $line_item->is_percent()) {
241
+			$qty += $line_item->quantity();
242
+			$line_item->set_quantity($qty);
243
+			$line_item->set_total($line_item->unit_price() * $qty);
244
+			$line_item->save();
245
+		}
246
+		foreach ($line_item->children() as $child) {
247
+			if ($child->is_sub_line_item()) {
248
+				EEH_Line_Item::update_quantity($child, $qty);
249
+			}
250
+		}
251
+	}
252
+
253
+
254
+	/**
255
+	 * Decrements the line item and all its children's quantity by $qty (but percent line items are unaffected).
256
+	 * Does NOT save or recalculate other line items totals
257
+	 *
258
+	 * @param EE_Line_Item $line_item
259
+	 * @param int          $qty
260
+	 * @return void
261
+	 * @throws EE_Error
262
+	 * @throws InvalidArgumentException
263
+	 * @throws InvalidDataTypeException
264
+	 * @throws InvalidInterfaceException
265
+	 * @throws ReflectionException
266
+	 */
267
+	public static function decrement_quantity(EE_Line_Item $line_item, $qty = 1)
268
+	{
269
+		if (! $line_item->is_percent()) {
270
+			$qty = $line_item->quantity() - $qty;
271
+			$qty = max($qty, 0);
272
+			$line_item->set_quantity($qty);
273
+			$line_item->set_total($line_item->unit_price() * $qty);
274
+			$line_item->save();
275
+		}
276
+		foreach ($line_item->children() as $child) {
277
+			if ($child->is_sub_line_item()) {
278
+				EEH_Line_Item::update_quantity($child, $qty);
279
+			}
280
+		}
281
+	}
282
+
283
+
284
+	/**
285
+	 * Updates the line item and its children's quantities to the specified number.
286
+	 * Does NOT save them or recalculate totals.
287
+	 *
288
+	 * @param EE_Line_Item $line_item
289
+	 * @param int          $new_quantity
290
+	 * @throws EE_Error
291
+	 * @throws InvalidArgumentException
292
+	 * @throws InvalidDataTypeException
293
+	 * @throws InvalidInterfaceException
294
+	 * @throws ReflectionException
295
+	 */
296
+	public static function update_quantity(EE_Line_Item $line_item, $new_quantity)
297
+	{
298
+		if (! $line_item->is_percent()) {
299
+			$line_item->set_quantity($new_quantity);
300
+			$line_item->set_total($line_item->unit_price() * $new_quantity);
301
+			$line_item->save();
302
+		}
303
+		foreach ($line_item->children() as $child) {
304
+			if ($child->is_sub_line_item()) {
305
+				EEH_Line_Item::update_quantity($child, $new_quantity);
306
+			}
307
+		}
308
+	}
309
+
310
+
311
+	/**
312
+	 * Returns the new line item created by adding a purchase of the ticket
313
+	 *
314
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
315
+	 * @param EE_Ticket    $ticket
316
+	 * @param int          $qty
317
+	 * @return EE_Line_Item
318
+	 * @throws EE_Error
319
+	 * @throws InvalidArgumentException
320
+	 * @throws InvalidDataTypeException
321
+	 * @throws InvalidInterfaceException
322
+	 * @throws ReflectionException
323
+	 */
324
+	public static function create_ticket_line_item(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
325
+	{
326
+		$datetimes = $ticket->datetimes();
327
+		$first_datetime = reset($datetimes);
328
+		$first_datetime_name = esc_html__('Event', 'event_espresso');
329
+		if ($first_datetime instanceof EE_Datetime && $first_datetime->event() instanceof EE_Event) {
330
+			$first_datetime_name = $first_datetime->event()->name();
331
+		}
332
+		$event = sprintf(_x('(For %1$s)', '(For Event Name)', 'event_espresso'), $first_datetime_name);
333
+		// get event subtotal line
334
+		$events_sub_total = self::get_event_line_item_for_ticket($total_line_item, $ticket);
335
+		$taxes = $ticket->tax_price_modifiers();
336
+		// add $ticket to cart
337
+		$line_item = EE_Line_Item::new_instance(array(
338
+			'LIN_name'       => $ticket->name(),
339
+			'LIN_desc'       => $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
340
+			'LIN_unit_price' => $ticket->price(),
341
+			'LIN_quantity'   => $qty,
342
+			'LIN_is_taxable' => empty($taxes) && $ticket->taxable(),
343
+			'LIN_order'      => count($events_sub_total->children()),
344
+			'LIN_total'      => $ticket->price() * $qty,
345
+			'LIN_type'       => EEM_Line_Item::type_line_item,
346
+			'OBJ_ID'         => $ticket->ID(),
347
+			'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_TICKET,
348
+		));
349
+		$line_item = apply_filters(
350
+			'FHEE__EEH_Line_Item__create_ticket_line_item__line_item',
351
+			$line_item
352
+		);
353
+		if (!$line_item instanceof EE_Line_Item) {
354
+			throw new DomainException(
355
+				esc_html__('Invalid EE_Line_Item received.', 'event_espresso')
356
+			);
357
+		}
358
+		$events_sub_total->add_child_line_item($line_item);
359
+		// now add the sub-line items
360
+		$running_total = 0;
361
+		$running_pre_tax_total = 0;
362
+		foreach ($ticket->prices() as $price) {
363
+			$sign = $price->is_discount() ? -1 : 1;
364
+			$price_total = $price->is_percent()
365
+				? $running_pre_tax_total * $price->amount() / 100
366
+				: $price->amount() * $qty;
367
+			if ($price->is_percent()) {
368
+				$percent = $sign * $price->amount();
369
+				$unit_price = 0;
370
+			} else {
371
+				$percent    = 0;
372
+				$unit_price = $sign * $price->amount();
373
+			}
374
+			$sub_line_item = EE_Line_Item::new_instance(array(
375
+				'LIN_name'       => $price->name(),
376
+				'LIN_desc'       => $price->desc(),
377
+				'LIN_quantity'   => $price->is_percent() ? null : $qty,
378
+				'LIN_is_taxable' => false,
379
+				'LIN_order'      => $price->order(),
380
+				'LIN_total'      => $price_total,
381
+				'LIN_pretax'     => 0,
382
+				'LIN_unit_price' => $unit_price,
383
+				'LIN_percent'    => $percent,
384
+				'LIN_type'       => $price->is_tax() ? EEM_Line_Item::type_sub_tax : EEM_Line_Item::type_sub_line_item,
385
+				'OBJ_ID'         => $price->ID(),
386
+				'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
387
+			));
388
+			$sub_line_item = apply_filters(
389
+				'FHEE__EEH_Line_Item__create_ticket_line_item__sub_line_item',
390
+				$sub_line_item
391
+			);
392
+			$running_total += $sign * $price_total;
393
+			$running_pre_tax_total += ! $price->is_tax() ? $sign * $price_total : 0;
394
+			$line_item->add_child_line_item($sub_line_item);
395
+		}
396
+		$line_item->setPretaxTotal($running_pre_tax_total);
397
+		return $line_item;
398
+	}
399
+
400
+
401
+	/**
402
+	 * Adds the specified item under the pre-tax-sub-total line item. Automatically
403
+	 * re-calculates the line item totals and updates the related transaction. But
404
+	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
405
+	 * should probably change because of this).
406
+	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
407
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
408
+	 *
409
+	 * @param EE_Line_Item $total_line_item
410
+	 * @param EE_Line_Item $item to be added
411
+	 * @return boolean
412
+	 * @throws EE_Error
413
+	 * @throws InvalidArgumentException
414
+	 * @throws InvalidDataTypeException
415
+	 * @throws InvalidInterfaceException
416
+	 * @throws ReflectionException
417
+	 */
418
+	public static function add_item(EE_Line_Item $total_line_item, EE_Line_Item $item, $recalculate_totals = true)
419
+	{
420
+		$pre_tax_subtotal = self::get_pre_tax_subtotal($total_line_item);
421
+		if ($pre_tax_subtotal instanceof EE_Line_Item) {
422
+			$success = $pre_tax_subtotal->add_child_line_item($item);
423
+		} else {
424
+			return false;
425
+		}
426
+		if ($recalculate_totals) {
427
+			$total_line_item->recalculate_total_including_taxes();
428
+		}
429
+		return $success;
430
+	}
431
+
432
+
433
+	/**
434
+	 * cancels an existing ticket line item,
435
+	 * by decrementing it's quantity by 1 and adding a new "type_cancellation" sub-line-item.
436
+	 * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
437
+	 *
438
+	 * @param EE_Line_Item $ticket_line_item
439
+	 * @param int          $qty
440
+	 * @return bool success
441
+	 * @throws EE_Error
442
+	 * @throws InvalidArgumentException
443
+	 * @throws InvalidDataTypeException
444
+	 * @throws InvalidInterfaceException
445
+	 * @throws ReflectionException
446
+	 */
447
+	public static function cancel_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
448
+	{
449
+		// validate incoming line_item
450
+		if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
451
+			throw new EE_Error(
452
+				sprintf(
453
+					esc_html__(
454
+						'The supplied line item must have an Object Type of "Ticket", not %1$s.',
455
+						'event_espresso'
456
+					),
457
+					$ticket_line_item->type()
458
+				)
459
+			);
460
+		}
461
+		if ($ticket_line_item->quantity() < $qty) {
462
+			throw new EE_Error(
463
+				sprintf(
464
+					esc_html__(
465
+						'Can not cancel %1$d ticket(s) because the supplied line item has a quantity of %2$d.',
466
+						'event_espresso'
467
+					),
468
+					$qty,
469
+					$ticket_line_item->quantity()
470
+				)
471
+			);
472
+		}
473
+		// decrement ticket quantity; don't rely on auto-fixing when recalculating totals to do this
474
+		$ticket_line_item->set_quantity($ticket_line_item->quantity() - $qty);
475
+		foreach ($ticket_line_item->children() as $child_line_item) {
476
+			if (
477
+				$child_line_item->is_sub_line_item()
478
+				&& ! $child_line_item->is_percent()
479
+				&& ! $child_line_item->is_cancellation()
480
+			) {
481
+				$child_line_item->set_quantity($child_line_item->quantity() - $qty);
482
+			}
483
+		}
484
+		// get cancellation sub line item
485
+		$cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
486
+			$ticket_line_item,
487
+			EEM_Line_Item::type_cancellation
488
+		);
489
+		$cancellation_line_item = reset($cancellation_line_item);
490
+		// verify that this ticket was indeed previously cancelled
491
+		if ($cancellation_line_item instanceof EE_Line_Item) {
492
+			// increment cancelled quantity
493
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() + $qty);
494
+		} else {
495
+			// create cancellation sub line item
496
+			$cancellation_line_item = EE_Line_Item::new_instance(array(
497
+				'LIN_name'       => esc_html__('Cancellation', 'event_espresso'),
498
+				'LIN_desc'       => sprintf(
499
+					esc_html_x(
500
+						'Cancelled %1$s : %2$s',
501
+						'Cancelled Ticket Name : 2015-01-01 11:11',
502
+						'event_espresso'
503
+					),
504
+					$ticket_line_item->name(),
505
+					current_time(get_option('date_format') . ' ' . get_option('time_format'))
506
+				),
507
+				'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
508
+				'LIN_quantity'   => $qty,
509
+				'LIN_is_taxable' => $ticket_line_item->is_taxable(),
510
+				'LIN_order'      => count($ticket_line_item->children()),
511
+				'LIN_total'      => 0, // $ticket_line_item->unit_price()
512
+				'LIN_type'       => EEM_Line_Item::type_cancellation,
513
+			));
514
+			$ticket_line_item->add_child_line_item($cancellation_line_item);
515
+		}
516
+		if ($ticket_line_item->save_this_and_descendants() > 0) {
517
+			// decrement parent line item quantity
518
+			$event_line_item = $ticket_line_item->parent();
519
+			if (
520
+				$event_line_item instanceof EE_Line_Item
521
+				&& $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
522
+			) {
523
+				$event_line_item->set_quantity($event_line_item->quantity() - $qty);
524
+				$event_line_item->save();
525
+			}
526
+			EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
527
+			return true;
528
+		}
529
+		return false;
530
+	}
531
+
532
+
533
+	/**
534
+	 * reinstates (un-cancels?) a previously canceled ticket line item,
535
+	 * by incrementing it's quantity by 1, and decrementing it's "type_cancellation" sub-line-item.
536
+	 * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
537
+	 *
538
+	 * @param EE_Line_Item $ticket_line_item
539
+	 * @param int          $qty
540
+	 * @return bool success
541
+	 * @throws EE_Error
542
+	 * @throws InvalidArgumentException
543
+	 * @throws InvalidDataTypeException
544
+	 * @throws InvalidInterfaceException
545
+	 * @throws ReflectionException
546
+	 */
547
+	public static function reinstate_canceled_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
548
+	{
549
+		// validate incoming line_item
550
+		if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
551
+			throw new EE_Error(
552
+				sprintf(
553
+					esc_html__(
554
+						'The supplied line item must have an Object Type of "Ticket", not %1$s.',
555
+						'event_espresso'
556
+					),
557
+					$ticket_line_item->type()
558
+				)
559
+			);
560
+		}
561
+		// get cancellation sub line item
562
+		$cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
563
+			$ticket_line_item,
564
+			EEM_Line_Item::type_cancellation
565
+		);
566
+		$cancellation_line_item = reset($cancellation_line_item);
567
+		// verify that this ticket was indeed previously cancelled
568
+		if (! $cancellation_line_item instanceof EE_Line_Item) {
569
+			return false;
570
+		}
571
+		if ($cancellation_line_item->quantity() > $qty) {
572
+			// decrement cancelled quantity
573
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
574
+		} elseif ($cancellation_line_item->quantity() === $qty) {
575
+			// decrement cancelled quantity in case anyone still has the object kicking around
576
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
577
+			// delete because quantity will end up as 0
578
+			$cancellation_line_item->delete();
579
+			// and attempt to destroy the object,
580
+			// even though PHP won't actually destroy it until it needs the memory
581
+			unset($cancellation_line_item);
582
+		} else {
583
+			// what ?!?! negative quantity ?!?!
584
+			throw new EE_Error(
585
+				sprintf(
586
+					esc_html__(
587
+						'Can not reinstate %1$d cancelled ticket(s) because the cancelled ticket quantity is only %2$d.',
588
+						'event_espresso'
589
+					),
590
+					$qty,
591
+					$cancellation_line_item->quantity()
592
+				)
593
+			);
594
+		}
595
+		// increment ticket quantity
596
+		$ticket_line_item->set_quantity($ticket_line_item->quantity() + $qty);
597
+		if ($ticket_line_item->save_this_and_descendants() > 0) {
598
+			// increment parent line item quantity
599
+			$event_line_item = $ticket_line_item->parent();
600
+			if (
601
+				$event_line_item instanceof EE_Line_Item
602
+				&& $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
603
+			) {
604
+				$event_line_item->set_quantity($event_line_item->quantity() + $qty);
605
+			}
606
+			EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
607
+			return true;
608
+		}
609
+		return false;
610
+	}
611
+
612
+
613
+	/**
614
+	 * calls EEH_Line_Item::find_transaction_grand_total_for_line_item()
615
+	 * then EE_Line_Item::recalculate_total_including_taxes() on the result
616
+	 *
617
+	 * @param EE_Line_Item $line_item
618
+	 * @return float
619
+	 * @throws EE_Error
620
+	 * @throws InvalidArgumentException
621
+	 * @throws InvalidDataTypeException
622
+	 * @throws InvalidInterfaceException
623
+	 * @throws ReflectionException
624
+	 */
625
+	public static function get_grand_total_and_recalculate_everything(EE_Line_Item $line_item)
626
+	{
627
+		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item);
628
+		return $grand_total_line_item->recalculate_total_including_taxes();
629
+	}
630
+
631
+
632
+	/**
633
+	 * Gets the line item which contains the subtotal of all the items
634
+	 *
635
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
636
+	 * @return EE_Line_Item
637
+	 * @throws EE_Error
638
+	 * @throws InvalidArgumentException
639
+	 * @throws InvalidDataTypeException
640
+	 * @throws InvalidInterfaceException
641
+	 * @throws ReflectionException
642
+	 */
643
+	public static function get_pre_tax_subtotal(EE_Line_Item $total_line_item)
644
+	{
645
+		$pre_tax_subtotal = $total_line_item->get_child_line_item('pre-tax-subtotal');
646
+		return $pre_tax_subtotal instanceof EE_Line_Item
647
+			? $pre_tax_subtotal
648
+			: self::create_pre_tax_subtotal($total_line_item);
649
+	}
650
+
651
+
652
+	/**
653
+	 * Gets the line item for the taxes subtotal
654
+	 *
655
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
656
+	 * @return EE_Line_Item
657
+	 * @throws EE_Error
658
+	 * @throws InvalidArgumentException
659
+	 * @throws InvalidDataTypeException
660
+	 * @throws InvalidInterfaceException
661
+	 * @throws ReflectionException
662
+	 */
663
+	public static function get_taxes_subtotal(EE_Line_Item $total_line_item)
664
+	{
665
+		$taxes = $total_line_item->get_child_line_item('taxes');
666
+		return $taxes ? $taxes : self::create_taxes_subtotal($total_line_item);
667
+	}
668
+
669
+
670
+	/**
671
+	 * sets the TXN ID on an EE_Line_Item if passed a valid EE_Transaction object
672
+	 *
673
+	 * @param EE_Line_Item   $line_item
674
+	 * @param EE_Transaction $transaction
675
+	 * @return void
676
+	 * @throws EE_Error
677
+	 * @throws InvalidArgumentException
678
+	 * @throws InvalidDataTypeException
679
+	 * @throws InvalidInterfaceException
680
+	 * @throws ReflectionException
681
+	 */
682
+	public static function set_TXN_ID(EE_Line_Item $line_item, $transaction = null)
683
+	{
684
+		if ($transaction) {
685
+			/** @type EEM_Transaction $EEM_Transaction */
686
+			$EEM_Transaction = EE_Registry::instance()->load_model('Transaction');
687
+			$TXN_ID = $EEM_Transaction->ensure_is_ID($transaction);
688
+			$line_item->set_TXN_ID($TXN_ID);
689
+		}
690
+	}
691
+
692
+
693
+	/**
694
+	 * Creates a new default total line item for the transaction,
695
+	 * and its tickets subtotal and taxes subtotal line items (and adds the
696
+	 * existing taxes as children of the taxes subtotal line item)
697
+	 *
698
+	 * @param EE_Transaction $transaction
699
+	 * @return EE_Line_Item of type total
700
+	 * @throws EE_Error
701
+	 * @throws InvalidArgumentException
702
+	 * @throws InvalidDataTypeException
703
+	 * @throws InvalidInterfaceException
704
+	 * @throws ReflectionException
705
+	 */
706
+	public static function create_total_line_item($transaction = null)
707
+	{
708
+		$total_line_item = EE_Line_Item::new_instance(array(
709
+			'LIN_code' => 'total',
710
+			'LIN_name' => esc_html__('Grand Total', 'event_espresso'),
711
+			'LIN_type' => EEM_Line_Item::type_total,
712
+			'OBJ_type' => EEM_Line_Item::OBJ_TYPE_TRANSACTION,
713
+		));
714
+		$total_line_item = apply_filters(
715
+			'FHEE__EEH_Line_Item__create_total_line_item__total_line_item',
716
+			$total_line_item
717
+		);
718
+		self::set_TXN_ID($total_line_item, $transaction);
719
+		self::create_pre_tax_subtotal($total_line_item, $transaction);
720
+		self::create_taxes_subtotal($total_line_item, $transaction);
721
+		return $total_line_item;
722
+	}
723
+
724
+
725
+	/**
726
+	 * Creates a default items subtotal line item
727
+	 *
728
+	 * @param EE_Line_Item   $total_line_item
729
+	 * @param EE_Transaction $transaction
730
+	 * @return EE_Line_Item
731
+	 * @throws EE_Error
732
+	 * @throws InvalidArgumentException
733
+	 * @throws InvalidDataTypeException
734
+	 * @throws InvalidInterfaceException
735
+	 * @throws ReflectionException
736
+	 */
737
+	protected static function create_pre_tax_subtotal(EE_Line_Item $total_line_item, $transaction = null)
738
+	{
739
+		$pre_tax_line_item = EE_Line_Item::new_instance(array(
740
+			'LIN_code' => 'pre-tax-subtotal',
741
+			'LIN_name' => esc_html__('Pre-Tax Subtotal', 'event_espresso'),
742
+			'LIN_type' => EEM_Line_Item::type_sub_total,
743
+		));
744
+		$pre_tax_line_item = apply_filters(
745
+			'FHEE__EEH_Line_Item__create_pre_tax_subtotal__pre_tax_line_item',
746
+			$pre_tax_line_item
747
+		);
748
+		self::set_TXN_ID($pre_tax_line_item, $transaction);
749
+		$total_line_item->add_child_line_item($pre_tax_line_item);
750
+		self::create_event_subtotal($pre_tax_line_item, $transaction);
751
+		return $pre_tax_line_item;
752
+	}
753
+
754
+
755
+	/**
756
+	 * Creates a line item for the taxes subtotal and finds all the tax prices
757
+	 * and applies taxes to it
758
+	 *
759
+	 * @param EE_Line_Item   $total_line_item of type EEM_Line_Item::type_total
760
+	 * @param EE_Transaction $transaction
761
+	 * @return EE_Line_Item
762
+	 * @throws EE_Error
763
+	 * @throws InvalidArgumentException
764
+	 * @throws InvalidDataTypeException
765
+	 * @throws InvalidInterfaceException
766
+	 * @throws ReflectionException
767
+	 */
768
+	protected static function create_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
769
+	{
770
+		$tax_line_item = EE_Line_Item::new_instance(array(
771
+			'LIN_code'  => 'taxes',
772
+			'LIN_name'  => esc_html__('Taxes', 'event_espresso'),
773
+			'LIN_type'  => EEM_Line_Item::type_tax_sub_total,
774
+			'LIN_order' => 1000,// this should always come last
775
+		));
776
+		$tax_line_item = apply_filters(
777
+			'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
778
+			$tax_line_item
779
+		);
780
+		self::set_TXN_ID($tax_line_item, $transaction);
781
+		$total_line_item->add_child_line_item($tax_line_item);
782
+		// and lastly, add the actual taxes
783
+		self::apply_taxes($total_line_item);
784
+		return $tax_line_item;
785
+	}
786
+
787
+
788
+	/**
789
+	 * Creates a default items subtotal line item
790
+	 *
791
+	 * @param EE_Line_Item   $pre_tax_line_item
792
+	 * @param EE_Transaction $transaction
793
+	 * @param EE_Event       $event
794
+	 * @return EE_Line_Item
795
+	 * @throws EE_Error
796
+	 * @throws InvalidArgumentException
797
+	 * @throws InvalidDataTypeException
798
+	 * @throws InvalidInterfaceException
799
+	 * @throws ReflectionException
800
+	 */
801
+	public static function create_event_subtotal(EE_Line_Item $pre_tax_line_item, $transaction = null, $event = null)
802
+	{
803
+		$event_line_item = EE_Line_Item::new_instance(array(
804
+			'LIN_code' => self::get_event_code($event),
805
+			'LIN_name' => self::get_event_name($event),
806
+			'LIN_desc' => self::get_event_desc($event),
807
+			'LIN_type' => EEM_Line_Item::type_sub_total,
808
+			'OBJ_type' => EEM_Line_Item::OBJ_TYPE_EVENT,
809
+			'OBJ_ID'   => $event instanceof EE_Event ? $event->ID() : 0,
810
+		));
811
+		$event_line_item = apply_filters(
812
+			'FHEE__EEH_Line_Item__create_event_subtotal__event_line_item',
813
+			$event_line_item
814
+		);
815
+		self::set_TXN_ID($event_line_item, $transaction);
816
+		$pre_tax_line_item->add_child_line_item($event_line_item);
817
+		return $event_line_item;
818
+	}
819
+
820
+
821
+	/**
822
+	 * Gets what the event ticket's code SHOULD be
823
+	 *
824
+	 * @param EE_Event $event
825
+	 * @return string
826
+	 * @throws EE_Error
827
+	 */
828
+	public static function get_event_code($event)
829
+	{
830
+		return 'event-' . ($event instanceof EE_Event ? $event->ID() : '0');
831
+	}
832
+
833
+
834
+	/**
835
+	 * Gets the event name
836
+	 *
837
+	 * @param EE_Event $event
838
+	 * @return string
839
+	 * @throws EE_Error
840
+	 */
841
+	public static function get_event_name($event)
842
+	{
843
+		return $event instanceof EE_Event
844
+			? mb_substr($event->name(), 0, 245)
845
+			: esc_html__('Event', 'event_espresso');
846
+	}
847
+
848
+
849
+	/**
850
+	 * Gets the event excerpt
851
+	 *
852
+	 * @param EE_Event $event
853
+	 * @return string
854
+	 * @throws EE_Error
855
+	 */
856
+	public static function get_event_desc($event)
857
+	{
858
+		return $event instanceof EE_Event ? $event->short_description() : '';
859
+	}
860
+
861
+
862
+	/**
863
+	 * Given the grand total line item and a ticket, finds the event sub-total
864
+	 * line item the ticket's purchase should be added onto
865
+	 *
866
+	 * @access public
867
+	 * @param EE_Line_Item $grand_total the grand total line item
868
+	 * @param EE_Ticket    $ticket
869
+	 * @return EE_Line_Item
870
+	 * @throws EE_Error
871
+	 * @throws InvalidArgumentException
872
+	 * @throws InvalidDataTypeException
873
+	 * @throws InvalidInterfaceException
874
+	 * @throws ReflectionException
875
+	 */
876
+	public static function get_event_line_item_for_ticket(EE_Line_Item $grand_total, EE_Ticket $ticket)
877
+	{
878
+		$first_datetime = $ticket->first_datetime();
879
+		if (! $first_datetime instanceof EE_Datetime) {
880
+			throw new EE_Error(
881
+				sprintf(
882
+					esc_html__('The supplied ticket (ID %d) has no datetimes', 'event_espresso'),
883
+					$ticket->ID()
884
+				)
885
+			);
886
+		}
887
+		$event = $first_datetime->event();
888
+		if (! $event instanceof EE_Event) {
889
+			throw new EE_Error(
890
+				sprintf(
891
+					esc_html__(
892
+						'The supplied ticket (ID %d) has no event data associated with it.',
893
+						'event_espresso'
894
+					),
895
+					$ticket->ID()
896
+				)
897
+			);
898
+		}
899
+		$events_sub_total = EEH_Line_Item::get_event_line_item($grand_total, $event);
900
+		if (! $events_sub_total instanceof EE_Line_Item) {
901
+			throw new EE_Error(
902
+				sprintf(
903
+					esc_html__(
904
+						'There is no events sub-total for ticket %s on total line item %d',
905
+						'event_espresso'
906
+					),
907
+					$ticket->ID(),
908
+					$grand_total->ID()
909
+				)
910
+			);
911
+		}
912
+		return $events_sub_total;
913
+	}
914
+
915
+
916
+	/**
917
+	 * Gets the event line item
918
+	 *
919
+	 * @param EE_Line_Item $grand_total
920
+	 * @param EE_Event     $event
921
+	 * @return EE_Line_Item for the event subtotal which is a child of $grand_total
922
+	 * @throws EE_Error
923
+	 * @throws InvalidArgumentException
924
+	 * @throws InvalidDataTypeException
925
+	 * @throws InvalidInterfaceException
926
+	 * @throws ReflectionException
927
+	 */
928
+	public static function get_event_line_item(EE_Line_Item $grand_total, $event)
929
+	{
930
+		/** @type EE_Event $event */
931
+		$event = EEM_Event::instance()->ensure_is_obj($event, true);
932
+		$event_line_item = null;
933
+		$found = false;
934
+		foreach (EEH_Line_Item::get_event_subtotals($grand_total) as $event_line_item) {
935
+			// default event subtotal, we should only ever find this the first time this method is called
936
+			if (! $event_line_item->OBJ_ID()) {
937
+				// let's use this! but first... set the event details
938
+				EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
939
+				$found = true;
940
+				break;
941
+			}
942
+			if ($event_line_item->OBJ_ID() === $event->ID()) {
943
+				// found existing line item for this event in the cart, so break out of loop and use this one
944
+				$found = true;
945
+				break;
946
+			}
947
+		}
948
+		if (! $found) {
949
+			// there is no event sub-total yet, so add it
950
+			$pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal($grand_total);
951
+			// create a new "event" subtotal below that
952
+			$event_line_item = EEH_Line_Item::create_event_subtotal($pre_tax_subtotal, null, $event);
953
+			// and set the event details
954
+			EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
955
+		}
956
+		return $event_line_item;
957
+	}
958
+
959
+
960
+	/**
961
+	 * Creates a default items subtotal line item
962
+	 *
963
+	 * @param EE_Line_Item   $event_line_item
964
+	 * @param EE_Event       $event
965
+	 * @param EE_Transaction $transaction
966
+	 * @return void
967
+	 * @throws EE_Error
968
+	 * @throws InvalidArgumentException
969
+	 * @throws InvalidDataTypeException
970
+	 * @throws InvalidInterfaceException
971
+	 * @throws ReflectionException
972
+	 */
973
+	public static function set_event_subtotal_details(
974
+		EE_Line_Item $event_line_item,
975
+		EE_Event $event,
976
+		$transaction = null
977
+	) {
978
+		if ($event instanceof EE_Event) {
979
+			$event_line_item->set_code(self::get_event_code($event));
980
+			$event_line_item->set_name(self::get_event_name($event));
981
+			$event_line_item->set_desc(self::get_event_desc($event));
982
+			$event_line_item->set_OBJ_ID($event->ID());
983
+		}
984
+		self::set_TXN_ID($event_line_item, $transaction);
985
+	}
986
+
987
+
988
+	/**
989
+	 * Finds what taxes should apply, adds them as tax line items under the taxes sub-total,
990
+	 * and recalculates the taxes sub-total and the grand total. Resets the taxes, so
991
+	 * any old taxes are removed
992
+	 *
993
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
994
+	 * @param bool         $update_txn_status
995
+	 * @return bool
996
+	 * @throws EE_Error
997
+	 * @throws InvalidArgumentException
998
+	 * @throws InvalidDataTypeException
999
+	 * @throws InvalidInterfaceException
1000
+	 * @throws ReflectionException
1001
+	 * @throws RuntimeException
1002
+	 */
1003
+	public static function apply_taxes(EE_Line_Item $total_line_item, $update_txn_status = false)
1004
+	{
1005
+		$total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($total_line_item);
1006
+		$taxes_line_item = self::get_taxes_subtotal($total_line_item);
1007
+		$existing_global_taxes = $taxes_line_item->tax_descendants();
1008
+		$updates = false;
1009
+		// loop thru taxes
1010
+		$global_taxes = EEH_Line_Item::getGlobalTaxes();
1011
+		foreach ($global_taxes as $order => $taxes) {
1012
+			foreach ($taxes as $tax) {
1013
+				if ($tax instanceof EE_Price) {
1014
+					$found = false;
1015
+					// check if this is already an existing tax
1016
+					foreach ($existing_global_taxes as $existing_global_tax) {
1017
+						if ($tax->ID() === $existing_global_tax->OBJ_ID()) {
1018
+							// maybe update the tax rate in case it has changed
1019
+							if ($existing_global_tax->percent() !== $tax->amount()) {
1020
+								$existing_global_tax->set_percent($tax->amount());
1021
+								$existing_global_tax->save();
1022
+								$updates = true;
1023
+							}
1024
+							$found = true;
1025
+							break;
1026
+						}
1027
+					}
1028
+					if (! $found) {
1029
+						// add a new line item for this global tax
1030
+						$tax_line_item = apply_filters(
1031
+							'FHEE__EEH_Line_Item__apply_taxes__tax_line_item',
1032
+							EE_Line_Item::new_instance(
1033
+								[
1034
+									'LIN_name'       => $tax->name(),
1035
+									'LIN_desc'       => $tax->desc(),
1036
+									'LIN_percent'    => $tax->amount(),
1037
+									'LIN_is_taxable' => false,
1038
+									'LIN_order'      => $order,
1039
+									'LIN_total'      => 0,
1040
+									'LIN_type'       => EEM_Line_Item::type_tax,
1041
+									'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
1042
+									'OBJ_ID'         => $tax->ID(),
1043
+								]
1044
+							)
1045
+						);
1046
+						$updates = $taxes_line_item->add_child_line_item($tax_line_item) ? true : $updates;
1047
+					}
1048
+				}
1049
+			}
1050
+		}
1051
+		// only recalculate totals if something changed
1052
+		if ($updates || $update_txn_status) {
1053
+			$total_line_item->recalculate_total_including_taxes($update_txn_status);
1054
+			return true;
1055
+		}
1056
+		return false;
1057
+	}
1058
+
1059
+
1060
+	/**
1061
+	 * Ensures that taxes have been applied to the order, if not applies them.
1062
+	 * Returns the total amount of tax
1063
+	 *
1064
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
1065
+	 * @return float
1066
+	 * @throws EE_Error
1067
+	 * @throws InvalidArgumentException
1068
+	 * @throws InvalidDataTypeException
1069
+	 * @throws InvalidInterfaceException
1070
+	 * @throws ReflectionException
1071
+	 */
1072
+	public static function ensure_taxes_applied($total_line_item)
1073
+	{
1074
+		$taxes_subtotal = self::get_taxes_subtotal($total_line_item);
1075
+		if (! $taxes_subtotal->children()) {
1076
+			self::apply_taxes($total_line_item);
1077
+		}
1078
+		return $taxes_subtotal->total();
1079
+	}
1080
+
1081
+
1082
+	/**
1083
+	 * Deletes ALL children of the passed line item
1084
+	 *
1085
+	 * @param EE_Line_Item $parent_line_item
1086
+	 * @return bool
1087
+	 * @throws EE_Error
1088
+	 * @throws InvalidArgumentException
1089
+	 * @throws InvalidDataTypeException
1090
+	 * @throws InvalidInterfaceException
1091
+	 * @throws ReflectionException
1092
+	 */
1093
+	public static function delete_all_child_items(EE_Line_Item $parent_line_item)
1094
+	{
1095
+		$deleted = 0;
1096
+		foreach ($parent_line_item->children() as $child_line_item) {
1097
+			if ($child_line_item instanceof EE_Line_Item) {
1098
+				$deleted += EEH_Line_Item::delete_all_child_items($child_line_item);
1099
+				if ($child_line_item->ID()) {
1100
+					$child_line_item->delete();
1101
+					unset($child_line_item);
1102
+				} else {
1103
+					$parent_line_item->delete_child_line_item($child_line_item->code());
1104
+				}
1105
+				$deleted++;
1106
+			}
1107
+		}
1108
+		return $deleted;
1109
+	}
1110
+
1111
+
1112
+	/**
1113
+	 * Deletes the line items as indicated by the line item code(s) provided,
1114
+	 * regardless of where they're found in the line item tree. Automatically
1115
+	 * re-calculates the line item totals and updates the related transaction. But
1116
+	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
1117
+	 * should probably change because of this).
1118
+	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
1119
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
1120
+	 *
1121
+	 * @param EE_Line_Item      $total_line_item of type EEM_Line_Item::type_total
1122
+	 * @param array|bool|string $line_item_codes
1123
+	 * @return int number of items successfully removed
1124
+	 * @throws EE_Error
1125
+	 */
1126
+	public static function delete_items(EE_Line_Item $total_line_item, $line_item_codes = false)
1127
+	{
1128
+
1129
+		if ($total_line_item->type() !== EEM_Line_Item::type_total) {
1130
+			EE_Error::doing_it_wrong(
1131
+				'EEH_Line_Item::delete_items',
1132
+				esc_html__(
1133
+					'This static method should only be called with a TOTAL line item, otherwise we won\'t recalculate the totals correctly',
1134
+					'event_espresso'
1135
+				),
1136
+				'4.6.18'
1137
+			);
1138
+		}
1139
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1140
+
1141
+		// check if only a single line_item_id was passed
1142
+		if (! empty($line_item_codes) && ! is_array($line_item_codes)) {
1143
+			// place single line_item_id in an array to appear as multiple line_item_ids
1144
+			$line_item_codes = array($line_item_codes);
1145
+		}
1146
+		$removals = 0;
1147
+		// cycle thru line_item_ids
1148
+		foreach ($line_item_codes as $line_item_id) {
1149
+			$removals += $total_line_item->delete_child_line_item($line_item_id);
1150
+		}
1151
+
1152
+		if ($removals > 0) {
1153
+			$total_line_item->recalculate_taxes_and_tax_total();
1154
+			return $removals;
1155
+		} else {
1156
+			return false;
1157
+		}
1158
+	}
1159
+
1160
+
1161
+	/**
1162
+	 * Overwrites the previous tax by clearing out the old taxes, and creates a new
1163
+	 * tax and updates the total line item accordingly
1164
+	 *
1165
+	 * @param EE_Line_Item $total_line_item
1166
+	 * @param float        $amount
1167
+	 * @param string       $name
1168
+	 * @param string       $description
1169
+	 * @param string       $code
1170
+	 * @param boolean      $add_to_existing_line_item
1171
+	 *                          if true, and a duplicate line item with the same code is found,
1172
+	 *                          $amount will be added onto it; otherwise will simply set the taxes to match $amount
1173
+	 * @return EE_Line_Item the new tax line item created
1174
+	 * @throws EE_Error
1175
+	 * @throws InvalidArgumentException
1176
+	 * @throws InvalidDataTypeException
1177
+	 * @throws InvalidInterfaceException
1178
+	 * @throws ReflectionException
1179
+	 */
1180
+	public static function set_total_tax_to(
1181
+		EE_Line_Item $total_line_item,
1182
+		$amount,
1183
+		$name = null,
1184
+		$description = null,
1185
+		$code = null,
1186
+		$add_to_existing_line_item = false
1187
+	) {
1188
+		$tax_subtotal = self::get_taxes_subtotal($total_line_item);
1189
+		$taxable_total = $total_line_item->taxable_total();
1190
+
1191
+		if ($add_to_existing_line_item) {
1192
+			$new_tax = $tax_subtotal->get_child_line_item($code);
1193
+			EEM_Line_Item::instance()->delete(
1194
+				array(array('LIN_code' => array('!=', $code), 'LIN_parent' => $tax_subtotal->ID()))
1195
+			);
1196
+		} else {
1197
+			$new_tax = null;
1198
+			$tax_subtotal->delete_children_line_items();
1199
+		}
1200
+		if ($new_tax) {
1201
+			$new_tax->set_total($new_tax->total() + $amount);
1202
+			$new_tax->set_percent($taxable_total ? $new_tax->total() / $taxable_total * 100 : 0);
1203
+		} else {
1204
+			// no existing tax item. Create it
1205
+			$new_tax = EE_Line_Item::new_instance(array(
1206
+				'TXN_ID'      => $total_line_item->TXN_ID(),
1207
+				'LIN_name'    => $name ?: esc_html__('Tax', 'event_espresso'),
1208
+				'LIN_desc'    => $description ?: '',
1209
+				'LIN_percent' => $taxable_total ? ($amount / $taxable_total * 100) : 0,
1210
+				'LIN_total'   => $amount,
1211
+				'LIN_parent'  => $tax_subtotal->ID(),
1212
+				'LIN_type'    => EEM_Line_Item::type_tax,
1213
+				'LIN_code'    => $code,
1214
+			));
1215
+		}
1216
+
1217
+		$new_tax = apply_filters(
1218
+			'FHEE__EEH_Line_Item__set_total_tax_to__new_tax_subtotal',
1219
+			$new_tax,
1220
+			$total_line_item
1221
+		);
1222
+		$new_tax->save();
1223
+		$tax_subtotal->set_total($new_tax->total());
1224
+		$tax_subtotal->save();
1225
+		$total_line_item->recalculate_total_including_taxes();
1226
+		return $new_tax;
1227
+	}
1228
+
1229
+
1230
+	/**
1231
+	 * Makes all the line items which are children of $line_item taxable (or not).
1232
+	 * Does NOT save the line items
1233
+	 *
1234
+	 * @param EE_Line_Item $line_item
1235
+	 * @param boolean      $taxable
1236
+	 * @param string       $code_substring_for_whitelist if this string is part of the line item's code
1237
+	 *                                                   it will be whitelisted (ie, except from becoming taxable)
1238
+	 * @throws EE_Error
1239
+	 */
1240
+	public static function set_line_items_taxable(
1241
+		EE_Line_Item $line_item,
1242
+		$taxable = true,
1243
+		$code_substring_for_whitelist = null
1244
+	) {
1245
+		$whitelisted = false;
1246
+		if ($code_substring_for_whitelist !== null) {
1247
+			$whitelisted = strpos($line_item->code(), $code_substring_for_whitelist) !== false;
1248
+		}
1249
+		if (! $whitelisted && $line_item->is_line_item()) {
1250
+			$line_item->set_is_taxable($taxable);
1251
+		}
1252
+		foreach ($line_item->children() as $child_line_item) {
1253
+			EEH_Line_Item::set_line_items_taxable(
1254
+				$child_line_item,
1255
+				$taxable,
1256
+				$code_substring_for_whitelist
1257
+			);
1258
+		}
1259
+	}
1260
+
1261
+
1262
+	/**
1263
+	 * Gets all descendants that are event subtotals
1264
+	 *
1265
+	 * @uses  EEH_Line_Item::get_subtotals_of_object_type()
1266
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1267
+	 * @return EE_Line_Item[]
1268
+	 * @throws EE_Error
1269
+	 */
1270
+	public static function get_event_subtotals(EE_Line_Item $parent_line_item)
1271
+	{
1272
+		return self::get_subtotals_of_object_type($parent_line_item, EEM_Line_Item::OBJ_TYPE_EVENT);
1273
+	}
1274
+
1275
+
1276
+	/**
1277
+	 * Gets all descendants subtotals that match the supplied object type
1278
+	 *
1279
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1280
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1281
+	 * @param string       $obj_type
1282
+	 * @return EE_Line_Item[]
1283
+	 * @throws EE_Error
1284
+	 */
1285
+	public static function get_subtotals_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1286
+	{
1287
+		return self::_get_descendants_by_type_and_object_type(
1288
+			$parent_line_item,
1289
+			EEM_Line_Item::type_sub_total,
1290
+			$obj_type
1291
+		);
1292
+	}
1293
+
1294
+
1295
+	/**
1296
+	 * Gets all descendants that are tickets
1297
+	 *
1298
+	 * @uses  EEH_Line_Item::get_line_items_of_object_type()
1299
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1300
+	 * @return EE_Line_Item[]
1301
+	 * @throws EE_Error
1302
+	 */
1303
+	public static function get_ticket_line_items(EE_Line_Item $parent_line_item)
1304
+	{
1305
+		return self::get_line_items_of_object_type(
1306
+			$parent_line_item,
1307
+			EEM_Line_Item::OBJ_TYPE_TICKET
1308
+		);
1309
+	}
1310
+
1311
+
1312
+	/**
1313
+	 * Gets all descendants subtotals that match the supplied object type
1314
+	 *
1315
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1316
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1317
+	 * @param string       $obj_type
1318
+	 * @return EE_Line_Item[]
1319
+	 * @throws EE_Error
1320
+	 */
1321
+	public static function get_line_items_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1322
+	{
1323
+		return self::_get_descendants_by_type_and_object_type(
1324
+			$parent_line_item,
1325
+			EEM_Line_Item::type_line_item,
1326
+			$obj_type
1327
+		);
1328
+	}
1329
+
1330
+
1331
+	/**
1332
+	 * Gets all the descendants (ie, children or children of children etc) that are of the type 'tax'
1333
+	 *
1334
+	 * @uses  EEH_Line_Item::get_descendants_of_type()
1335
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1336
+	 * @return EE_Line_Item[]
1337
+	 * @throws EE_Error
1338
+	 */
1339
+	public static function get_tax_descendants(EE_Line_Item $parent_line_item)
1340
+	{
1341
+		return EEH_Line_Item::get_descendants_of_type(
1342
+			$parent_line_item,
1343
+			EEM_Line_Item::type_tax
1344
+		);
1345
+	}
1346
+
1347
+
1348
+	/**
1349
+	 * Gets all the real items purchased which are children of this item
1350
+	 *
1351
+	 * @uses  EEH_Line_Item::get_descendants_of_type()
1352
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1353
+	 * @return EE_Line_Item[]
1354
+	 * @throws EE_Error
1355
+	 */
1356
+	public static function get_line_item_descendants(EE_Line_Item $parent_line_item)
1357
+	{
1358
+		return EEH_Line_Item::get_descendants_of_type(
1359
+			$parent_line_item,
1360
+			EEM_Line_Item::type_line_item
1361
+		);
1362
+	}
1363
+
1364
+
1365
+	/**
1366
+	 * Gets all descendants of supplied line item that match the supplied line item type
1367
+	 *
1368
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1369
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1370
+	 * @param string       $line_item_type   one of the EEM_Line_Item constants
1371
+	 * @return EE_Line_Item[]
1372
+	 * @throws EE_Error
1373
+	 */
1374
+	public static function get_descendants_of_type(EE_Line_Item $parent_line_item, $line_item_type)
1375
+	{
1376
+		return self::_get_descendants_by_type_and_object_type(
1377
+			$parent_line_item,
1378
+			$line_item_type,
1379
+			null
1380
+		);
1381
+	}
1382
+
1383
+
1384
+	/**
1385
+	 * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1386
+	 * as well
1387
+	 *
1388
+	 * @param EE_Line_Item  $parent_line_item - the line item to find descendants of
1389
+	 * @param string        $line_item_type   one of the EEM_Line_Item constants
1390
+	 * @param string | NULL $obj_type         object model class name (minus prefix) or NULL to ignore object type when
1391
+	 *                                        searching
1392
+	 * @return EE_Line_Item[]
1393
+	 * @throws EE_Error
1394
+	 */
1395
+	protected static function _get_descendants_by_type_and_object_type(
1396
+		EE_Line_Item $parent_line_item,
1397
+		$line_item_type,
1398
+		$obj_type = null
1399
+	) {
1400
+		$objects = array();
1401
+		foreach ($parent_line_item->children() as $child_line_item) {
1402
+			if ($child_line_item instanceof EE_Line_Item) {
1403
+				if (
1404
+					$child_line_item->type() === $line_item_type
1405
+					&& (
1406
+						$child_line_item->OBJ_type() === $obj_type || $obj_type === null
1407
+					)
1408
+				) {
1409
+					$objects[] = $child_line_item;
1410
+				} else {
1411
+					// go-through-all-its children looking for more matches
1412
+					$objects = array_merge(
1413
+						$objects,
1414
+						self::_get_descendants_by_type_and_object_type(
1415
+							$child_line_item,
1416
+							$line_item_type,
1417
+							$obj_type
1418
+						)
1419
+					);
1420
+				}
1421
+			}
1422
+		}
1423
+		return $objects;
1424
+	}
1425
+
1426
+
1427
+	/**
1428
+	 * Gets all descendants subtotals that match the supplied object type
1429
+	 *
1430
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1431
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1432
+	 * @param string       $OBJ_type         object type (like Event)
1433
+	 * @param array        $OBJ_IDs          array of OBJ_IDs
1434
+	 * @return EE_Line_Item[]
1435
+	 * @throws EE_Error
1436
+	 */
1437
+	public static function get_line_items_by_object_type_and_IDs(
1438
+		EE_Line_Item $parent_line_item,
1439
+		$OBJ_type = '',
1440
+		$OBJ_IDs = array()
1441
+	) {
1442
+		return self::_get_descendants_by_object_type_and_object_ID(
1443
+			$parent_line_item,
1444
+			$OBJ_type,
1445
+			$OBJ_IDs
1446
+		);
1447
+	}
1448
+
1449
+
1450
+	/**
1451
+	 * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1452
+	 * as well
1453
+	 *
1454
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1455
+	 * @param string       $OBJ_type         object type (like Event)
1456
+	 * @param array        $OBJ_IDs          array of OBJ_IDs
1457
+	 * @return EE_Line_Item[]
1458
+	 * @throws EE_Error
1459
+	 */
1460
+	protected static function _get_descendants_by_object_type_and_object_ID(
1461
+		EE_Line_Item $parent_line_item,
1462
+		$OBJ_type,
1463
+		$OBJ_IDs
1464
+	) {
1465
+		$objects = array();
1466
+		foreach ($parent_line_item->children() as $child_line_item) {
1467
+			if ($child_line_item instanceof EE_Line_Item) {
1468
+				if (
1469
+					$child_line_item->OBJ_type() === $OBJ_type
1470
+					&& is_array($OBJ_IDs)
1471
+					&& in_array($child_line_item->OBJ_ID(), $OBJ_IDs)
1472
+				) {
1473
+					$objects[] = $child_line_item;
1474
+				} else {
1475
+					// go-through-all-its children looking for more matches
1476
+					$objects = array_merge(
1477
+						$objects,
1478
+						self::_get_descendants_by_object_type_and_object_ID(
1479
+							$child_line_item,
1480
+							$OBJ_type,
1481
+							$OBJ_IDs
1482
+						)
1483
+					);
1484
+				}
1485
+			}
1486
+		}
1487
+		return $objects;
1488
+	}
1489
+
1490
+
1491
+	/**
1492
+	 * Uses a breadth-first-search in order to find the nearest descendant of
1493
+	 * the specified type and returns it, else NULL
1494
+	 *
1495
+	 * @uses  EEH_Line_Item::_get_nearest_descendant()
1496
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1497
+	 * @param string       $type             like one of the EEM_Line_Item::type_*
1498
+	 * @return EE_Line_Item
1499
+	 * @throws EE_Error
1500
+	 * @throws InvalidArgumentException
1501
+	 * @throws InvalidDataTypeException
1502
+	 * @throws InvalidInterfaceException
1503
+	 * @throws ReflectionException
1504
+	 */
1505
+	public static function get_nearest_descendant_of_type(EE_Line_Item $parent_line_item, $type)
1506
+	{
1507
+		return self::_get_nearest_descendant($parent_line_item, 'LIN_type', $type);
1508
+	}
1509
+
1510
+
1511
+	/**
1512
+	 * Uses a breadth-first-search in order to find the nearest descendant
1513
+	 * having the specified LIN_code and returns it, else NULL
1514
+	 *
1515
+	 * @uses  EEH_Line_Item::_get_nearest_descendant()
1516
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1517
+	 * @param string       $code             any value used for LIN_code
1518
+	 * @return EE_Line_Item
1519
+	 * @throws EE_Error
1520
+	 * @throws InvalidArgumentException
1521
+	 * @throws InvalidDataTypeException
1522
+	 * @throws InvalidInterfaceException
1523
+	 * @throws ReflectionException
1524
+	 */
1525
+	public static function get_nearest_descendant_having_code(EE_Line_Item $parent_line_item, $code)
1526
+	{
1527
+		return self::_get_nearest_descendant($parent_line_item, 'LIN_code', $code);
1528
+	}
1529
+
1530
+
1531
+	/**
1532
+	 * Uses a breadth-first-search in order to find the nearest descendant
1533
+	 * having the specified LIN_code and returns it, else NULL
1534
+	 *
1535
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1536
+	 * @param string       $search_field     name of EE_Line_Item property
1537
+	 * @param string       $value            any value stored in $search_field
1538
+	 * @return EE_Line_Item
1539
+	 * @throws EE_Error
1540
+	 * @throws InvalidArgumentException
1541
+	 * @throws InvalidDataTypeException
1542
+	 * @throws InvalidInterfaceException
1543
+	 * @throws ReflectionException
1544
+	 */
1545
+	protected static function _get_nearest_descendant(EE_Line_Item $parent_line_item, $search_field, $value)
1546
+	{
1547
+		foreach ($parent_line_item->children() as $child) {
1548
+			if ($child->get($search_field) == $value) {
1549
+				return $child;
1550
+			}
1551
+		}
1552
+		foreach ($parent_line_item->children() as $child) {
1553
+			$descendant_found = self::_get_nearest_descendant(
1554
+				$child,
1555
+				$search_field,
1556
+				$value
1557
+			);
1558
+			if ($descendant_found) {
1559
+				return $descendant_found;
1560
+			}
1561
+		}
1562
+		return null;
1563
+	}
1564
+
1565
+
1566
+	/**
1567
+	 * if passed line item has a TXN ID, uses that to jump directly to the grand total line item for the transaction,
1568
+	 * else recursively walks up the line item tree until a parent of type total is found,
1569
+	 *
1570
+	 * @param EE_Line_Item $line_item
1571
+	 * @return EE_Line_Item
1572
+	 * @throws EE_Error
1573
+	 * @throws ReflectionException
1574
+	 */
1575
+	public static function find_transaction_grand_total_for_line_item(EE_Line_Item $line_item): EE_Line_Item
1576
+	{
1577
+		if ($line_item->is_total()) {
1578
+			return $line_item;
1579
+		}
1580
+		if ($line_item->TXN_ID()) {
1581
+			$total_line_item = $line_item->transaction()->total_line_item(false);
1582
+			if ($total_line_item instanceof EE_Line_Item) {
1583
+				return $total_line_item;
1584
+			}
1585
+		} else {
1586
+			$line_item_parent = $line_item->parent();
1587
+			if ($line_item_parent instanceof EE_Line_Item) {
1588
+				if ($line_item_parent->is_total()) {
1589
+					return $line_item_parent;
1590
+				}
1591
+				return EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item_parent);
1592
+			}
1593
+		}
1594
+		throw new EE_Error(
1595
+			sprintf(
1596
+				esc_html__(
1597
+					'A valid grand total for line item %1$d was not found.',
1598
+					'event_espresso'
1599
+				),
1600
+				$line_item->ID()
1601
+			)
1602
+		);
1603
+	}
1604
+
1605
+
1606
+	/**
1607
+	 * Prints out a representation of the line item tree
1608
+	 *
1609
+	 * @param EE_Line_Item $line_item
1610
+	 * @param int          $indentation
1611
+	 * @return void
1612
+	 * @throws EE_Error
1613
+	 */
1614
+	public static function visualize(EE_Line_Item $line_item, $indentation = 0)
1615
+	{
1616
+		$new_line = defined('EE_TESTS_DIR') ? "\n" : '<br />';
1617
+		echo $new_line;
1618
+		if (! $indentation) {
1619
+			echo $new_line;
1620
+		}
1621
+		echo str_repeat('. ', $indentation);
1622
+		$breakdown = '';
1623
+		if ($line_item->is_line_item() || $line_item->is_sub_line_item() || $line_item->isSubTax()) {
1624
+			if ($line_item->is_percent()) {
1625
+				$breakdown = "{$line_item->percent()}%";
1626
+			} else {
1627
+				$breakdown = "\${$line_item->unit_price()} x {$line_item->quantity()}";
1628
+			}
1629
+		}
1630
+		echo wp_kses($line_item->name(), AllowedTags::getAllowedTags());
1631
+		echo " [ ID:{$line_item->ID()} | qty:{$line_item->quantity()} ] {$line_item->type()} : ";
1632
+		echo "\${$line_item->total()}";
1633
+		if ($breakdown) {
1634
+			echo " ( {$breakdown} )";
1635
+		}
1636
+		if ($line_item->is_taxable()) {
1637
+			echo '  * taxable';
1638
+		}
1639
+		if ($line_item->children()) {
1640
+			foreach ($line_item->children() as $child) {
1641
+				self::visualize($child, $indentation + 1);
1642
+			}
1643
+		}
1644
+		if (! $indentation) {
1645
+			echo $new_line . $new_line;
1646
+		}
1647
+	}
1648
+
1649
+
1650
+	/**
1651
+	 * Calculates the registration's final price, taking into account that they
1652
+	 * need to not only help pay for their OWN ticket, but also any transaction-wide surcharges and taxes,
1653
+	 * and receive a portion of any transaction-wide discounts.
1654
+	 * eg1, if I buy a $1 ticket and brent buys a $9 ticket, and we receive a $5 discount
1655
+	 * then I'll get 1/10 of that $5 discount, which is $0.50, and brent will get
1656
+	 * 9/10ths of that $5 discount, which is $4.50. So my final price should be $0.50
1657
+	 * and brent's final price should be $5.50.
1658
+	 * In order to do this, we basically need to traverse the line item tree calculating
1659
+	 * the running totals (just as if we were recalculating the total), but when we identify
1660
+	 * regular line items, we need to keep track of their share of the grand total.
1661
+	 * Also, we need to keep track of the TAXABLE total for each ticket purchase, so
1662
+	 * we can know how to apply taxes to it. (Note: "taxable total" does not equal the "pretax total"
1663
+	 * when there are non-taxable items; otherwise they would be the same)
1664
+	 *
1665
+	 * @param EE_Line_Item $line_item
1666
+	 * @param array        $billable_ticket_quantities  array of EE_Ticket IDs and their corresponding quantity that
1667
+	 *                                                  can be included in price calculations at this moment
1668
+	 * @return array        keys are line items for tickets IDs and values are their share of the running total,
1669
+	 *                                                  plus the key 'total', and 'taxable' which also has keys of all
1670
+	 *                                                  the ticket IDs.
1671
+	 *                                                  Eg array(
1672
+	 *                                                      12 => 4.3
1673
+	 *                                                      23 => 8.0
1674
+	 *                                                      'total' => 16.6,
1675
+	 *                                                      'taxable' => array(
1676
+	 *                                                          12 => 10,
1677
+	 *                                                          23 => 4
1678
+	 *                                                      ).
1679
+	 *                                                  So to find which registrations have which final price, we need
1680
+	 *                                                  to find which line item is theirs, which can be done with
1681
+	 *                                                  `EEM_Line_Item::instance()->get_line_item_for_registration(
1682
+	 *                                                  $registration );`
1683
+	 * @throws EE_Error
1684
+	 * @throws InvalidArgumentException
1685
+	 * @throws InvalidDataTypeException
1686
+	 * @throws InvalidInterfaceException
1687
+	 * @throws ReflectionException
1688
+	 */
1689
+	public static function calculate_reg_final_prices_per_line_item(
1690
+		EE_Line_Item $line_item,
1691
+		$billable_ticket_quantities = array()
1692
+	) {
1693
+		$running_totals = [
1694
+			'total'   => 0,
1695
+			'taxable' => ['total' => 0]
1696
+		];
1697
+		foreach ($line_item->children() as $child_line_item) {
1698
+			switch ($child_line_item->type()) {
1699
+				case EEM_Line_Item::type_sub_total:
1700
+					$running_totals_from_subtotal = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1701
+						$child_line_item,
1702
+						$billable_ticket_quantities
1703
+					);
1704
+					// combine arrays but preserve numeric keys
1705
+					$running_totals = array_replace_recursive($running_totals_from_subtotal, $running_totals);
1706
+					$running_totals['total'] += $running_totals_from_subtotal['total'];
1707
+					$running_totals['taxable']['total'] += $running_totals_from_subtotal['taxable']['total'];
1708
+					break;
1709
+
1710
+				case EEM_Line_Item::type_tax_sub_total:
1711
+					// find how much the taxes percentage is
1712
+					if ($child_line_item->percent() !== 0) {
1713
+						$tax_percent_decimal = $child_line_item->percent() / 100;
1714
+					} else {
1715
+						$tax_percent_decimal = EE_Taxes::get_total_taxes_percentage() / 100;
1716
+					}
1717
+					// and apply to all the taxable totals, and add to the pretax totals
1718
+					foreach ($running_totals as $line_item_id => $this_running_total) {
1719
+						// "total" and "taxable" array key is an exception
1720
+						if ($line_item_id === 'taxable') {
1721
+							continue;
1722
+						}
1723
+						$taxable_total = $running_totals['taxable'][ $line_item_id ];
1724
+						$running_totals[ $line_item_id ] += ($taxable_total * $tax_percent_decimal);
1725
+					}
1726
+					break;
1727
+
1728
+				case EEM_Line_Item::type_line_item:
1729
+					// ticket line items or ????
1730
+					if ($child_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
1731
+						// kk it's a ticket
1732
+						if (isset($running_totals[ $child_line_item->ID() ])) {
1733
+							// huh? that shouldn't happen.
1734
+							$running_totals['total'] += $child_line_item->total();
1735
+						} else {
1736
+							// its not in our running totals yet. great.
1737
+							if ($child_line_item->is_taxable()) {
1738
+								$taxable_amount = $child_line_item->unit_price();
1739
+							} else {
1740
+								$taxable_amount = 0;
1741
+							}
1742
+							// are we only calculating totals for some tickets?
1743
+							if (isset($billable_ticket_quantities[ $child_line_item->OBJ_ID() ])) {
1744
+								$quantity = $billable_ticket_quantities[ $child_line_item->OBJ_ID() ];
1745
+								$running_totals[ $child_line_item->ID() ] = $quantity
1746
+									? $child_line_item->unit_price()
1747
+									: 0;
1748
+								$running_totals['taxable'][ $child_line_item->ID() ] = $quantity
1749
+									? $taxable_amount
1750
+									: 0;
1751
+							} else {
1752
+								$quantity = $child_line_item->quantity();
1753
+								$running_totals[ $child_line_item->ID() ] = $child_line_item->unit_price();
1754
+								$running_totals['taxable'][ $child_line_item->ID() ] = $taxable_amount;
1755
+							}
1756
+							$running_totals['taxable']['total'] += $taxable_amount * $quantity;
1757
+							$running_totals['total'] += $child_line_item->unit_price() * $quantity;
1758
+						}
1759
+					} else {
1760
+						// it's some other type of item added to the cart
1761
+						// it should affect the running totals
1762
+						// basically we want to convert it into a PERCENT modifier. Because
1763
+						// more clearly affect all registration's final price equally
1764
+						$line_items_percent_of_running_total = $running_totals['total'] > 0
1765
+							? ($child_line_item->total() / $running_totals['total']) + 1
1766
+							: 1;
1767
+						foreach ($running_totals as $line_item_id => $this_running_total) {
1768
+							// the "taxable" array key is an exception
1769
+							if ($line_item_id === 'taxable') {
1770
+								continue;
1771
+							}
1772
+							// update the running totals
1773
+							// yes this actually even works for the running grand total!
1774
+							$running_totals[ $line_item_id ] =
1775
+								$line_items_percent_of_running_total * $this_running_total;
1776
+
1777
+							if ($child_line_item->is_taxable()) {
1778
+								$running_totals['taxable'][ $line_item_id ] =
1779
+									$line_items_percent_of_running_total * $running_totals['taxable'][ $line_item_id ];
1780
+							}
1781
+						}
1782
+					}
1783
+					break;
1784
+			}
1785
+		}
1786
+		return $running_totals;
1787
+	}
1788
+
1789
+
1790
+	/**
1791
+	 * @param EE_Line_Item $total_line_item
1792
+	 * @param EE_Line_Item $ticket_line_item
1793
+	 * @return float | null
1794
+	 * @throws EE_Error
1795
+	 * @throws InvalidArgumentException
1796
+	 * @throws InvalidDataTypeException
1797
+	 * @throws InvalidInterfaceException
1798
+	 * @throws OutOfRangeException
1799
+	 * @throws ReflectionException
1800
+	 */
1801
+	public static function calculate_final_price_for_ticket_line_item(
1802
+		EE_Line_Item $total_line_item,
1803
+		EE_Line_Item $ticket_line_item
1804
+	) {
1805
+		static $final_prices_per_ticket_line_item = array();
1806
+		if (empty($final_prices_per_ticket_line_item) || empty($final_prices_per_ticket_line_item[ $total_line_item->ID() ])) {
1807
+			$final_prices_per_ticket_line_item[ $total_line_item->ID() ] = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1808
+				$total_line_item
1809
+			);
1810
+		}
1811
+		// ok now find this new registration's final price
1812
+		if (isset($final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ])) {
1813
+			return $final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ];
1814
+		}
1815
+		$message = sprintf(
1816
+			esc_html__(
1817
+				'The final price for the ticket line item (ID:%1$d) on the total line item (ID:%2$d) could not be calculated.',
1818
+				'event_espresso'
1819
+			),
1820
+			$ticket_line_item->ID(),
1821
+			$total_line_item->ID()
1822
+		);
1823
+		if (WP_DEBUG) {
1824
+			$message .= '<br>' . print_r($final_prices_per_ticket_line_item, true);
1825
+			throw new OutOfRangeException($message);
1826
+		}
1827
+		EE_Log::instance()->log(__CLASS__, __FUNCTION__, $message);
1828
+		return null;
1829
+	}
1830
+
1831
+
1832
+	/**
1833
+	 * Creates a duplicate of the line item tree, except only includes billable items
1834
+	 * and the portion of line items attributed to billable things
1835
+	 *
1836
+	 * @param EE_Line_Item      $line_item
1837
+	 * @param EE_Registration[] $registrations
1838
+	 * @return EE_Line_Item
1839
+	 * @throws EE_Error
1840
+	 * @throws InvalidArgumentException
1841
+	 * @throws InvalidDataTypeException
1842
+	 * @throws InvalidInterfaceException
1843
+	 * @throws ReflectionException
1844
+	 */
1845
+	public static function billable_line_item_tree(EE_Line_Item $line_item, $registrations)
1846
+	{
1847
+		$copy_li = EEH_Line_Item::billable_line_item($line_item, $registrations);
1848
+		foreach ($line_item->children() as $child_li) {
1849
+			$copy_li->add_child_line_item(
1850
+				EEH_Line_Item::billable_line_item_tree($child_li, $registrations)
1851
+			);
1852
+		}
1853
+		// if this is the grand total line item, make sure the totals all add up
1854
+		// (we could have duplicated this logic AS we copied the line items, but
1855
+		// it seems DRYer this way)
1856
+		if ($copy_li->type() === EEM_Line_Item::type_total) {
1857
+			$copy_li->recalculate_total_including_taxes();
1858
+		}
1859
+		return $copy_li;
1860
+	}
1861
+
1862
+
1863
+	/**
1864
+	 * Creates a new, unsaved line item from $line_item that factors in the
1865
+	 * number of billable registrations on $registrations.
1866
+	 *
1867
+	 * @param EE_Line_Item      $line_item
1868
+	 * @param EE_Registration[] $registrations
1869
+	 * @return EE_Line_Item
1870
+	 * @throws EE_Error
1871
+	 * @throws InvalidArgumentException
1872
+	 * @throws InvalidDataTypeException
1873
+	 * @throws InvalidInterfaceException
1874
+	 * @throws ReflectionException
1875
+	 */
1876
+	public static function billable_line_item(EE_Line_Item $line_item, $registrations)
1877
+	{
1878
+		$new_li_fields = $line_item->model_field_array();
1879
+		if (
1880
+			$line_item->type() === EEM_Line_Item::type_line_item &&
1881
+			$line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1882
+		) {
1883
+			$count = 0;
1884
+			foreach ($registrations as $registration) {
1885
+				if (
1886
+					$line_item->OBJ_ID() === $registration->ticket_ID() &&
1887
+					in_array(
1888
+						$registration->status_ID(),
1889
+						EEM_Registration::reg_statuses_that_allow_payment(),
1890
+						true
1891
+					)
1892
+				) {
1893
+					$count++;
1894
+				}
1895
+			}
1896
+			$new_li_fields['LIN_quantity'] = $count;
1897
+		}
1898
+		// don't set the total. We'll leave that up to the code that calculates it
1899
+		unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent'], $new_li_fields['LIN_total']);
1900
+		return EE_Line_Item::new_instance($new_li_fields);
1901
+	}
1902
+
1903
+
1904
+	/**
1905
+	 * Returns a modified line item tree where all the subtotals which have a total of 0
1906
+	 * are removed, and line items with a quantity of 0
1907
+	 *
1908
+	 * @param EE_Line_Item $line_item |null
1909
+	 * @return EE_Line_Item|null
1910
+	 * @throws EE_Error
1911
+	 * @throws InvalidArgumentException
1912
+	 * @throws InvalidDataTypeException
1913
+	 * @throws InvalidInterfaceException
1914
+	 * @throws ReflectionException
1915
+	 */
1916
+	public static function non_empty_line_items(EE_Line_Item $line_item)
1917
+	{
1918
+		$copied_li = EEH_Line_Item::non_empty_line_item($line_item);
1919
+		if ($copied_li === null) {
1920
+			return null;
1921
+		}
1922
+		// if this is an event subtotal, we want to only include it if it
1923
+		// has a non-zero total and at least one ticket line item child
1924
+		$ticket_children = 0;
1925
+		foreach ($line_item->children() as $child_li) {
1926
+			$child_li_copy = EEH_Line_Item::non_empty_line_items($child_li);
1927
+			if ($child_li_copy !== null) {
1928
+				$copied_li->add_child_line_item($child_li_copy);
1929
+				if (
1930
+					$child_li_copy->type() === EEM_Line_Item::type_line_item &&
1931
+					$child_li_copy->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1932
+				) {
1933
+					$ticket_children++;
1934
+				}
1935
+			}
1936
+		}
1937
+		// if this is an event subtotal with NO ticket children
1938
+		// we basically want to ignore it
1939
+		if (
1940
+			$ticket_children === 0
1941
+			&& $line_item->type() === EEM_Line_Item::type_sub_total
1942
+			&& $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
1943
+			&& $line_item->total() === 0
1944
+		) {
1945
+			return null;
1946
+		}
1947
+		return $copied_li;
1948
+	}
1949
+
1950
+
1951
+	/**
1952
+	 * Creates a new, unsaved line item, but if it's a ticket line item
1953
+	 * with a total of 0, or a subtotal of 0, returns null instead
1954
+	 *
1955
+	 * @param EE_Line_Item $line_item
1956
+	 * @return EE_Line_Item
1957
+	 * @throws EE_Error
1958
+	 * @throws InvalidArgumentException
1959
+	 * @throws InvalidDataTypeException
1960
+	 * @throws InvalidInterfaceException
1961
+	 * @throws ReflectionException
1962
+	 */
1963
+	public static function non_empty_line_item(EE_Line_Item $line_item)
1964
+	{
1965
+		if (
1966
+			$line_item->type() === EEM_Line_Item::type_line_item
1967
+			&& $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1968
+			&& $line_item->quantity() === 0
1969
+		) {
1970
+			return null;
1971
+		}
1972
+		$new_li_fields = $line_item->model_field_array();
1973
+		// don't set the total. We'll leave that up to the code that calculates it
1974
+		unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent']);
1975
+		return EE_Line_Item::new_instance($new_li_fields);
1976
+	}
1977
+
1978
+
1979
+	/**
1980
+	 * Cycles through all of the ticket line items for the supplied total line item
1981
+	 * and ensures that the line item's "is_taxable" field matches that of its corresponding ticket
1982
+	 *
1983
+	 * @param EE_Line_Item $total_line_item
1984
+	 * @since 4.9.79.p
1985
+	 * @throws EE_Error
1986
+	 * @throws InvalidArgumentException
1987
+	 * @throws InvalidDataTypeException
1988
+	 * @throws InvalidInterfaceException
1989
+	 * @throws ReflectionException
1990
+	 */
1991
+	public static function resetIsTaxableForTickets(EE_Line_Item $total_line_item)
1992
+	{
1993
+		$ticket_line_items = self::get_ticket_line_items($total_line_item);
1994
+		foreach ($ticket_line_items as $ticket_line_item) {
1995
+			if (
1996
+				$ticket_line_item instanceof EE_Line_Item
1997
+				&& $ticket_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1998
+			) {
1999
+				$ticket = $ticket_line_item->ticket();
2000
+				if ($ticket instanceof EE_Ticket && $ticket->taxable() !== $ticket_line_item->is_taxable()) {
2001
+					$ticket_line_item->set_is_taxable($ticket->taxable());
2002
+					$ticket_line_item->save();
2003
+				}
2004
+			}
2005
+		}
2006
+	}
2007
+
2008
+
2009
+	/**
2010
+	 * @return EE_Line_Item[]
2011
+	 * @throws EE_Error
2012
+	 * @throws ReflectionException
2013
+	 * @since   5.0.0.p
2014
+	 */
2015
+	private static function getGlobalTaxes(): array
2016
+	{
2017
+		if (EEH_Line_Item::$global_taxes === null) {
2018
+
2019
+			/** @type EEM_Price $EEM_Price */
2020
+			$EEM_Price = EE_Registry::instance()->load_model('Price');
2021
+			// get array of taxes via Price Model
2022
+			EEH_Line_Item::$global_taxes = $EEM_Price->get_all_prices_that_are_taxes();
2023
+			ksort(EEH_Line_Item::$global_taxes);
2024
+		}
2025
+		return EEH_Line_Item::$global_taxes;
2026
+	}
2027
+
2028
+
2029
+
2030
+	/**************************************** @DEPRECATED METHODS *************************************** */
2031
+	/**
2032
+	 * @deprecated
2033
+	 * @param EE_Line_Item $total_line_item
2034
+	 * @return EE_Line_Item
2035
+	 * @throws EE_Error
2036
+	 * @throws InvalidArgumentException
2037
+	 * @throws InvalidDataTypeException
2038
+	 * @throws InvalidInterfaceException
2039
+	 * @throws ReflectionException
2040
+	 */
2041
+	public static function get_items_subtotal(EE_Line_Item $total_line_item)
2042
+	{
2043
+		EE_Error::doing_it_wrong(
2044
+			'EEH_Line_Item::get_items_subtotal()',
2045
+			sprintf(
2046
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2047
+				'EEH_Line_Item::get_pre_tax_subtotal()'
2048
+			),
2049
+			'4.6.0'
2050
+		);
2051
+		return self::get_pre_tax_subtotal($total_line_item);
2052
+	}
2053
+
2054
+
2055
+	/**
2056
+	 * @deprecated
2057
+	 * @param EE_Transaction $transaction
2058
+	 * @return EE_Line_Item
2059
+	 * @throws EE_Error
2060
+	 * @throws InvalidArgumentException
2061
+	 * @throws InvalidDataTypeException
2062
+	 * @throws InvalidInterfaceException
2063
+	 * @throws ReflectionException
2064
+	 */
2065
+	public static function create_default_total_line_item($transaction = null)
2066
+	{
2067
+		EE_Error::doing_it_wrong(
2068
+			'EEH_Line_Item::create_default_total_line_item()',
2069
+			sprintf(
2070
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2071
+				'EEH_Line_Item::create_total_line_item()'
2072
+			),
2073
+			'4.6.0'
2074
+		);
2075
+		return self::create_total_line_item($transaction);
2076
+	}
2077
+
2078
+
2079
+	/**
2080
+	 * @deprecated
2081
+	 * @param EE_Line_Item   $total_line_item
2082
+	 * @param EE_Transaction $transaction
2083
+	 * @return EE_Line_Item
2084
+	 * @throws EE_Error
2085
+	 * @throws InvalidArgumentException
2086
+	 * @throws InvalidDataTypeException
2087
+	 * @throws InvalidInterfaceException
2088
+	 * @throws ReflectionException
2089
+	 */
2090
+	public static function create_default_tickets_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2091
+	{
2092
+		EE_Error::doing_it_wrong(
2093
+			'EEH_Line_Item::create_default_tickets_subtotal()',
2094
+			sprintf(
2095
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2096
+				'EEH_Line_Item::create_pre_tax_subtotal()'
2097
+			),
2098
+			'4.6.0'
2099
+		);
2100
+		return self::create_pre_tax_subtotal($total_line_item, $transaction);
2101
+	}
2102
+
2103
+
2104
+	/**
2105
+	 * @deprecated
2106
+	 * @param EE_Line_Item   $total_line_item
2107
+	 * @param EE_Transaction $transaction
2108
+	 * @return EE_Line_Item
2109
+	 * @throws EE_Error
2110
+	 * @throws InvalidArgumentException
2111
+	 * @throws InvalidDataTypeException
2112
+	 * @throws InvalidInterfaceException
2113
+	 * @throws ReflectionException
2114
+	 */
2115
+	public static function create_default_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2116
+	{
2117
+		EE_Error::doing_it_wrong(
2118
+			'EEH_Line_Item::create_default_taxes_subtotal()',
2119
+			sprintf(
2120
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2121
+				'EEH_Line_Item::create_taxes_subtotal()'
2122
+			),
2123
+			'4.6.0'
2124
+		);
2125
+		return self::create_taxes_subtotal($total_line_item, $transaction);
2126
+	}
2127
+
2128
+
2129
+	/**
2130
+	 * @deprecated
2131
+	 * @param EE_Line_Item   $total_line_item
2132
+	 * @param EE_Transaction $transaction
2133
+	 * @return EE_Line_Item
2134
+	 * @throws EE_Error
2135
+	 * @throws InvalidArgumentException
2136
+	 * @throws InvalidDataTypeException
2137
+	 * @throws InvalidInterfaceException
2138
+	 * @throws ReflectionException
2139
+	 */
2140
+	public static function create_default_event_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2141
+	{
2142
+		EE_Error::doing_it_wrong(
2143
+			'EEH_Line_Item::create_default_event_subtotal()',
2144
+			sprintf(
2145
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2146
+				'EEH_Line_Item::create_event_subtotal()'
2147
+			),
2148
+			'4.6.0'
2149
+		);
2150
+		return self::create_event_subtotal($total_line_item, $transaction);
2151
+	}
2152 2152
 }
Please login to merge, or discard this patch.
core/db_models/fields/EE_Datetime_Field.php 1 patch
Indentation   +749 added lines, -749 removed lines patch added patch discarded remove patch
@@ -17,753 +17,753 @@
 block discarded – undo
17 17
  */
18 18
 class EE_Datetime_Field extends EE_Model_Field_Base
19 19
 {
20
-    /**
21
-     * The pattern we're looking for is if only the characters 0-9 are found and there are only
22
-     * 10 or more numbers (because 9 numbers even with all 9's would be sometime in 2001 )
23
-     *
24
-     * @type string unix_timestamp_regex
25
-     */
26
-    const unix_timestamp_regex = '/[0-9]{10,}/';
27
-
28
-    /**
29
-     * @type string mysql_timestamp_format
30
-     */
31
-    const mysql_timestamp_format = 'Y-m-d H:i:s';
32
-
33
-    /**
34
-     * @type string mysql_date_format
35
-     */
36
-    const mysql_date_format = 'Y-m-d';
37
-
38
-    /**
39
-     * @type string mysql_time_format
40
-     */
41
-    const mysql_time_format = 'H:i:s';
42
-
43
-    /**
44
-     * Const for using in the default value. If the field's default is set to this,
45
-     * then we will return the time of calling `get_default_value()`, not
46
-     * just the current time at construction
47
-     */
48
-    const now = 'now';
49
-
50
-    /**
51
-     * The following properties hold the default formats for date and time.
52
-     * Defaults are set via the constructor and can be overridden on class instantiation.
53
-     * However they can also be overridden later by the set_format() method
54
-     * (and corresponding set_date_format, set_time_format methods);
55
-     */
56
-
57
-    protected string        $_date_format        = '';
58
-
59
-    protected string        $_time_format        = '';
60
-
61
-    protected string        $_pretty_date_format = '';
62
-
63
-    protected string        $_pretty_time_format = '';
64
-
65
-    protected ?DateTimeZone $_DateTimeZone       = null;
66
-
67
-    protected ?DateTimeZone $_UTC_DateTimeZone   = null;
68
-
69
-    protected ?DateTimeZone $_blog_DateTimeZone  = null;
70
-
71
-
72
-    /**
73
-     * This property holds how we want the output returned when getting a datetime string.  It is set for the
74
-     * set_date_time_output() method.  By default this is empty.  When empty, we are assuming that we want both date
75
-     * and time returned via getters.
76
-     *
77
-     * @var mixed (null|string)
78
-     */
79
-    protected $_date_time_output;
80
-
81
-
82
-    /**
83
-     * timezone string
84
-     * This gets set by the constructor and can be changed by the "set_timezone()" method so that we know what timezone
85
-     * incoming strings|timestamps are in.  This can also be used before a get to set what timezone you want strings
86
-     * coming out of the object to be in.  Default timezone is the current WP timezone option setting
87
-     */
88
-    protected ?string $_timezone_string = null;
89
-
90
-
91
-    /**
92
-     * This holds whatever UTC offset for the blog (we automatically convert timezone strings into their related
93
-     * offsets for comparison purposes).
94
-     *
95
-     * @var int
96
-     */
97
-    protected int $_blog_offset;
98
-
99
-
100
-    /**
101
-     * @param string      $table_column
102
-     * @param string      $nice_name
103
-     * @param bool        $nullable
104
-     * @param string|null $default_value
105
-     * @param string|null $timezone_string
106
-     * @param string|null $date_format
107
-     * @param string|null $time_format
108
-     * @param string|null $pretty_date_format
109
-     * @param string|null $pretty_time_format
110
-     * @throws InvalidArgumentException
111
-     * @throws Exception
112
-     */
113
-    public function __construct(
114
-        string $table_column,
115
-        string $nice_name,
116
-        bool $nullable,
117
-        ?string $default_value,
118
-        ?string $timezone_string = '',
119
-        ?string $date_format = '',
120
-        ?string $time_format = '',
121
-        ?string $pretty_date_format = '',
122
-        ?string $pretty_time_format = ''
123
-    ) {
124
-        $this->set_date_format($date_format);
125
-        $this->set_time_format($time_format);
126
-        $this->set_date_format($pretty_date_format, true);
127
-        $this->set_time_format($pretty_time_format, true);
128
-
129
-        parent::__construct($table_column, $nice_name, $nullable, $default_value);
130
-        $this->set_timezone($timezone_string);
131
-        $this->setSchemaFormat('date-time');
132
-    }
133
-
134
-
135
-    /**
136
-     * @return DateTimeZone
137
-     * @throws Exception
138
-     */
139
-    public function get_UTC_DateTimeZone(): DateTimeZone
140
-    {
141
-        return $this->_UTC_DateTimeZone instanceof DateTimeZone
142
-            ? $this->_UTC_DateTimeZone
143
-            : $this->_create_timezone_object_from_timezone_string('UTC');
144
-    }
145
-
146
-
147
-    /**
148
-     * @return DateTimeZone
149
-     * @throws Exception
150
-     */
151
-    public function get_blog_DateTimeZone(): DateTimeZone
152
-    {
153
-        return $this->_blog_DateTimeZone instanceof DateTimeZone
154
-            ? $this->_blog_DateTimeZone
155
-            : $this->_create_timezone_object_from_timezone_string();
156
-    }
157
-
158
-
159
-    /**
160
-     * this prepares any incoming date data and make sure its converted to a utc unix timestamp
161
-     *
162
-     * @param string|int $value_inputted_for_field_on_model_object  could be a string formatted date time or int unix
163
-     *                                                              timestamp
164
-     * @return DateTime
165
-     * @throws Exception
166
-     */
167
-    public function prepare_for_set($value_inputted_for_field_on_model_object)
168
-    {
169
-        return $this->_get_date_object($value_inputted_for_field_on_model_object);
170
-    }
171
-
172
-
173
-    /**
174
-     * This returns the format string to be used by getters depending on what the $_date_time_output property is set at.
175
-     * getters need to know whether we're just returning the date or the time or both.  By default we return both.
176
-     *
177
-     * @param bool $pretty If we're returning the pretty formats or standard format string.
178
-     * @return string    The final assembled format string.
179
-     */
180
-    protected function _get_date_time_output(bool $pretty = false): string
181
-    {
182
-        switch ($this->_date_time_output) {
183
-            case 'time':
184
-                return $pretty
185
-                    ? $this->_pretty_time_format
186
-                    : $this->_time_format;
187
-
188
-            case 'date':
189
-                return $pretty
190
-                    ? $this->_pretty_date_format
191
-                    : $this->_date_format;
192
-
193
-            default:
194
-                return $pretty
195
-                    ? trim($this->_pretty_date_format . ' ' . $this->_pretty_time_format)
196
-                    : trim($this->_date_format . ' ' . $this->_time_format);
197
-        }
198
-    }
199
-
200
-
201
-    /**
202
-     * This just sets the $_date_time_output property so we can flag how date and times are formatted before being
203
-     * returned (using the format properties)
204
-     *
205
-     * @param string|null $what acceptable values are 'time' or 'date'.
206
-     *                          Any other value will be set but will always result
207
-     *                          in both 'date' and 'time' being returned.
208
-     * @return void
209
-     */
210
-    public function set_date_time_output(?string $what = null)
211
-    {
212
-        $this->_date_time_output = $what;
213
-    }
214
-
215
-
216
-    /**
217
-     * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
218
-     * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
219
-     * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp).
220
-     * We also set some other properties in this method.
221
-     *
222
-     * @param string|null $timezone_string A valid timezone string as described by @link
223
-     *                                     http://www.php.net/manual/en/timezones.php
224
-     * @return void
225
-     * @throws InvalidArgumentException
226
-     * @throws InvalidDataTypeException
227
-     * @throws InvalidInterfaceException
228
-     * @throws Exception
229
-     */
230
-    public function set_timezone(?string $timezone_string)
231
-    {
232
-        if (empty($timezone_string) && $this->_timezone_string !== null) {
233
-            // leave the timezone AS-IS if we already have one and
234
-            // the function arg didn't provide one
235
-            return;
236
-        }
237
-        $timezone_string        = EEH_DTT_Helper::get_valid_timezone_string($timezone_string);
238
-        $this->_timezone_string = ! empty($timezone_string)
239
-            ? $timezone_string
240
-            : 'UTC';
241
-        $this->_DateTimeZone    = $this->_create_timezone_object_from_timezone_string($this->_timezone_string);
242
-    }
243
-
244
-
245
-    /**
246
-     * _create_timezone_object_from_timezone_name
247
-     *
248
-     * @access protected
249
-     * @param string|null $timezone_string
250
-     * @return DateTimeZone
251
-     * @throws InvalidArgumentException
252
-     * @throws InvalidDataTypeException
253
-     * @throws InvalidInterfaceException
254
-     * @throws Exception
255
-     */
256
-    protected function _create_timezone_object_from_timezone_string(?string $timezone_string = ''): DateTimeZone
257
-    {
258
-        return new DateTimeZone(EEH_DTT_Helper::get_valid_timezone_string($timezone_string));
259
-    }
260
-
261
-
262
-    /**
263
-     * This just returns whatever is set for the current timezone.
264
-     *
265
-     * @access public
266
-     * @return string timezone string
267
-     */
268
-    public function get_timezone(): ?string
269
-    {
270
-        return $this->_timezone_string;
271
-    }
272
-
273
-
274
-    /**
275
-     * set the $_date_format property
276
-     *
277
-     * @access public
278
-     * @param string $format a new date format (corresponding to formats accepted by PHP date() function)
279
-     * @param bool   $pretty Whether to set pretty format or not.
280
-     * @return void
281
-     */
282
-    public function set_date_format(string $format, bool $pretty = false)
283
-    {
284
-        if ($pretty) {
285
-            $this->_pretty_date_format = new DateFormat($format);
286
-        } else {
287
-            $this->_date_format = new DateFormat($format);
288
-        }
289
-    }
290
-
291
-
292
-    /**
293
-     * return the $_date_format property value.
294
-     *
295
-     * @param bool $pretty Whether to get pretty format or not.
296
-     * @return string
297
-     */
298
-    public function get_date_format(bool $pretty = false): string
299
-    {
300
-        return $pretty
301
-            ? $this->_pretty_date_format
302
-            : $this->_date_format;
303
-    }
304
-
305
-
306
-    /**
307
-     * set the $_time_format property
308
-     *
309
-     * @access public
310
-     * @param string $format a new time format (corresponding to formats accepted by PHP date() function)
311
-     * @param bool   $pretty Whether to set pretty format or not.
312
-     * @return void
313
-     */
314
-    public function set_time_format(string $format, bool $pretty = false)
315
-    {
316
-        if ($pretty) {
317
-            $this->_pretty_time_format = new TimeFormat($format);
318
-        } else {
319
-            $this->_time_format = new TimeFormat($format);
320
-        }
321
-    }
322
-
323
-
324
-    /**
325
-     * return the $_time_format property value.
326
-     *
327
-     * @param bool $pretty Whether to get pretty format or not.
328
-     * @return string
329
-     */
330
-    public function get_time_format(bool $pretty = false): string
331
-    {
332
-        return $pretty
333
-            ? $this->_pretty_time_format
334
-            : $this->_time_format;
335
-    }
336
-
337
-
338
-    /**
339
-     * set the $_pretty_date_format property
340
-     *
341
-     * @access public
342
-     * @param string|null $format a new pretty date format (corresponding to formats accepted by PHP date() function)
343
-     * @return void
344
-     */
345
-    public function set_pretty_date_format(?string $format)
346
-    {
347
-        $this->set_date_format($format, true);
348
-    }
349
-
350
-
351
-    /**
352
-     * set the $_pretty_time_format property
353
-     *
354
-     * @access public
355
-     * @param string|null $format a new pretty time format (corresponding to formats accepted by PHP date() function)
356
-     * @return void
357
-     */
358
-    public function set_pretty_time_format(?string $format)
359
-    {
360
-        $this->set_time_format($format, true);
361
-    }
362
-
363
-
364
-    /**
365
-     * Only sets the time portion of the datetime.
366
-     *
367
-     * @param string|DateTime $time_to_set_string like 8am OR a DateTime object.
368
-     * @param DateTime        $current            current DateTime object for the datetime field
369
-     * @return DateTime
370
-     */
371
-    public function prepare_for_set_with_new_time($time_to_set_string, DateTime $current): DateTime
372
-    {
373
-        // if $time_to_set_string is datetime object, then let's use it to set the parse array.
374
-        // Otherwise parse the string.
375
-        if ($time_to_set_string instanceof DateTime) {
376
-            $parsed = [
377
-                'hour'   => $time_to_set_string->format('H'),
378
-                'minute' => $time_to_set_string->format('i'),
379
-                'second' => $time_to_set_string->format('s'),
380
-            ];
381
-        } else {
382
-            // parse incoming string
383
-            $parsed = date_parse_from_format($this->_time_format, $time_to_set_string);
384
-        }
385
-        EEH_DTT_Helper::setTimezone($current, $this->_DateTimeZone);
386
-        return $current->setTime($parsed['hour'], $parsed['minute'], $parsed['second']);
387
-    }
388
-
389
-
390
-    /**
391
-     * Only sets the date portion of the datetime.
392
-     *
393
-     * @param string|DateTime $date_to_set_string like Friday, January 8th or a DateTime object.
394
-     * @param DateTime        $current            current DateTime object for the datetime field
395
-     * @return DateTime
396
-     */
397
-    public function prepare_for_set_with_new_date($date_to_set_string, DateTime $current): DateTime
398
-    {
399
-        // if $time_to_set_string is datetime object, then let's use it to set the parse array.
400
-        // Otherwise parse the string.
401
-        if ($date_to_set_string instanceof DateTime) {
402
-            $parsed = [
403
-                'year'  => $date_to_set_string->format('Y'),
404
-                'month' => $date_to_set_string->format('m'),
405
-                'day'   => $date_to_set_string->format('d'),
406
-            ];
407
-        } else {
408
-            // parse incoming string
409
-            $parsed = date_parse_from_format($this->_date_format, $date_to_set_string);
410
-        }
411
-        EEH_DTT_Helper::setTimezone($current, $this->_DateTimeZone);
412
-        return $current->setDate($parsed['year'], $parsed['month'], $parsed['day']);
413
-    }
414
-
415
-
416
-    /**
417
-     * This prepares the EE_DateTime value to be saved to the db as mysql timestamp (UTC +0 timezone).  When the
418
-     * datetime gets to this stage it should ALREADY be in UTC time
419
-     *
420
-     * @param DateTime $DateTime
421
-     * @return string formatted date time for given timezone
422
-     * @throws EE_Error
423
-     */
424
-    public function prepare_for_get($DateTime): string
425
-    {
426
-        return $this->_prepare_for_display($DateTime);
427
-    }
428
-
429
-
430
-    /**
431
-     * This differs from prepare_for_get in that it considers whether the internal $_timezone differs
432
-     * from the set wp timezone.  If so, then it returns the datetime string formatted via
433
-     * _pretty_date_format, and _pretty_time_format.  However, it also appends a timezone
434
-     * abbreviation to the date_string.
435
-     *
436
-     * @param mixed       $DateTime
437
-     * @param string|null $schema
438
-     * @return string
439
-     * @throws EE_Error
440
-     */
441
-    public function prepare_for_pretty_echoing($DateTime, ?string $schema = null): string
442
-    {
443
-        return $this->_prepare_for_display($DateTime, ($schema
444
-            ?: true));
445
-    }
446
-
447
-
448
-    /**
449
-     * This prepares the EE_DateTime value to be saved to the db as mysql timestamp (UTC +0
450
-     * timezone).
451
-     *
452
-     * @param DateTime|null $DateTime
453
-     * @param bool|string   $schema
454
-     * @return string
455
-     * @throws EE_Error
456
-     * @throws Exception
457
-     */
458
-    protected function _prepare_for_display(?DateTime $DateTime, $schema = false): string
459
-    {
460
-        if (! $DateTime instanceof DateTime) {
461
-            if ($this->_nullable) {
462
-                return '';
463
-            }
464
-            if (WP_DEBUG) {
465
-                throw new EE_Error(
466
-                    sprintf(
467
-                        esc_html__(
468
-                            'EE_Datetime_Field::_prepare_for_display requires a DateTime class to be the value for the $DateTime argument because the %s field is not nullable.',
469
-                            'event_espresso'
470
-                        ),
471
-                        $this->_nicename
472
-                    )
473
-                );
474
-            }
475
-            $DateTime = new DbSafeDateTime(EE_Datetime_Field::now);
476
-            EE_Error::add_error(
477
-                sprintf(
478
-                    esc_html__(
479
-                        'EE_Datetime_Field::_prepare_for_display requires a DateTime class to be the value for the $DateTime argument because the %s field is not nullable.  When WP_DEBUG is false, the value is set to "now" instead of throwing an exception.',
480
-                        'event_espresso'
481
-                    ),
482
-                    $this->_nicename
483
-                ),
484
-                __FILE__, __FUNCTION__, __LINE__
485
-            );
486
-        }
487
-        $format_string = $this->_get_date_time_output($schema);
488
-        EEH_DTT_Helper::setTimezone($DateTime, $this->_DateTimeZone);
489
-        if ($schema) {
490
-            $timezone_string = '';
491
-            if ($this->_display_timezone()) {
492
-                // must be explicit because schema could equal true.
493
-                if ($schema === 'no_html') {
494
-                    $timezone_string = ' (' . $DateTime->format('T') . ')';
495
-                } else {
496
-                    $timezone_string = ' <span class="ee_dtt_timezone_string">(' . $DateTime->format('T') . ')</span>';
497
-                }
498
-            }
499
-
500
-            return $DateTime->format($format_string) . $timezone_string;
501
-        }
502
-        return $DateTime->format($format_string);
503
-    }
504
-
505
-
506
-    /**
507
-     * This prepares the EE_DateTime value to be saved to the db as mysql timestamp (UTC +0
508
-     * timezone).
509
-     *
510
-     * @param mixed $datetime_value u
511
-     * @return string mysql timestamp in UTC
512
-     * @throws EE_Error
513
-     * @throws Exception
514
-     */
515
-    public function prepare_for_use_in_db($datetime_value): ?string
516
-    {
517
-        // we allow an empty value or DateTime object, but nothing else.
518
-        if (! empty($datetime_value) && ! $datetime_value instanceof DateTime) {
519
-            throw new EE_Error(
520
-                sprintf(
521
-                    esc_html__(
522
-                        'The incoming value being prepared for setting in the database must either be empty or a php DateTime object, instead of: %1$s %2$s',
523
-                        'event_espresso'
524
-                    ),
525
-                    '<br />',
526
-                    print_r($datetime_value, true)
527
-                )
528
-            );
529
-        }
530
-
531
-        if ($datetime_value instanceof DateTime) {
532
-            if (! $datetime_value instanceof DbSafeDateTime) {
533
-                $datetime_value = DbSafeDateTime::createFromDateTime($datetime_value);
534
-            }
535
-            EEH_DTT_Helper::setTimezone($datetime_value, $this->get_UTC_DateTimeZone());
536
-            return $datetime_value->format(
537
-                EE_Datetime_Field::mysql_timestamp_format
538
-            );
539
-        }
540
-
541
-        // if $datetime_value is empty, and ! $this->_nullable, use current_time() but set the GMT flag to true
542
-        return ! $this->_nullable
543
-            ? current_time('mysql', true)
544
-            : null;
545
-    }
546
-
547
-
548
-    /**
549
-     * This prepares the datetime for internal usage as a PHP DateTime object OR null (if nullable is
550
-     * allowed)
551
-     *
552
-     * @param string $datetime_string mysql timestamp in UTC
553
-     * @return  bool|DbSafeDateTime|null
554
-     * @throws EE_Error
555
-     * @throws Exception
556
-     */
557
-    public function prepare_for_set_from_db($datetime_string)
558
-    {
559
-        // if $datetime_value is empty, and ! $this->_nullable, just use time()
560
-        if (empty($datetime_string) && $this->_nullable) {
561
-            return null;
562
-        }
563
-        // datetime strings from the db should ALWAYS be in UTC+0, so use UTC_DateTimeZone when creating
564
-        $DateTime = empty($datetime_string)
565
-            ? new DbSafeDateTime(EE_Datetime_Field::now, $this->get_UTC_DateTimeZone())
566
-            : DbSafeDateTime::createFromFormat(
567
-                EE_Datetime_Field::mysql_timestamp_format,
568
-                $datetime_string,
569
-                $this->get_UTC_DateTimeZone()
570
-            );
571
-
572
-        if (! $DateTime instanceof DbSafeDateTime) {
573
-            // if still no datetime object, then let's just use now
574
-            $DateTime = new DbSafeDateTime(EE_Datetime_Field::now, $this->get_UTC_DateTimeZone());
575
-        }
576
-        // THEN apply the field's set DateTimeZone
577
-        EEH_DTT_Helper::setTimezone($DateTime, $this->_DateTimeZone);
578
-        return $DateTime;
579
-    }
580
-
581
-
582
-    /**
583
-     * All this method does is determine if we're going to display the timezone string or not on any output.
584
-     * To determine this we check if the set timezone offset is different than the blog's set timezone offset.
585
-     * If so, then true.
586
-     *
587
-     * @return bool true for yes false for no
588
-     * @throws Exception
589
-     */
590
-    protected function _display_timezone(): bool
591
-    {
592
-        // first let's do a comparison of timezone strings.
593
-        // If they match then we can get out without any further calculations
594
-        $blog_string = get_option('timezone_string');
595
-        if ($blog_string === $this->_timezone_string) {
596
-            return false;
597
-        }
598
-        // now we need to calc the offset for the timezone string so we can compare with the blog offset.
599
-        $this_offset = $this->get_timezone_offset($this->_DateTimeZone);
600
-        $blog_offset = $this->get_timezone_offset($this->get_blog_DateTimeZone());
601
-        // now compare
602
-        return $blog_offset !== $this_offset;
603
-    }
604
-
605
-
606
-    /**
607
-     * This method returns a php DateTime object for setting on the EE_Base_Class model.
608
-     * EE passes around DateTime objects because they are MUCH easier to manipulate and deal
609
-     * with.
610
-     *
611
-     * @param int|string|DateTime $date_string            This should be the incoming date string.  It's assumed to be
612
-     *                                                    in the format that is set on the date_field (or DateTime
613
-     *                                                    object)!
614
-     * @return DateTime
615
-     * @throws Exception
616
-     * @throws Exception
617
-     */
618
-    protected function _get_date_object($date_string)
619
-    {
620
-        // first if this is an empty date_string and nullable is allowed, just return null.
621
-        if ($this->_nullable && empty($date_string)) {
622
-            return null;
623
-        }
624
-
625
-        // if incoming date
626
-        if ($date_string instanceof DateTime) {
627
-            EEH_DTT_Helper::setTimezone($date_string, $this->_DateTimeZone);
628
-            return $date_string;
629
-        }
630
-        // if empty date_string and made it here.
631
-        // Return a datetime object for now in the given timezone.
632
-        if (empty($date_string)) {
633
-            return new DbSafeDateTime(EE_Datetime_Field::now, $this->_DateTimeZone);
634
-        }
635
-        // if $date_string is matches something that looks like a Unix timestamp let's just use it.
636
-        if (preg_match(EE_Datetime_Field::unix_timestamp_regex, $date_string)) {
637
-            try {
638
-                // This is operating under the assumption that the incoming Unix timestamp
639
-                // is an ACTUAL Unix timestamp and not the calculated one output by current_time('timestamp');
640
-                $DateTime = new DbSafeDateTime(EE_Datetime_Field::now, $this->_DateTimeZone);
641
-                $DateTime->setTimestamp($date_string);
642
-
643
-                return $DateTime;
644
-            } catch (Exception $e) {
645
-                // should be rare, but if things got fooled then let's just continue
646
-            }
647
-        }
648
-        // not a unix timestamp.  So we will use the set format on this object and set timezone to
649
-        // create the DateTime object.
650
-        $format = $this->_date_format . ' ' . $this->_time_format;
651
-        try {
652
-            $DateTime = DbSafeDateTime::createFromFormat($format, $date_string, $this->_DateTimeZone);
653
-            if (! $DateTime instanceof DbSafeDateTime) {
654
-                throw new EE_Error(
655
-                    sprintf(
656
-                        esc_html__('"%1$s" does not represent a valid Date Time in the format "%2$s".',
657
-                            'event_espresso'),
658
-                        $date_string,
659
-                        $format
660
-                    )
661
-                );
662
-            }
663
-        } catch (Exception $e) {
664
-            // if we made it here then likely then something went really wrong.
665
-            // Instead of throwing an exception, let's just return a DateTime object for now, in the set timezone.
666
-            $DateTime = new DbSafeDateTime(EE_Datetime_Field::now, $this->_DateTimeZone);
667
-        }
668
-
669
-        return $DateTime;
670
-    }
671
-
672
-
673
-    /**
674
-     * get_timezone_transitions
675
-     *
676
-     * @param DateTimeZone $DateTimeZone
677
-     * @param int|null     $time
678
-     * @param bool|null    $first_only
679
-     * @return array
680
-     */
681
-    public function get_timezone_transitions(
682
-        DateTimeZone $DateTimeZone,
683
-        ?int $time = null,
684
-        bool $first_only = true
685
-    ): array {
686
-        return EEH_DTT_Helper::get_timezone_transitions($DateTimeZone, $time, $first_only);
687
-    }
688
-
689
-
690
-    /**
691
-     * get_timezone_offset
692
-     *
693
-     * @param DateTimeZone    $DateTimeZone
694
-     * @param int|string|null $time
695
-     * @return mixed
696
-     * @throws DomainException
697
-     */
698
-    public function get_timezone_offset(DateTimeZone $DateTimeZone, $time = null)
699
-    {
700
-        return EEH_DTT_Helper::get_timezone_offset($DateTimeZone, $time);
701
-    }
702
-
703
-
704
-    /**
705
-     * This will take an incoming timezone string and return the abbreviation for that timezone
706
-     *
707
-     * @param string|null $timezone_string
708
-     * @return string           abbreviation
709
-     * @throws Exception
710
-     */
711
-    public function get_timezone_abbrev(?string $timezone_string): string
712
-    {
713
-        $timezone_string = EEH_DTT_Helper::get_valid_timezone_string($timezone_string);
714
-        $dateTime        = new DateTime(EE_Datetime_Field::now, new DateTimeZone($timezone_string));
715
-        return $dateTime->format('T');
716
-    }
717
-
718
-
719
-    /**
720
-     * Overrides the parent to allow for having a dynamic "now" value
721
-     *
722
-     * @return mixed
723
-     */
724
-    public function get_default_value()
725
-    {
726
-        if ($this->_default_value === EE_Datetime_Field::now) {
727
-            return time();
728
-        }
729
-        return parent::get_default_value();
730
-    }
731
-
732
-
733
-    /**
734
-     * Gets the default datetime object from the field's default time
735
-     *
736
-     * @return DbSafeDateTime|DateTime|null
737
-     * @throws InvalidArgumentException
738
-     * @throws InvalidDataTypeException
739
-     * @throws InvalidInterfaceException
740
-     * @throws Exception
741
-     * @since 4.9.66.p
742
-     */
743
-    public function getDefaultDateTimeObj()
744
-    {
745
-        $default_raw = $this->get_default_value();
746
-        if ($default_raw instanceof DateTime) {
747
-            return $default_raw;
748
-        }
749
-        if (is_null($default_raw)) {
750
-            return null;
751
-        }
752
-        $timezone_string = EEH_DTT_Helper::get_valid_timezone_string($this->get_timezone());
753
-        $timezone        = new DateTimeZone($timezone_string);
754
-
755
-        return new DbSafeDateTime(
756
-            $this->get_default_value(),
757
-            $timezone
758
-        );
759
-    }
760
-
761
-
762
-    public function getSchemaDescription(): string
763
-    {
764
-        return sprintf(
765
-            esc_html__('%s - the value for this field is in the timezone of the site.', 'event_espresso'),
766
-            $this->get_nicename()
767
-        );
768
-    }
20
+	/**
21
+	 * The pattern we're looking for is if only the characters 0-9 are found and there are only
22
+	 * 10 or more numbers (because 9 numbers even with all 9's would be sometime in 2001 )
23
+	 *
24
+	 * @type string unix_timestamp_regex
25
+	 */
26
+	const unix_timestamp_regex = '/[0-9]{10,}/';
27
+
28
+	/**
29
+	 * @type string mysql_timestamp_format
30
+	 */
31
+	const mysql_timestamp_format = 'Y-m-d H:i:s';
32
+
33
+	/**
34
+	 * @type string mysql_date_format
35
+	 */
36
+	const mysql_date_format = 'Y-m-d';
37
+
38
+	/**
39
+	 * @type string mysql_time_format
40
+	 */
41
+	const mysql_time_format = 'H:i:s';
42
+
43
+	/**
44
+	 * Const for using in the default value. If the field's default is set to this,
45
+	 * then we will return the time of calling `get_default_value()`, not
46
+	 * just the current time at construction
47
+	 */
48
+	const now = 'now';
49
+
50
+	/**
51
+	 * The following properties hold the default formats for date and time.
52
+	 * Defaults are set via the constructor and can be overridden on class instantiation.
53
+	 * However they can also be overridden later by the set_format() method
54
+	 * (and corresponding set_date_format, set_time_format methods);
55
+	 */
56
+
57
+	protected string        $_date_format        = '';
58
+
59
+	protected string        $_time_format        = '';
60
+
61
+	protected string        $_pretty_date_format = '';
62
+
63
+	protected string        $_pretty_time_format = '';
64
+
65
+	protected ?DateTimeZone $_DateTimeZone       = null;
66
+
67
+	protected ?DateTimeZone $_UTC_DateTimeZone   = null;
68
+
69
+	protected ?DateTimeZone $_blog_DateTimeZone  = null;
70
+
71
+
72
+	/**
73
+	 * This property holds how we want the output returned when getting a datetime string.  It is set for the
74
+	 * set_date_time_output() method.  By default this is empty.  When empty, we are assuming that we want both date
75
+	 * and time returned via getters.
76
+	 *
77
+	 * @var mixed (null|string)
78
+	 */
79
+	protected $_date_time_output;
80
+
81
+
82
+	/**
83
+	 * timezone string
84
+	 * This gets set by the constructor and can be changed by the "set_timezone()" method so that we know what timezone
85
+	 * incoming strings|timestamps are in.  This can also be used before a get to set what timezone you want strings
86
+	 * coming out of the object to be in.  Default timezone is the current WP timezone option setting
87
+	 */
88
+	protected ?string $_timezone_string = null;
89
+
90
+
91
+	/**
92
+	 * This holds whatever UTC offset for the blog (we automatically convert timezone strings into their related
93
+	 * offsets for comparison purposes).
94
+	 *
95
+	 * @var int
96
+	 */
97
+	protected int $_blog_offset;
98
+
99
+
100
+	/**
101
+	 * @param string      $table_column
102
+	 * @param string      $nice_name
103
+	 * @param bool        $nullable
104
+	 * @param string|null $default_value
105
+	 * @param string|null $timezone_string
106
+	 * @param string|null $date_format
107
+	 * @param string|null $time_format
108
+	 * @param string|null $pretty_date_format
109
+	 * @param string|null $pretty_time_format
110
+	 * @throws InvalidArgumentException
111
+	 * @throws Exception
112
+	 */
113
+	public function __construct(
114
+		string $table_column,
115
+		string $nice_name,
116
+		bool $nullable,
117
+		?string $default_value,
118
+		?string $timezone_string = '',
119
+		?string $date_format = '',
120
+		?string $time_format = '',
121
+		?string $pretty_date_format = '',
122
+		?string $pretty_time_format = ''
123
+	) {
124
+		$this->set_date_format($date_format);
125
+		$this->set_time_format($time_format);
126
+		$this->set_date_format($pretty_date_format, true);
127
+		$this->set_time_format($pretty_time_format, true);
128
+
129
+		parent::__construct($table_column, $nice_name, $nullable, $default_value);
130
+		$this->set_timezone($timezone_string);
131
+		$this->setSchemaFormat('date-time');
132
+	}
133
+
134
+
135
+	/**
136
+	 * @return DateTimeZone
137
+	 * @throws Exception
138
+	 */
139
+	public function get_UTC_DateTimeZone(): DateTimeZone
140
+	{
141
+		return $this->_UTC_DateTimeZone instanceof DateTimeZone
142
+			? $this->_UTC_DateTimeZone
143
+			: $this->_create_timezone_object_from_timezone_string('UTC');
144
+	}
145
+
146
+
147
+	/**
148
+	 * @return DateTimeZone
149
+	 * @throws Exception
150
+	 */
151
+	public function get_blog_DateTimeZone(): DateTimeZone
152
+	{
153
+		return $this->_blog_DateTimeZone instanceof DateTimeZone
154
+			? $this->_blog_DateTimeZone
155
+			: $this->_create_timezone_object_from_timezone_string();
156
+	}
157
+
158
+
159
+	/**
160
+	 * this prepares any incoming date data and make sure its converted to a utc unix timestamp
161
+	 *
162
+	 * @param string|int $value_inputted_for_field_on_model_object  could be a string formatted date time or int unix
163
+	 *                                                              timestamp
164
+	 * @return DateTime
165
+	 * @throws Exception
166
+	 */
167
+	public function prepare_for_set($value_inputted_for_field_on_model_object)
168
+	{
169
+		return $this->_get_date_object($value_inputted_for_field_on_model_object);
170
+	}
171
+
172
+
173
+	/**
174
+	 * This returns the format string to be used by getters depending on what the $_date_time_output property is set at.
175
+	 * getters need to know whether we're just returning the date or the time or both.  By default we return both.
176
+	 *
177
+	 * @param bool $pretty If we're returning the pretty formats or standard format string.
178
+	 * @return string    The final assembled format string.
179
+	 */
180
+	protected function _get_date_time_output(bool $pretty = false): string
181
+	{
182
+		switch ($this->_date_time_output) {
183
+			case 'time':
184
+				return $pretty
185
+					? $this->_pretty_time_format
186
+					: $this->_time_format;
187
+
188
+			case 'date':
189
+				return $pretty
190
+					? $this->_pretty_date_format
191
+					: $this->_date_format;
192
+
193
+			default:
194
+				return $pretty
195
+					? trim($this->_pretty_date_format . ' ' . $this->_pretty_time_format)
196
+					: trim($this->_date_format . ' ' . $this->_time_format);
197
+		}
198
+	}
199
+
200
+
201
+	/**
202
+	 * This just sets the $_date_time_output property so we can flag how date and times are formatted before being
203
+	 * returned (using the format properties)
204
+	 *
205
+	 * @param string|null $what acceptable values are 'time' or 'date'.
206
+	 *                          Any other value will be set but will always result
207
+	 *                          in both 'date' and 'time' being returned.
208
+	 * @return void
209
+	 */
210
+	public function set_date_time_output(?string $what = null)
211
+	{
212
+		$this->_date_time_output = $what;
213
+	}
214
+
215
+
216
+	/**
217
+	 * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
218
+	 * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
219
+	 * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp).
220
+	 * We also set some other properties in this method.
221
+	 *
222
+	 * @param string|null $timezone_string A valid timezone string as described by @link
223
+	 *                                     http://www.php.net/manual/en/timezones.php
224
+	 * @return void
225
+	 * @throws InvalidArgumentException
226
+	 * @throws InvalidDataTypeException
227
+	 * @throws InvalidInterfaceException
228
+	 * @throws Exception
229
+	 */
230
+	public function set_timezone(?string $timezone_string)
231
+	{
232
+		if (empty($timezone_string) && $this->_timezone_string !== null) {
233
+			// leave the timezone AS-IS if we already have one and
234
+			// the function arg didn't provide one
235
+			return;
236
+		}
237
+		$timezone_string        = EEH_DTT_Helper::get_valid_timezone_string($timezone_string);
238
+		$this->_timezone_string = ! empty($timezone_string)
239
+			? $timezone_string
240
+			: 'UTC';
241
+		$this->_DateTimeZone    = $this->_create_timezone_object_from_timezone_string($this->_timezone_string);
242
+	}
243
+
244
+
245
+	/**
246
+	 * _create_timezone_object_from_timezone_name
247
+	 *
248
+	 * @access protected
249
+	 * @param string|null $timezone_string
250
+	 * @return DateTimeZone
251
+	 * @throws InvalidArgumentException
252
+	 * @throws InvalidDataTypeException
253
+	 * @throws InvalidInterfaceException
254
+	 * @throws Exception
255
+	 */
256
+	protected function _create_timezone_object_from_timezone_string(?string $timezone_string = ''): DateTimeZone
257
+	{
258
+		return new DateTimeZone(EEH_DTT_Helper::get_valid_timezone_string($timezone_string));
259
+	}
260
+
261
+
262
+	/**
263
+	 * This just returns whatever is set for the current timezone.
264
+	 *
265
+	 * @access public
266
+	 * @return string timezone string
267
+	 */
268
+	public function get_timezone(): ?string
269
+	{
270
+		return $this->_timezone_string;
271
+	}
272
+
273
+
274
+	/**
275
+	 * set the $_date_format property
276
+	 *
277
+	 * @access public
278
+	 * @param string $format a new date format (corresponding to formats accepted by PHP date() function)
279
+	 * @param bool   $pretty Whether to set pretty format or not.
280
+	 * @return void
281
+	 */
282
+	public function set_date_format(string $format, bool $pretty = false)
283
+	{
284
+		if ($pretty) {
285
+			$this->_pretty_date_format = new DateFormat($format);
286
+		} else {
287
+			$this->_date_format = new DateFormat($format);
288
+		}
289
+	}
290
+
291
+
292
+	/**
293
+	 * return the $_date_format property value.
294
+	 *
295
+	 * @param bool $pretty Whether to get pretty format or not.
296
+	 * @return string
297
+	 */
298
+	public function get_date_format(bool $pretty = false): string
299
+	{
300
+		return $pretty
301
+			? $this->_pretty_date_format
302
+			: $this->_date_format;
303
+	}
304
+
305
+
306
+	/**
307
+	 * set the $_time_format property
308
+	 *
309
+	 * @access public
310
+	 * @param string $format a new time format (corresponding to formats accepted by PHP date() function)
311
+	 * @param bool   $pretty Whether to set pretty format or not.
312
+	 * @return void
313
+	 */
314
+	public function set_time_format(string $format, bool $pretty = false)
315
+	{
316
+		if ($pretty) {
317
+			$this->_pretty_time_format = new TimeFormat($format);
318
+		} else {
319
+			$this->_time_format = new TimeFormat($format);
320
+		}
321
+	}
322
+
323
+
324
+	/**
325
+	 * return the $_time_format property value.
326
+	 *
327
+	 * @param bool $pretty Whether to get pretty format or not.
328
+	 * @return string
329
+	 */
330
+	public function get_time_format(bool $pretty = false): string
331
+	{
332
+		return $pretty
333
+			? $this->_pretty_time_format
334
+			: $this->_time_format;
335
+	}
336
+
337
+
338
+	/**
339
+	 * set the $_pretty_date_format property
340
+	 *
341
+	 * @access public
342
+	 * @param string|null $format a new pretty date format (corresponding to formats accepted by PHP date() function)
343
+	 * @return void
344
+	 */
345
+	public function set_pretty_date_format(?string $format)
346
+	{
347
+		$this->set_date_format($format, true);
348
+	}
349
+
350
+
351
+	/**
352
+	 * set the $_pretty_time_format property
353
+	 *
354
+	 * @access public
355
+	 * @param string|null $format a new pretty time format (corresponding to formats accepted by PHP date() function)
356
+	 * @return void
357
+	 */
358
+	public function set_pretty_time_format(?string $format)
359
+	{
360
+		$this->set_time_format($format, true);
361
+	}
362
+
363
+
364
+	/**
365
+	 * Only sets the time portion of the datetime.
366
+	 *
367
+	 * @param string|DateTime $time_to_set_string like 8am OR a DateTime object.
368
+	 * @param DateTime        $current            current DateTime object for the datetime field
369
+	 * @return DateTime
370
+	 */
371
+	public function prepare_for_set_with_new_time($time_to_set_string, DateTime $current): DateTime
372
+	{
373
+		// if $time_to_set_string is datetime object, then let's use it to set the parse array.
374
+		// Otherwise parse the string.
375
+		if ($time_to_set_string instanceof DateTime) {
376
+			$parsed = [
377
+				'hour'   => $time_to_set_string->format('H'),
378
+				'minute' => $time_to_set_string->format('i'),
379
+				'second' => $time_to_set_string->format('s'),
380
+			];
381
+		} else {
382
+			// parse incoming string
383
+			$parsed = date_parse_from_format($this->_time_format, $time_to_set_string);
384
+		}
385
+		EEH_DTT_Helper::setTimezone($current, $this->_DateTimeZone);
386
+		return $current->setTime($parsed['hour'], $parsed['minute'], $parsed['second']);
387
+	}
388
+
389
+
390
+	/**
391
+	 * Only sets the date portion of the datetime.
392
+	 *
393
+	 * @param string|DateTime $date_to_set_string like Friday, January 8th or a DateTime object.
394
+	 * @param DateTime        $current            current DateTime object for the datetime field
395
+	 * @return DateTime
396
+	 */
397
+	public function prepare_for_set_with_new_date($date_to_set_string, DateTime $current): DateTime
398
+	{
399
+		// if $time_to_set_string is datetime object, then let's use it to set the parse array.
400
+		// Otherwise parse the string.
401
+		if ($date_to_set_string instanceof DateTime) {
402
+			$parsed = [
403
+				'year'  => $date_to_set_string->format('Y'),
404
+				'month' => $date_to_set_string->format('m'),
405
+				'day'   => $date_to_set_string->format('d'),
406
+			];
407
+		} else {
408
+			// parse incoming string
409
+			$parsed = date_parse_from_format($this->_date_format, $date_to_set_string);
410
+		}
411
+		EEH_DTT_Helper::setTimezone($current, $this->_DateTimeZone);
412
+		return $current->setDate($parsed['year'], $parsed['month'], $parsed['day']);
413
+	}
414
+
415
+
416
+	/**
417
+	 * This prepares the EE_DateTime value to be saved to the db as mysql timestamp (UTC +0 timezone).  When the
418
+	 * datetime gets to this stage it should ALREADY be in UTC time
419
+	 *
420
+	 * @param DateTime $DateTime
421
+	 * @return string formatted date time for given timezone
422
+	 * @throws EE_Error
423
+	 */
424
+	public function prepare_for_get($DateTime): string
425
+	{
426
+		return $this->_prepare_for_display($DateTime);
427
+	}
428
+
429
+
430
+	/**
431
+	 * This differs from prepare_for_get in that it considers whether the internal $_timezone differs
432
+	 * from the set wp timezone.  If so, then it returns the datetime string formatted via
433
+	 * _pretty_date_format, and _pretty_time_format.  However, it also appends a timezone
434
+	 * abbreviation to the date_string.
435
+	 *
436
+	 * @param mixed       $DateTime
437
+	 * @param string|null $schema
438
+	 * @return string
439
+	 * @throws EE_Error
440
+	 */
441
+	public function prepare_for_pretty_echoing($DateTime, ?string $schema = null): string
442
+	{
443
+		return $this->_prepare_for_display($DateTime, ($schema
444
+			?: true));
445
+	}
446
+
447
+
448
+	/**
449
+	 * This prepares the EE_DateTime value to be saved to the db as mysql timestamp (UTC +0
450
+	 * timezone).
451
+	 *
452
+	 * @param DateTime|null $DateTime
453
+	 * @param bool|string   $schema
454
+	 * @return string
455
+	 * @throws EE_Error
456
+	 * @throws Exception
457
+	 */
458
+	protected function _prepare_for_display(?DateTime $DateTime, $schema = false): string
459
+	{
460
+		if (! $DateTime instanceof DateTime) {
461
+			if ($this->_nullable) {
462
+				return '';
463
+			}
464
+			if (WP_DEBUG) {
465
+				throw new EE_Error(
466
+					sprintf(
467
+						esc_html__(
468
+							'EE_Datetime_Field::_prepare_for_display requires a DateTime class to be the value for the $DateTime argument because the %s field is not nullable.',
469
+							'event_espresso'
470
+						),
471
+						$this->_nicename
472
+					)
473
+				);
474
+			}
475
+			$DateTime = new DbSafeDateTime(EE_Datetime_Field::now);
476
+			EE_Error::add_error(
477
+				sprintf(
478
+					esc_html__(
479
+						'EE_Datetime_Field::_prepare_for_display requires a DateTime class to be the value for the $DateTime argument because the %s field is not nullable.  When WP_DEBUG is false, the value is set to "now" instead of throwing an exception.',
480
+						'event_espresso'
481
+					),
482
+					$this->_nicename
483
+				),
484
+				__FILE__, __FUNCTION__, __LINE__
485
+			);
486
+		}
487
+		$format_string = $this->_get_date_time_output($schema);
488
+		EEH_DTT_Helper::setTimezone($DateTime, $this->_DateTimeZone);
489
+		if ($schema) {
490
+			$timezone_string = '';
491
+			if ($this->_display_timezone()) {
492
+				// must be explicit because schema could equal true.
493
+				if ($schema === 'no_html') {
494
+					$timezone_string = ' (' . $DateTime->format('T') . ')';
495
+				} else {
496
+					$timezone_string = ' <span class="ee_dtt_timezone_string">(' . $DateTime->format('T') . ')</span>';
497
+				}
498
+			}
499
+
500
+			return $DateTime->format($format_string) . $timezone_string;
501
+		}
502
+		return $DateTime->format($format_string);
503
+	}
504
+
505
+
506
+	/**
507
+	 * This prepares the EE_DateTime value to be saved to the db as mysql timestamp (UTC +0
508
+	 * timezone).
509
+	 *
510
+	 * @param mixed $datetime_value u
511
+	 * @return string mysql timestamp in UTC
512
+	 * @throws EE_Error
513
+	 * @throws Exception
514
+	 */
515
+	public function prepare_for_use_in_db($datetime_value): ?string
516
+	{
517
+		// we allow an empty value or DateTime object, but nothing else.
518
+		if (! empty($datetime_value) && ! $datetime_value instanceof DateTime) {
519
+			throw new EE_Error(
520
+				sprintf(
521
+					esc_html__(
522
+						'The incoming value being prepared for setting in the database must either be empty or a php DateTime object, instead of: %1$s %2$s',
523
+						'event_espresso'
524
+					),
525
+					'<br />',
526
+					print_r($datetime_value, true)
527
+				)
528
+			);
529
+		}
530
+
531
+		if ($datetime_value instanceof DateTime) {
532
+			if (! $datetime_value instanceof DbSafeDateTime) {
533
+				$datetime_value = DbSafeDateTime::createFromDateTime($datetime_value);
534
+			}
535
+			EEH_DTT_Helper::setTimezone($datetime_value, $this->get_UTC_DateTimeZone());
536
+			return $datetime_value->format(
537
+				EE_Datetime_Field::mysql_timestamp_format
538
+			);
539
+		}
540
+
541
+		// if $datetime_value is empty, and ! $this->_nullable, use current_time() but set the GMT flag to true
542
+		return ! $this->_nullable
543
+			? current_time('mysql', true)
544
+			: null;
545
+	}
546
+
547
+
548
+	/**
549
+	 * This prepares the datetime for internal usage as a PHP DateTime object OR null (if nullable is
550
+	 * allowed)
551
+	 *
552
+	 * @param string $datetime_string mysql timestamp in UTC
553
+	 * @return  bool|DbSafeDateTime|null
554
+	 * @throws EE_Error
555
+	 * @throws Exception
556
+	 */
557
+	public function prepare_for_set_from_db($datetime_string)
558
+	{
559
+		// if $datetime_value is empty, and ! $this->_nullable, just use time()
560
+		if (empty($datetime_string) && $this->_nullable) {
561
+			return null;
562
+		}
563
+		// datetime strings from the db should ALWAYS be in UTC+0, so use UTC_DateTimeZone when creating
564
+		$DateTime = empty($datetime_string)
565
+			? new DbSafeDateTime(EE_Datetime_Field::now, $this->get_UTC_DateTimeZone())
566
+			: DbSafeDateTime::createFromFormat(
567
+				EE_Datetime_Field::mysql_timestamp_format,
568
+				$datetime_string,
569
+				$this->get_UTC_DateTimeZone()
570
+			);
571
+
572
+		if (! $DateTime instanceof DbSafeDateTime) {
573
+			// if still no datetime object, then let's just use now
574
+			$DateTime = new DbSafeDateTime(EE_Datetime_Field::now, $this->get_UTC_DateTimeZone());
575
+		}
576
+		// THEN apply the field's set DateTimeZone
577
+		EEH_DTT_Helper::setTimezone($DateTime, $this->_DateTimeZone);
578
+		return $DateTime;
579
+	}
580
+
581
+
582
+	/**
583
+	 * All this method does is determine if we're going to display the timezone string or not on any output.
584
+	 * To determine this we check if the set timezone offset is different than the blog's set timezone offset.
585
+	 * If so, then true.
586
+	 *
587
+	 * @return bool true for yes false for no
588
+	 * @throws Exception
589
+	 */
590
+	protected function _display_timezone(): bool
591
+	{
592
+		// first let's do a comparison of timezone strings.
593
+		// If they match then we can get out without any further calculations
594
+		$blog_string = get_option('timezone_string');
595
+		if ($blog_string === $this->_timezone_string) {
596
+			return false;
597
+		}
598
+		// now we need to calc the offset for the timezone string so we can compare with the blog offset.
599
+		$this_offset = $this->get_timezone_offset($this->_DateTimeZone);
600
+		$blog_offset = $this->get_timezone_offset($this->get_blog_DateTimeZone());
601
+		// now compare
602
+		return $blog_offset !== $this_offset;
603
+	}
604
+
605
+
606
+	/**
607
+	 * This method returns a php DateTime object for setting on the EE_Base_Class model.
608
+	 * EE passes around DateTime objects because they are MUCH easier to manipulate and deal
609
+	 * with.
610
+	 *
611
+	 * @param int|string|DateTime $date_string            This should be the incoming date string.  It's assumed to be
612
+	 *                                                    in the format that is set on the date_field (or DateTime
613
+	 *                                                    object)!
614
+	 * @return DateTime
615
+	 * @throws Exception
616
+	 * @throws Exception
617
+	 */
618
+	protected function _get_date_object($date_string)
619
+	{
620
+		// first if this is an empty date_string and nullable is allowed, just return null.
621
+		if ($this->_nullable && empty($date_string)) {
622
+			return null;
623
+		}
624
+
625
+		// if incoming date
626
+		if ($date_string instanceof DateTime) {
627
+			EEH_DTT_Helper::setTimezone($date_string, $this->_DateTimeZone);
628
+			return $date_string;
629
+		}
630
+		// if empty date_string and made it here.
631
+		// Return a datetime object for now in the given timezone.
632
+		if (empty($date_string)) {
633
+			return new DbSafeDateTime(EE_Datetime_Field::now, $this->_DateTimeZone);
634
+		}
635
+		// if $date_string is matches something that looks like a Unix timestamp let's just use it.
636
+		if (preg_match(EE_Datetime_Field::unix_timestamp_regex, $date_string)) {
637
+			try {
638
+				// This is operating under the assumption that the incoming Unix timestamp
639
+				// is an ACTUAL Unix timestamp and not the calculated one output by current_time('timestamp');
640
+				$DateTime = new DbSafeDateTime(EE_Datetime_Field::now, $this->_DateTimeZone);
641
+				$DateTime->setTimestamp($date_string);
642
+
643
+				return $DateTime;
644
+			} catch (Exception $e) {
645
+				// should be rare, but if things got fooled then let's just continue
646
+			}
647
+		}
648
+		// not a unix timestamp.  So we will use the set format on this object and set timezone to
649
+		// create the DateTime object.
650
+		$format = $this->_date_format . ' ' . $this->_time_format;
651
+		try {
652
+			$DateTime = DbSafeDateTime::createFromFormat($format, $date_string, $this->_DateTimeZone);
653
+			if (! $DateTime instanceof DbSafeDateTime) {
654
+				throw new EE_Error(
655
+					sprintf(
656
+						esc_html__('"%1$s" does not represent a valid Date Time in the format "%2$s".',
657
+							'event_espresso'),
658
+						$date_string,
659
+						$format
660
+					)
661
+				);
662
+			}
663
+		} catch (Exception $e) {
664
+			// if we made it here then likely then something went really wrong.
665
+			// Instead of throwing an exception, let's just return a DateTime object for now, in the set timezone.
666
+			$DateTime = new DbSafeDateTime(EE_Datetime_Field::now, $this->_DateTimeZone);
667
+		}
668
+
669
+		return $DateTime;
670
+	}
671
+
672
+
673
+	/**
674
+	 * get_timezone_transitions
675
+	 *
676
+	 * @param DateTimeZone $DateTimeZone
677
+	 * @param int|null     $time
678
+	 * @param bool|null    $first_only
679
+	 * @return array
680
+	 */
681
+	public function get_timezone_transitions(
682
+		DateTimeZone $DateTimeZone,
683
+		?int $time = null,
684
+		bool $first_only = true
685
+	): array {
686
+		return EEH_DTT_Helper::get_timezone_transitions($DateTimeZone, $time, $first_only);
687
+	}
688
+
689
+
690
+	/**
691
+	 * get_timezone_offset
692
+	 *
693
+	 * @param DateTimeZone    $DateTimeZone
694
+	 * @param int|string|null $time
695
+	 * @return mixed
696
+	 * @throws DomainException
697
+	 */
698
+	public function get_timezone_offset(DateTimeZone $DateTimeZone, $time = null)
699
+	{
700
+		return EEH_DTT_Helper::get_timezone_offset($DateTimeZone, $time);
701
+	}
702
+
703
+
704
+	/**
705
+	 * This will take an incoming timezone string and return the abbreviation for that timezone
706
+	 *
707
+	 * @param string|null $timezone_string
708
+	 * @return string           abbreviation
709
+	 * @throws Exception
710
+	 */
711
+	public function get_timezone_abbrev(?string $timezone_string): string
712
+	{
713
+		$timezone_string = EEH_DTT_Helper::get_valid_timezone_string($timezone_string);
714
+		$dateTime        = new DateTime(EE_Datetime_Field::now, new DateTimeZone($timezone_string));
715
+		return $dateTime->format('T');
716
+	}
717
+
718
+
719
+	/**
720
+	 * Overrides the parent to allow for having a dynamic "now" value
721
+	 *
722
+	 * @return mixed
723
+	 */
724
+	public function get_default_value()
725
+	{
726
+		if ($this->_default_value === EE_Datetime_Field::now) {
727
+			return time();
728
+		}
729
+		return parent::get_default_value();
730
+	}
731
+
732
+
733
+	/**
734
+	 * Gets the default datetime object from the field's default time
735
+	 *
736
+	 * @return DbSafeDateTime|DateTime|null
737
+	 * @throws InvalidArgumentException
738
+	 * @throws InvalidDataTypeException
739
+	 * @throws InvalidInterfaceException
740
+	 * @throws Exception
741
+	 * @since 4.9.66.p
742
+	 */
743
+	public function getDefaultDateTimeObj()
744
+	{
745
+		$default_raw = $this->get_default_value();
746
+		if ($default_raw instanceof DateTime) {
747
+			return $default_raw;
748
+		}
749
+		if (is_null($default_raw)) {
750
+			return null;
751
+		}
752
+		$timezone_string = EEH_DTT_Helper::get_valid_timezone_string($this->get_timezone());
753
+		$timezone        = new DateTimeZone($timezone_string);
754
+
755
+		return new DbSafeDateTime(
756
+			$this->get_default_value(),
757
+			$timezone
758
+		);
759
+	}
760
+
761
+
762
+	public function getSchemaDescription(): string
763
+	{
764
+		return sprintf(
765
+			esc_html__('%s - the value for this field is in the timezone of the site.', 'event_espresso'),
766
+			$this->get_nicename()
767
+		);
768
+	}
769 769
 }
Please login to merge, or discard this patch.
core/domain/entities/routing/handlers/shared/SessionRequests.php 1 patch
Indentation   +46 added lines, -46 removed lines patch added patch discarded remove patch
@@ -15,54 +15,54 @@
 block discarded – undo
15 15
  */
16 16
 class SessionRequests extends Route
17 17
 {
18
-    /**
19
-     * returns true if the current request matches this route
20
-     *
21
-     * @return bool
22
-     * @since   5.0.0.p
23
-     */
24
-    public function matchesCurrentRequest(): bool
25
-    {
26
-        return $this->request->isAdmin()
27
-               || $this->request->isEeAjax()
28
-               || $this->request->isFrontend()
29
-               || $this->request->isIframe()
30
-               || $this->request->isApi()
31
-               || $this->request->isWordPressApi();
32
-    }
18
+	/**
19
+	 * returns true if the current request matches this route
20
+	 *
21
+	 * @return bool
22
+	 * @since   5.0.0.p
23
+	 */
24
+	public function matchesCurrentRequest(): bool
25
+	{
26
+		return $this->request->isAdmin()
27
+			   || $this->request->isEeAjax()
28
+			   || $this->request->isFrontend()
29
+			   || $this->request->isIframe()
30
+			   || $this->request->isApi()
31
+			   || $this->request->isWordPressApi();
32
+	}
33 33
 
34 34
 
35
-    /**
36
-     * @since 5.0.0.p
37
-     */
38
-    protected function registerDependencies()
39
-    {
40
-        $this->dependency_map->registerDependencies(
41
-            'EE_Session',
42
-            [
43
-                'EventEspresso\core\services\cache\TransientCacheStorage'  => EE_Dependency_Map::load_from_cache,
44
-                'EventEspresso\core\domain\values\session\SessionLifespan' => EE_Dependency_Map::load_from_cache,
45
-                'EventEspresso\core\services\request\Request'              => EE_Dependency_Map::load_from_cache,
46
-                'EventEspresso\core\services\session\SessionStartHandler'  => EE_Dependency_Map::load_from_cache,
47
-                'EE_Encryption'                                            => EE_Dependency_Map::load_from_cache,
48
-            ]
49
-        );
50
-        $this->dependency_map->registerDependencies(
51
-            'EventEspresso\core\services\session\SessionStartHandler',
52
-            ['EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache]
53
-        );
54
-    }
35
+	/**
36
+	 * @since 5.0.0.p
37
+	 */
38
+	protected function registerDependencies()
39
+	{
40
+		$this->dependency_map->registerDependencies(
41
+			'EE_Session',
42
+			[
43
+				'EventEspresso\core\services\cache\TransientCacheStorage'  => EE_Dependency_Map::load_from_cache,
44
+				'EventEspresso\core\domain\values\session\SessionLifespan' => EE_Dependency_Map::load_from_cache,
45
+				'EventEspresso\core\services\request\Request'              => EE_Dependency_Map::load_from_cache,
46
+				'EventEspresso\core\services\session\SessionStartHandler'  => EE_Dependency_Map::load_from_cache,
47
+				'EE_Encryption'                                            => EE_Dependency_Map::load_from_cache,
48
+			]
49
+		);
50
+		$this->dependency_map->registerDependencies(
51
+			'EventEspresso\core\services\session\SessionStartHandler',
52
+			['EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache]
53
+		);
54
+	}
55 55
 
56 56
 
57
-    /**
58
-     * implements logic required to run during request
59
-     *
60
-     * @return bool
61
-     * @since   5.0.0.p
62
-     */
63
-    protected function requestHandler(): bool
64
-    {
65
-        $this->loader->getShared('EE_Session');
66
-        return true;
67
-    }
57
+	/**
58
+	 * implements logic required to run during request
59
+	 *
60
+	 * @return bool
61
+	 * @since   5.0.0.p
62
+	 */
63
+	protected function requestHandler(): bool
64
+	{
65
+		$this->loader->getShared('EE_Session');
66
+		return true;
67
+	}
68 68
 }
Please login to merge, or discard this patch.
core/services/helpers/datetime/HelperInterface.php 1 patch
Indentation   +90 added lines, -90 removed lines patch added patch discarded remove patch
@@ -15,94 +15,94 @@
 block discarded – undo
15 15
  */
16 16
 interface HelperInterface
17 17
 {
18
-    /**
19
-     * Ensures that a valid timezone string is returned.
20
-     *
21
-     * @param string $timezone_string  When not provided then attempt to use the timezone_string set in the WP Time
22
-     *                                 settings (or derive from set UTC offset).
23
-     * @return string
24
-     */
25
-    public function getValidTimezoneString($timezone_string = '');
26
-
27
-
28
-    /**
29
-     * The only purpose for this static method is to validate that the incoming timezone is a valid php timezone.
30
-     *
31
-     * @param string $timezone_string
32
-     * @param bool   $throw_error
33
-     * @return bool
34
-     */
35
-    public function validateTimezone($timezone_string, $throw_error = true);
36
-
37
-
38
-    /**
39
-     * Returns a timezone string for the provided gmt_offset.
40
-     * @param float|string $gmt_offset
41
-     * @return string
42
-     */
43
-    public function getTimezoneStringFromGmtOffset($gmt_offset = '');
44
-
45
-
46
-    /**
47
-     * Gets the site's GMT offset based on either the timezone string
48
-     * (in which case the gmt offset will vary depending on the location's
49
-     * observance of daylight savings time) or the gmt_offset wp option
50
-     *
51
-     * @return int  seconds offset
52
-     */
53
-    public function getSiteTimezoneGmtOffset();
54
-
55
-
56
-    /**
57
-     * Get timezone transitions
58
-     * @param DateTimeZone $date_time_zone
59
-     * @param int|null     $time
60
-     * @param bool         $first_only
61
-     * @return array
62
-     */
63
-    public function getTimezoneTransitions(DateTimeZone $date_time_zone, $time = null, $first_only = true);
64
-
65
-
66
-    /**
67
-     * Get Timezone offset for given timezone object
68
-     * @param DateTimeZone      $date_time_zone
69
-     * @param int|string|null   $time
70
-     * @return int
71
-     */
72
-    public function getTimezoneOffset(DateTimeZone $date_time_zone, $time = null);
73
-
74
-
75
-    /**
76
-     * Provide a timezone select input
77
-     * @param string $timezone_string
78
-     * @return string
79
-     */
80
-    public function timezoneSelectInput($timezone_string = '');
81
-
82
-
83
-    /**
84
-     * This method will take an incoming unix timestamp and add the offset to it for the given timezone_string.
85
-     * If no unix timestamp is given then time() is used.  If no timezone is given then the set timezone string for
86
-     * the site is used.
87
-     * This is used typically when using a Unix timestamp any core WP functions that expect their specially
88
-     * computed timestamp (i.e. date_i18n() )
89
-     *
90
-     * @param int    $unix_timestamp    if 0, then time() will be used.
91
-     * @param string $timezone_string timezone_string. If empty, then the current set timezone for the
92
-     *                                site will be used.
93
-     * @return int      unix_timestamp value with the offset applied for the given timezone.
94
-     */
95
-    public function getTimestampWithOffset($unix_timestamp = 0, $timezone_string = '');
96
-
97
-
98
-    /**
99
-     * Depending on PHP version,
100
-     * there might not be valid current timezone strings to match these gmt_offsets in its timezone tables.
101
-     * To get around that, for these fringe timezones we bump them to a known valid offset.
102
-     * This method should ONLY be called after first verifying an timezone_string cannot be retrieved for the offset.
103
-     *
104
-     * @param int $gmt_offset
105
-     * @return int
106
-     */
107
-    public function adjustInvalidGmtOffsets($gmt_offset);
18
+	/**
19
+	 * Ensures that a valid timezone string is returned.
20
+	 *
21
+	 * @param string $timezone_string  When not provided then attempt to use the timezone_string set in the WP Time
22
+	 *                                 settings (or derive from set UTC offset).
23
+	 * @return string
24
+	 */
25
+	public function getValidTimezoneString($timezone_string = '');
26
+
27
+
28
+	/**
29
+	 * The only purpose for this static method is to validate that the incoming timezone is a valid php timezone.
30
+	 *
31
+	 * @param string $timezone_string
32
+	 * @param bool   $throw_error
33
+	 * @return bool
34
+	 */
35
+	public function validateTimezone($timezone_string, $throw_error = true);
36
+
37
+
38
+	/**
39
+	 * Returns a timezone string for the provided gmt_offset.
40
+	 * @param float|string $gmt_offset
41
+	 * @return string
42
+	 */
43
+	public function getTimezoneStringFromGmtOffset($gmt_offset = '');
44
+
45
+
46
+	/**
47
+	 * Gets the site's GMT offset based on either the timezone string
48
+	 * (in which case the gmt offset will vary depending on the location's
49
+	 * observance of daylight savings time) or the gmt_offset wp option
50
+	 *
51
+	 * @return int  seconds offset
52
+	 */
53
+	public function getSiteTimezoneGmtOffset();
54
+
55
+
56
+	/**
57
+	 * Get timezone transitions
58
+	 * @param DateTimeZone $date_time_zone
59
+	 * @param int|null     $time
60
+	 * @param bool         $first_only
61
+	 * @return array
62
+	 */
63
+	public function getTimezoneTransitions(DateTimeZone $date_time_zone, $time = null, $first_only = true);
64
+
65
+
66
+	/**
67
+	 * Get Timezone offset for given timezone object
68
+	 * @param DateTimeZone      $date_time_zone
69
+	 * @param int|string|null   $time
70
+	 * @return int
71
+	 */
72
+	public function getTimezoneOffset(DateTimeZone $date_time_zone, $time = null);
73
+
74
+
75
+	/**
76
+	 * Provide a timezone select input
77
+	 * @param string $timezone_string
78
+	 * @return string
79
+	 */
80
+	public function timezoneSelectInput($timezone_string = '');
81
+
82
+
83
+	/**
84
+	 * This method will take an incoming unix timestamp and add the offset to it for the given timezone_string.
85
+	 * If no unix timestamp is given then time() is used.  If no timezone is given then the set timezone string for
86
+	 * the site is used.
87
+	 * This is used typically when using a Unix timestamp any core WP functions that expect their specially
88
+	 * computed timestamp (i.e. date_i18n() )
89
+	 *
90
+	 * @param int    $unix_timestamp    if 0, then time() will be used.
91
+	 * @param string $timezone_string timezone_string. If empty, then the current set timezone for the
92
+	 *                                site will be used.
93
+	 * @return int      unix_timestamp value with the offset applied for the given timezone.
94
+	 */
95
+	public function getTimestampWithOffset($unix_timestamp = 0, $timezone_string = '');
96
+
97
+
98
+	/**
99
+	 * Depending on PHP version,
100
+	 * there might not be valid current timezone strings to match these gmt_offsets in its timezone tables.
101
+	 * To get around that, for these fringe timezones we bump them to a known valid offset.
102
+	 * This method should ONLY be called after first verifying an timezone_string cannot be retrieved for the offset.
103
+	 *
104
+	 * @param int $gmt_offset
105
+	 * @return int
106
+	 */
107
+	public function adjustInvalidGmtOffsets($gmt_offset);
108 108
 }
Please login to merge, or discard this patch.
core/services/helpers/datetime/PhpCompatGreaterFiveSixHelper.php 1 patch
Indentation   +79 added lines, -79 removed lines patch added patch discarded remove patch
@@ -9,91 +9,91 @@
 block discarded – undo
9 9
 
10 10
 class PhpCompatGreaterFiveSixHelper extends AbstractHelper
11 11
 {
12
-    /**
13
-     * PhpCompatGreaterFiveSixHelper constructor.
14
-     *
15
-     * @throws DomainException
16
-     */
17
-    public function __construct()
18
-    {
19
-        if (PHP_VERSION_ID < 50600) {
20
-            throw new DomainException(
21
-                sprintf(
22
-                    esc_html__(
23
-                        'The %1$s is only usable on php versions greater than 5.6.  You\'ll want to use %2$s instead.',
24
-                        'event_espresso'
25
-                    ),
26
-                    __CLASS__,
27
-                    'EventEspresso\core\services\helpers\datetime\PhpCompatLessFiveSixHelper'
28
-                )
29
-            );
30
-        }
31
-    }
12
+	/**
13
+	 * PhpCompatGreaterFiveSixHelper constructor.
14
+	 *
15
+	 * @throws DomainException
16
+	 */
17
+	public function __construct()
18
+	{
19
+		if (PHP_VERSION_ID < 50600) {
20
+			throw new DomainException(
21
+				sprintf(
22
+					esc_html__(
23
+						'The %1$s is only usable on php versions greater than 5.6.  You\'ll want to use %2$s instead.',
24
+						'event_espresso'
25
+					),
26
+					__CLASS__,
27
+					'EventEspresso\core\services\helpers\datetime\PhpCompatLessFiveSixHelper'
28
+				)
29
+			);
30
+		}
31
+	}
32 32
 
33
-    /**
34
-     * Returns a timezone string for the provided gmt_offset.
35
-     * This is a valid timezone string that can be sent into DateTimeZone
36
-     *
37
-     * @param float|string $gmt_offset
38
-     * @return string
39
-     */
40
-    public function getTimezoneStringFromGmtOffset($gmt_offset = '')
41
-    {
42
-        $gmt_offset_or_timezone_string = $this->sanitizeInitialIncomingGmtOffsetForGettingTimezoneString($gmt_offset);
43
-        return is_float($gmt_offset_or_timezone_string)
44
-            ? $this->convertWpGmtOffsetForDateTimeZone($gmt_offset_or_timezone_string)
45
-            : $gmt_offset_or_timezone_string;
46
-    }
33
+	/**
34
+	 * Returns a timezone string for the provided gmt_offset.
35
+	 * This is a valid timezone string that can be sent into DateTimeZone
36
+	 *
37
+	 * @param float|string $gmt_offset
38
+	 * @return string
39
+	 */
40
+	public function getTimezoneStringFromGmtOffset($gmt_offset = '')
41
+	{
42
+		$gmt_offset_or_timezone_string = $this->sanitizeInitialIncomingGmtOffsetForGettingTimezoneString($gmt_offset);
43
+		return is_float($gmt_offset_or_timezone_string)
44
+			? $this->convertWpGmtOffsetForDateTimeZone($gmt_offset_or_timezone_string)
45
+			: $gmt_offset_or_timezone_string;
46
+	}
47 47
 
48 48
 
49 49
 
50
-    /**
51
-     * Returns a formatted offset for use as an argument for constructing DateTimeZone
52
-     * @param float $gmt_offset This should be a float representing the gmt_offset.
53
-     * @return string
54
-     */
55
-    protected function convertWpGmtOffsetForDateTimeZone($gmt_offset)
56
-    {
57
-        $gmt_offset = (float) $gmt_offset;
58
-        $is_negative = $gmt_offset < 0;
59
-        $gmt_offset *= 100;
60
-        $gmt_offset = absint($gmt_offset);
61
-        // negative and need zero padding?
62
-        if (strlen($gmt_offset) < 4) {
63
-            $gmt_offset = str_pad($gmt_offset, 4, '0', STR_PAD_LEFT);
64
-        }
65
-        $gmt_offset = $this->convertToTimeFraction($gmt_offset);
66
-        // return something like -1300, -0200 or +1300, +0200
67
-        return $is_negative ? '-' . $gmt_offset : '+' . $gmt_offset;
68
-    }
50
+	/**
51
+	 * Returns a formatted offset for use as an argument for constructing DateTimeZone
52
+	 * @param float $gmt_offset This should be a float representing the gmt_offset.
53
+	 * @return string
54
+	 */
55
+	protected function convertWpGmtOffsetForDateTimeZone($gmt_offset)
56
+	{
57
+		$gmt_offset = (float) $gmt_offset;
58
+		$is_negative = $gmt_offset < 0;
59
+		$gmt_offset *= 100;
60
+		$gmt_offset = absint($gmt_offset);
61
+		// negative and need zero padding?
62
+		if (strlen($gmt_offset) < 4) {
63
+			$gmt_offset = str_pad($gmt_offset, 4, '0', STR_PAD_LEFT);
64
+		}
65
+		$gmt_offset = $this->convertToTimeFraction($gmt_offset);
66
+		// return something like -1300, -0200 or +1300, +0200
67
+		return $is_negative ? '-' . $gmt_offset : '+' . $gmt_offset;
68
+	}
69 69
 
70 70
 
71
-    /**
72
-     * Converts something like `1550` to `1530` or `0275` to `0245`
73
-     * Incoming offset should be a positive value, this will mutate negative values. Be aware!
74
-     * @param int $offset
75
-     * @return mixed
76
-     */
77
-    protected function convertToTimeFraction($offset)
78
-    {
79
-        $first_part = substr($offset, 0, 2);
80
-        $second_part = substr($offset, 2, 2);
81
-        $second_part = str_replace(array('25', '50', '75'), array('15', '30', '45'), $second_part);
82
-        return $first_part . $second_part;
83
-    }
71
+	/**
72
+	 * Converts something like `1550` to `1530` or `0275` to `0245`
73
+	 * Incoming offset should be a positive value, this will mutate negative values. Be aware!
74
+	 * @param int $offset
75
+	 * @return mixed
76
+	 */
77
+	protected function convertToTimeFraction($offset)
78
+	{
79
+		$first_part = substr($offset, 0, 2);
80
+		$second_part = substr($offset, 2, 2);
81
+		$second_part = str_replace(array('25', '50', '75'), array('15', '30', '45'), $second_part);
82
+		return $first_part . $second_part;
83
+	}
84 84
 
85 85
 
86
-    /**
87
-     * Get Timezone offset for given timezone object
88
-     *
89
-     * @param DateTimeZone $date_time_zone
90
-     * @param null|int     $time
91
-     * @return int
92
-     */
93
-    public function getTimezoneOffset(DateTimezone $date_time_zone, $time = null)
94
-    {
95
-        $time = is_int($time) || $time === null ? $time : (int) strtotime($time);
96
-        $time = preg_match(EE_Datetime_Field::unix_timestamp_regex, $time) ? $time : time();
97
-        return $date_time_zone->getOffset(new DateTime('@' . $time));
98
-    }
86
+	/**
87
+	 * Get Timezone offset for given timezone object
88
+	 *
89
+	 * @param DateTimeZone $date_time_zone
90
+	 * @param null|int     $time
91
+	 * @return int
92
+	 */
93
+	public function getTimezoneOffset(DateTimezone $date_time_zone, $time = null)
94
+	{
95
+		$time = is_int($time) || $time === null ? $time : (int) strtotime($time);
96
+		$time = preg_match(EE_Datetime_Field::unix_timestamp_regex, $time) ? $time : time();
97
+		return $date_time_zone->getOffset(new DateTime('@' . $time));
98
+	}
99 99
 }
Please login to merge, or discard this patch.