Completed
Branch EDTR/master (8c09db)
by
unknown
09:27 queued 29s
created
core/domain/services/graphql/mutators/BulkEntityDelete.php 2 patches
Indentation   +66 added lines, -66 removed lines patch added patch discarded remove patch
@@ -18,76 +18,76 @@
 block discarded – undo
18 18
 
19 19
 class BulkEntityDelete extends EntityMutator
20 20
 {
21
-    /**
22
-     * Defines the mutation data modification closure.
23
-     *
24
-     * @return callable
25
-     */
26
-    public static function mutateAndGetPayload()
27
-    {
28
-        /**
29
-         * Updates an entity.
30
-         *
31
-         * @param array       $input   The input for the mutation
32
-         * @param AppContext  $context The AppContext passed down to all resolvers
33
-         * @param ResolveInfo $info    The ResolveInfo passed down to all resolvers
34
-         * @return array
35
-         * @throws UserError
36
-         * @throws ReflectionException
37
-         * @throws InvalidArgumentException
38
-         * @throws InvalidInterfaceException
39
-         * @throws InvalidDataTypeException
40
-         * @throws EE_Error
41
-         */
42
-        return static function ($input, AppContext $context, ResolveInfo $info) {
43
-            /**
44
-             * Stop now if a user isn't allowed to delete.
45
-             */
46
-            if (! current_user_can('ee_delete_events')) {
47
-                throw new UserError(
48
-                    esc_html__('Sorry, you do not have the required permissions to delete entities', 'event_espresso')
49
-                );
50
-            }
21
+	/**
22
+	 * Defines the mutation data modification closure.
23
+	 *
24
+	 * @return callable
25
+	 */
26
+	public static function mutateAndGetPayload()
27
+	{
28
+		/**
29
+		 * Updates an entity.
30
+		 *
31
+		 * @param array       $input   The input for the mutation
32
+		 * @param AppContext  $context The AppContext passed down to all resolvers
33
+		 * @param ResolveInfo $info    The ResolveInfo passed down to all resolvers
34
+		 * @return array
35
+		 * @throws UserError
36
+		 * @throws ReflectionException
37
+		 * @throws InvalidArgumentException
38
+		 * @throws InvalidInterfaceException
39
+		 * @throws InvalidDataTypeException
40
+		 * @throws EE_Error
41
+		 */
42
+		return static function ($input, AppContext $context, ResolveInfo $info) {
43
+			/**
44
+			 * Stop now if a user isn't allowed to delete.
45
+			 */
46
+			if (! current_user_can('ee_delete_events')) {
47
+				throw new UserError(
48
+					esc_html__('Sorry, you do not have the required permissions to delete entities', 'event_espresso')
49
+				);
50
+			}
51 51
 
52
-            $details = EntityReorder::prepareEntityDetailsFromInput($input);
52
+			$details = EntityReorder::prepareEntityDetailsFromInput($input);
53 53
 
54
-            $deletePermanently = ! empty($input['deletePermanently']);
54
+			$deletePermanently = ! empty($input['deletePermanently']);
55 55
 
56
-            $deletionMethod = __NAMESPACE__;
57
-            // if it's for datetimes.
58
-            if ($details['entityType'] === EEM_Datetime::instance()->item_name()) {
59
-                $deletionMethod .= '\DatetimeDelete::' . ($deletePermanently ? 'deleteDatetimeAndRelations' : 'trashDatetimeAndRelations');
60
-            } elseif ($details['entityType'] === EEM_Ticket::instance()->item_name()) {
61
-                $deletionMethod .= '\TicketDelete::' . ($deletePermanently ? 'deleteTicketAndRelations' : 'trashTicket');
62
-            } elseif ($details['entityType'] === EEM_Price::instance()->item_name()) {
63
-                $deletionMethod .= '\PriceDelete::deletePriceAndRelations';
64
-            } else {
65
-                throw new UserError(
66
-                    esc_html__(
67
-                        'A valid data model could not be obtained. Did you supply a valid entity type?',
68
-                        'event_espresso'
69
-                    )
70
-                );
71
-            }
56
+			$deletionMethod = __NAMESPACE__;
57
+			// if it's for datetimes.
58
+			if ($details['entityType'] === EEM_Datetime::instance()->item_name()) {
59
+				$deletionMethod .= '\DatetimeDelete::' . ($deletePermanently ? 'deleteDatetimeAndRelations' : 'trashDatetimeAndRelations');
60
+			} elseif ($details['entityType'] === EEM_Ticket::instance()->item_name()) {
61
+				$deletionMethod .= '\TicketDelete::' . ($deletePermanently ? 'deleteTicketAndRelations' : 'trashTicket');
62
+			} elseif ($details['entityType'] === EEM_Price::instance()->item_name()) {
63
+				$deletionMethod .= '\PriceDelete::deletePriceAndRelations';
64
+			} else {
65
+				throw new UserError(
66
+					esc_html__(
67
+						'A valid data model could not be obtained. Did you supply a valid entity type?',
68
+						'event_espresso'
69
+					)
70
+				);
71
+			}
72 72
 
73
-            $deleted = [];
74
-            $failed  = [];
73
+			$deleted = [];
74
+			$failed  = [];
75 75
 
76
-            foreach ($details['entityDbids'] as $key => $entityDbid) {
77
-                $guid = $details['entityGuids'][ $key ];
78
-                $entity = $details['entities'][ $entityDbid ];
79
-                try {
80
-                    $result = $deletionMethod($entity);
81
-                    EntityMutator::validateResults($result);
82
-                    // we are here it means the deletion was successful.
83
-                    $deleted[] = $guid;
84
-                } catch (Exception $e) {
85
-                    // sorry mate, couldn't help you :(
86
-                    $failed[] = $guid;
87
-                }
88
-            }
76
+			foreach ($details['entityDbids'] as $key => $entityDbid) {
77
+				$guid = $details['entityGuids'][ $key ];
78
+				$entity = $details['entities'][ $entityDbid ];
79
+				try {
80
+					$result = $deletionMethod($entity);
81
+					EntityMutator::validateResults($result);
82
+					// we are here it means the deletion was successful.
83
+					$deleted[] = $guid;
84
+				} catch (Exception $e) {
85
+					// sorry mate, couldn't help you :(
86
+					$failed[] = $guid;
87
+				}
88
+			}
89 89
 
90
-            return compact('deleted', 'failed');
91
-        };
92
-    }
90
+			return compact('deleted', 'failed');
91
+		};
92
+	}
93 93
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -39,11 +39,11 @@  discard block
 block discarded – undo
39 39
          * @throws InvalidDataTypeException
40 40
          * @throws EE_Error
41 41
          */
42
-        return static function ($input, AppContext $context, ResolveInfo $info) {
42
+        return static function($input, AppContext $context, ResolveInfo $info) {
43 43
             /**
44 44
              * Stop now if a user isn't allowed to delete.
45 45
              */
46
-            if (! current_user_can('ee_delete_events')) {
46
+            if ( ! current_user_can('ee_delete_events')) {
47 47
                 throw new UserError(
48 48
                     esc_html__('Sorry, you do not have the required permissions to delete entities', 'event_espresso')
49 49
                 );
@@ -56,9 +56,9 @@  discard block
 block discarded – undo
56 56
             $deletionMethod = __NAMESPACE__;
57 57
             // if it's for datetimes.
58 58
             if ($details['entityType'] === EEM_Datetime::instance()->item_name()) {
59
-                $deletionMethod .= '\DatetimeDelete::' . ($deletePermanently ? 'deleteDatetimeAndRelations' : 'trashDatetimeAndRelations');
59
+                $deletionMethod .= '\DatetimeDelete::'.($deletePermanently ? 'deleteDatetimeAndRelations' : 'trashDatetimeAndRelations');
60 60
             } elseif ($details['entityType'] === EEM_Ticket::instance()->item_name()) {
61
-                $deletionMethod .= '\TicketDelete::' . ($deletePermanently ? 'deleteTicketAndRelations' : 'trashTicket');
61
+                $deletionMethod .= '\TicketDelete::'.($deletePermanently ? 'deleteTicketAndRelations' : 'trashTicket');
62 62
             } elseif ($details['entityType'] === EEM_Price::instance()->item_name()) {
63 63
                 $deletionMethod .= '\PriceDelete::deletePriceAndRelations';
64 64
             } else {
@@ -74,8 +74,8 @@  discard block
 block discarded – undo
74 74
             $failed  = [];
75 75
 
76 76
             foreach ($details['entityDbids'] as $key => $entityDbid) {
77
-                $guid = $details['entityGuids'][ $key ];
78
-                $entity = $details['entities'][ $entityDbid ];
77
+                $guid = $details['entityGuids'][$key];
78
+                $entity = $details['entities'][$entityDbid];
79 79
                 try {
80 80
                     $result = $deletionMethod($entity);
81 81
                     EntityMutator::validateResults($result);
Please login to merge, or discard this patch.
core/domain/services/graphql/enums/ModelNameEnum.php 1 patch
Indentation   +27 added lines, -27 removed lines patch added patch discarded remove patch
@@ -18,33 +18,33 @@
 block discarded – undo
18 18
 class ModelNameEnum extends EnumBase
19 19
 {
20 20
 
21
-    /**
22
-     * ModelNameEnum constructor.
23
-     */
24
-    public function __construct()
25
-    {
26
-        $this->setName($this->namespace . 'ModelNameEnum');
27
-        $this->setDescription(esc_html__('Entity model name', 'event_espresso'));
28
-        parent::__construct();
29
-    }
21
+	/**
22
+	 * ModelNameEnum constructor.
23
+	 */
24
+	public function __construct()
25
+	{
26
+		$this->setName($this->namespace . 'ModelNameEnum');
27
+		$this->setDescription(esc_html__('Entity model name', 'event_espresso'));
28
+		parent::__construct();
29
+	}
30 30
 
31 31
 
32
-    /**
33
-     * @return array
34
-     * @since $VID:$
35
-     */
36
-    protected function getValues()
37
-    {
38
-        return [
39
-            'DATETIME' => [
40
-                'value' => EEM_Datetime::instance()->item_name(),
41
-            ],
42
-            'TICKET'   => [
43
-                'value' => EEM_Ticket::instance()->item_name(),
44
-            ],
45
-            'PRICE'    => [
46
-                'value' => EEM_Price::instance()->item_name(),
47
-            ],
48
-        ];
49
-    }
32
+	/**
33
+	 * @return array
34
+	 * @since $VID:$
35
+	 */
36
+	protected function getValues()
37
+	{
38
+		return [
39
+			'DATETIME' => [
40
+				'value' => EEM_Datetime::instance()->item_name(),
41
+			],
42
+			'TICKET'   => [
43
+				'value' => EEM_Ticket::instance()->item_name(),
44
+			],
45
+			'PRICE'    => [
46
+				'value' => EEM_Price::instance()->item_name(),
47
+			],
48
+		];
49
+	}
50 50
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Ticket.class.php 3 patches
Doc Comments   +2 added lines, -1 removed lines patch added patch discarded remove patch
@@ -137,7 +137,8 @@
 block discarded – undo
137 137
      *                               relevant status const
138 138
      * @param bool | null $remaining if it is already known that tickets are available, then simply pass a bool to save
139 139
      *                               further processing
140
-     * @return mixed status int if the display string isn't requested
140
+     * @param boolean $remaining
141
+     * @return string status int if the display string isn't requested
141 142
      * @throws EE_Error
142 143
      * @throws ReflectionException
143 144
      */
Please login to merge, or discard this patch.
Indentation   +1871 added lines, -1871 removed lines patch added patch discarded remove patch
@@ -14,1879 +14,1879 @@
 block discarded – undo
14 14
 class EE_Ticket extends EE_Soft_Delete_Base_Class implements EEI_Line_Item_Object, EEI_Event_Relation, EEI_Has_Icon
15 15
 {
16 16
 
17
-    /**
18
-     * TicKet Sold out:
19
-     * constant used by ticket_status() to indicate that a ticket is sold out
20
-     * and no longer available for purchases
21
-     */
22
-    const sold_out = 'TKS';
23
-
24
-    /**
25
-     * TicKet Expired:
26
-     * constant used by ticket_status() to indicate that a ticket is expired
27
-     * and no longer available for purchase
28
-     */
29
-    const expired = 'TKE';
30
-
31
-    /**
32
-     * TicKet Archived:
33
-     * constant used by ticket_status() to indicate that a ticket is archived
34
-     * and no longer available for purchase
35
-     */
36
-    const archived = 'TKA';
37
-
38
-    /**
39
-     * TicKet Pending:
40
-     * constant used by ticket_status() to indicate that a ticket is pending
41
-     * and is NOT YET available for purchase
42
-     */
43
-    const pending = 'TKP';
44
-
45
-    /**
46
-     * TicKet On sale:
47
-     * constant used by ticket_status() to indicate that a ticket is On Sale
48
-     * and IS available for purchase
49
-     */
50
-    const onsale = 'TKO';
51
-
52
-    /**
53
-     * extra meta key for tracking ticket reservations
54
-     *
55
-     * @type string
56
-     */
57
-    const META_KEY_TICKET_RESERVATIONS = 'ticket_reservations';
58
-
59
-    /**
60
-     * cached result from method of the same name
61
-     *
62
-     * @var float $_ticket_total_with_taxes
63
-     */
64
-    private $_ticket_total_with_taxes;
65
-
66
-
67
-    /**
68
-     * @param array  $props_n_values          incoming values
69
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
70
-     *                                        used.)
71
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
72
-     *                                        date_format and the second value is the time format
73
-     * @return EE_Ticket
74
-     * @throws EE_Error
75
-     * @throws ReflectionException
76
-     */
77
-    public static function new_instance($props_n_values = [], $timezone = null, $date_formats = [])
78
-    {
79
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
80
-        return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
81
-    }
82
-
83
-
84
-    /**
85
-     * @param array  $props_n_values  incoming values from the database
86
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
87
-     *                                the website will be used.
88
-     * @return EE_Ticket
89
-     * @throws EE_Error
90
-     * @throws ReflectionException
91
-     */
92
-    public static function new_instance_from_db($props_n_values = [], $timezone = null)
93
-    {
94
-        return new self($props_n_values, true, $timezone);
95
-    }
96
-
97
-
98
-    /**
99
-     * @return bool
100
-     * @throws EE_Error
101
-     * @throws ReflectionException
102
-     */
103
-    public function parent()
104
-    {
105
-        return $this->get('TKT_parent');
106
-    }
107
-
108
-
109
-    /**
110
-     * return if a ticket has quantities available for purchase
111
-     *
112
-     * @param int $DTT_ID the primary key for a particular datetime
113
-     * @return boolean
114
-     * @throws EE_Error
115
-     * @throws ReflectionException
116
-     */
117
-    public function available($DTT_ID = 0)
118
-    {
119
-        // are we checking availability for a particular datetime ?
120
-        if ($DTT_ID) {
121
-            // get that datetime object
122
-            $datetime = $this->get_first_related('Datetime', [['DTT_ID' => $DTT_ID]]);
123
-            // if  ticket sales for this datetime have exceeded the reg limit...
124
-            if ($datetime instanceof EE_Datetime && $datetime->sold_out()) {
125
-                return false;
126
-            }
127
-        }
128
-        // datetime is still open for registration, but is this ticket sold out ?
129
-        return $this->qty() < 1 || $this->qty() > $this->sold();
130
-    }
131
-
132
-
133
-    /**
134
-     * Using the start date and end date this method calculates whether the ticket is On Sale, Pending, or Expired
135
-     *
136
-     * @param bool        $display   true = we'll return a localized string, otherwise we just return the value of the
137
-     *                               relevant status const
138
-     * @param bool | null $remaining if it is already known that tickets are available, then simply pass a bool to save
139
-     *                               further processing
140
-     * @return mixed status int if the display string isn't requested
141
-     * @throws EE_Error
142
-     * @throws ReflectionException
143
-     */
144
-    public function ticket_status($display = false, $remaining = null)
145
-    {
146
-        $remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
147
-        if (! $remaining) {
148
-            return $display ? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence') : EE_Ticket::sold_out;
149
-        }
150
-        if ($this->get('TKT_deleted')) {
151
-            return $display ? EEH_Template::pretty_status(EE_Ticket::archived, false, 'sentence') : EE_Ticket::archived;
152
-        }
153
-        if ($this->is_expired()) {
154
-            return $display ? EEH_Template::pretty_status(EE_Ticket::expired, false, 'sentence') : EE_Ticket::expired;
155
-        }
156
-        if ($this->is_pending()) {
157
-            return $display ? EEH_Template::pretty_status(EE_Ticket::pending, false, 'sentence') : EE_Ticket::pending;
158
-        }
159
-        if ($this->is_on_sale()) {
160
-            return $display ? EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence') : EE_Ticket::onsale;
161
-        }
162
-        return '';
163
-    }
164
-
165
-
166
-    /**
167
-     * The purpose of this method is to simply return a boolean for whether there are any tickets remaining for sale
168
-     * considering ALL the factors used for figuring that out.
169
-     *
170
-     * @access public
171
-     * @param int $DTT_ID if an int above 0 is included here then we get a specific dtt.
172
-     * @return boolean         true = tickets remaining, false not.
173
-     * @throws EE_Error
174
-     * @throws ReflectionException
175
-     */
176
-    public function is_remaining($DTT_ID = 0)
177
-    {
178
-        $num_remaining = $this->remaining($DTT_ID);
179
-        if ($num_remaining === 0) {
180
-            return false;
181
-        }
182
-        if ($num_remaining > 0 && $num_remaining < $this->min()) {
183
-            return false;
184
-        }
185
-        return true;
186
-    }
187
-
188
-
189
-    /**
190
-     * return the total number of tickets available for purchase
191
-     *
192
-     * @param int $DTT_ID  the primary key for a particular datetime.
193
-     *                     set to 0 for all related datetimes
194
-     * @return int
195
-     * @throws EE_Error
196
-     * @throws ReflectionException
197
-     */
198
-    public function remaining($DTT_ID = 0)
199
-    {
200
-        return $this->real_quantity_on_ticket('saleable', $DTT_ID);
201
-    }
202
-
203
-
204
-    /**
205
-     * Gets min
206
-     *
207
-     * @return int
208
-     * @throws EE_Error
209
-     * @throws ReflectionException
210
-     */
211
-    public function min()
212
-    {
213
-        return $this->get('TKT_min');
214
-    }
215
-
216
-
217
-    /**
218
-     * return if a ticket is no longer available cause its available dates have expired.
219
-     *
220
-     * @return boolean
221
-     * @throws EE_Error
222
-     * @throws ReflectionException
223
-     */
224
-    public function is_expired()
225
-    {
226
-        return ($this->get_raw('TKT_end_date') < time());
227
-    }
228
-
229
-
230
-    /**
231
-     * Return if a ticket is yet to go on sale or not
232
-     *
233
-     * @return boolean
234
-     * @throws EE_Error
235
-     * @throws ReflectionException
236
-     */
237
-    public function is_pending()
238
-    {
239
-        return ($this->get_raw('TKT_start_date') >= time());
240
-    }
241
-
242
-
243
-    /**
244
-     * Return if a ticket is on sale or not
245
-     *
246
-     * @return boolean
247
-     * @throws EE_Error
248
-     * @throws ReflectionException
249
-     */
250
-    public function is_on_sale()
251
-    {
252
-        return ($this->get_raw('TKT_start_date') <= time() && $this->get_raw('TKT_end_date') >= time());
253
-    }
254
-
255
-
256
-    /**
257
-     * This returns the chronologically last datetime that this ticket is associated with
258
-     *
259
-     * @param string $date_format
260
-     * @param string $conjunction - conjunction junction what's your function ? this string joins the start date with
261
-     *                            the end date ie: Jan 01 "to" Dec 31
262
-     * @return string
263
-     * @throws EE_Error
264
-     * @throws ReflectionException
265
-     */
266
-    public function date_range($date_format = '', $conjunction = ' - ')
267
-    {
268
-        $date_format = ! empty($date_format) ? $date_format : $this->_dt_frmt;
269
-        $first_date  = $this->first_datetime() instanceof EE_Datetime
270
-            ? $this->first_datetime()->get_i18n_datetime('DTT_EVT_start', $date_format)
271
-            : '';
272
-        $last_date   = $this->last_datetime() instanceof EE_Datetime
273
-            ? $this->last_datetime()->get_i18n_datetime('DTT_EVT_end', $date_format)
274
-            : '';
275
-
276
-        return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
277
-    }
278
-
279
-
280
-    /**
281
-     * This returns the chronologically first datetime that this ticket is associated with
282
-     *
283
-     * @return EE_Datetime
284
-     * @throws EE_Error
285
-     * @throws ReflectionException
286
-     */
287
-    public function first_datetime()
288
-    {
289
-        $datetimes = $this->datetimes(['limit' => 1]);
290
-        return reset($datetimes);
291
-    }
292
-
293
-
294
-    /**
295
-     * Gets all the datetimes this ticket can be used for attending.
296
-     * Unless otherwise specified, orders datetimes by start date.
297
-     *
298
-     * @param array $query_params @see
299
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
300
-     * @return EE_Datetime[]|EE_Base_Class[]
301
-     * @throws EE_Error
302
-     * @throws ReflectionException
303
-     */
304
-    public function datetimes($query_params = [])
305
-    {
306
-        if (! isset($query_params['order_by'])) {
307
-            $query_params['order_by']['DTT_order'] = 'ASC';
308
-        }
309
-        return $this->get_many_related('Datetime', $query_params);
310
-    }
311
-
312
-
313
-    /**
314
-     * This returns the chronologically last datetime that this ticket is associated with
315
-     *
316
-     * @return EE_Datetime
317
-     * @throws EE_Error
318
-     * @throws ReflectionException
319
-     */
320
-    public function last_datetime()
321
-    {
322
-        $datetimes = $this->datetimes(['limit' => 1, 'order_by' => ['DTT_EVT_start' => 'DESC']]);
323
-        return end($datetimes);
324
-    }
325
-
326
-
327
-    /**
328
-     * This returns the total tickets sold depending on the given parameters.
329
-     *
330
-     * @param string $what    Can be one of two options: 'ticket', 'datetime'.
331
-     *                        'ticket' = total ticket sales for all datetimes this ticket is related to
332
-     *                        'datetime' = total ticket sales for a specified datetime (required $dtt_id)
333
-     *                        'datetime' = total ticket sales in the datetime_ticket table.
334
-     *                        If $dtt_id is not given then we return an array of sales indexed by datetime.
335
-     *                        If $dtt_id IS given then we return the tickets sold for that given datetime.
336
-     * @param int    $dtt_id  [optional] include the dtt_id with $what = 'datetime'.
337
-     * @return mixed (array|int)          how many tickets have sold
338
-     * @throws EE_Error
339
-     * @throws ReflectionException
340
-     */
341
-    public function tickets_sold($what = 'ticket', $dtt_id = null)
342
-    {
343
-        $total        = 0;
344
-        $tickets_sold = $this->_all_tickets_sold();
345
-        switch ($what) {
346
-            case 'ticket':
347
-                return $tickets_sold['ticket'];
348
-                break;
349
-            case 'datetime':
350
-                if (empty($tickets_sold['datetime'])) {
351
-                    return $total;
352
-                }
353
-                if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
354
-                    EE_Error::add_error(
355
-                        __(
356
-                            'You\'ve requested the amount of tickets sold for a given ticket and datetime, however there are no records for the datetime id you included.  Are you SURE that is a datetime related to this ticket?',
357
-                            'event_espresso'
358
-                        ),
359
-                        __FILE__,
360
-                        __FUNCTION__,
361
-                        __LINE__
362
-                    );
363
-                    return $total;
364
-                }
365
-                return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
366
-                break;
367
-            default:
368
-                return $total;
369
-        }
370
-    }
371
-
372
-
373
-    /**
374
-     * This returns an array indexed by datetime_id for tickets sold with this ticket.
375
-     *
376
-     * @return EE_Ticket[]
377
-     * @throws EE_Error
378
-     * @throws ReflectionException
379
-     */
380
-    protected function _all_tickets_sold()
381
-    {
382
-        $datetimes    = $this->get_many_related('Datetime');
383
-        $tickets_sold = [];
384
-        if (! empty($datetimes)) {
385
-            foreach ($datetimes as $datetime) {
386
-                $tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
387
-            }
388
-        }
389
-        // Tickets sold
390
-        $tickets_sold['ticket'] = $this->sold();
391
-        return $tickets_sold;
392
-    }
393
-
394
-
395
-    /**
396
-     * This returns the base price object for the ticket.
397
-     *
398
-     * @param bool $return_array whether to return as an array indexed by price id or just the object.
399
-     * @return EE_Price|EE_Base_Class|EE_Price[]|EE_Base_Class[]
400
-     * @throws EE_Error
401
-     * @throws ReflectionException
402
-     */
403
-    public function base_price($return_array = false)
404
-    {
405
-        $_where = ['Price_Type.PBT_ID' => EEM_Price_Type::base_type_base_price];
406
-        return $return_array
407
-            ? $this->get_many_related('Price', [$_where])
408
-            : $this->get_first_related('Price', [$_where]);
409
-    }
410
-
411
-
412
-    /**
413
-     * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
414
-     *
415
-     * @access public
416
-     * @return EE_Price[]
417
-     * @throws EE_Error
418
-     * @throws ReflectionException
419
-     */
420
-    public function price_modifiers()
421
-    {
422
-        $query_params = [
423
-            0 => [
424
-                'Price_Type.PBT_ID' => [
425
-                    'NOT IN',
426
-                    [EEM_Price_Type::base_type_base_price, EEM_Price_Type::base_type_tax],
427
-                ],
428
-            ],
429
-        ];
430
-        return $this->prices($query_params);
431
-    }
432
-
433
-
434
-    /**
435
-     * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
436
-     *
437
-     * @access public
438
-     * @return EE_Price[]
439
-     * @throws EE_Error
440
-     * @throws ReflectionException
441
-     */
442
-    public function tax_price_modifiers()
443
-    {
444
-        $query_params = [
445
-            0 => [
446
-                'Price_Type.PBT_ID' => EEM_Price_Type::base_type_tax,
447
-            ],
448
-        ];
449
-        return $this->prices($query_params);
450
-    }
451
-
452
-
453
-    /**
454
-     * Gets all the prices that combine to form the final price of this ticket
455
-     *
456
-     * @param array $query_params @see
457
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
458
-     * @return EE_Price[]|EE_Base_Class[]
459
-     * @throws EE_Error
460
-     * @throws ReflectionException
461
-     */
462
-    public function prices($query_params = [])
463
-    {
464
-        return $this->get_many_related('Price', $query_params);
465
-    }
466
-
467
-
468
-    /**
469
-     * Gets all the ticket datetimes (ie, relations between datetimes and tickets)
470
-     *
471
-     * @param array $query_params @see
472
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
473
-     * @return EE_Datetime_Ticket|EE_Base_Class[]
474
-     * @throws EE_Error
475
-     * @throws ReflectionException
476
-     */
477
-    public function datetime_tickets($query_params = [])
478
-    {
479
-        return $this->get_many_related('Datetime_Ticket', $query_params);
480
-    }
481
-
482
-
483
-    /**
484
-     * Gets all the datetimes from the db ordered by DTT_order
485
-     *
486
-     * @param boolean $show_expired
487
-     * @param boolean $show_deleted
488
-     * @return EE_Datetime[]
489
-     * @throws EE_Error
490
-     * @throws ReflectionException
491
-     */
492
-    public function datetimes_ordered($show_expired = true, $show_deleted = false)
493
-    {
494
-        return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_ticket_ordered_by_DTT_order(
495
-            $this->ID(),
496
-            $show_expired,
497
-            $show_deleted
498
-        );
499
-    }
500
-
501
-
502
-    /**
503
-     * Gets ID
504
-     *
505
-     * @return string
506
-     * @throws EE_Error
507
-     * @throws ReflectionException
508
-     */
509
-    public function ID()
510
-    {
511
-        return $this->get('TKT_ID');
512
-    }
513
-
514
-
515
-    /**
516
-     * get the author of the ticket.
517
-     *
518
-     * @return int
519
-     * @throws EE_Error
520
-     * @throws ReflectionException
521
-     * @since 4.5.0
522
-     */
523
-    public function wp_user()
524
-    {
525
-        return $this->get('TKT_wp_user');
526
-    }
527
-
528
-
529
-    /**
530
-     * Gets the template for the ticket
531
-     *
532
-     * @return EE_Ticket_Template|EE_Base_Class
533
-     * @throws EE_Error
534
-     * @throws ReflectionException
535
-     */
536
-    public function template()
537
-    {
538
-        return $this->get_first_related('Ticket_Template');
539
-    }
540
-
541
-
542
-    /**
543
-     * Simply returns an array of EE_Price objects that are taxes.
544
-     *
545
-     * @return EE_Price[]
546
-     * @throws EE_Error
547
-     */
548
-    public function get_ticket_taxes_for_admin()
549
-    {
550
-        return EE_Taxes::get_taxes_for_admin();
551
-    }
552
-
553
-
554
-    /**
555
-     * @return float
556
-     * @throws EE_Error
557
-     * @throws ReflectionException
558
-     */
559
-    public function ticket_price()
560
-    {
561
-        return $this->get('TKT_price');
562
-    }
563
-
564
-
565
-    /**
566
-     * @return mixed
567
-     * @throws EE_Error
568
-     * @throws ReflectionException
569
-     */
570
-    public function pretty_price()
571
-    {
572
-        return $this->get_pretty('TKT_price');
573
-    }
574
-
575
-
576
-    /**
577
-     * @return bool
578
-     * @throws EE_Error
579
-     * @throws ReflectionException
580
-     */
581
-    public function is_free()
582
-    {
583
-        return $this->get_ticket_total_with_taxes() === (float) 0;
584
-    }
585
-
586
-
587
-    /**
588
-     * get_ticket_total_with_taxes
589
-     *
590
-     * @param bool $no_cache
591
-     * @return float
592
-     * @throws EE_Error
593
-     * @throws ReflectionException
594
-     */
595
-    public function get_ticket_total_with_taxes($no_cache = false)
596
-    {
597
-        if ($this->_ticket_total_with_taxes === null || $no_cache) {
598
-            $this->_ticket_total_with_taxes = $this->get_ticket_subtotal() + $this->get_ticket_taxes_total_for_admin();
599
-        }
600
-        return (float) $this->_ticket_total_with_taxes;
601
-    }
602
-
603
-
604
-    /**
605
-     * @throws EE_Error
606
-     * @throws ReflectionException
607
-     */
608
-    public function ensure_TKT_Price_correct()
609
-    {
610
-        $this->set('TKT_price', EE_Taxes::get_subtotal_for_admin($this));
611
-        $this->save();
612
-    }
613
-
614
-
615
-    /**
616
-     * @return float
617
-     * @throws EE_Error
618
-     * @throws ReflectionException
619
-     */
620
-    public function get_ticket_subtotal()
621
-    {
622
-        return EE_Taxes::get_subtotal_for_admin($this);
623
-    }
624
-
625
-
626
-    /**
627
-     * Returns the total taxes applied to this ticket
628
-     *
629
-     * @return float
630
-     * @throws EE_Error
631
-     * @throws ReflectionException
632
-     */
633
-    public function get_ticket_taxes_total_for_admin()
634
-    {
635
-        return EE_Taxes::get_total_taxes_for_admin($this);
636
-    }
637
-
638
-
639
-    /**
640
-     * Sets name
641
-     *
642
-     * @param string $name
643
-     * @throws EE_Error
644
-     * @throws ReflectionException
645
-     */
646
-    public function set_name($name)
647
-    {
648
-        $this->set('TKT_name', $name);
649
-    }
650
-
651
-
652
-    /**
653
-     * Gets description
654
-     *
655
-     * @return string
656
-     * @throws EE_Error
657
-     * @throws ReflectionException
658
-     */
659
-    public function description()
660
-    {
661
-        return $this->get('TKT_description');
662
-    }
663
-
664
-
665
-    /**
666
-     * Sets description
667
-     *
668
-     * @param string $description
669
-     * @throws EE_Error
670
-     * @throws ReflectionException
671
-     */
672
-    public function set_description($description)
673
-    {
674
-        $this->set('TKT_description', $description);
675
-    }
676
-
677
-
678
-    /**
679
-     * Gets start_date
680
-     *
681
-     * @param string $date_format
682
-     * @param string $time_format
683
-     * @return string
684
-     * @throws EE_Error
685
-     * @throws ReflectionException
686
-     */
687
-    public function start_date($date_format = '', $time_format = '')
688
-    {
689
-        return $this->_get_datetime('TKT_start_date', $date_format, $time_format);
690
-    }
691
-
692
-
693
-    /**
694
-     * Sets start_date
695
-     *
696
-     * @param string $start_date
697
-     * @return void
698
-     * @throws EE_Error
699
-     * @throws ReflectionException
700
-     */
701
-    public function set_start_date($start_date)
702
-    {
703
-        $this->_set_date_time('B', $start_date, 'TKT_start_date');
704
-    }
705
-
706
-
707
-    /**
708
-     * Gets end_date
709
-     *
710
-     * @param string $date_format
711
-     * @param string $time_format
712
-     * @return string
713
-     * @throws EE_Error
714
-     * @throws ReflectionException
715
-     */
716
-    public function end_date($date_format = '', $time_format = '')
717
-    {
718
-        return $this->_get_datetime('TKT_end_date', $date_format, $time_format);
719
-    }
720
-
721
-
722
-    /**
723
-     * Sets end_date
724
-     *
725
-     * @param string $end_date
726
-     * @return void
727
-     * @throws EE_Error
728
-     * @throws ReflectionException
729
-     */
730
-    public function set_end_date($end_date)
731
-    {
732
-        $this->_set_date_time('B', $end_date, 'TKT_end_date');
733
-    }
734
-
735
-
736
-    /**
737
-     * Sets sell until time
738
-     *
739
-     * @param string $time a string representation of the sell until time (ex 9am or 7:30pm)
740
-     * @throws EE_Error
741
-     * @throws ReflectionException
742
-     * @since 4.5.0
743
-     */
744
-    public function set_end_time($time)
745
-    {
746
-        $this->_set_time_for($time, 'TKT_end_date');
747
-    }
748
-
749
-
750
-    /**
751
-     * Sets min
752
-     *
753
-     * @param int $min
754
-     * @return void
755
-     * @throws EE_Error
756
-     * @throws ReflectionException
757
-     */
758
-    public function set_min($min)
759
-    {
760
-        $this->set('TKT_min', $min);
761
-    }
762
-
763
-
764
-    /**
765
-     * Gets max
766
-     *
767
-     * @return int
768
-     * @throws EE_Error
769
-     * @throws ReflectionException
770
-     */
771
-    public function max()
772
-    {
773
-        return $this->get('TKT_max');
774
-    }
775
-
776
-
777
-    /**
778
-     * Sets max
779
-     *
780
-     * @param int $max
781
-     * @return void
782
-     * @throws EE_Error
783
-     * @throws ReflectionException
784
-     */
785
-    public function set_max($max)
786
-    {
787
-        $this->set('TKT_max', $max);
788
-    }
789
-
790
-
791
-    /**
792
-     * Sets price
793
-     *
794
-     * @param float $price
795
-     * @return void
796
-     * @throws EE_Error
797
-     * @throws ReflectionException
798
-     */
799
-    public function set_price($price)
800
-    {
801
-        $this->set('TKT_price', $price);
802
-    }
803
-
804
-
805
-    /**
806
-     * Gets sold
807
-     *
808
-     * @return int
809
-     * @throws EE_Error
810
-     * @throws ReflectionException
811
-     */
812
-    public function sold()
813
-    {
814
-        return $this->get_raw('TKT_sold');
815
-    }
816
-
817
-
818
-    /**
819
-     * Sets sold
820
-     *
821
-     * @param int $sold
822
-     * @return void
823
-     * @throws EE_Error
824
-     * @throws ReflectionException
825
-     */
826
-    public function set_sold($sold)
827
-    {
828
-        // sold can not go below zero
829
-        $sold = max(0, $sold);
830
-        $this->set('TKT_sold', $sold);
831
-    }
832
-
833
-
834
-    /**
835
-     * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
836
-     * associated datetimes.
837
-     *
838
-     * @param int $qty
839
-     * @return boolean
840
-     * @throws EE_Error
841
-     * @throws InvalidArgumentException
842
-     * @throws InvalidDataTypeException
843
-     * @throws InvalidInterfaceException
844
-     * @throws ReflectionException
845
-     * @since 4.9.80.p
846
-     */
847
-    public function increaseSold($qty = 1)
848
-    {
849
-        $qty = absint($qty);
850
-        // increment sold and decrement reserved datetime quantities simultaneously
851
-        // don't worry about failures, because they must have already had a spot reserved
852
-        $this->increaseSoldForDatetimes($qty);
853
-        // Increment and decrement ticket quantities simultaneously
854
-        $success = $this->adjustNumericFieldsInDb(
855
-            [
856
-                'TKT_reserved' => $qty * -1,
857
-                'TKT_sold'     => $qty,
858
-            ]
859
-        );
860
-        do_action(
861
-            'AHEE__EE_Ticket__increase_sold',
862
-            $this,
863
-            $qty,
864
-            $this->sold(),
865
-            $success
866
-        );
867
-        return $success;
868
-    }
869
-
870
-
871
-    /**
872
-     * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
873
-     *
874
-     * @param int           $qty positive or negative. Positive means to increase sold counts (and decrease reserved
875
-     *                           counts), Negative means to decreases old counts (and increase reserved counts).
876
-     * @param EE_Datetime[] $datetimes
877
-     * @throws EE_Error
878
-     * @throws InvalidArgumentException
879
-     * @throws InvalidDataTypeException
880
-     * @throws InvalidInterfaceException
881
-     * @throws ReflectionException
882
-     * @since 4.9.80.p
883
-     */
884
-    protected function increaseSoldForDatetimes($qty, array $datetimes = [])
885
-    {
886
-        $datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
887
-        foreach ($datetimes as $datetime) {
888
-            $datetime->increaseSold($qty);
889
-        }
890
-    }
891
-
892
-
893
-    /**
894
-     * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
895
-     * DB and then updates the model objects.
896
-     * Does not affect the reserved counts.
897
-     *
898
-     * @param int $qty
899
-     * @return boolean
900
-     * @throws EE_Error
901
-     * @throws InvalidArgumentException
902
-     * @throws InvalidDataTypeException
903
-     * @throws InvalidInterfaceException
904
-     * @throws ReflectionException
905
-     * @since 4.9.80.p
906
-     */
907
-    public function decreaseSold($qty = 1)
908
-    {
909
-        $qty = absint($qty);
910
-        $this->decreaseSoldForDatetimes($qty);
911
-        $success = $this->adjustNumericFieldsInDb(
912
-            [
913
-                'TKT_sold' => $qty * -1,
914
-            ]
915
-        );
916
-        do_action(
917
-            'AHEE__EE_Ticket__decrease_sold',
918
-            $this,
919
-            $qty,
920
-            $this->sold(),
921
-            $success
922
-        );
923
-        return $success;
924
-    }
925
-
926
-
927
-    /**
928
-     * Decreases sold on related datetimes
929
-     *
930
-     * @param int           $qty
931
-     * @param EE_Datetime[] $datetimes
932
-     * @return void
933
-     * @throws EE_Error
934
-     * @throws InvalidArgumentException
935
-     * @throws InvalidDataTypeException
936
-     * @throws InvalidInterfaceException
937
-     * @throws ReflectionException
938
-     * @since 4.9.80.p
939
-     */
940
-    protected function decreaseSoldForDatetimes($qty = 1, array $datetimes = [])
941
-    {
942
-        $datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
943
-        if (is_array($datetimes)) {
944
-            foreach ($datetimes as $datetime) {
945
-                if ($datetime instanceof EE_Datetime) {
946
-                    $datetime->decreaseSold($qty);
947
-                }
948
-            }
949
-        }
950
-    }
951
-
952
-
953
-    /**
954
-     * Gets qty of reserved tickets
955
-     *
956
-     * @return int
957
-     * @throws EE_Error
958
-     * @throws ReflectionException
959
-     */
960
-    public function reserved()
961
-    {
962
-        return $this->get_raw('TKT_reserved');
963
-    }
964
-
965
-
966
-    /**
967
-     * Sets reserved
968
-     *
969
-     * @param int $reserved
970
-     * @return void
971
-     * @throws EE_Error
972
-     * @throws ReflectionException
973
-     */
974
-    public function set_reserved($reserved)
975
-    {
976
-        // reserved can not go below zero
977
-        $reserved = max(0, (int) $reserved);
978
-        $this->set('TKT_reserved', $reserved);
979
-    }
980
-
981
-
982
-    /**
983
-     * Increments reserved by amount passed by $qty, and persists it immediately to the database.
984
-     *
985
-     * @param int    $qty
986
-     * @param string $source
987
-     * @return bool whether we successfully reserved the ticket or not.
988
-     * @throws EE_Error
989
-     * @throws InvalidArgumentException
990
-     * @throws ReflectionException
991
-     * @throws InvalidDataTypeException
992
-     * @throws InvalidInterfaceException
993
-     * @since 4.9.80.p
994
-     */
995
-    public function increaseReserved($qty = 1, $source = 'unknown')
996
-    {
997
-        $qty = absint($qty);
998
-        do_action(
999
-            'AHEE__EE_Ticket__increase_reserved__begin',
1000
-            $this,
1001
-            $qty,
1002
-            $source
1003
-        );
1004
-        $this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "{$qty} from {$source}");
1005
-        $success                         = false;
1006
-        $datetimes_adjusted_successfully = $this->increaseReservedForDatetimes($qty);
1007
-        if ($datetimes_adjusted_successfully) {
1008
-            $success = $this->incrementFieldConditionallyInDb(
1009
-                'TKT_reserved',
1010
-                'TKT_sold',
1011
-                'TKT_qty',
1012
-                $qty
1013
-            );
1014
-            if (! $success) {
1015
-                // The datetimes were successfully bumped, but not the
1016
-                // ticket. So we need to manually rollback the datetimes.
1017
-                $this->decreaseReservedForDatetimes($qty);
1018
-            }
1019
-        }
1020
-        do_action(
1021
-            'AHEE__EE_Ticket__increase_reserved',
1022
-            $this,
1023
-            $qty,
1024
-            $this->reserved(),
1025
-            $success
1026
-        );
1027
-        return $success;
1028
-    }
1029
-
1030
-
1031
-    /**
1032
-     * Increases reserved counts on related datetimes
1033
-     *
1034
-     * @param int           $qty
1035
-     * @param EE_Datetime[] $datetimes
1036
-     * @return boolean indicating success
1037
-     * @throws EE_Error
1038
-     * @throws InvalidArgumentException
1039
-     * @throws InvalidDataTypeException
1040
-     * @throws InvalidInterfaceException
1041
-     * @throws ReflectionException
1042
-     * @since 4.9.80.p
1043
-     */
1044
-    protected function increaseReservedForDatetimes($qty = 1, array $datetimes = [])
1045
-    {
1046
-        $datetimes         = ! empty($datetimes) ? $datetimes : $this->datetimes();
1047
-        $datetimes_updated = [];
1048
-        $limit_exceeded    = false;
1049
-        if (is_array($datetimes)) {
1050
-            foreach ($datetimes as $datetime) {
1051
-                if ($datetime instanceof EE_Datetime) {
1052
-                    if ($datetime->increaseReserved($qty)) {
1053
-                        $datetimes_updated[] = $datetime;
1054
-                    } else {
1055
-                        $limit_exceeded = true;
1056
-                        break;
1057
-                    }
1058
-                }
1059
-            }
1060
-            // If somewhere along the way we detected a datetime whose
1061
-            // limit was exceeded, do a manual rollback.
1062
-            if ($limit_exceeded) {
1063
-                $this->decreaseReservedForDatetimes($qty, $datetimes_updated);
1064
-                return false;
1065
-            }
1066
-        }
1067
-        return true;
1068
-    }
1069
-
1070
-
1071
-    /**
1072
-     * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
1073
-     *
1074
-     * @param int    $qty
1075
-     * @param bool   $adjust_datetimes
1076
-     * @param string $source
1077
-     * @return boolean
1078
-     * @throws EE_Error
1079
-     * @throws InvalidArgumentException
1080
-     * @throws ReflectionException
1081
-     * @throws InvalidDataTypeException
1082
-     * @throws InvalidInterfaceException
1083
-     * @since 4.9.80.p
1084
-     */
1085
-    public function decreaseReserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
1086
-    {
1087
-        $qty = absint($qty);
1088
-        $this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "-{$qty} from {$source}");
1089
-        if ($adjust_datetimes) {
1090
-            $this->decreaseReservedForDatetimes($qty);
1091
-        }
1092
-        $success = $this->adjustNumericFieldsInDb(
1093
-            [
1094
-                'TKT_reserved' => $qty * -1,
1095
-            ]
1096
-        );
1097
-        do_action(
1098
-            'AHEE__EE_Ticket__decrease_reserved',
1099
-            $this,
1100
-            $qty,
1101
-            $this->reserved(),
1102
-            $success
1103
-        );
1104
-        return $success;
1105
-    }
1106
-
1107
-
1108
-    /**
1109
-     * Decreases the reserved count on the specified datetimes.
1110
-     *
1111
-     * @param int           $qty
1112
-     * @param EE_Datetime[] $datetimes
1113
-     * @throws EE_Error
1114
-     * @throws InvalidArgumentException
1115
-     * @throws ReflectionException
1116
-     * @throws InvalidDataTypeException
1117
-     * @throws InvalidInterfaceException
1118
-     * @since 4.9.80.p
1119
-     */
1120
-    protected function decreaseReservedForDatetimes($qty = 1, array $datetimes = [])
1121
-    {
1122
-        $datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
1123
-        foreach ($datetimes as $datetime) {
1124
-            if ($datetime instanceof EE_Datetime) {
1125
-                $datetime->decreaseReserved($qty);
1126
-            }
1127
-        }
1128
-    }
1129
-
1130
-
1131
-    /**
1132
-     * Gets ticket quantity
1133
-     *
1134
-     * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1135
-     *                            therefore $context can be one of three values: '', 'reg_limit', or 'saleable'
1136
-     *                            '' (default) quantity is the actual db value for TKT_qty, unaffected by other objects
1137
-     *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1138
-     *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1139
-     *                            is therefore the truest measure of tickets that can be purchased at the moment
1140
-     * @return int
1141
-     * @throws EE_Error
1142
-     * @throws ReflectionException
1143
-     */
1144
-    public function qty($context = '')
1145
-    {
1146
-        switch ($context) {
1147
-            case 'reg_limit':
1148
-                return $this->real_quantity_on_ticket();
1149
-            case 'saleable':
1150
-                return $this->real_quantity_on_ticket('saleable');
1151
-            default:
1152
-                return $this->get_raw('TKT_qty');
1153
-        }
1154
-    }
1155
-
1156
-
1157
-    /**
1158
-     * Gets ticket quantity
1159
-     *
1160
-     * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1161
-     *                            therefore $context can be one of two values: 'reg_limit', or 'saleable'
1162
-     *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1163
-     *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1164
-     *                            is therefore the truest measure of tickets that can be purchased at the moment
1165
-     * @param int    $DTT_ID      the primary key for a particular datetime.
1166
-     *                            set to 0 for all related datetimes
1167
-     * @return int
1168
-     * @throws EE_Error
1169
-     * @throws ReflectionException
1170
-     */
1171
-    public function real_quantity_on_ticket($context = 'reg_limit', $DTT_ID = 0)
1172
-    {
1173
-        $raw = $this->get_raw('TKT_qty');
1174
-        // return immediately if it's zero
1175
-        if ($raw === 0) {
1176
-            return $raw;
1177
-        }
1178
-        // echo "\n\n<br />Ticket: " . $this->name() . '<br />';
1179
-        // ensure qty doesn't exceed raw value for THIS ticket
1180
-        $qty = min(EE_INF, $raw);
1181
-        // echo "\n . qty: " . $qty . '<br />';
1182
-        // calculate this ticket's total sales and reservations
1183
-        $sold_and_reserved_for_this_ticket = $this->sold() + $this->reserved();
1184
-        // echo "\n . sold: " . $this->sold() . '<br />';
1185
-        // echo "\n . reserved: " . $this->reserved() . '<br />';
1186
-        // echo "\n . sold_and_reserved_for_this_ticket: " . $sold_and_reserved_for_this_ticket . '<br />';
1187
-        // first we need to calculate the maximum number of tickets available for the datetime
1188
-        // do we want data for one datetime or all of them ?
1189
-        $query_params = $DTT_ID ? [['DTT_ID' => $DTT_ID]] : [];
1190
-        $datetimes    = $this->datetimes($query_params);
1191
-        if (is_array($datetimes) && ! empty($datetimes)) {
1192
-            foreach ($datetimes as $datetime) {
1193
-                if ($datetime instanceof EE_Datetime) {
1194
-                    $datetime->refresh_from_db();
1195
-                    // echo "\n . . datetime name: " . $datetime->name() . '<br />';
1196
-                    // echo "\n . . datetime ID: " . $datetime->ID() . '<br />';
1197
-                    // initialize with no restrictions for each datetime
1198
-                    // but adjust datetime qty based on datetime reg limit
1199
-                    $datetime_qty = min(EE_INF, $datetime->reg_limit());
1200
-                    // echo "\n . . . datetime reg_limit: " . $datetime->reg_limit() . '<br />';
1201
-                    // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1202
-                    // if we want the actual saleable amount, then we need to consider OTHER ticket sales
1203
-                    // and reservations for this datetime, that do NOT include sales and reservations
1204
-                    // for this ticket (so we add $this->sold() and $this->reserved() back in)
1205
-                    if ($context === 'saleable') {
1206
-                        $datetime_qty = max(
1207
-                            $datetime_qty - $datetime->sold_and_reserved() + $sold_and_reserved_for_this_ticket,
1208
-                            0
1209
-                        );
1210
-                        // echo "\n . . . datetime sold: " . $datetime->sold() . '<br />';
1211
-                        // echo "\n . . . datetime reserved: " . $datetime->reserved() . '<br />';
1212
-                        // echo "\n . . . datetime sold_and_reserved: " . $datetime->sold_and_reserved() . '<br />';
1213
-                        // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1214
-                        $datetime_qty = ! $datetime->sold_out() ? $datetime_qty : 0;
1215
-                        // echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1216
-                    }
1217
-                    $qty = min($datetime_qty, $qty);
1218
-                    // echo "\n . . qty: " . $qty . '<br />';
1219
-                }
1220
-            }
1221
-        }
1222
-        // NOW that we know the  maximum number of tickets available for the datetime
1223
-        // we can finally factor in the details for this specific ticket
1224
-        if ($qty > 0 && $context === 'saleable') {
1225
-            // and subtract the sales for THIS ticket
1226
-            $qty = max($qty - $sold_and_reserved_for_this_ticket, 0);
1227
-            // echo "\n . qty: " . $qty . '<br />';
1228
-        }
1229
-        // echo "\nFINAL QTY: " . $qty . "<br /><br />";
1230
-        return $qty;
1231
-    }
1232
-
1233
-
1234
-    /**
1235
-     * Sets qty - IMPORTANT!!! Does NOT allow QTY to be set higher than the lowest reg limit of any related datetimes
1236
-     *
1237
-     * @param int $qty
1238
-     * @return void
1239
-     * @throws EE_Error
1240
-     * @throws ReflectionException
1241
-     */
1242
-    public function set_qty($qty)
1243
-    {
1244
-        $datetimes = $this->datetimes();
1245
-        foreach ($datetimes as $datetime) {
1246
-            if ($datetime instanceof EE_Datetime) {
1247
-                $qty = min($qty, $datetime->reg_limit());
1248
-            }
1249
-        }
1250
-        $this->set('TKT_qty', $qty);
1251
-    }
1252
-
1253
-
1254
-    /**
1255
-     * Gets uses
1256
-     *
1257
-     * @return int
1258
-     * @throws EE_Error
1259
-     * @throws ReflectionException
1260
-     */
1261
-    public function uses()
1262
-    {
1263
-        return $this->get('TKT_uses');
1264
-    }
1265
-
1266
-
1267
-    /**
1268
-     * Sets uses
1269
-     *
1270
-     * @param int $uses
1271
-     * @return void
1272
-     * @throws EE_Error
1273
-     * @throws ReflectionException
1274
-     */
1275
-    public function set_uses($uses)
1276
-    {
1277
-        $this->set('TKT_uses', $uses);
1278
-    }
1279
-
1280
-
1281
-    /**
1282
-     * returns whether ticket is required or not.
1283
-     *
1284
-     * @return boolean
1285
-     * @throws EE_Error
1286
-     * @throws ReflectionException
1287
-     */
1288
-    public function required()
1289
-    {
1290
-        return $this->get('TKT_required');
1291
-    }
1292
-
1293
-
1294
-    /**
1295
-     * sets the TKT_required property
1296
-     *
1297
-     * @param boolean $required
1298
-     * @return void
1299
-     * @throws EE_Error
1300
-     * @throws ReflectionException
1301
-     */
1302
-    public function set_required($required)
1303
-    {
1304
-        $this->set('TKT_required', $required);
1305
-    }
1306
-
1307
-
1308
-    /**
1309
-     * Gets taxable
1310
-     *
1311
-     * @return boolean
1312
-     * @throws EE_Error
1313
-     * @throws ReflectionException
1314
-     */
1315
-    public function taxable()
1316
-    {
1317
-        return $this->get('TKT_taxable');
1318
-    }
1319
-
1320
-
1321
-    /**
1322
-     * Sets taxable
1323
-     *
1324
-     * @param boolean $taxable
1325
-     * @return void
1326
-     * @throws EE_Error
1327
-     * @throws ReflectionException
1328
-     */
1329
-    public function set_taxable($taxable)
1330
-    {
1331
-        $this->set('TKT_taxable', $taxable);
1332
-    }
1333
-
1334
-
1335
-    /**
1336
-     * Gets is_default
1337
-     *
1338
-     * @return boolean
1339
-     * @throws EE_Error
1340
-     * @throws ReflectionException
1341
-     */
1342
-    public function is_default()
1343
-    {
1344
-        return $this->get('TKT_is_default');
1345
-    }
1346
-
1347
-
1348
-    /**
1349
-     * Sets is_default
1350
-     *
1351
-     * @param boolean $is_default
1352
-     * @return void
1353
-     * @throws EE_Error
1354
-     * @throws ReflectionException
1355
-     */
1356
-    public function set_is_default($is_default)
1357
-    {
1358
-        $this->set('TKT_is_default', $is_default);
1359
-    }
1360
-
1361
-
1362
-    /**
1363
-     * Gets order
1364
-     *
1365
-     * @return int
1366
-     * @throws EE_Error
1367
-     * @throws ReflectionException
1368
-     */
1369
-    public function order()
1370
-    {
1371
-        return $this->get('TKT_order');
1372
-    }
1373
-
1374
-
1375
-    /**
1376
-     * Sets order
1377
-     *
1378
-     * @param int $order
1379
-     * @return void
1380
-     * @throws EE_Error
1381
-     * @throws ReflectionException
1382
-     */
1383
-    public function set_order($order)
1384
-    {
1385
-        $this->set('TKT_order', $order);
1386
-    }
1387
-
1388
-
1389
-    /**
1390
-     * Gets row
1391
-     *
1392
-     * @return int
1393
-     * @throws EE_Error
1394
-     * @throws ReflectionException
1395
-     */
1396
-    public function row()
1397
-    {
1398
-        return $this->get('TKT_row');
1399
-    }
1400
-
1401
-
1402
-    /**
1403
-     * Sets row
1404
-     *
1405
-     * @param int $row
1406
-     * @return void
1407
-     * @throws EE_Error
1408
-     * @throws ReflectionException
1409
-     */
1410
-    public function set_row($row)
1411
-    {
1412
-        $this->set('TKT_row', $row);
1413
-    }
1414
-
1415
-
1416
-    /**
1417
-     * Gets deleted
1418
-     *
1419
-     * @return boolean
1420
-     * @throws EE_Error
1421
-     * @throws ReflectionException
1422
-     */
1423
-    public function deleted()
1424
-    {
1425
-        return $this->get('TKT_deleted');
1426
-    }
1427
-
1428
-
1429
-    /**
1430
-     * Sets deleted
1431
-     *
1432
-     * @param boolean $deleted
1433
-     * @return void
1434
-     * @throws EE_Error
1435
-     * @throws ReflectionException
1436
-     */
1437
-    public function set_deleted($deleted)
1438
-    {
1439
-        $this->set('TKT_deleted', $deleted);
1440
-    }
1441
-
1442
-
1443
-    /**
1444
-     * Gets parent
1445
-     *
1446
-     * @return int
1447
-     * @throws EE_Error
1448
-     * @throws ReflectionException
1449
-     */
1450
-    public function parent_ID()
1451
-    {
1452
-        return $this->get('TKT_parent');
1453
-    }
1454
-
1455
-
1456
-    /**
1457
-     * Sets parent
1458
-     *
1459
-     * @param int $parent
1460
-     * @return void
1461
-     * @throws EE_Error
1462
-     * @throws ReflectionException
1463
-     */
1464
-    public function set_parent_ID($parent)
1465
-    {
1466
-        $this->set('TKT_parent', $parent);
1467
-    }
1468
-
1469
-
1470
-    /**
1471
-     * @return boolean
1472
-     * @throws EE_Error
1473
-     * @throws InvalidArgumentException
1474
-     * @throws InvalidDataTypeException
1475
-     * @throws InvalidInterfaceException
1476
-     * @throws ReflectionException
1477
-     */
1478
-    public function reverse_calculate()
1479
-    {
1480
-        return $this->get('TKT_reverse_calculate');
1481
-    }
1482
-
1483
-
1484
-    /**
1485
-     * @param boolean $reverse_calculate
1486
-     * @throws EE_Error
1487
-     * @throws InvalidArgumentException
1488
-     * @throws InvalidDataTypeException
1489
-     * @throws InvalidInterfaceException
1490
-     * @throws ReflectionException
1491
-     */
1492
-    public function set_reverse_calculate($reverse_calculate)
1493
-    {
1494
-        $this->set('TKT_reverse_calculate', $reverse_calculate);
1495
-    }
1496
-
1497
-
1498
-    /**
1499
-     * Gets a string which is handy for showing in gateways etc that describes the ticket.
1500
-     *
1501
-     * @return string
1502
-     * @throws EE_Error
1503
-     * @throws ReflectionException
1504
-     */
1505
-    public function name_and_info()
1506
-    {
1507
-        $times = [];
1508
-        foreach ($this->datetimes() as $datetime) {
1509
-            $times[] = $datetime->start_date_and_time();
1510
-        }
1511
-        return $this->name() . ' @ ' . implode(', ', $times) . ' for ' . $this->pretty_price();
1512
-    }
1513
-
1514
-
1515
-    /**
1516
-     * Gets name
1517
-     *
1518
-     * @return string
1519
-     * @throws EE_Error
1520
-     * @throws ReflectionException
1521
-     */
1522
-    public function name()
1523
-    {
1524
-        return $this->get('TKT_name');
1525
-    }
1526
-
1527
-
1528
-    /**
1529
-     * Gets price
1530
-     *
1531
-     * @return float
1532
-     * @throws EE_Error
1533
-     * @throws ReflectionException
1534
-     */
1535
-    public function price()
1536
-    {
1537
-        return $this->get('TKT_price');
1538
-    }
1539
-
1540
-
1541
-    /**
1542
-     * Gets all the registrations for this ticket
1543
-     *
1544
-     * @param array $query_params @see
1545
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1546
-     * @return EE_Registration[]|EE_Base_Class[]
1547
-     * @throws EE_Error
1548
-     * @throws ReflectionException
1549
-     */
1550
-    public function registrations($query_params = [])
1551
-    {
1552
-        return $this->get_many_related('Registration', $query_params);
1553
-    }
1554
-
1555
-
1556
-    /**
1557
-     * Updates the TKT_sold attribute (and saves) based on the number of APPROVED registrations for this ticket.
1558
-     *
1559
-     * @return int
1560
-     * @throws EE_Error
1561
-     * @throws ReflectionException
1562
-     */
1563
-    public function update_tickets_sold()
1564
-    {
1565
-        $count_regs_for_this_ticket = $this->count_registrations(
1566
-            [
1567
-                [
1568
-                    'STS_ID'      => EEM_Registration::status_id_approved,
1569
-                    'REG_deleted' => 0,
1570
-                ],
1571
-            ]
1572
-        );
1573
-        $this->set_sold($count_regs_for_this_ticket);
1574
-        $this->save();
1575
-        return $count_regs_for_this_ticket;
1576
-    }
1577
-
1578
-
1579
-    /**
1580
-     * Counts the registrations for this ticket
1581
-     *
1582
-     * @param array $query_params @see
1583
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1584
-     * @return int
1585
-     * @throws EE_Error
1586
-     * @throws ReflectionException
1587
-     */
1588
-    public function count_registrations($query_params = [])
1589
-    {
1590
-        return $this->count_related('Registration', $query_params);
1591
-    }
1592
-
1593
-
1594
-    /**
1595
-     * Implementation for EEI_Has_Icon interface method.
1596
-     *
1597
-     * @return string
1598
-     * @see EEI_Visual_Representation for comments
1599
-     */
1600
-    public function get_icon()
1601
-    {
1602
-        return '<span class="dashicons dashicons-tickets-alt"/>';
1603
-    }
1604
-
1605
-
1606
-    /**
1607
-     * Implementation of the EEI_Event_Relation interface method
1608
-     *
1609
-     * @return EE_Event
1610
-     * @throws EE_Error
1611
-     * @throws UnexpectedEntityException
1612
-     * @throws ReflectionException
1613
-     * @see EEI_Event_Relation for comments
1614
-     */
1615
-    public function get_related_event()
1616
-    {
1617
-        // get one datetime to use for getting the event
1618
-        $datetime = $this->first_datetime();
1619
-        if (! $datetime instanceof EE_Datetime) {
1620
-            throw new UnexpectedEntityException(
1621
-                $datetime,
1622
-                'EE_Datetime',
1623
-                sprintf(
1624
-                    __('The ticket (%s) is not associated with any valid datetimes.', 'event_espresso'),
1625
-                    $this->name()
1626
-                )
1627
-            );
1628
-        }
1629
-        $event = $datetime->event();
1630
-        if (! $event instanceof EE_Event) {
1631
-            throw new UnexpectedEntityException(
1632
-                $event,
1633
-                'EE_Event',
1634
-                sprintf(
1635
-                    __('The ticket (%s) is not associated with a valid event.', 'event_espresso'),
1636
-                    $this->name()
1637
-                )
1638
-            );
1639
-        }
1640
-        return $event;
1641
-    }
1642
-
1643
-
1644
-    /**
1645
-     * Implementation of the EEI_Event_Relation interface method
1646
-     *
1647
-     * @return string
1648
-     * @throws UnexpectedEntityException
1649
-     * @throws EE_Error
1650
-     * @throws ReflectionException
1651
-     * @see EEI_Event_Relation for comments
1652
-     */
1653
-    public function get_event_name()
1654
-    {
1655
-        $event = $this->get_related_event();
1656
-        return $event instanceof EE_Event ? $event->name() : '';
1657
-    }
1658
-
1659
-
1660
-    /**
1661
-     * Implementation of the EEI_Event_Relation interface method
1662
-     *
1663
-     * @return int
1664
-     * @throws UnexpectedEntityException
1665
-     * @throws EE_Error
1666
-     * @throws ReflectionException
1667
-     * @see EEI_Event_Relation for comments
1668
-     */
1669
-    public function get_event_ID()
1670
-    {
1671
-        $event = $this->get_related_event();
1672
-        return $event instanceof EE_Event ? $event->ID() : 0;
1673
-    }
1674
-
1675
-
1676
-    /**
1677
-     * This simply returns whether a ticket can be permanently deleted or not.
1678
-     * The criteria for determining this is whether the ticket has any related registrations.
1679
-     * If there are none then it can be permanently deleted.
1680
-     *
1681
-     * @return bool
1682
-     * @throws EE_Error
1683
-     * @throws ReflectionException
1684
-     */
1685
-    public function is_permanently_deleteable()
1686
-    {
1687
-        return $this->count_registrations() === 0;
1688
-    }
1689
-
1690
-
1691
-    /*******************************************************************
17
+	/**
18
+	 * TicKet Sold out:
19
+	 * constant used by ticket_status() to indicate that a ticket is sold out
20
+	 * and no longer available for purchases
21
+	 */
22
+	const sold_out = 'TKS';
23
+
24
+	/**
25
+	 * TicKet Expired:
26
+	 * constant used by ticket_status() to indicate that a ticket is expired
27
+	 * and no longer available for purchase
28
+	 */
29
+	const expired = 'TKE';
30
+
31
+	/**
32
+	 * TicKet Archived:
33
+	 * constant used by ticket_status() to indicate that a ticket is archived
34
+	 * and no longer available for purchase
35
+	 */
36
+	const archived = 'TKA';
37
+
38
+	/**
39
+	 * TicKet Pending:
40
+	 * constant used by ticket_status() to indicate that a ticket is pending
41
+	 * and is NOT YET available for purchase
42
+	 */
43
+	const pending = 'TKP';
44
+
45
+	/**
46
+	 * TicKet On sale:
47
+	 * constant used by ticket_status() to indicate that a ticket is On Sale
48
+	 * and IS available for purchase
49
+	 */
50
+	const onsale = 'TKO';
51
+
52
+	/**
53
+	 * extra meta key for tracking ticket reservations
54
+	 *
55
+	 * @type string
56
+	 */
57
+	const META_KEY_TICKET_RESERVATIONS = 'ticket_reservations';
58
+
59
+	/**
60
+	 * cached result from method of the same name
61
+	 *
62
+	 * @var float $_ticket_total_with_taxes
63
+	 */
64
+	private $_ticket_total_with_taxes;
65
+
66
+
67
+	/**
68
+	 * @param array  $props_n_values          incoming values
69
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
70
+	 *                                        used.)
71
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
72
+	 *                                        date_format and the second value is the time format
73
+	 * @return EE_Ticket
74
+	 * @throws EE_Error
75
+	 * @throws ReflectionException
76
+	 */
77
+	public static function new_instance($props_n_values = [], $timezone = null, $date_formats = [])
78
+	{
79
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
80
+		return $has_object ? $has_object : new self($props_n_values, false, $timezone, $date_formats);
81
+	}
82
+
83
+
84
+	/**
85
+	 * @param array  $props_n_values  incoming values from the database
86
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
87
+	 *                                the website will be used.
88
+	 * @return EE_Ticket
89
+	 * @throws EE_Error
90
+	 * @throws ReflectionException
91
+	 */
92
+	public static function new_instance_from_db($props_n_values = [], $timezone = null)
93
+	{
94
+		return new self($props_n_values, true, $timezone);
95
+	}
96
+
97
+
98
+	/**
99
+	 * @return bool
100
+	 * @throws EE_Error
101
+	 * @throws ReflectionException
102
+	 */
103
+	public function parent()
104
+	{
105
+		return $this->get('TKT_parent');
106
+	}
107
+
108
+
109
+	/**
110
+	 * return if a ticket has quantities available for purchase
111
+	 *
112
+	 * @param int $DTT_ID the primary key for a particular datetime
113
+	 * @return boolean
114
+	 * @throws EE_Error
115
+	 * @throws ReflectionException
116
+	 */
117
+	public function available($DTT_ID = 0)
118
+	{
119
+		// are we checking availability for a particular datetime ?
120
+		if ($DTT_ID) {
121
+			// get that datetime object
122
+			$datetime = $this->get_first_related('Datetime', [['DTT_ID' => $DTT_ID]]);
123
+			// if  ticket sales for this datetime have exceeded the reg limit...
124
+			if ($datetime instanceof EE_Datetime && $datetime->sold_out()) {
125
+				return false;
126
+			}
127
+		}
128
+		// datetime is still open for registration, but is this ticket sold out ?
129
+		return $this->qty() < 1 || $this->qty() > $this->sold();
130
+	}
131
+
132
+
133
+	/**
134
+	 * Using the start date and end date this method calculates whether the ticket is On Sale, Pending, or Expired
135
+	 *
136
+	 * @param bool        $display   true = we'll return a localized string, otherwise we just return the value of the
137
+	 *                               relevant status const
138
+	 * @param bool | null $remaining if it is already known that tickets are available, then simply pass a bool to save
139
+	 *                               further processing
140
+	 * @return mixed status int if the display string isn't requested
141
+	 * @throws EE_Error
142
+	 * @throws ReflectionException
143
+	 */
144
+	public function ticket_status($display = false, $remaining = null)
145
+	{
146
+		$remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
147
+		if (! $remaining) {
148
+			return $display ? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence') : EE_Ticket::sold_out;
149
+		}
150
+		if ($this->get('TKT_deleted')) {
151
+			return $display ? EEH_Template::pretty_status(EE_Ticket::archived, false, 'sentence') : EE_Ticket::archived;
152
+		}
153
+		if ($this->is_expired()) {
154
+			return $display ? EEH_Template::pretty_status(EE_Ticket::expired, false, 'sentence') : EE_Ticket::expired;
155
+		}
156
+		if ($this->is_pending()) {
157
+			return $display ? EEH_Template::pretty_status(EE_Ticket::pending, false, 'sentence') : EE_Ticket::pending;
158
+		}
159
+		if ($this->is_on_sale()) {
160
+			return $display ? EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence') : EE_Ticket::onsale;
161
+		}
162
+		return '';
163
+	}
164
+
165
+
166
+	/**
167
+	 * The purpose of this method is to simply return a boolean for whether there are any tickets remaining for sale
168
+	 * considering ALL the factors used for figuring that out.
169
+	 *
170
+	 * @access public
171
+	 * @param int $DTT_ID if an int above 0 is included here then we get a specific dtt.
172
+	 * @return boolean         true = tickets remaining, false not.
173
+	 * @throws EE_Error
174
+	 * @throws ReflectionException
175
+	 */
176
+	public function is_remaining($DTT_ID = 0)
177
+	{
178
+		$num_remaining = $this->remaining($DTT_ID);
179
+		if ($num_remaining === 0) {
180
+			return false;
181
+		}
182
+		if ($num_remaining > 0 && $num_remaining < $this->min()) {
183
+			return false;
184
+		}
185
+		return true;
186
+	}
187
+
188
+
189
+	/**
190
+	 * return the total number of tickets available for purchase
191
+	 *
192
+	 * @param int $DTT_ID  the primary key for a particular datetime.
193
+	 *                     set to 0 for all related datetimes
194
+	 * @return int
195
+	 * @throws EE_Error
196
+	 * @throws ReflectionException
197
+	 */
198
+	public function remaining($DTT_ID = 0)
199
+	{
200
+		return $this->real_quantity_on_ticket('saleable', $DTT_ID);
201
+	}
202
+
203
+
204
+	/**
205
+	 * Gets min
206
+	 *
207
+	 * @return int
208
+	 * @throws EE_Error
209
+	 * @throws ReflectionException
210
+	 */
211
+	public function min()
212
+	{
213
+		return $this->get('TKT_min');
214
+	}
215
+
216
+
217
+	/**
218
+	 * return if a ticket is no longer available cause its available dates have expired.
219
+	 *
220
+	 * @return boolean
221
+	 * @throws EE_Error
222
+	 * @throws ReflectionException
223
+	 */
224
+	public function is_expired()
225
+	{
226
+		return ($this->get_raw('TKT_end_date') < time());
227
+	}
228
+
229
+
230
+	/**
231
+	 * Return if a ticket is yet to go on sale or not
232
+	 *
233
+	 * @return boolean
234
+	 * @throws EE_Error
235
+	 * @throws ReflectionException
236
+	 */
237
+	public function is_pending()
238
+	{
239
+		return ($this->get_raw('TKT_start_date') >= time());
240
+	}
241
+
242
+
243
+	/**
244
+	 * Return if a ticket is on sale or not
245
+	 *
246
+	 * @return boolean
247
+	 * @throws EE_Error
248
+	 * @throws ReflectionException
249
+	 */
250
+	public function is_on_sale()
251
+	{
252
+		return ($this->get_raw('TKT_start_date') <= time() && $this->get_raw('TKT_end_date') >= time());
253
+	}
254
+
255
+
256
+	/**
257
+	 * This returns the chronologically last datetime that this ticket is associated with
258
+	 *
259
+	 * @param string $date_format
260
+	 * @param string $conjunction - conjunction junction what's your function ? this string joins the start date with
261
+	 *                            the end date ie: Jan 01 "to" Dec 31
262
+	 * @return string
263
+	 * @throws EE_Error
264
+	 * @throws ReflectionException
265
+	 */
266
+	public function date_range($date_format = '', $conjunction = ' - ')
267
+	{
268
+		$date_format = ! empty($date_format) ? $date_format : $this->_dt_frmt;
269
+		$first_date  = $this->first_datetime() instanceof EE_Datetime
270
+			? $this->first_datetime()->get_i18n_datetime('DTT_EVT_start', $date_format)
271
+			: '';
272
+		$last_date   = $this->last_datetime() instanceof EE_Datetime
273
+			? $this->last_datetime()->get_i18n_datetime('DTT_EVT_end', $date_format)
274
+			: '';
275
+
276
+		return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
277
+	}
278
+
279
+
280
+	/**
281
+	 * This returns the chronologically first datetime that this ticket is associated with
282
+	 *
283
+	 * @return EE_Datetime
284
+	 * @throws EE_Error
285
+	 * @throws ReflectionException
286
+	 */
287
+	public function first_datetime()
288
+	{
289
+		$datetimes = $this->datetimes(['limit' => 1]);
290
+		return reset($datetimes);
291
+	}
292
+
293
+
294
+	/**
295
+	 * Gets all the datetimes this ticket can be used for attending.
296
+	 * Unless otherwise specified, orders datetimes by start date.
297
+	 *
298
+	 * @param array $query_params @see
299
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
300
+	 * @return EE_Datetime[]|EE_Base_Class[]
301
+	 * @throws EE_Error
302
+	 * @throws ReflectionException
303
+	 */
304
+	public function datetimes($query_params = [])
305
+	{
306
+		if (! isset($query_params['order_by'])) {
307
+			$query_params['order_by']['DTT_order'] = 'ASC';
308
+		}
309
+		return $this->get_many_related('Datetime', $query_params);
310
+	}
311
+
312
+
313
+	/**
314
+	 * This returns the chronologically last datetime that this ticket is associated with
315
+	 *
316
+	 * @return EE_Datetime
317
+	 * @throws EE_Error
318
+	 * @throws ReflectionException
319
+	 */
320
+	public function last_datetime()
321
+	{
322
+		$datetimes = $this->datetimes(['limit' => 1, 'order_by' => ['DTT_EVT_start' => 'DESC']]);
323
+		return end($datetimes);
324
+	}
325
+
326
+
327
+	/**
328
+	 * This returns the total tickets sold depending on the given parameters.
329
+	 *
330
+	 * @param string $what    Can be one of two options: 'ticket', 'datetime'.
331
+	 *                        'ticket' = total ticket sales for all datetimes this ticket is related to
332
+	 *                        'datetime' = total ticket sales for a specified datetime (required $dtt_id)
333
+	 *                        'datetime' = total ticket sales in the datetime_ticket table.
334
+	 *                        If $dtt_id is not given then we return an array of sales indexed by datetime.
335
+	 *                        If $dtt_id IS given then we return the tickets sold for that given datetime.
336
+	 * @param int    $dtt_id  [optional] include the dtt_id with $what = 'datetime'.
337
+	 * @return mixed (array|int)          how many tickets have sold
338
+	 * @throws EE_Error
339
+	 * @throws ReflectionException
340
+	 */
341
+	public function tickets_sold($what = 'ticket', $dtt_id = null)
342
+	{
343
+		$total        = 0;
344
+		$tickets_sold = $this->_all_tickets_sold();
345
+		switch ($what) {
346
+			case 'ticket':
347
+				return $tickets_sold['ticket'];
348
+				break;
349
+			case 'datetime':
350
+				if (empty($tickets_sold['datetime'])) {
351
+					return $total;
352
+				}
353
+				if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
354
+					EE_Error::add_error(
355
+						__(
356
+							'You\'ve requested the amount of tickets sold for a given ticket and datetime, however there are no records for the datetime id you included.  Are you SURE that is a datetime related to this ticket?',
357
+							'event_espresso'
358
+						),
359
+						__FILE__,
360
+						__FUNCTION__,
361
+						__LINE__
362
+					);
363
+					return $total;
364
+				}
365
+				return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
366
+				break;
367
+			default:
368
+				return $total;
369
+		}
370
+	}
371
+
372
+
373
+	/**
374
+	 * This returns an array indexed by datetime_id for tickets sold with this ticket.
375
+	 *
376
+	 * @return EE_Ticket[]
377
+	 * @throws EE_Error
378
+	 * @throws ReflectionException
379
+	 */
380
+	protected function _all_tickets_sold()
381
+	{
382
+		$datetimes    = $this->get_many_related('Datetime');
383
+		$tickets_sold = [];
384
+		if (! empty($datetimes)) {
385
+			foreach ($datetimes as $datetime) {
386
+				$tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
387
+			}
388
+		}
389
+		// Tickets sold
390
+		$tickets_sold['ticket'] = $this->sold();
391
+		return $tickets_sold;
392
+	}
393
+
394
+
395
+	/**
396
+	 * This returns the base price object for the ticket.
397
+	 *
398
+	 * @param bool $return_array whether to return as an array indexed by price id or just the object.
399
+	 * @return EE_Price|EE_Base_Class|EE_Price[]|EE_Base_Class[]
400
+	 * @throws EE_Error
401
+	 * @throws ReflectionException
402
+	 */
403
+	public function base_price($return_array = false)
404
+	{
405
+		$_where = ['Price_Type.PBT_ID' => EEM_Price_Type::base_type_base_price];
406
+		return $return_array
407
+			? $this->get_many_related('Price', [$_where])
408
+			: $this->get_first_related('Price', [$_where]);
409
+	}
410
+
411
+
412
+	/**
413
+	 * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
414
+	 *
415
+	 * @access public
416
+	 * @return EE_Price[]
417
+	 * @throws EE_Error
418
+	 * @throws ReflectionException
419
+	 */
420
+	public function price_modifiers()
421
+	{
422
+		$query_params = [
423
+			0 => [
424
+				'Price_Type.PBT_ID' => [
425
+					'NOT IN',
426
+					[EEM_Price_Type::base_type_base_price, EEM_Price_Type::base_type_tax],
427
+				],
428
+			],
429
+		];
430
+		return $this->prices($query_params);
431
+	}
432
+
433
+
434
+	/**
435
+	 * This returns ONLY the price modifiers for the ticket (i.e. no taxes or base price)
436
+	 *
437
+	 * @access public
438
+	 * @return EE_Price[]
439
+	 * @throws EE_Error
440
+	 * @throws ReflectionException
441
+	 */
442
+	public function tax_price_modifiers()
443
+	{
444
+		$query_params = [
445
+			0 => [
446
+				'Price_Type.PBT_ID' => EEM_Price_Type::base_type_tax,
447
+			],
448
+		];
449
+		return $this->prices($query_params);
450
+	}
451
+
452
+
453
+	/**
454
+	 * Gets all the prices that combine to form the final price of this ticket
455
+	 *
456
+	 * @param array $query_params @see
457
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
458
+	 * @return EE_Price[]|EE_Base_Class[]
459
+	 * @throws EE_Error
460
+	 * @throws ReflectionException
461
+	 */
462
+	public function prices($query_params = [])
463
+	{
464
+		return $this->get_many_related('Price', $query_params);
465
+	}
466
+
467
+
468
+	/**
469
+	 * Gets all the ticket datetimes (ie, relations between datetimes and tickets)
470
+	 *
471
+	 * @param array $query_params @see
472
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
473
+	 * @return EE_Datetime_Ticket|EE_Base_Class[]
474
+	 * @throws EE_Error
475
+	 * @throws ReflectionException
476
+	 */
477
+	public function datetime_tickets($query_params = [])
478
+	{
479
+		return $this->get_many_related('Datetime_Ticket', $query_params);
480
+	}
481
+
482
+
483
+	/**
484
+	 * Gets all the datetimes from the db ordered by DTT_order
485
+	 *
486
+	 * @param boolean $show_expired
487
+	 * @param boolean $show_deleted
488
+	 * @return EE_Datetime[]
489
+	 * @throws EE_Error
490
+	 * @throws ReflectionException
491
+	 */
492
+	public function datetimes_ordered($show_expired = true, $show_deleted = false)
493
+	{
494
+		return EEM_Datetime::instance($this->_timezone)->get_datetimes_for_ticket_ordered_by_DTT_order(
495
+			$this->ID(),
496
+			$show_expired,
497
+			$show_deleted
498
+		);
499
+	}
500
+
501
+
502
+	/**
503
+	 * Gets ID
504
+	 *
505
+	 * @return string
506
+	 * @throws EE_Error
507
+	 * @throws ReflectionException
508
+	 */
509
+	public function ID()
510
+	{
511
+		return $this->get('TKT_ID');
512
+	}
513
+
514
+
515
+	/**
516
+	 * get the author of the ticket.
517
+	 *
518
+	 * @return int
519
+	 * @throws EE_Error
520
+	 * @throws ReflectionException
521
+	 * @since 4.5.0
522
+	 */
523
+	public function wp_user()
524
+	{
525
+		return $this->get('TKT_wp_user');
526
+	}
527
+
528
+
529
+	/**
530
+	 * Gets the template for the ticket
531
+	 *
532
+	 * @return EE_Ticket_Template|EE_Base_Class
533
+	 * @throws EE_Error
534
+	 * @throws ReflectionException
535
+	 */
536
+	public function template()
537
+	{
538
+		return $this->get_first_related('Ticket_Template');
539
+	}
540
+
541
+
542
+	/**
543
+	 * Simply returns an array of EE_Price objects that are taxes.
544
+	 *
545
+	 * @return EE_Price[]
546
+	 * @throws EE_Error
547
+	 */
548
+	public function get_ticket_taxes_for_admin()
549
+	{
550
+		return EE_Taxes::get_taxes_for_admin();
551
+	}
552
+
553
+
554
+	/**
555
+	 * @return float
556
+	 * @throws EE_Error
557
+	 * @throws ReflectionException
558
+	 */
559
+	public function ticket_price()
560
+	{
561
+		return $this->get('TKT_price');
562
+	}
563
+
564
+
565
+	/**
566
+	 * @return mixed
567
+	 * @throws EE_Error
568
+	 * @throws ReflectionException
569
+	 */
570
+	public function pretty_price()
571
+	{
572
+		return $this->get_pretty('TKT_price');
573
+	}
574
+
575
+
576
+	/**
577
+	 * @return bool
578
+	 * @throws EE_Error
579
+	 * @throws ReflectionException
580
+	 */
581
+	public function is_free()
582
+	{
583
+		return $this->get_ticket_total_with_taxes() === (float) 0;
584
+	}
585
+
586
+
587
+	/**
588
+	 * get_ticket_total_with_taxes
589
+	 *
590
+	 * @param bool $no_cache
591
+	 * @return float
592
+	 * @throws EE_Error
593
+	 * @throws ReflectionException
594
+	 */
595
+	public function get_ticket_total_with_taxes($no_cache = false)
596
+	{
597
+		if ($this->_ticket_total_with_taxes === null || $no_cache) {
598
+			$this->_ticket_total_with_taxes = $this->get_ticket_subtotal() + $this->get_ticket_taxes_total_for_admin();
599
+		}
600
+		return (float) $this->_ticket_total_with_taxes;
601
+	}
602
+
603
+
604
+	/**
605
+	 * @throws EE_Error
606
+	 * @throws ReflectionException
607
+	 */
608
+	public function ensure_TKT_Price_correct()
609
+	{
610
+		$this->set('TKT_price', EE_Taxes::get_subtotal_for_admin($this));
611
+		$this->save();
612
+	}
613
+
614
+
615
+	/**
616
+	 * @return float
617
+	 * @throws EE_Error
618
+	 * @throws ReflectionException
619
+	 */
620
+	public function get_ticket_subtotal()
621
+	{
622
+		return EE_Taxes::get_subtotal_for_admin($this);
623
+	}
624
+
625
+
626
+	/**
627
+	 * Returns the total taxes applied to this ticket
628
+	 *
629
+	 * @return float
630
+	 * @throws EE_Error
631
+	 * @throws ReflectionException
632
+	 */
633
+	public function get_ticket_taxes_total_for_admin()
634
+	{
635
+		return EE_Taxes::get_total_taxes_for_admin($this);
636
+	}
637
+
638
+
639
+	/**
640
+	 * Sets name
641
+	 *
642
+	 * @param string $name
643
+	 * @throws EE_Error
644
+	 * @throws ReflectionException
645
+	 */
646
+	public function set_name($name)
647
+	{
648
+		$this->set('TKT_name', $name);
649
+	}
650
+
651
+
652
+	/**
653
+	 * Gets description
654
+	 *
655
+	 * @return string
656
+	 * @throws EE_Error
657
+	 * @throws ReflectionException
658
+	 */
659
+	public function description()
660
+	{
661
+		return $this->get('TKT_description');
662
+	}
663
+
664
+
665
+	/**
666
+	 * Sets description
667
+	 *
668
+	 * @param string $description
669
+	 * @throws EE_Error
670
+	 * @throws ReflectionException
671
+	 */
672
+	public function set_description($description)
673
+	{
674
+		$this->set('TKT_description', $description);
675
+	}
676
+
677
+
678
+	/**
679
+	 * Gets start_date
680
+	 *
681
+	 * @param string $date_format
682
+	 * @param string $time_format
683
+	 * @return string
684
+	 * @throws EE_Error
685
+	 * @throws ReflectionException
686
+	 */
687
+	public function start_date($date_format = '', $time_format = '')
688
+	{
689
+		return $this->_get_datetime('TKT_start_date', $date_format, $time_format);
690
+	}
691
+
692
+
693
+	/**
694
+	 * Sets start_date
695
+	 *
696
+	 * @param string $start_date
697
+	 * @return void
698
+	 * @throws EE_Error
699
+	 * @throws ReflectionException
700
+	 */
701
+	public function set_start_date($start_date)
702
+	{
703
+		$this->_set_date_time('B', $start_date, 'TKT_start_date');
704
+	}
705
+
706
+
707
+	/**
708
+	 * Gets end_date
709
+	 *
710
+	 * @param string $date_format
711
+	 * @param string $time_format
712
+	 * @return string
713
+	 * @throws EE_Error
714
+	 * @throws ReflectionException
715
+	 */
716
+	public function end_date($date_format = '', $time_format = '')
717
+	{
718
+		return $this->_get_datetime('TKT_end_date', $date_format, $time_format);
719
+	}
720
+
721
+
722
+	/**
723
+	 * Sets end_date
724
+	 *
725
+	 * @param string $end_date
726
+	 * @return void
727
+	 * @throws EE_Error
728
+	 * @throws ReflectionException
729
+	 */
730
+	public function set_end_date($end_date)
731
+	{
732
+		$this->_set_date_time('B', $end_date, 'TKT_end_date');
733
+	}
734
+
735
+
736
+	/**
737
+	 * Sets sell until time
738
+	 *
739
+	 * @param string $time a string representation of the sell until time (ex 9am or 7:30pm)
740
+	 * @throws EE_Error
741
+	 * @throws ReflectionException
742
+	 * @since 4.5.0
743
+	 */
744
+	public function set_end_time($time)
745
+	{
746
+		$this->_set_time_for($time, 'TKT_end_date');
747
+	}
748
+
749
+
750
+	/**
751
+	 * Sets min
752
+	 *
753
+	 * @param int $min
754
+	 * @return void
755
+	 * @throws EE_Error
756
+	 * @throws ReflectionException
757
+	 */
758
+	public function set_min($min)
759
+	{
760
+		$this->set('TKT_min', $min);
761
+	}
762
+
763
+
764
+	/**
765
+	 * Gets max
766
+	 *
767
+	 * @return int
768
+	 * @throws EE_Error
769
+	 * @throws ReflectionException
770
+	 */
771
+	public function max()
772
+	{
773
+		return $this->get('TKT_max');
774
+	}
775
+
776
+
777
+	/**
778
+	 * Sets max
779
+	 *
780
+	 * @param int $max
781
+	 * @return void
782
+	 * @throws EE_Error
783
+	 * @throws ReflectionException
784
+	 */
785
+	public function set_max($max)
786
+	{
787
+		$this->set('TKT_max', $max);
788
+	}
789
+
790
+
791
+	/**
792
+	 * Sets price
793
+	 *
794
+	 * @param float $price
795
+	 * @return void
796
+	 * @throws EE_Error
797
+	 * @throws ReflectionException
798
+	 */
799
+	public function set_price($price)
800
+	{
801
+		$this->set('TKT_price', $price);
802
+	}
803
+
804
+
805
+	/**
806
+	 * Gets sold
807
+	 *
808
+	 * @return int
809
+	 * @throws EE_Error
810
+	 * @throws ReflectionException
811
+	 */
812
+	public function sold()
813
+	{
814
+		return $this->get_raw('TKT_sold');
815
+	}
816
+
817
+
818
+	/**
819
+	 * Sets sold
820
+	 *
821
+	 * @param int $sold
822
+	 * @return void
823
+	 * @throws EE_Error
824
+	 * @throws ReflectionException
825
+	 */
826
+	public function set_sold($sold)
827
+	{
828
+		// sold can not go below zero
829
+		$sold = max(0, $sold);
830
+		$this->set('TKT_sold', $sold);
831
+	}
832
+
833
+
834
+	/**
835
+	 * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
836
+	 * associated datetimes.
837
+	 *
838
+	 * @param int $qty
839
+	 * @return boolean
840
+	 * @throws EE_Error
841
+	 * @throws InvalidArgumentException
842
+	 * @throws InvalidDataTypeException
843
+	 * @throws InvalidInterfaceException
844
+	 * @throws ReflectionException
845
+	 * @since 4.9.80.p
846
+	 */
847
+	public function increaseSold($qty = 1)
848
+	{
849
+		$qty = absint($qty);
850
+		// increment sold and decrement reserved datetime quantities simultaneously
851
+		// don't worry about failures, because they must have already had a spot reserved
852
+		$this->increaseSoldForDatetimes($qty);
853
+		// Increment and decrement ticket quantities simultaneously
854
+		$success = $this->adjustNumericFieldsInDb(
855
+			[
856
+				'TKT_reserved' => $qty * -1,
857
+				'TKT_sold'     => $qty,
858
+			]
859
+		);
860
+		do_action(
861
+			'AHEE__EE_Ticket__increase_sold',
862
+			$this,
863
+			$qty,
864
+			$this->sold(),
865
+			$success
866
+		);
867
+		return $success;
868
+	}
869
+
870
+
871
+	/**
872
+	 * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
873
+	 *
874
+	 * @param int           $qty positive or negative. Positive means to increase sold counts (and decrease reserved
875
+	 *                           counts), Negative means to decreases old counts (and increase reserved counts).
876
+	 * @param EE_Datetime[] $datetimes
877
+	 * @throws EE_Error
878
+	 * @throws InvalidArgumentException
879
+	 * @throws InvalidDataTypeException
880
+	 * @throws InvalidInterfaceException
881
+	 * @throws ReflectionException
882
+	 * @since 4.9.80.p
883
+	 */
884
+	protected function increaseSoldForDatetimes($qty, array $datetimes = [])
885
+	{
886
+		$datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
887
+		foreach ($datetimes as $datetime) {
888
+			$datetime->increaseSold($qty);
889
+		}
890
+	}
891
+
892
+
893
+	/**
894
+	 * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
895
+	 * DB and then updates the model objects.
896
+	 * Does not affect the reserved counts.
897
+	 *
898
+	 * @param int $qty
899
+	 * @return boolean
900
+	 * @throws EE_Error
901
+	 * @throws InvalidArgumentException
902
+	 * @throws InvalidDataTypeException
903
+	 * @throws InvalidInterfaceException
904
+	 * @throws ReflectionException
905
+	 * @since 4.9.80.p
906
+	 */
907
+	public function decreaseSold($qty = 1)
908
+	{
909
+		$qty = absint($qty);
910
+		$this->decreaseSoldForDatetimes($qty);
911
+		$success = $this->adjustNumericFieldsInDb(
912
+			[
913
+				'TKT_sold' => $qty * -1,
914
+			]
915
+		);
916
+		do_action(
917
+			'AHEE__EE_Ticket__decrease_sold',
918
+			$this,
919
+			$qty,
920
+			$this->sold(),
921
+			$success
922
+		);
923
+		return $success;
924
+	}
925
+
926
+
927
+	/**
928
+	 * Decreases sold on related datetimes
929
+	 *
930
+	 * @param int           $qty
931
+	 * @param EE_Datetime[] $datetimes
932
+	 * @return void
933
+	 * @throws EE_Error
934
+	 * @throws InvalidArgumentException
935
+	 * @throws InvalidDataTypeException
936
+	 * @throws InvalidInterfaceException
937
+	 * @throws ReflectionException
938
+	 * @since 4.9.80.p
939
+	 */
940
+	protected function decreaseSoldForDatetimes($qty = 1, array $datetimes = [])
941
+	{
942
+		$datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
943
+		if (is_array($datetimes)) {
944
+			foreach ($datetimes as $datetime) {
945
+				if ($datetime instanceof EE_Datetime) {
946
+					$datetime->decreaseSold($qty);
947
+				}
948
+			}
949
+		}
950
+	}
951
+
952
+
953
+	/**
954
+	 * Gets qty of reserved tickets
955
+	 *
956
+	 * @return int
957
+	 * @throws EE_Error
958
+	 * @throws ReflectionException
959
+	 */
960
+	public function reserved()
961
+	{
962
+		return $this->get_raw('TKT_reserved');
963
+	}
964
+
965
+
966
+	/**
967
+	 * Sets reserved
968
+	 *
969
+	 * @param int $reserved
970
+	 * @return void
971
+	 * @throws EE_Error
972
+	 * @throws ReflectionException
973
+	 */
974
+	public function set_reserved($reserved)
975
+	{
976
+		// reserved can not go below zero
977
+		$reserved = max(0, (int) $reserved);
978
+		$this->set('TKT_reserved', $reserved);
979
+	}
980
+
981
+
982
+	/**
983
+	 * Increments reserved by amount passed by $qty, and persists it immediately to the database.
984
+	 *
985
+	 * @param int    $qty
986
+	 * @param string $source
987
+	 * @return bool whether we successfully reserved the ticket or not.
988
+	 * @throws EE_Error
989
+	 * @throws InvalidArgumentException
990
+	 * @throws ReflectionException
991
+	 * @throws InvalidDataTypeException
992
+	 * @throws InvalidInterfaceException
993
+	 * @since 4.9.80.p
994
+	 */
995
+	public function increaseReserved($qty = 1, $source = 'unknown')
996
+	{
997
+		$qty = absint($qty);
998
+		do_action(
999
+			'AHEE__EE_Ticket__increase_reserved__begin',
1000
+			$this,
1001
+			$qty,
1002
+			$source
1003
+		);
1004
+		$this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "{$qty} from {$source}");
1005
+		$success                         = false;
1006
+		$datetimes_adjusted_successfully = $this->increaseReservedForDatetimes($qty);
1007
+		if ($datetimes_adjusted_successfully) {
1008
+			$success = $this->incrementFieldConditionallyInDb(
1009
+				'TKT_reserved',
1010
+				'TKT_sold',
1011
+				'TKT_qty',
1012
+				$qty
1013
+			);
1014
+			if (! $success) {
1015
+				// The datetimes were successfully bumped, but not the
1016
+				// ticket. So we need to manually rollback the datetimes.
1017
+				$this->decreaseReservedForDatetimes($qty);
1018
+			}
1019
+		}
1020
+		do_action(
1021
+			'AHEE__EE_Ticket__increase_reserved',
1022
+			$this,
1023
+			$qty,
1024
+			$this->reserved(),
1025
+			$success
1026
+		);
1027
+		return $success;
1028
+	}
1029
+
1030
+
1031
+	/**
1032
+	 * Increases reserved counts on related datetimes
1033
+	 *
1034
+	 * @param int           $qty
1035
+	 * @param EE_Datetime[] $datetimes
1036
+	 * @return boolean indicating success
1037
+	 * @throws EE_Error
1038
+	 * @throws InvalidArgumentException
1039
+	 * @throws InvalidDataTypeException
1040
+	 * @throws InvalidInterfaceException
1041
+	 * @throws ReflectionException
1042
+	 * @since 4.9.80.p
1043
+	 */
1044
+	protected function increaseReservedForDatetimes($qty = 1, array $datetimes = [])
1045
+	{
1046
+		$datetimes         = ! empty($datetimes) ? $datetimes : $this->datetimes();
1047
+		$datetimes_updated = [];
1048
+		$limit_exceeded    = false;
1049
+		if (is_array($datetimes)) {
1050
+			foreach ($datetimes as $datetime) {
1051
+				if ($datetime instanceof EE_Datetime) {
1052
+					if ($datetime->increaseReserved($qty)) {
1053
+						$datetimes_updated[] = $datetime;
1054
+					} else {
1055
+						$limit_exceeded = true;
1056
+						break;
1057
+					}
1058
+				}
1059
+			}
1060
+			// If somewhere along the way we detected a datetime whose
1061
+			// limit was exceeded, do a manual rollback.
1062
+			if ($limit_exceeded) {
1063
+				$this->decreaseReservedForDatetimes($qty, $datetimes_updated);
1064
+				return false;
1065
+			}
1066
+		}
1067
+		return true;
1068
+	}
1069
+
1070
+
1071
+	/**
1072
+	 * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
1073
+	 *
1074
+	 * @param int    $qty
1075
+	 * @param bool   $adjust_datetimes
1076
+	 * @param string $source
1077
+	 * @return boolean
1078
+	 * @throws EE_Error
1079
+	 * @throws InvalidArgumentException
1080
+	 * @throws ReflectionException
1081
+	 * @throws InvalidDataTypeException
1082
+	 * @throws InvalidInterfaceException
1083
+	 * @since 4.9.80.p
1084
+	 */
1085
+	public function decreaseReserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
1086
+	{
1087
+		$qty = absint($qty);
1088
+		$this->add_extra_meta(EE_Ticket::META_KEY_TICKET_RESERVATIONS, "-{$qty} from {$source}");
1089
+		if ($adjust_datetimes) {
1090
+			$this->decreaseReservedForDatetimes($qty);
1091
+		}
1092
+		$success = $this->adjustNumericFieldsInDb(
1093
+			[
1094
+				'TKT_reserved' => $qty * -1,
1095
+			]
1096
+		);
1097
+		do_action(
1098
+			'AHEE__EE_Ticket__decrease_reserved',
1099
+			$this,
1100
+			$qty,
1101
+			$this->reserved(),
1102
+			$success
1103
+		);
1104
+		return $success;
1105
+	}
1106
+
1107
+
1108
+	/**
1109
+	 * Decreases the reserved count on the specified datetimes.
1110
+	 *
1111
+	 * @param int           $qty
1112
+	 * @param EE_Datetime[] $datetimes
1113
+	 * @throws EE_Error
1114
+	 * @throws InvalidArgumentException
1115
+	 * @throws ReflectionException
1116
+	 * @throws InvalidDataTypeException
1117
+	 * @throws InvalidInterfaceException
1118
+	 * @since 4.9.80.p
1119
+	 */
1120
+	protected function decreaseReservedForDatetimes($qty = 1, array $datetimes = [])
1121
+	{
1122
+		$datetimes = ! empty($datetimes) ? $datetimes : $this->datetimes();
1123
+		foreach ($datetimes as $datetime) {
1124
+			if ($datetime instanceof EE_Datetime) {
1125
+				$datetime->decreaseReserved($qty);
1126
+			}
1127
+		}
1128
+	}
1129
+
1130
+
1131
+	/**
1132
+	 * Gets ticket quantity
1133
+	 *
1134
+	 * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1135
+	 *                            therefore $context can be one of three values: '', 'reg_limit', or 'saleable'
1136
+	 *                            '' (default) quantity is the actual db value for TKT_qty, unaffected by other objects
1137
+	 *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1138
+	 *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1139
+	 *                            is therefore the truest measure of tickets that can be purchased at the moment
1140
+	 * @return int
1141
+	 * @throws EE_Error
1142
+	 * @throws ReflectionException
1143
+	 */
1144
+	public function qty($context = '')
1145
+	{
1146
+		switch ($context) {
1147
+			case 'reg_limit':
1148
+				return $this->real_quantity_on_ticket();
1149
+			case 'saleable':
1150
+				return $this->real_quantity_on_ticket('saleable');
1151
+			default:
1152
+				return $this->get_raw('TKT_qty');
1153
+		}
1154
+	}
1155
+
1156
+
1157
+	/**
1158
+	 * Gets ticket quantity
1159
+	 *
1160
+	 * @param string $context     ticket quantity is somewhat subjective depending on the exact information sought
1161
+	 *                            therefore $context can be one of two values: 'reg_limit', or 'saleable'
1162
+	 *                            REG LIMIT: caps qty based on DTT_reg_limit for ALL related datetimes
1163
+	 *                            SALEABLE: also considers datetime sold and returns zero if ANY DTT is sold out, and
1164
+	 *                            is therefore the truest measure of tickets that can be purchased at the moment
1165
+	 * @param int    $DTT_ID      the primary key for a particular datetime.
1166
+	 *                            set to 0 for all related datetimes
1167
+	 * @return int
1168
+	 * @throws EE_Error
1169
+	 * @throws ReflectionException
1170
+	 */
1171
+	public function real_quantity_on_ticket($context = 'reg_limit', $DTT_ID = 0)
1172
+	{
1173
+		$raw = $this->get_raw('TKT_qty');
1174
+		// return immediately if it's zero
1175
+		if ($raw === 0) {
1176
+			return $raw;
1177
+		}
1178
+		// echo "\n\n<br />Ticket: " . $this->name() . '<br />';
1179
+		// ensure qty doesn't exceed raw value for THIS ticket
1180
+		$qty = min(EE_INF, $raw);
1181
+		// echo "\n . qty: " . $qty . '<br />';
1182
+		// calculate this ticket's total sales and reservations
1183
+		$sold_and_reserved_for_this_ticket = $this->sold() + $this->reserved();
1184
+		// echo "\n . sold: " . $this->sold() . '<br />';
1185
+		// echo "\n . reserved: " . $this->reserved() . '<br />';
1186
+		// echo "\n . sold_and_reserved_for_this_ticket: " . $sold_and_reserved_for_this_ticket . '<br />';
1187
+		// first we need to calculate the maximum number of tickets available for the datetime
1188
+		// do we want data for one datetime or all of them ?
1189
+		$query_params = $DTT_ID ? [['DTT_ID' => $DTT_ID]] : [];
1190
+		$datetimes    = $this->datetimes($query_params);
1191
+		if (is_array($datetimes) && ! empty($datetimes)) {
1192
+			foreach ($datetimes as $datetime) {
1193
+				if ($datetime instanceof EE_Datetime) {
1194
+					$datetime->refresh_from_db();
1195
+					// echo "\n . . datetime name: " . $datetime->name() . '<br />';
1196
+					// echo "\n . . datetime ID: " . $datetime->ID() . '<br />';
1197
+					// initialize with no restrictions for each datetime
1198
+					// but adjust datetime qty based on datetime reg limit
1199
+					$datetime_qty = min(EE_INF, $datetime->reg_limit());
1200
+					// echo "\n . . . datetime reg_limit: " . $datetime->reg_limit() . '<br />';
1201
+					// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1202
+					// if we want the actual saleable amount, then we need to consider OTHER ticket sales
1203
+					// and reservations for this datetime, that do NOT include sales and reservations
1204
+					// for this ticket (so we add $this->sold() and $this->reserved() back in)
1205
+					if ($context === 'saleable') {
1206
+						$datetime_qty = max(
1207
+							$datetime_qty - $datetime->sold_and_reserved() + $sold_and_reserved_for_this_ticket,
1208
+							0
1209
+						);
1210
+						// echo "\n . . . datetime sold: " . $datetime->sold() . '<br />';
1211
+						// echo "\n . . . datetime reserved: " . $datetime->reserved() . '<br />';
1212
+						// echo "\n . . . datetime sold_and_reserved: " . $datetime->sold_and_reserved() . '<br />';
1213
+						// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1214
+						$datetime_qty = ! $datetime->sold_out() ? $datetime_qty : 0;
1215
+						// echo "\n . . . datetime_qty: " . $datetime_qty . '<br />';
1216
+					}
1217
+					$qty = min($datetime_qty, $qty);
1218
+					// echo "\n . . qty: " . $qty . '<br />';
1219
+				}
1220
+			}
1221
+		}
1222
+		// NOW that we know the  maximum number of tickets available for the datetime
1223
+		// we can finally factor in the details for this specific ticket
1224
+		if ($qty > 0 && $context === 'saleable') {
1225
+			// and subtract the sales for THIS ticket
1226
+			$qty = max($qty - $sold_and_reserved_for_this_ticket, 0);
1227
+			// echo "\n . qty: " . $qty . '<br />';
1228
+		}
1229
+		// echo "\nFINAL QTY: " . $qty . "<br /><br />";
1230
+		return $qty;
1231
+	}
1232
+
1233
+
1234
+	/**
1235
+	 * Sets qty - IMPORTANT!!! Does NOT allow QTY to be set higher than the lowest reg limit of any related datetimes
1236
+	 *
1237
+	 * @param int $qty
1238
+	 * @return void
1239
+	 * @throws EE_Error
1240
+	 * @throws ReflectionException
1241
+	 */
1242
+	public function set_qty($qty)
1243
+	{
1244
+		$datetimes = $this->datetimes();
1245
+		foreach ($datetimes as $datetime) {
1246
+			if ($datetime instanceof EE_Datetime) {
1247
+				$qty = min($qty, $datetime->reg_limit());
1248
+			}
1249
+		}
1250
+		$this->set('TKT_qty', $qty);
1251
+	}
1252
+
1253
+
1254
+	/**
1255
+	 * Gets uses
1256
+	 *
1257
+	 * @return int
1258
+	 * @throws EE_Error
1259
+	 * @throws ReflectionException
1260
+	 */
1261
+	public function uses()
1262
+	{
1263
+		return $this->get('TKT_uses');
1264
+	}
1265
+
1266
+
1267
+	/**
1268
+	 * Sets uses
1269
+	 *
1270
+	 * @param int $uses
1271
+	 * @return void
1272
+	 * @throws EE_Error
1273
+	 * @throws ReflectionException
1274
+	 */
1275
+	public function set_uses($uses)
1276
+	{
1277
+		$this->set('TKT_uses', $uses);
1278
+	}
1279
+
1280
+
1281
+	/**
1282
+	 * returns whether ticket is required or not.
1283
+	 *
1284
+	 * @return boolean
1285
+	 * @throws EE_Error
1286
+	 * @throws ReflectionException
1287
+	 */
1288
+	public function required()
1289
+	{
1290
+		return $this->get('TKT_required');
1291
+	}
1292
+
1293
+
1294
+	/**
1295
+	 * sets the TKT_required property
1296
+	 *
1297
+	 * @param boolean $required
1298
+	 * @return void
1299
+	 * @throws EE_Error
1300
+	 * @throws ReflectionException
1301
+	 */
1302
+	public function set_required($required)
1303
+	{
1304
+		$this->set('TKT_required', $required);
1305
+	}
1306
+
1307
+
1308
+	/**
1309
+	 * Gets taxable
1310
+	 *
1311
+	 * @return boolean
1312
+	 * @throws EE_Error
1313
+	 * @throws ReflectionException
1314
+	 */
1315
+	public function taxable()
1316
+	{
1317
+		return $this->get('TKT_taxable');
1318
+	}
1319
+
1320
+
1321
+	/**
1322
+	 * Sets taxable
1323
+	 *
1324
+	 * @param boolean $taxable
1325
+	 * @return void
1326
+	 * @throws EE_Error
1327
+	 * @throws ReflectionException
1328
+	 */
1329
+	public function set_taxable($taxable)
1330
+	{
1331
+		$this->set('TKT_taxable', $taxable);
1332
+	}
1333
+
1334
+
1335
+	/**
1336
+	 * Gets is_default
1337
+	 *
1338
+	 * @return boolean
1339
+	 * @throws EE_Error
1340
+	 * @throws ReflectionException
1341
+	 */
1342
+	public function is_default()
1343
+	{
1344
+		return $this->get('TKT_is_default');
1345
+	}
1346
+
1347
+
1348
+	/**
1349
+	 * Sets is_default
1350
+	 *
1351
+	 * @param boolean $is_default
1352
+	 * @return void
1353
+	 * @throws EE_Error
1354
+	 * @throws ReflectionException
1355
+	 */
1356
+	public function set_is_default($is_default)
1357
+	{
1358
+		$this->set('TKT_is_default', $is_default);
1359
+	}
1360
+
1361
+
1362
+	/**
1363
+	 * Gets order
1364
+	 *
1365
+	 * @return int
1366
+	 * @throws EE_Error
1367
+	 * @throws ReflectionException
1368
+	 */
1369
+	public function order()
1370
+	{
1371
+		return $this->get('TKT_order');
1372
+	}
1373
+
1374
+
1375
+	/**
1376
+	 * Sets order
1377
+	 *
1378
+	 * @param int $order
1379
+	 * @return void
1380
+	 * @throws EE_Error
1381
+	 * @throws ReflectionException
1382
+	 */
1383
+	public function set_order($order)
1384
+	{
1385
+		$this->set('TKT_order', $order);
1386
+	}
1387
+
1388
+
1389
+	/**
1390
+	 * Gets row
1391
+	 *
1392
+	 * @return int
1393
+	 * @throws EE_Error
1394
+	 * @throws ReflectionException
1395
+	 */
1396
+	public function row()
1397
+	{
1398
+		return $this->get('TKT_row');
1399
+	}
1400
+
1401
+
1402
+	/**
1403
+	 * Sets row
1404
+	 *
1405
+	 * @param int $row
1406
+	 * @return void
1407
+	 * @throws EE_Error
1408
+	 * @throws ReflectionException
1409
+	 */
1410
+	public function set_row($row)
1411
+	{
1412
+		$this->set('TKT_row', $row);
1413
+	}
1414
+
1415
+
1416
+	/**
1417
+	 * Gets deleted
1418
+	 *
1419
+	 * @return boolean
1420
+	 * @throws EE_Error
1421
+	 * @throws ReflectionException
1422
+	 */
1423
+	public function deleted()
1424
+	{
1425
+		return $this->get('TKT_deleted');
1426
+	}
1427
+
1428
+
1429
+	/**
1430
+	 * Sets deleted
1431
+	 *
1432
+	 * @param boolean $deleted
1433
+	 * @return void
1434
+	 * @throws EE_Error
1435
+	 * @throws ReflectionException
1436
+	 */
1437
+	public function set_deleted($deleted)
1438
+	{
1439
+		$this->set('TKT_deleted', $deleted);
1440
+	}
1441
+
1442
+
1443
+	/**
1444
+	 * Gets parent
1445
+	 *
1446
+	 * @return int
1447
+	 * @throws EE_Error
1448
+	 * @throws ReflectionException
1449
+	 */
1450
+	public function parent_ID()
1451
+	{
1452
+		return $this->get('TKT_parent');
1453
+	}
1454
+
1455
+
1456
+	/**
1457
+	 * Sets parent
1458
+	 *
1459
+	 * @param int $parent
1460
+	 * @return void
1461
+	 * @throws EE_Error
1462
+	 * @throws ReflectionException
1463
+	 */
1464
+	public function set_parent_ID($parent)
1465
+	{
1466
+		$this->set('TKT_parent', $parent);
1467
+	}
1468
+
1469
+
1470
+	/**
1471
+	 * @return boolean
1472
+	 * @throws EE_Error
1473
+	 * @throws InvalidArgumentException
1474
+	 * @throws InvalidDataTypeException
1475
+	 * @throws InvalidInterfaceException
1476
+	 * @throws ReflectionException
1477
+	 */
1478
+	public function reverse_calculate()
1479
+	{
1480
+		return $this->get('TKT_reverse_calculate');
1481
+	}
1482
+
1483
+
1484
+	/**
1485
+	 * @param boolean $reverse_calculate
1486
+	 * @throws EE_Error
1487
+	 * @throws InvalidArgumentException
1488
+	 * @throws InvalidDataTypeException
1489
+	 * @throws InvalidInterfaceException
1490
+	 * @throws ReflectionException
1491
+	 */
1492
+	public function set_reverse_calculate($reverse_calculate)
1493
+	{
1494
+		$this->set('TKT_reverse_calculate', $reverse_calculate);
1495
+	}
1496
+
1497
+
1498
+	/**
1499
+	 * Gets a string which is handy for showing in gateways etc that describes the ticket.
1500
+	 *
1501
+	 * @return string
1502
+	 * @throws EE_Error
1503
+	 * @throws ReflectionException
1504
+	 */
1505
+	public function name_and_info()
1506
+	{
1507
+		$times = [];
1508
+		foreach ($this->datetimes() as $datetime) {
1509
+			$times[] = $datetime->start_date_and_time();
1510
+		}
1511
+		return $this->name() . ' @ ' . implode(', ', $times) . ' for ' . $this->pretty_price();
1512
+	}
1513
+
1514
+
1515
+	/**
1516
+	 * Gets name
1517
+	 *
1518
+	 * @return string
1519
+	 * @throws EE_Error
1520
+	 * @throws ReflectionException
1521
+	 */
1522
+	public function name()
1523
+	{
1524
+		return $this->get('TKT_name');
1525
+	}
1526
+
1527
+
1528
+	/**
1529
+	 * Gets price
1530
+	 *
1531
+	 * @return float
1532
+	 * @throws EE_Error
1533
+	 * @throws ReflectionException
1534
+	 */
1535
+	public function price()
1536
+	{
1537
+		return $this->get('TKT_price');
1538
+	}
1539
+
1540
+
1541
+	/**
1542
+	 * Gets all the registrations for this ticket
1543
+	 *
1544
+	 * @param array $query_params @see
1545
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1546
+	 * @return EE_Registration[]|EE_Base_Class[]
1547
+	 * @throws EE_Error
1548
+	 * @throws ReflectionException
1549
+	 */
1550
+	public function registrations($query_params = [])
1551
+	{
1552
+		return $this->get_many_related('Registration', $query_params);
1553
+	}
1554
+
1555
+
1556
+	/**
1557
+	 * Updates the TKT_sold attribute (and saves) based on the number of APPROVED registrations for this ticket.
1558
+	 *
1559
+	 * @return int
1560
+	 * @throws EE_Error
1561
+	 * @throws ReflectionException
1562
+	 */
1563
+	public function update_tickets_sold()
1564
+	{
1565
+		$count_regs_for_this_ticket = $this->count_registrations(
1566
+			[
1567
+				[
1568
+					'STS_ID'      => EEM_Registration::status_id_approved,
1569
+					'REG_deleted' => 0,
1570
+				],
1571
+			]
1572
+		);
1573
+		$this->set_sold($count_regs_for_this_ticket);
1574
+		$this->save();
1575
+		return $count_regs_for_this_ticket;
1576
+	}
1577
+
1578
+
1579
+	/**
1580
+	 * Counts the registrations for this ticket
1581
+	 *
1582
+	 * @param array $query_params @see
1583
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1584
+	 * @return int
1585
+	 * @throws EE_Error
1586
+	 * @throws ReflectionException
1587
+	 */
1588
+	public function count_registrations($query_params = [])
1589
+	{
1590
+		return $this->count_related('Registration', $query_params);
1591
+	}
1592
+
1593
+
1594
+	/**
1595
+	 * Implementation for EEI_Has_Icon interface method.
1596
+	 *
1597
+	 * @return string
1598
+	 * @see EEI_Visual_Representation for comments
1599
+	 */
1600
+	public function get_icon()
1601
+	{
1602
+		return '<span class="dashicons dashicons-tickets-alt"/>';
1603
+	}
1604
+
1605
+
1606
+	/**
1607
+	 * Implementation of the EEI_Event_Relation interface method
1608
+	 *
1609
+	 * @return EE_Event
1610
+	 * @throws EE_Error
1611
+	 * @throws UnexpectedEntityException
1612
+	 * @throws ReflectionException
1613
+	 * @see EEI_Event_Relation for comments
1614
+	 */
1615
+	public function get_related_event()
1616
+	{
1617
+		// get one datetime to use for getting the event
1618
+		$datetime = $this->first_datetime();
1619
+		if (! $datetime instanceof EE_Datetime) {
1620
+			throw new UnexpectedEntityException(
1621
+				$datetime,
1622
+				'EE_Datetime',
1623
+				sprintf(
1624
+					__('The ticket (%s) is not associated with any valid datetimes.', 'event_espresso'),
1625
+					$this->name()
1626
+				)
1627
+			);
1628
+		}
1629
+		$event = $datetime->event();
1630
+		if (! $event instanceof EE_Event) {
1631
+			throw new UnexpectedEntityException(
1632
+				$event,
1633
+				'EE_Event',
1634
+				sprintf(
1635
+					__('The ticket (%s) is not associated with a valid event.', 'event_espresso'),
1636
+					$this->name()
1637
+				)
1638
+			);
1639
+		}
1640
+		return $event;
1641
+	}
1642
+
1643
+
1644
+	/**
1645
+	 * Implementation of the EEI_Event_Relation interface method
1646
+	 *
1647
+	 * @return string
1648
+	 * @throws UnexpectedEntityException
1649
+	 * @throws EE_Error
1650
+	 * @throws ReflectionException
1651
+	 * @see EEI_Event_Relation for comments
1652
+	 */
1653
+	public function get_event_name()
1654
+	{
1655
+		$event = $this->get_related_event();
1656
+		return $event instanceof EE_Event ? $event->name() : '';
1657
+	}
1658
+
1659
+
1660
+	/**
1661
+	 * Implementation of the EEI_Event_Relation interface method
1662
+	 *
1663
+	 * @return int
1664
+	 * @throws UnexpectedEntityException
1665
+	 * @throws EE_Error
1666
+	 * @throws ReflectionException
1667
+	 * @see EEI_Event_Relation for comments
1668
+	 */
1669
+	public function get_event_ID()
1670
+	{
1671
+		$event = $this->get_related_event();
1672
+		return $event instanceof EE_Event ? $event->ID() : 0;
1673
+	}
1674
+
1675
+
1676
+	/**
1677
+	 * This simply returns whether a ticket can be permanently deleted or not.
1678
+	 * The criteria for determining this is whether the ticket has any related registrations.
1679
+	 * If there are none then it can be permanently deleted.
1680
+	 *
1681
+	 * @return bool
1682
+	 * @throws EE_Error
1683
+	 * @throws ReflectionException
1684
+	 */
1685
+	public function is_permanently_deleteable()
1686
+	{
1687
+		return $this->count_registrations() === 0;
1688
+	}
1689
+
1690
+
1691
+	/*******************************************************************
1692 1692
      ***********************  DEPRECATED METHODS  **********************
1693 1693
      *******************************************************************/
1694 1694
 
1695 1695
 
1696
-    /**
1697
-     * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
1698
-     * associated datetimes.
1699
-     *
1700
-     * @param int $qty
1701
-     * @return void
1702
-     * @throws EE_Error
1703
-     * @throws InvalidArgumentException
1704
-     * @throws InvalidDataTypeException
1705
-     * @throws InvalidInterfaceException
1706
-     * @throws ReflectionException
1707
-     * @deprecated 4.9.80.p
1708
-     */
1709
-    public function increase_sold($qty = 1)
1710
-    {
1711
-        EE_Error::doing_it_wrong(
1712
-            __FUNCTION__,
1713
-            esc_html__('Please use EE_Ticket::increaseSold() instead', 'event_espresso'),
1714
-            '4.9.80.p',
1715
-            '5.0.0.p'
1716
-        );
1717
-        $this->increaseSold($qty);
1718
-    }
1719
-
1720
-
1721
-    /**
1722
-     * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
1723
-     *
1724
-     * @param int $qty positive or negative. Positive means to increase sold counts (and decrease reserved counts),
1725
-     *                 Negative means to decreases old counts (and increase reserved counts).
1726
-     * @throws EE_Error
1727
-     * @throws InvalidArgumentException
1728
-     * @throws InvalidDataTypeException
1729
-     * @throws InvalidInterfaceException
1730
-     * @throws ReflectionException
1731
-     * @deprecated 4.9.80.p
1732
-     */
1733
-    protected function _increase_sold_for_datetimes($qty)
1734
-    {
1735
-        EE_Error::doing_it_wrong(
1736
-            __FUNCTION__,
1737
-            esc_html__('Please use EE_Ticket::increaseSoldForDatetimes() instead', 'event_espresso'),
1738
-            '4.9.80.p',
1739
-            '5.0.0.p'
1740
-        );
1741
-        $this->increaseSoldForDatetimes($qty);
1742
-    }
1743
-
1744
-
1745
-    /**
1746
-     * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
1747
-     * DB and then updates the model objects.
1748
-     * Does not affect the reserved counts.
1749
-     *
1750
-     * @param int $qty
1751
-     * @return void
1752
-     * @throws EE_Error
1753
-     * @throws InvalidArgumentException
1754
-     * @throws InvalidDataTypeException
1755
-     * @throws InvalidInterfaceException
1756
-     * @throws ReflectionException
1757
-     * @deprecated 4.9.80.p
1758
-     */
1759
-    public function decrease_sold($qty = 1)
1760
-    {
1761
-        EE_Error::doing_it_wrong(
1762
-            __FUNCTION__,
1763
-            esc_html__('Please use EE_Ticket::decreaseSold() instead', 'event_espresso'),
1764
-            '4.9.80.p',
1765
-            '5.0.0.p'
1766
-        );
1767
-        $this->decreaseSold($qty);
1768
-    }
1769
-
1770
-
1771
-    /**
1772
-     * Decreases sold on related datetimes
1773
-     *
1774
-     * @param int $qty
1775
-     * @return void
1776
-     * @throws EE_Error
1777
-     * @throws InvalidArgumentException
1778
-     * @throws InvalidDataTypeException
1779
-     * @throws InvalidInterfaceException
1780
-     * @throws ReflectionException
1781
-     * @deprecated 4.9.80.p
1782
-     */
1783
-    protected function _decrease_sold_for_datetimes($qty = 1)
1784
-    {
1785
-        EE_Error::doing_it_wrong(
1786
-            __FUNCTION__,
1787
-            esc_html__('Please use EE_Ticket::decreaseSoldForDatetimes() instead', 'event_espresso'),
1788
-            '4.9.80.p',
1789
-            '5.0.0.p'
1790
-        );
1791
-        $this->decreaseSoldForDatetimes($qty);
1792
-    }
1793
-
1794
-
1795
-    /**
1796
-     * Increments reserved by amount passed by $qty, and persists it immediately to the database.
1797
-     *
1798
-     * @param int    $qty
1799
-     * @param string $source
1800
-     * @return bool whether we successfully reserved the ticket or not.
1801
-     * @throws EE_Error
1802
-     * @throws InvalidArgumentException
1803
-     * @throws ReflectionException
1804
-     * @throws InvalidDataTypeException
1805
-     * @throws InvalidInterfaceException
1806
-     * @deprecated 4.9.80.p
1807
-     */
1808
-    public function increase_reserved($qty = 1, $source = 'unknown')
1809
-    {
1810
-        EE_Error::doing_it_wrong(
1811
-            __FUNCTION__,
1812
-            esc_html__('Please use EE_Ticket::increaseReserved() instead', 'event_espresso'),
1813
-            '4.9.80.p',
1814
-            '5.0.0.p'
1815
-        );
1816
-        return $this->increaseReserved($qty);
1817
-    }
1818
-
1819
-
1820
-    /**
1821
-     * Increases sold on related datetimes
1822
-     *
1823
-     * @param int $qty
1824
-     * @return boolean indicating success
1825
-     * @throws EE_Error
1826
-     * @throws InvalidArgumentException
1827
-     * @throws InvalidDataTypeException
1828
-     * @throws InvalidInterfaceException
1829
-     * @throws ReflectionException
1830
-     * @deprecated 4.9.80.p
1831
-     */
1832
-    protected function _increase_reserved_for_datetimes($qty = 1)
1833
-    {
1834
-        EE_Error::doing_it_wrong(
1835
-            __FUNCTION__,
1836
-            esc_html__('Please use EE_Ticket::increaseReservedForDatetimes() instead', 'event_espresso'),
1837
-            '4.9.80.p',
1838
-            '5.0.0.p'
1839
-        );
1840
-        return $this->increaseReservedForDatetimes($qty);
1841
-    }
1842
-
1843
-
1844
-    /**
1845
-     * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
1846
-     *
1847
-     * @param int    $qty
1848
-     * @param bool   $adjust_datetimes
1849
-     * @param string $source
1850
-     * @return void
1851
-     * @throws EE_Error
1852
-     * @throws InvalidArgumentException
1853
-     * @throws ReflectionException
1854
-     * @throws InvalidDataTypeException
1855
-     * @throws InvalidInterfaceException
1856
-     * @deprecated 4.9.80.p
1857
-     */
1858
-    public function decrease_reserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
1859
-    {
1860
-        EE_Error::doing_it_wrong(
1861
-            __FUNCTION__,
1862
-            esc_html__('Please use EE_Ticket::decreaseReserved() instead', 'event_espresso'),
1863
-            '4.9.80.p',
1864
-            '5.0.0.p'
1865
-        );
1866
-        $this->decreaseReserved($qty);
1867
-    }
1868
-
1869
-
1870
-    /**
1871
-     * Decreases reserved on related datetimes
1872
-     *
1873
-     * @param int $qty
1874
-     * @return void
1875
-     * @throws EE_Error
1876
-     * @throws InvalidArgumentException
1877
-     * @throws ReflectionException
1878
-     * @throws InvalidDataTypeException
1879
-     * @throws InvalidInterfaceException
1880
-     * @deprecated 4.9.80.p
1881
-     */
1882
-    protected function _decrease_reserved_for_datetimes($qty = 1)
1883
-    {
1884
-        EE_Error::doing_it_wrong(
1885
-            __FUNCTION__,
1886
-            esc_html__('Please use EE_Ticket::decreaseReservedForDatetimes() instead', 'event_espresso'),
1887
-            '4.9.80.p',
1888
-            '5.0.0.p'
1889
-        );
1890
-        $this->decreaseReservedForDatetimes($qty);
1891
-    }
1696
+	/**
1697
+	 * Increments sold by amount passed by $qty AND decrements the reserved count on both this ticket and its
1698
+	 * associated datetimes.
1699
+	 *
1700
+	 * @param int $qty
1701
+	 * @return void
1702
+	 * @throws EE_Error
1703
+	 * @throws InvalidArgumentException
1704
+	 * @throws InvalidDataTypeException
1705
+	 * @throws InvalidInterfaceException
1706
+	 * @throws ReflectionException
1707
+	 * @deprecated 4.9.80.p
1708
+	 */
1709
+	public function increase_sold($qty = 1)
1710
+	{
1711
+		EE_Error::doing_it_wrong(
1712
+			__FUNCTION__,
1713
+			esc_html__('Please use EE_Ticket::increaseSold() instead', 'event_espresso'),
1714
+			'4.9.80.p',
1715
+			'5.0.0.p'
1716
+		);
1717
+		$this->increaseSold($qty);
1718
+	}
1719
+
1720
+
1721
+	/**
1722
+	 * On each datetime related to this ticket, increases its sold count and decreases its reserved count by $qty.
1723
+	 *
1724
+	 * @param int $qty positive or negative. Positive means to increase sold counts (and decrease reserved counts),
1725
+	 *                 Negative means to decreases old counts (and increase reserved counts).
1726
+	 * @throws EE_Error
1727
+	 * @throws InvalidArgumentException
1728
+	 * @throws InvalidDataTypeException
1729
+	 * @throws InvalidInterfaceException
1730
+	 * @throws ReflectionException
1731
+	 * @deprecated 4.9.80.p
1732
+	 */
1733
+	protected function _increase_sold_for_datetimes($qty)
1734
+	{
1735
+		EE_Error::doing_it_wrong(
1736
+			__FUNCTION__,
1737
+			esc_html__('Please use EE_Ticket::increaseSoldForDatetimes() instead', 'event_espresso'),
1738
+			'4.9.80.p',
1739
+			'5.0.0.p'
1740
+		);
1741
+		$this->increaseSoldForDatetimes($qty);
1742
+	}
1743
+
1744
+
1745
+	/**
1746
+	 * Decrements (subtracts) sold by amount passed by $qty on both the ticket and its related datetimes directly in the
1747
+	 * DB and then updates the model objects.
1748
+	 * Does not affect the reserved counts.
1749
+	 *
1750
+	 * @param int $qty
1751
+	 * @return void
1752
+	 * @throws EE_Error
1753
+	 * @throws InvalidArgumentException
1754
+	 * @throws InvalidDataTypeException
1755
+	 * @throws InvalidInterfaceException
1756
+	 * @throws ReflectionException
1757
+	 * @deprecated 4.9.80.p
1758
+	 */
1759
+	public function decrease_sold($qty = 1)
1760
+	{
1761
+		EE_Error::doing_it_wrong(
1762
+			__FUNCTION__,
1763
+			esc_html__('Please use EE_Ticket::decreaseSold() instead', 'event_espresso'),
1764
+			'4.9.80.p',
1765
+			'5.0.0.p'
1766
+		);
1767
+		$this->decreaseSold($qty);
1768
+	}
1769
+
1770
+
1771
+	/**
1772
+	 * Decreases sold on related datetimes
1773
+	 *
1774
+	 * @param int $qty
1775
+	 * @return void
1776
+	 * @throws EE_Error
1777
+	 * @throws InvalidArgumentException
1778
+	 * @throws InvalidDataTypeException
1779
+	 * @throws InvalidInterfaceException
1780
+	 * @throws ReflectionException
1781
+	 * @deprecated 4.9.80.p
1782
+	 */
1783
+	protected function _decrease_sold_for_datetimes($qty = 1)
1784
+	{
1785
+		EE_Error::doing_it_wrong(
1786
+			__FUNCTION__,
1787
+			esc_html__('Please use EE_Ticket::decreaseSoldForDatetimes() instead', 'event_espresso'),
1788
+			'4.9.80.p',
1789
+			'5.0.0.p'
1790
+		);
1791
+		$this->decreaseSoldForDatetimes($qty);
1792
+	}
1793
+
1794
+
1795
+	/**
1796
+	 * Increments reserved by amount passed by $qty, and persists it immediately to the database.
1797
+	 *
1798
+	 * @param int    $qty
1799
+	 * @param string $source
1800
+	 * @return bool whether we successfully reserved the ticket or not.
1801
+	 * @throws EE_Error
1802
+	 * @throws InvalidArgumentException
1803
+	 * @throws ReflectionException
1804
+	 * @throws InvalidDataTypeException
1805
+	 * @throws InvalidInterfaceException
1806
+	 * @deprecated 4.9.80.p
1807
+	 */
1808
+	public function increase_reserved($qty = 1, $source = 'unknown')
1809
+	{
1810
+		EE_Error::doing_it_wrong(
1811
+			__FUNCTION__,
1812
+			esc_html__('Please use EE_Ticket::increaseReserved() instead', 'event_espresso'),
1813
+			'4.9.80.p',
1814
+			'5.0.0.p'
1815
+		);
1816
+		return $this->increaseReserved($qty);
1817
+	}
1818
+
1819
+
1820
+	/**
1821
+	 * Increases sold on related datetimes
1822
+	 *
1823
+	 * @param int $qty
1824
+	 * @return boolean indicating success
1825
+	 * @throws EE_Error
1826
+	 * @throws InvalidArgumentException
1827
+	 * @throws InvalidDataTypeException
1828
+	 * @throws InvalidInterfaceException
1829
+	 * @throws ReflectionException
1830
+	 * @deprecated 4.9.80.p
1831
+	 */
1832
+	protected function _increase_reserved_for_datetimes($qty = 1)
1833
+	{
1834
+		EE_Error::doing_it_wrong(
1835
+			__FUNCTION__,
1836
+			esc_html__('Please use EE_Ticket::increaseReservedForDatetimes() instead', 'event_espresso'),
1837
+			'4.9.80.p',
1838
+			'5.0.0.p'
1839
+		);
1840
+		return $this->increaseReservedForDatetimes($qty);
1841
+	}
1842
+
1843
+
1844
+	/**
1845
+	 * Decrements (subtracts) reserved by amount passed by $qty, and persists it immediately to the database.
1846
+	 *
1847
+	 * @param int    $qty
1848
+	 * @param bool   $adjust_datetimes
1849
+	 * @param string $source
1850
+	 * @return void
1851
+	 * @throws EE_Error
1852
+	 * @throws InvalidArgumentException
1853
+	 * @throws ReflectionException
1854
+	 * @throws InvalidDataTypeException
1855
+	 * @throws InvalidInterfaceException
1856
+	 * @deprecated 4.9.80.p
1857
+	 */
1858
+	public function decrease_reserved($qty = 1, $adjust_datetimes = true, $source = 'unknown')
1859
+	{
1860
+		EE_Error::doing_it_wrong(
1861
+			__FUNCTION__,
1862
+			esc_html__('Please use EE_Ticket::decreaseReserved() instead', 'event_espresso'),
1863
+			'4.9.80.p',
1864
+			'5.0.0.p'
1865
+		);
1866
+		$this->decreaseReserved($qty);
1867
+	}
1868
+
1869
+
1870
+	/**
1871
+	 * Decreases reserved on related datetimes
1872
+	 *
1873
+	 * @param int $qty
1874
+	 * @return void
1875
+	 * @throws EE_Error
1876
+	 * @throws InvalidArgumentException
1877
+	 * @throws ReflectionException
1878
+	 * @throws InvalidDataTypeException
1879
+	 * @throws InvalidInterfaceException
1880
+	 * @deprecated 4.9.80.p
1881
+	 */
1882
+	protected function _decrease_reserved_for_datetimes($qty = 1)
1883
+	{
1884
+		EE_Error::doing_it_wrong(
1885
+			__FUNCTION__,
1886
+			esc_html__('Please use EE_Ticket::decreaseReservedForDatetimes() instead', 'event_espresso'),
1887
+			'4.9.80.p',
1888
+			'5.0.0.p'
1889
+		);
1890
+		$this->decreaseReservedForDatetimes($qty);
1891
+	}
1892 1892
 }
Please login to merge, or discard this patch.
Spacing   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -144,7 +144,7 @@  discard block
 block discarded – undo
144 144
     public function ticket_status($display = false, $remaining = null)
145 145
     {
146 146
         $remaining = is_bool($remaining) ? $remaining : $this->is_remaining();
147
-        if (! $remaining) {
147
+        if ( ! $remaining) {
148 148
             return $display ? EEH_Template::pretty_status(EE_Ticket::sold_out, false, 'sentence') : EE_Ticket::sold_out;
149 149
         }
150 150
         if ($this->get('TKT_deleted')) {
@@ -273,7 +273,7 @@  discard block
 block discarded – undo
273 273
             ? $this->last_datetime()->get_i18n_datetime('DTT_EVT_end', $date_format)
274 274
             : '';
275 275
 
276
-        return $first_date && $last_date ? $first_date . $conjunction . $last_date : '';
276
+        return $first_date && $last_date ? $first_date.$conjunction.$last_date : '';
277 277
     }
278 278
 
279 279
 
@@ -303,7 +303,7 @@  discard block
 block discarded – undo
303 303
      */
304 304
     public function datetimes($query_params = [])
305 305
     {
306
-        if (! isset($query_params['order_by'])) {
306
+        if ( ! isset($query_params['order_by'])) {
307 307
             $query_params['order_by']['DTT_order'] = 'ASC';
308 308
         }
309 309
         return $this->get_many_related('Datetime', $query_params);
@@ -350,7 +350,7 @@  discard block
 block discarded – undo
350 350
                 if (empty($tickets_sold['datetime'])) {
351 351
                     return $total;
352 352
                 }
353
-                if (! empty($dtt_id) && ! isset($tickets_sold['datetime'][ $dtt_id ])) {
353
+                if ( ! empty($dtt_id) && ! isset($tickets_sold['datetime'][$dtt_id])) {
354 354
                     EE_Error::add_error(
355 355
                         __(
356 356
                             'You\'ve requested the amount of tickets sold for a given ticket and datetime, however there are no records for the datetime id you included.  Are you SURE that is a datetime related to this ticket?',
@@ -362,7 +362,7 @@  discard block
 block discarded – undo
362 362
                     );
363 363
                     return $total;
364 364
                 }
365
-                return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][ $dtt_id ];
365
+                return empty($dtt_id) ? $tickets_sold['datetime'] : $tickets_sold['datetime'][$dtt_id];
366 366
                 break;
367 367
             default:
368 368
                 return $total;
@@ -381,9 +381,9 @@  discard block
 block discarded – undo
381 381
     {
382 382
         $datetimes    = $this->get_many_related('Datetime');
383 383
         $tickets_sold = [];
384
-        if (! empty($datetimes)) {
384
+        if ( ! empty($datetimes)) {
385 385
             foreach ($datetimes as $datetime) {
386
-                $tickets_sold['datetime'][ $datetime->ID() ] = $datetime->get('DTT_sold');
386
+                $tickets_sold['datetime'][$datetime->ID()] = $datetime->get('DTT_sold');
387 387
             }
388 388
         }
389 389
         // Tickets sold
@@ -1011,7 +1011,7 @@  discard block
 block discarded – undo
1011 1011
                 'TKT_qty',
1012 1012
                 $qty
1013 1013
             );
1014
-            if (! $success) {
1014
+            if ( ! $success) {
1015 1015
                 // The datetimes were successfully bumped, but not the
1016 1016
                 // ticket. So we need to manually rollback the datetimes.
1017 1017
                 $this->decreaseReservedForDatetimes($qty);
@@ -1508,7 +1508,7 @@  discard block
 block discarded – undo
1508 1508
         foreach ($this->datetimes() as $datetime) {
1509 1509
             $times[] = $datetime->start_date_and_time();
1510 1510
         }
1511
-        return $this->name() . ' @ ' . implode(', ', $times) . ' for ' . $this->pretty_price();
1511
+        return $this->name().' @ '.implode(', ', $times).' for '.$this->pretty_price();
1512 1512
     }
1513 1513
 
1514 1514
 
@@ -1616,7 +1616,7 @@  discard block
 block discarded – undo
1616 1616
     {
1617 1617
         // get one datetime to use for getting the event
1618 1618
         $datetime = $this->first_datetime();
1619
-        if (! $datetime instanceof EE_Datetime) {
1619
+        if ( ! $datetime instanceof EE_Datetime) {
1620 1620
             throw new UnexpectedEntityException(
1621 1621
                 $datetime,
1622 1622
                 'EE_Datetime',
@@ -1627,7 +1627,7 @@  discard block
 block discarded – undo
1627 1627
             );
1628 1628
         }
1629 1629
         $event = $datetime->event();
1630
-        if (! $event instanceof EE_Event) {
1630
+        if ( ! $event instanceof EE_Event) {
1631 1631
             throw new UnexpectedEntityException(
1632 1632
                 $event,
1633 1633
                 'EE_Event',
Please login to merge, or discard this patch.
core/helpers/EEH_Line_Item.helper.php 2 patches
Indentation   +2043 added lines, -2043 removed lines patch added patch discarded remove patch
@@ -21,2047 +21,2047 @@
 block discarded – undo
21 21
 class EEH_Line_Item
22 22
 {
23 23
 
24
-    /**
25
-     * Adds a simple item (unrelated to any other model object) to the provided PARENT line item.
26
-     * Does NOT automatically re-calculate the line item totals or update the related transaction.
27
-     * You should call recalculate_total_including_taxes() on the grant total line item after this
28
-     * to update the subtotals, and EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
29
-     * to keep the registration final prices in-sync with the transaction's total.
30
-     *
31
-     * @param EE_Line_Item $parent_line_item
32
-     * @param string       $name
33
-     * @param float        $unit_price
34
-     * @param string       $description
35
-     * @param int          $quantity
36
-     * @param boolean      $taxable
37
-     * @param boolean      $code if set to a value, ensures there is only one line item with that code
38
-     * @return boolean success
39
-     * @throws EE_Error
40
-     * @throws InvalidArgumentException
41
-     * @throws InvalidDataTypeException
42
-     * @throws InvalidInterfaceException
43
-     * @throws ReflectionException
44
-     */
45
-    public static function add_unrelated_item(
46
-        EE_Line_Item $parent_line_item,
47
-        $name,
48
-        $unit_price,
49
-        $description = '',
50
-        $quantity = 1,
51
-        $taxable = false,
52
-        $code = null
53
-    ) {
54
-        $items_subtotal = self::get_pre_tax_subtotal($parent_line_item);
55
-        $line_item = EE_Line_Item::new_instance(array(
56
-            'LIN_name'       => $name,
57
-            'LIN_desc'       => $description,
58
-            'LIN_unit_price' => $unit_price,
59
-            'LIN_quantity'   => $quantity,
60
-            'LIN_percent'    => null,
61
-            'LIN_is_taxable' => $taxable,
62
-            'LIN_order'      => $items_subtotal instanceof EE_Line_Item ? count($items_subtotal->children()) : 0,
63
-            'LIN_total'      => (float) $unit_price * (int) $quantity,
64
-            'LIN_type'       => EEM_Line_Item::type_line_item,
65
-            'LIN_code'       => $code,
66
-        ));
67
-        $line_item = apply_filters(
68
-            'FHEE__EEH_Line_Item__add_unrelated_item__line_item',
69
-            $line_item,
70
-            $parent_line_item
71
-        );
72
-        return self::add_item($parent_line_item, $line_item);
73
-    }
74
-
75
-
76
-    /**
77
-     * Adds a simple item ( unrelated to any other model object) to the total line item,
78
-     * in the correct spot in the line item tree. Does not automatically
79
-     * re-calculate the line item totals, nor update the related transaction, nor upgrade the transaction's
80
-     * registrations' final prices (which should probably change because of this).
81
-     * You should call recalculate_total_including_taxes() on the grand total line item, then
82
-     * update the transaction's total, and EE_Registration_Processor::update_registration_final_prices()
83
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
84
-     *
85
-     * @param EE_Line_Item $parent_line_item
86
-     * @param string       $name
87
-     * @param float        $percentage_amount
88
-     * @param string       $description
89
-     * @param boolean      $taxable
90
-     * @return boolean success
91
-     * @throws EE_Error
92
-     */
93
-    public static function add_percentage_based_item(
94
-        EE_Line_Item $parent_line_item,
95
-        $name,
96
-        $percentage_amount,
97
-        $description = '',
98
-        $taxable = false
99
-    ) {
100
-        $line_item = EE_Line_Item::new_instance(array(
101
-            'LIN_name'       => $name,
102
-            'LIN_desc'       => $description,
103
-            'LIN_unit_price' => 0,
104
-            'LIN_percent'    => $percentage_amount,
105
-            'LIN_quantity'   => 1,
106
-            'LIN_is_taxable' => $taxable,
107
-            'LIN_total'      => (float) ($percentage_amount * ($parent_line_item->total() / 100)),
108
-            'LIN_type'       => EEM_Line_Item::type_line_item,
109
-            'LIN_parent'     => $parent_line_item->ID(),
110
-        ));
111
-        $line_item = apply_filters(
112
-            'FHEE__EEH_Line_Item__add_percentage_based_item__line_item',
113
-            $line_item
114
-        );
115
-        return $parent_line_item->add_child_line_item($line_item, false);
116
-    }
117
-
118
-
119
-    /**
120
-     * Returns the new line item created by adding a purchase of the ticket
121
-     * ensures that ticket line item is saved, and that cart total has been recalculated.
122
-     * If this ticket has already been purchased, just increments its count.
123
-     * Automatically re-calculates the line item totals and updates the related transaction. But
124
-     * DOES NOT automatically upgrade the transaction's registrations' final prices (which
125
-     * should probably change because of this).
126
-     * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
127
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
128
-     *
129
-     * @param EE_Line_Item $total_line_item grand total line item of type EEM_Line_Item::type_total
130
-     * @param EE_Ticket    $ticket
131
-     * @param int          $qty
132
-     * @return EE_Line_Item
133
-     * @throws EE_Error
134
-     * @throws InvalidArgumentException
135
-     * @throws InvalidDataTypeException
136
-     * @throws InvalidInterfaceException
137
-     * @throws ReflectionException
138
-     */
139
-    public static function add_ticket_purchase(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
140
-    {
141
-        if (! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
142
-            throw new EE_Error(
143
-                sprintf(
144
-                    esc_html__(
145
-                        'A valid line item total is required in order to add tickets. A line item of type "%s" was passed.',
146
-                        'event_espresso'
147
-                    ),
148
-                    $ticket->ID(),
149
-                    $total_line_item->ID()
150
-                )
151
-            );
152
-        }
153
-        // either increment the qty for an existing ticket
154
-        $line_item = self::increment_ticket_qty_if_already_in_cart($total_line_item, $ticket, $qty);
155
-        // or add a new one
156
-        if (! $line_item instanceof EE_Line_Item) {
157
-            $line_item = self::create_ticket_line_item($total_line_item, $ticket, $qty);
158
-        }
159
-        $total_line_item->recalculate_total_including_taxes();
160
-        return $line_item;
161
-    }
162
-
163
-
164
-    /**
165
-     * Returns the new line item created by adding a purchase of the ticket
166
-     *
167
-     * @param EE_Line_Item $total_line_item
168
-     * @param EE_Ticket    $ticket
169
-     * @param int          $qty
170
-     * @return EE_Line_Item
171
-     * @throws EE_Error
172
-     * @throws InvalidArgumentException
173
-     * @throws InvalidDataTypeException
174
-     * @throws InvalidInterfaceException
175
-     * @throws ReflectionException
176
-     */
177
-    public static function increment_ticket_qty_if_already_in_cart(
178
-        EE_Line_Item $total_line_item,
179
-        EE_Ticket $ticket,
180
-        $qty = 1
181
-    ) {
182
-        $line_item = null;
183
-        if ($total_line_item instanceof EE_Line_Item && $total_line_item->is_total()) {
184
-            $ticket_line_items = EEH_Line_Item::get_ticket_line_items($total_line_item);
185
-            foreach ((array) $ticket_line_items as $ticket_line_item) {
186
-                if ($ticket_line_item instanceof EE_Line_Item
187
-                    && (int) $ticket_line_item->OBJ_ID() === (int) $ticket->ID()
188
-                ) {
189
-                    $line_item = $ticket_line_item;
190
-                    break;
191
-                }
192
-            }
193
-        }
194
-        if ($line_item instanceof EE_Line_Item) {
195
-            EEH_Line_Item::increment_quantity($line_item, $qty);
196
-            return $line_item;
197
-        }
198
-        return null;
199
-    }
200
-
201
-
202
-    /**
203
-     * Increments the line item and all its children's quantity by $qty (but percent line items are unaffected).
204
-     * Does NOT save or recalculate other line items totals
205
-     *
206
-     * @param EE_Line_Item $line_item
207
-     * @param int          $qty
208
-     * @return void
209
-     * @throws EE_Error
210
-     * @throws InvalidArgumentException
211
-     * @throws InvalidDataTypeException
212
-     * @throws InvalidInterfaceException
213
-     * @throws ReflectionException
214
-     */
215
-    public static function increment_quantity(EE_Line_Item $line_item, $qty = 1)
216
-    {
217
-        if (! $line_item->is_percent()) {
218
-            $qty += $line_item->quantity();
219
-            $line_item->set_quantity($qty);
220
-            $line_item->set_total($line_item->unit_price() * $qty);
221
-            $line_item->save();
222
-        }
223
-        foreach ($line_item->children() as $child) {
224
-            if ($child->is_sub_line_item()) {
225
-                EEH_Line_Item::update_quantity($child, $qty);
226
-            }
227
-        }
228
-    }
229
-
230
-
231
-    /**
232
-     * Decrements the line item and all its children's quantity by $qty (but percent line items are unaffected).
233
-     * Does NOT save or recalculate other line items totals
234
-     *
235
-     * @param EE_Line_Item $line_item
236
-     * @param int          $qty
237
-     * @return void
238
-     * @throws EE_Error
239
-     * @throws InvalidArgumentException
240
-     * @throws InvalidDataTypeException
241
-     * @throws InvalidInterfaceException
242
-     * @throws ReflectionException
243
-     */
244
-    public static function decrement_quantity(EE_Line_Item $line_item, $qty = 1)
245
-    {
246
-        if (! $line_item->is_percent()) {
247
-            $qty = $line_item->quantity() - $qty;
248
-            $qty = max($qty, 0);
249
-            $line_item->set_quantity($qty);
250
-            $line_item->set_total($line_item->unit_price() * $qty);
251
-            $line_item->save();
252
-        }
253
-        foreach ($line_item->children() as $child) {
254
-            if ($child->is_sub_line_item()) {
255
-                EEH_Line_Item::update_quantity($child, $qty);
256
-            }
257
-        }
258
-    }
259
-
260
-
261
-    /**
262
-     * Updates the line item and its children's quantities to the specified number.
263
-     * Does NOT save them or recalculate totals.
264
-     *
265
-     * @param EE_Line_Item $line_item
266
-     * @param int          $new_quantity
267
-     * @throws EE_Error
268
-     * @throws InvalidArgumentException
269
-     * @throws InvalidDataTypeException
270
-     * @throws InvalidInterfaceException
271
-     * @throws ReflectionException
272
-     */
273
-    public static function update_quantity(EE_Line_Item $line_item, $new_quantity)
274
-    {
275
-        if (! $line_item->is_percent()) {
276
-            $line_item->set_quantity($new_quantity);
277
-            $line_item->set_total($line_item->unit_price() * $new_quantity);
278
-            $line_item->save();
279
-        }
280
-        foreach ($line_item->children() as $child) {
281
-            if ($child->is_sub_line_item()) {
282
-                EEH_Line_Item::update_quantity($child, $new_quantity);
283
-            }
284
-        }
285
-    }
286
-
287
-
288
-    /**
289
-     * Returns the new line item created by adding a purchase of the ticket
290
-     *
291
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
292
-     * @param EE_Ticket    $ticket
293
-     * @param int          $qty
294
-     * @return EE_Line_Item
295
-     * @throws EE_Error
296
-     * @throws InvalidArgumentException
297
-     * @throws InvalidDataTypeException
298
-     * @throws InvalidInterfaceException
299
-     * @throws ReflectionException
300
-     */
301
-    public static function create_ticket_line_item(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
302
-    {
303
-        $datetimes = $ticket->datetimes();
304
-        $first_datetime = reset($datetimes);
305
-        $first_datetime_name = esc_html__('Event', 'event_espresso');
306
-        if ($first_datetime instanceof EE_Datetime && $first_datetime->event() instanceof EE_Event) {
307
-            $first_datetime_name = $first_datetime->event()->name();
308
-        }
309
-        $event = sprintf(_x('(For %1$s)', '(For Event Name)', 'event_espresso'), $first_datetime_name);
310
-        // get event subtotal line
311
-        $events_sub_total = self::get_event_line_item_for_ticket($total_line_item, $ticket);
312
-        $taxes = $ticket->tax_price_modifiers();
313
-        $is_taxable = empty($taxes) ? $ticket->taxable() : false;
314
-        // add $ticket to cart
315
-        $line_item = EE_Line_Item::new_instance(array(
316
-            'LIN_name'       => $ticket->name(),
317
-            'LIN_desc'       => $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
318
-            'LIN_unit_price' => $ticket->price(),
319
-            'LIN_quantity'   => $qty,
320
-            'LIN_is_taxable' => $is_taxable,
321
-            'LIN_order'      => count($events_sub_total->children()),
322
-            'LIN_total'      => $ticket->price() * $qty,
323
-            'LIN_type'       => EEM_Line_Item::type_line_item,
324
-            'OBJ_ID'         => $ticket->ID(),
325
-            'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_TICKET,
326
-        ));
327
-        $line_item = apply_filters(
328
-            'FHEE__EEH_Line_Item__create_ticket_line_item__line_item',
329
-            $line_item
330
-        );
331
-        $events_sub_total->add_child_line_item($line_item);
332
-        // now add the sub-line items
333
-        $running_total_for_ticket = 0;
334
-        foreach ($ticket->prices(array('order_by' => array('PRC_order' => 'ASC'))) as $price) {
335
-            $sign = $price->is_discount() ? -1 : 1;
336
-            $price_total = $price->is_percent()
337
-                ? $running_total_for_ticket * $price->amount() / 100
338
-                : $price->amount() * $qty;
339
-            $sub_line_item = EE_Line_Item::new_instance(array(
340
-                'LIN_name'       => $price->name(),
341
-                'LIN_desc'       => $price->desc(),
342
-                'LIN_quantity'   => $price->is_percent() ? null : $qty,
343
-                'LIN_is_taxable' => false,
344
-                'LIN_order'      => $price->order(),
345
-                'LIN_total'      => $sign * $price_total,
346
-                'LIN_type'       => EEM_Line_Item::type_sub_line_item,
347
-                'OBJ_ID'         => $price->ID(),
348
-                'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
349
-            ));
350
-            $sub_line_item = apply_filters(
351
-                'FHEE__EEH_Line_Item__create_ticket_line_item__sub_line_item',
352
-                $sub_line_item
353
-            );
354
-            if ($price->is_percent()) {
355
-                $sub_line_item->set_percent($sign * $price->amount());
356
-            } else {
357
-                $sub_line_item->set_unit_price($sign * $price->amount());
358
-            }
359
-            $running_total_for_ticket += $price_total;
360
-            $line_item->add_child_line_item($sub_line_item);
361
-        }
362
-        return $line_item;
363
-    }
364
-
365
-
366
-    /**
367
-     * Adds the specified item under the pre-tax-sub-total line item. Automatically
368
-     * re-calculates the line item totals and updates the related transaction. But
369
-     * DOES NOT automatically upgrade the transaction's registrations' final prices (which
370
-     * should probably change because of this).
371
-     * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
372
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
373
-     *
374
-     * @param EE_Line_Item $total_line_item
375
-     * @param EE_Line_Item $item to be added
376
-     * @return boolean
377
-     * @throws EE_Error
378
-     * @throws InvalidArgumentException
379
-     * @throws InvalidDataTypeException
380
-     * @throws InvalidInterfaceException
381
-     * @throws ReflectionException
382
-     */
383
-    public static function add_item(EE_Line_Item $total_line_item, EE_Line_Item $item)
384
-    {
385
-        $pre_tax_subtotal = self::get_pre_tax_subtotal($total_line_item);
386
-        if ($pre_tax_subtotal instanceof EE_Line_Item) {
387
-            $success = $pre_tax_subtotal->add_child_line_item($item);
388
-        } else {
389
-            return false;
390
-        }
391
-        $total_line_item->recalculate_total_including_taxes();
392
-        return $success;
393
-    }
394
-
395
-
396
-    /**
397
-     * cancels an existing ticket line item,
398
-     * by decrementing it's quantity by 1 and adding a new "type_cancellation" sub-line-item.
399
-     * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
400
-     *
401
-     * @param EE_Line_Item $ticket_line_item
402
-     * @param int          $qty
403
-     * @return bool success
404
-     * @throws EE_Error
405
-     * @throws InvalidArgumentException
406
-     * @throws InvalidDataTypeException
407
-     * @throws InvalidInterfaceException
408
-     * @throws ReflectionException
409
-     */
410
-    public static function cancel_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
411
-    {
412
-        // validate incoming line_item
413
-        if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
414
-            throw new EE_Error(
415
-                sprintf(
416
-                    esc_html__(
417
-                        'The supplied line item must have an Object Type of "Ticket", not %1$s.',
418
-                        'event_espresso'
419
-                    ),
420
-                    $ticket_line_item->type()
421
-                )
422
-            );
423
-        }
424
-        if ($ticket_line_item->quantity() < $qty) {
425
-            throw new EE_Error(
426
-                sprintf(
427
-                    esc_html__(
428
-                        'Can not cancel %1$d ticket(s) because the supplied line item has a quantity of %2$d.',
429
-                        'event_espresso'
430
-                    ),
431
-                    $qty,
432
-                    $ticket_line_item->quantity()
433
-                )
434
-            );
435
-        }
436
-        // decrement ticket quantity; don't rely on auto-fixing when recalculating totals to do this
437
-        $ticket_line_item->set_quantity($ticket_line_item->quantity() - $qty);
438
-        foreach ($ticket_line_item->children() as $child_line_item) {
439
-            if ($child_line_item->is_sub_line_item()
440
-                && ! $child_line_item->is_percent()
441
-                && ! $child_line_item->is_cancellation()
442
-            ) {
443
-                $child_line_item->set_quantity($child_line_item->quantity() - $qty);
444
-            }
445
-        }
446
-        // get cancellation sub line item
447
-        $cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
448
-            $ticket_line_item,
449
-            EEM_Line_Item::type_cancellation
450
-        );
451
-        $cancellation_line_item = reset($cancellation_line_item);
452
-        // verify that this ticket was indeed previously cancelled
453
-        if ($cancellation_line_item instanceof EE_Line_Item) {
454
-            // increment cancelled quantity
455
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() + $qty);
456
-        } else {
457
-            // create cancellation sub line item
458
-            $cancellation_line_item = EE_Line_Item::new_instance(array(
459
-                'LIN_name'       => esc_html__('Cancellation', 'event_espresso'),
460
-                'LIN_desc'       => sprintf(
461
-                    esc_html_x(
462
-                        'Cancelled %1$s : %2$s',
463
-                        'Cancelled Ticket Name : 2015-01-01 11:11',
464
-                        'event_espresso'
465
-                    ),
466
-                    $ticket_line_item->name(),
467
-                    current_time(get_option('date_format') . ' ' . get_option('time_format'))
468
-                ),
469
-                'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
470
-                'LIN_quantity'   => $qty,
471
-                'LIN_is_taxable' => $ticket_line_item->is_taxable(),
472
-                'LIN_order'      => count($ticket_line_item->children()),
473
-                'LIN_total'      => 0, // $ticket_line_item->unit_price()
474
-                'LIN_type'       => EEM_Line_Item::type_cancellation,
475
-            ));
476
-            $ticket_line_item->add_child_line_item($cancellation_line_item);
477
-        }
478
-        if ($ticket_line_item->save_this_and_descendants() > 0) {
479
-            // decrement parent line item quantity
480
-            $event_line_item = $ticket_line_item->parent();
481
-            if ($event_line_item instanceof EE_Line_Item
482
-                && $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
483
-            ) {
484
-                $event_line_item->set_quantity($event_line_item->quantity() - $qty);
485
-                $event_line_item->save();
486
-            }
487
-            EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
488
-            return true;
489
-        }
490
-        return false;
491
-    }
492
-
493
-
494
-    /**
495
-     * reinstates (un-cancels?) a previously canceled ticket line item,
496
-     * by incrementing it's quantity by 1, and decrementing it's "type_cancellation" sub-line-item.
497
-     * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
498
-     *
499
-     * @param EE_Line_Item $ticket_line_item
500
-     * @param int          $qty
501
-     * @return bool success
502
-     * @throws EE_Error
503
-     * @throws InvalidArgumentException
504
-     * @throws InvalidDataTypeException
505
-     * @throws InvalidInterfaceException
506
-     * @throws ReflectionException
507
-     */
508
-    public static function reinstate_canceled_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
509
-    {
510
-        // validate incoming line_item
511
-        if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
512
-            throw new EE_Error(
513
-                sprintf(
514
-                    esc_html__(
515
-                        'The supplied line item must have an Object Type of "Ticket", not %1$s.',
516
-                        'event_espresso'
517
-                    ),
518
-                    $ticket_line_item->type()
519
-                )
520
-            );
521
-        }
522
-        // get cancellation sub line item
523
-        $cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
524
-            $ticket_line_item,
525
-            EEM_Line_Item::type_cancellation
526
-        );
527
-        $cancellation_line_item = reset($cancellation_line_item);
528
-        // verify that this ticket was indeed previously cancelled
529
-        if (! $cancellation_line_item instanceof EE_Line_Item) {
530
-            return false;
531
-        }
532
-        if ($cancellation_line_item->quantity() > $qty) {
533
-            // decrement cancelled quantity
534
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
535
-        } elseif ($cancellation_line_item->quantity() === $qty) {
536
-            // decrement cancelled quantity in case anyone still has the object kicking around
537
-            $cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
538
-            // delete because quantity will end up as 0
539
-            $cancellation_line_item->delete();
540
-            // and attempt to destroy the object,
541
-            // even though PHP won't actually destroy it until it needs the memory
542
-            unset($cancellation_line_item);
543
-        } else {
544
-            // what ?!?! negative quantity ?!?!
545
-            throw new EE_Error(
546
-                sprintf(
547
-                    esc_html__(
548
-                        'Can not reinstate %1$d cancelled ticket(s) because the cancelled ticket quantity is only %2$d.',
549
-                        'event_espresso'
550
-                    ),
551
-                    $qty,
552
-                    $cancellation_line_item->quantity()
553
-                )
554
-            );
555
-        }
556
-        // increment ticket quantity
557
-        $ticket_line_item->set_quantity($ticket_line_item->quantity() + $qty);
558
-        if ($ticket_line_item->save_this_and_descendants() > 0) {
559
-            // increment parent line item quantity
560
-            $event_line_item = $ticket_line_item->parent();
561
-            if ($event_line_item instanceof EE_Line_Item
562
-                && $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
563
-            ) {
564
-                $event_line_item->set_quantity($event_line_item->quantity() + $qty);
565
-            }
566
-            EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
567
-            return true;
568
-        }
569
-        return false;
570
-    }
571
-
572
-
573
-    /**
574
-     * calls EEH_Line_Item::find_transaction_grand_total_for_line_item()
575
-     * then EE_Line_Item::recalculate_total_including_taxes() on the result
576
-     *
577
-     * @param EE_Line_Item $line_item
578
-     * @return float
579
-     * @throws EE_Error
580
-     * @throws InvalidArgumentException
581
-     * @throws InvalidDataTypeException
582
-     * @throws InvalidInterfaceException
583
-     * @throws ReflectionException
584
-     */
585
-    public static function get_grand_total_and_recalculate_everything(EE_Line_Item $line_item)
586
-    {
587
-        $grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item);
588
-        return $grand_total_line_item->recalculate_total_including_taxes();
589
-    }
590
-
591
-
592
-    /**
593
-     * Gets the line item which contains the subtotal of all the items
594
-     *
595
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
596
-     * @return EE_Line_Item
597
-     * @throws EE_Error
598
-     * @throws InvalidArgumentException
599
-     * @throws InvalidDataTypeException
600
-     * @throws InvalidInterfaceException
601
-     * @throws ReflectionException
602
-     */
603
-    public static function get_pre_tax_subtotal(EE_Line_Item $total_line_item)
604
-    {
605
-        $pre_tax_subtotal = $total_line_item->get_child_line_item('pre-tax-subtotal');
606
-        return $pre_tax_subtotal instanceof EE_Line_Item
607
-            ? $pre_tax_subtotal
608
-            : self::create_pre_tax_subtotal($total_line_item);
609
-    }
610
-
611
-
612
-    /**
613
-     * Gets the line item for the taxes subtotal
614
-     *
615
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
616
-     * @return EE_Line_Item
617
-     * @throws EE_Error
618
-     * @throws InvalidArgumentException
619
-     * @throws InvalidDataTypeException
620
-     * @throws InvalidInterfaceException
621
-     * @throws ReflectionException
622
-     */
623
-    public static function get_taxes_subtotal(EE_Line_Item $total_line_item)
624
-    {
625
-        $taxes = $total_line_item->get_child_line_item('taxes');
626
-        return $taxes ? $taxes : self::create_taxes_subtotal($total_line_item);
627
-    }
628
-
629
-
630
-    /**
631
-     * sets the TXN ID on an EE_Line_Item if passed a valid EE_Transaction object
632
-     *
633
-     * @param EE_Line_Item   $line_item
634
-     * @param EE_Transaction $transaction
635
-     * @return void
636
-     * @throws EE_Error
637
-     * @throws InvalidArgumentException
638
-     * @throws InvalidDataTypeException
639
-     * @throws InvalidInterfaceException
640
-     * @throws ReflectionException
641
-     */
642
-    public static function set_TXN_ID(EE_Line_Item $line_item, $transaction = null)
643
-    {
644
-        if ($transaction) {
645
-            /** @type EEM_Transaction $EEM_Transaction */
646
-            $EEM_Transaction = EE_Registry::instance()->load_model('Transaction');
647
-            $TXN_ID = $EEM_Transaction->ensure_is_ID($transaction);
648
-            $line_item->set_TXN_ID($TXN_ID);
649
-        }
650
-    }
651
-
652
-
653
-    /**
654
-     * Creates a new default total line item for the transaction,
655
-     * and its tickets subtotal and taxes subtotal line items (and adds the
656
-     * existing taxes as children of the taxes subtotal line item)
657
-     *
658
-     * @param EE_Transaction $transaction
659
-     * @return EE_Line_Item of type total
660
-     * @throws EE_Error
661
-     * @throws InvalidArgumentException
662
-     * @throws InvalidDataTypeException
663
-     * @throws InvalidInterfaceException
664
-     * @throws ReflectionException
665
-     */
666
-    public static function create_total_line_item($transaction = null)
667
-    {
668
-        $total_line_item = EE_Line_Item::new_instance(array(
669
-            'LIN_code' => 'total',
670
-            'LIN_name' => esc_html__('Grand Total', 'event_espresso'),
671
-            'LIN_type' => EEM_Line_Item::type_total,
672
-            'OBJ_type' => EEM_Line_Item::OBJ_TYPE_TRANSACTION,
673
-        ));
674
-        $total_line_item = apply_filters(
675
-            'FHEE__EEH_Line_Item__create_total_line_item__total_line_item',
676
-            $total_line_item
677
-        );
678
-        self::set_TXN_ID($total_line_item, $transaction);
679
-        self::create_pre_tax_subtotal($total_line_item, $transaction);
680
-        self::create_taxes_subtotal($total_line_item, $transaction);
681
-        return $total_line_item;
682
-    }
683
-
684
-
685
-    /**
686
-     * Creates a default items subtotal line item
687
-     *
688
-     * @param EE_Line_Item   $total_line_item
689
-     * @param EE_Transaction $transaction
690
-     * @return EE_Line_Item
691
-     * @throws EE_Error
692
-     * @throws InvalidArgumentException
693
-     * @throws InvalidDataTypeException
694
-     * @throws InvalidInterfaceException
695
-     * @throws ReflectionException
696
-     */
697
-    protected static function create_pre_tax_subtotal(EE_Line_Item $total_line_item, $transaction = null)
698
-    {
699
-        $pre_tax_line_item = EE_Line_Item::new_instance(array(
700
-            'LIN_code' => 'pre-tax-subtotal',
701
-            'LIN_name' => esc_html__('Pre-Tax Subtotal', 'event_espresso'),
702
-            'LIN_type' => EEM_Line_Item::type_sub_total,
703
-        ));
704
-        $pre_tax_line_item = apply_filters(
705
-            'FHEE__EEH_Line_Item__create_pre_tax_subtotal__pre_tax_line_item',
706
-            $pre_tax_line_item
707
-        );
708
-        self::set_TXN_ID($pre_tax_line_item, $transaction);
709
-        $total_line_item->add_child_line_item($pre_tax_line_item);
710
-        self::create_event_subtotal($pre_tax_line_item, $transaction);
711
-        return $pre_tax_line_item;
712
-    }
713
-
714
-
715
-    /**
716
-     * Creates a line item for the taxes subtotal and finds all the tax prices
717
-     * and applies taxes to it
718
-     *
719
-     * @param EE_Line_Item   $total_line_item of type EEM_Line_Item::type_total
720
-     * @param EE_Transaction $transaction
721
-     * @return EE_Line_Item
722
-     * @throws EE_Error
723
-     * @throws InvalidArgumentException
724
-     * @throws InvalidDataTypeException
725
-     * @throws InvalidInterfaceException
726
-     * @throws ReflectionException
727
-     */
728
-    protected static function create_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
729
-    {
730
-        $tax_line_item = EE_Line_Item::new_instance(array(
731
-            'LIN_code'  => 'taxes',
732
-            'LIN_name'  => esc_html__('Taxes', 'event_espresso'),
733
-            'LIN_type'  => EEM_Line_Item::type_tax_sub_total,
734
-            'LIN_order' => 1000,// this should always come last
735
-        ));
736
-        $tax_line_item = apply_filters(
737
-            'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
738
-            $tax_line_item
739
-        );
740
-        self::set_TXN_ID($tax_line_item, $transaction);
741
-        $total_line_item->add_child_line_item($tax_line_item);
742
-        // and lastly, add the actual taxes
743
-        self::apply_taxes($total_line_item);
744
-        return $tax_line_item;
745
-    }
746
-
747
-
748
-    /**
749
-     * Creates a default items subtotal line item
750
-     *
751
-     * @param EE_Line_Item   $pre_tax_line_item
752
-     * @param EE_Transaction $transaction
753
-     * @param EE_Event       $event
754
-     * @return EE_Line_Item
755
-     * @throws EE_Error
756
-     * @throws InvalidArgumentException
757
-     * @throws InvalidDataTypeException
758
-     * @throws InvalidInterfaceException
759
-     * @throws ReflectionException
760
-     */
761
-    public static function create_event_subtotal(EE_Line_Item $pre_tax_line_item, $transaction = null, $event = null)
762
-    {
763
-        $event_line_item = EE_Line_Item::new_instance(array(
764
-            'LIN_code' => self::get_event_code($event),
765
-            'LIN_name' => self::get_event_name($event),
766
-            'LIN_desc' => self::get_event_desc($event),
767
-            'LIN_type' => EEM_Line_Item::type_sub_total,
768
-            'OBJ_type' => EEM_Line_Item::OBJ_TYPE_EVENT,
769
-            'OBJ_ID'   => $event instanceof EE_Event ? $event->ID() : 0,
770
-        ));
771
-        $event_line_item = apply_filters(
772
-            'FHEE__EEH_Line_Item__create_event_subtotal__event_line_item',
773
-            $event_line_item
774
-        );
775
-        self::set_TXN_ID($event_line_item, $transaction);
776
-        $pre_tax_line_item->add_child_line_item($event_line_item);
777
-        return $event_line_item;
778
-    }
779
-
780
-
781
-    /**
782
-     * Gets what the event ticket's code SHOULD be
783
-     *
784
-     * @param EE_Event $event
785
-     * @return string
786
-     * @throws EE_Error
787
-     */
788
-    public static function get_event_code($event)
789
-    {
790
-        return 'event-' . ($event instanceof EE_Event ? $event->ID() : '0');
791
-    }
792
-
793
-
794
-    /**
795
-     * Gets the event name
796
-     *
797
-     * @param EE_Event $event
798
-     * @return string
799
-     * @throws EE_Error
800
-     */
801
-    public static function get_event_name($event)
802
-    {
803
-        return $event instanceof EE_Event
804
-            ? mb_substr($event->name(), 0, 245)
805
-            : esc_html__('Event', 'event_espresso');
806
-    }
807
-
808
-
809
-    /**
810
-     * Gets the event excerpt
811
-     *
812
-     * @param EE_Event $event
813
-     * @return string
814
-     * @throws EE_Error
815
-     */
816
-    public static function get_event_desc($event)
817
-    {
818
-        return $event instanceof EE_Event ? $event->short_description() : '';
819
-    }
820
-
821
-
822
-    /**
823
-     * Given the grand total line item and a ticket, finds the event sub-total
824
-     * line item the ticket's purchase should be added onto
825
-     *
826
-     * @access public
827
-     * @param EE_Line_Item $grand_total the grand total line item
828
-     * @param EE_Ticket    $ticket
829
-     * @return EE_Line_Item
830
-     * @throws EE_Error
831
-     * @throws InvalidArgumentException
832
-     * @throws InvalidDataTypeException
833
-     * @throws InvalidInterfaceException
834
-     * @throws ReflectionException
835
-     */
836
-    public static function get_event_line_item_for_ticket(EE_Line_Item $grand_total, EE_Ticket $ticket)
837
-    {
838
-        $first_datetime = $ticket->first_datetime();
839
-        if (! $first_datetime instanceof EE_Datetime) {
840
-            throw new EE_Error(
841
-                sprintf(
842
-                    __('The supplied ticket (ID %d) has no datetimes', 'event_espresso'),
843
-                    $ticket->ID()
844
-                )
845
-            );
846
-        }
847
-        $event = $first_datetime->event();
848
-        if (! $event instanceof EE_Event) {
849
-            throw new EE_Error(
850
-                sprintf(
851
-                    esc_html__(
852
-                        'The supplied ticket (ID %d) has no event data associated with it.',
853
-                        'event_espresso'
854
-                    ),
855
-                    $ticket->ID()
856
-                )
857
-            );
858
-        }
859
-        $events_sub_total = EEH_Line_Item::get_event_line_item($grand_total, $event);
860
-        if (! $events_sub_total instanceof EE_Line_Item) {
861
-            throw new EE_Error(
862
-                sprintf(
863
-                    esc_html__(
864
-                        'There is no events sub-total for ticket %s on total line item %d',
865
-                        'event_espresso'
866
-                    ),
867
-                    $ticket->ID(),
868
-                    $grand_total->ID()
869
-                )
870
-            );
871
-        }
872
-        return $events_sub_total;
873
-    }
874
-
875
-
876
-    /**
877
-     * Gets the event line item
878
-     *
879
-     * @param EE_Line_Item $grand_total
880
-     * @param EE_Event     $event
881
-     * @return EE_Line_Item for the event subtotal which is a child of $grand_total
882
-     * @throws EE_Error
883
-     * @throws InvalidArgumentException
884
-     * @throws InvalidDataTypeException
885
-     * @throws InvalidInterfaceException
886
-     * @throws ReflectionException
887
-     */
888
-    public static function get_event_line_item(EE_Line_Item $grand_total, $event)
889
-    {
890
-        /** @type EE_Event $event */
891
-        $event = EEM_Event::instance()->ensure_is_obj($event, true);
892
-        $event_line_item = null;
893
-        $found = false;
894
-        foreach (EEH_Line_Item::get_event_subtotals($grand_total) as $event_line_item) {
895
-            // default event subtotal, we should only ever find this the first time this method is called
896
-            if (! $event_line_item->OBJ_ID()) {
897
-                // let's use this! but first... set the event details
898
-                EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
899
-                $found = true;
900
-                break;
901
-            }
902
-            if ($event_line_item->OBJ_ID() === $event->ID()) {
903
-                // found existing line item for this event in the cart, so break out of loop and use this one
904
-                $found = true;
905
-                break;
906
-            }
907
-        }
908
-        if (! $found) {
909
-            // there is no event sub-total yet, so add it
910
-            $pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal($grand_total);
911
-            // create a new "event" subtotal below that
912
-            $event_line_item = EEH_Line_Item::create_event_subtotal($pre_tax_subtotal, null, $event);
913
-            // and set the event details
914
-            EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
915
-        }
916
-        return $event_line_item;
917
-    }
918
-
919
-
920
-    /**
921
-     * Creates a default items subtotal line item
922
-     *
923
-     * @param EE_Line_Item   $event_line_item
924
-     * @param EE_Event       $event
925
-     * @param EE_Transaction $transaction
926
-     * @return void
927
-     * @throws EE_Error
928
-     * @throws InvalidArgumentException
929
-     * @throws InvalidDataTypeException
930
-     * @throws InvalidInterfaceException
931
-     * @throws ReflectionException
932
-     */
933
-    public static function set_event_subtotal_details(
934
-        EE_Line_Item $event_line_item,
935
-        EE_Event $event,
936
-        $transaction = null
937
-    ) {
938
-        if ($event instanceof EE_Event) {
939
-            $event_line_item->set_code(self::get_event_code($event));
940
-            $event_line_item->set_name(self::get_event_name($event));
941
-            $event_line_item->set_desc(self::get_event_desc($event));
942
-            $event_line_item->set_OBJ_ID($event->ID());
943
-        }
944
-        self::set_TXN_ID($event_line_item, $transaction);
945
-    }
946
-
947
-
948
-    /**
949
-     * Finds what taxes should apply, adds them as tax line items under the taxes sub-total,
950
-     * and recalculates the taxes sub-total and the grand total. Resets the taxes, so
951
-     * any old taxes are removed
952
-     *
953
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
954
-     * @param bool         $update_txn_status
955
-     * @return bool
956
-     * @throws EE_Error
957
-     * @throws InvalidArgumentException
958
-     * @throws InvalidDataTypeException
959
-     * @throws InvalidInterfaceException
960
-     * @throws ReflectionException
961
-     * @throws RuntimeException
962
-     */
963
-    public static function apply_taxes(EE_Line_Item $total_line_item, $update_txn_status = false)
964
-    {
965
-        /** @type EEM_Price $EEM_Price */
966
-        $EEM_Price = EE_Registry::instance()->load_model('Price');
967
-        // get array of taxes via Price Model
968
-        $ordered_taxes = $EEM_Price->get_all_prices_that_are_taxes();
969
-        ksort($ordered_taxes);
970
-        $taxes_line_item = self::get_taxes_subtotal($total_line_item);
971
-        // just to be safe, remove its old tax line items
972
-        $deleted = $taxes_line_item->delete_children_line_items();
973
-        $updates = false;
974
-        // loop thru taxes
975
-        foreach ($ordered_taxes as $order => $taxes) {
976
-            foreach ($taxes as $tax) {
977
-                if ($tax instanceof EE_Price) {
978
-                    $tax_line_item = EE_Line_Item::new_instance(
979
-                        array(
980
-                            'LIN_name'       => $tax->name(),
981
-                            'LIN_desc'       => $tax->desc(),
982
-                            'LIN_percent'    => $tax->amount(),
983
-                            'LIN_is_taxable' => false,
984
-                            'LIN_order'      => $order,
985
-                            'LIN_total'      => 0,
986
-                            'LIN_type'       => EEM_Line_Item::type_tax,
987
-                            'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
988
-                            'OBJ_ID'         => $tax->ID(),
989
-                        )
990
-                    );
991
-                    $tax_line_item = apply_filters(
992
-                        'FHEE__EEH_Line_Item__apply_taxes__tax_line_item',
993
-                        $tax_line_item
994
-                    );
995
-                    $updates = $taxes_line_item->add_child_line_item($tax_line_item) ?
996
-                        true :
997
-                        $updates;
998
-                }
999
-            }
1000
-        }
1001
-        // only recalculate totals if something changed
1002
-        if ($deleted || $updates) {
1003
-            $total_line_item->recalculate_total_including_taxes($update_txn_status);
1004
-            return true;
1005
-        }
1006
-        return false;
1007
-    }
1008
-
1009
-
1010
-    /**
1011
-     * Ensures that taxes have been applied to the order, if not applies them.
1012
-     * Returns the total amount of tax
1013
-     *
1014
-     * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
1015
-     * @return float
1016
-     * @throws EE_Error
1017
-     * @throws InvalidArgumentException
1018
-     * @throws InvalidDataTypeException
1019
-     * @throws InvalidInterfaceException
1020
-     * @throws ReflectionException
1021
-     */
1022
-    public static function ensure_taxes_applied($total_line_item)
1023
-    {
1024
-        $taxes_subtotal = self::get_taxes_subtotal($total_line_item);
1025
-        if (! $taxes_subtotal->children()) {
1026
-            self::apply_taxes($total_line_item);
1027
-        }
1028
-        return $taxes_subtotal->total();
1029
-    }
1030
-
1031
-
1032
-    /**
1033
-     * Deletes ALL children of the passed line item
1034
-     *
1035
-     * @param EE_Line_Item $parent_line_item
1036
-     * @return bool
1037
-     * @throws EE_Error
1038
-     * @throws InvalidArgumentException
1039
-     * @throws InvalidDataTypeException
1040
-     * @throws InvalidInterfaceException
1041
-     * @throws ReflectionException
1042
-     */
1043
-    public static function delete_all_child_items(EE_Line_Item $parent_line_item)
1044
-    {
1045
-        $deleted = 0;
1046
-        foreach ($parent_line_item->children() as $child_line_item) {
1047
-            if ($child_line_item instanceof EE_Line_Item) {
1048
-                $deleted += EEH_Line_Item::delete_all_child_items($child_line_item);
1049
-                if ($child_line_item->ID()) {
1050
-                    $child_line_item->delete();
1051
-                    unset($child_line_item);
1052
-                } else {
1053
-                    $parent_line_item->delete_child_line_item($child_line_item->code());
1054
-                }
1055
-                $deleted++;
1056
-            }
1057
-        }
1058
-        return $deleted;
1059
-    }
1060
-
1061
-
1062
-    /**
1063
-     * Deletes the line items as indicated by the line item code(s) provided,
1064
-     * regardless of where they're found in the line item tree. Automatically
1065
-     * re-calculates the line item totals and updates the related transaction. But
1066
-     * DOES NOT automatically upgrade the transaction's registrations' final prices (which
1067
-     * should probably change because of this).
1068
-     * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
1069
-     * after using this, to keep the registration final prices in-sync with the transaction's total.
1070
-     *
1071
-     * @param EE_Line_Item      $total_line_item of type EEM_Line_Item::type_total
1072
-     * @param array|bool|string $line_item_codes
1073
-     * @return int number of items successfully removed
1074
-     * @throws EE_Error
1075
-     */
1076
-    public static function delete_items(EE_Line_Item $total_line_item, $line_item_codes = false)
1077
-    {
1078
-
1079
-        if ($total_line_item->type() !== EEM_Line_Item::type_total) {
1080
-            EE_Error::doing_it_wrong(
1081
-                'EEH_Line_Item::delete_items',
1082
-                esc_html__(
1083
-                    'This static method should only be called with a TOTAL line item, otherwise we won\'t recalculate the totals correctly',
1084
-                    'event_espresso'
1085
-                ),
1086
-                '4.6.18'
1087
-            );
1088
-        }
1089
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1090
-
1091
-        // check if only a single line_item_id was passed
1092
-        if (! empty($line_item_codes) && ! is_array($line_item_codes)) {
1093
-            // place single line_item_id in an array to appear as multiple line_item_ids
1094
-            $line_item_codes = array($line_item_codes);
1095
-        }
1096
-        $removals = 0;
1097
-        // cycle thru line_item_ids
1098
-        foreach ($line_item_codes as $line_item_id) {
1099
-            $removals += $total_line_item->delete_child_line_item($line_item_id);
1100
-        }
1101
-
1102
-        if ($removals > 0) {
1103
-            $total_line_item->recalculate_taxes_and_tax_total();
1104
-            return $removals;
1105
-        } else {
1106
-            return false;
1107
-        }
1108
-    }
1109
-
1110
-
1111
-    /**
1112
-     * Overwrites the previous tax by clearing out the old taxes, and creates a new
1113
-     * tax and updates the total line item accordingly
1114
-     *
1115
-     * @param EE_Line_Item $total_line_item
1116
-     * @param float        $amount
1117
-     * @param string       $name
1118
-     * @param string       $description
1119
-     * @param string       $code
1120
-     * @param boolean      $add_to_existing_line_item
1121
-     *                          if true, and a duplicate line item with the same code is found,
1122
-     *                          $amount will be added onto it; otherwise will simply set the taxes to match $amount
1123
-     * @return EE_Line_Item the new tax line item created
1124
-     * @throws EE_Error
1125
-     * @throws InvalidArgumentException
1126
-     * @throws InvalidDataTypeException
1127
-     * @throws InvalidInterfaceException
1128
-     * @throws ReflectionException
1129
-     */
1130
-    public static function set_total_tax_to(
1131
-        EE_Line_Item $total_line_item,
1132
-        $amount,
1133
-        $name = null,
1134
-        $description = null,
1135
-        $code = null,
1136
-        $add_to_existing_line_item = false
1137
-    ) {
1138
-        $tax_subtotal = self::get_taxes_subtotal($total_line_item);
1139
-        $taxable_total = $total_line_item->taxable_total();
1140
-
1141
-        if ($add_to_existing_line_item) {
1142
-            $new_tax = $tax_subtotal->get_child_line_item($code);
1143
-            EEM_Line_Item::instance()->delete(
1144
-                array(array('LIN_code' => array('!=', $code), 'LIN_parent' => $tax_subtotal->ID()))
1145
-            );
1146
-        } else {
1147
-            $new_tax = null;
1148
-            $tax_subtotal->delete_children_line_items();
1149
-        }
1150
-        if ($new_tax) {
1151
-            $new_tax->set_total($new_tax->total() + $amount);
1152
-            $new_tax->set_percent($taxable_total ? $new_tax->total() / $taxable_total * 100 : 0);
1153
-        } else {
1154
-            // no existing tax item. Create it
1155
-            $new_tax = EE_Line_Item::new_instance(array(
1156
-                'TXN_ID'      => $total_line_item->TXN_ID(),
1157
-                'LIN_name'    => $name ? $name : esc_html__('Tax', 'event_espresso'),
1158
-                'LIN_desc'    => $description ? $description : '',
1159
-                'LIN_percent' => $taxable_total ? ($amount / $taxable_total * 100) : 0,
1160
-                'LIN_total'   => $amount,
1161
-                'LIN_parent'  => $tax_subtotal->ID(),
1162
-                'LIN_type'    => EEM_Line_Item::type_tax,
1163
-                'LIN_code'    => $code,
1164
-            ));
1165
-        }
1166
-
1167
-        $new_tax = apply_filters(
1168
-            'FHEE__EEH_Line_Item__set_total_tax_to__new_tax_subtotal',
1169
-            $new_tax,
1170
-            $total_line_item
1171
-        );
1172
-        $new_tax->save();
1173
-        $tax_subtotal->set_total($new_tax->total());
1174
-        $tax_subtotal->save();
1175
-        $total_line_item->recalculate_total_including_taxes();
1176
-        return $new_tax;
1177
-    }
1178
-
1179
-
1180
-    /**
1181
-     * Makes all the line items which are children of $line_item taxable (or not).
1182
-     * Does NOT save the line items
1183
-     *
1184
-     * @param EE_Line_Item $line_item
1185
-     * @param boolean      $taxable
1186
-     * @param string       $code_substring_for_whitelist if this string is part of the line item's code
1187
-     *                                                   it will be whitelisted (ie, except from becoming taxable)
1188
-     * @throws EE_Error
1189
-     */
1190
-    public static function set_line_items_taxable(
1191
-        EE_Line_Item $line_item,
1192
-        $taxable = true,
1193
-        $code_substring_for_whitelist = null
1194
-    ) {
1195
-        $whitelisted = false;
1196
-        if ($code_substring_for_whitelist !== null) {
1197
-            $whitelisted = strpos($line_item->code(), $code_substring_for_whitelist) !== false;
1198
-        }
1199
-        if (! $whitelisted && $line_item->is_line_item()) {
1200
-            $line_item->set_is_taxable($taxable);
1201
-        }
1202
-        foreach ($line_item->children() as $child_line_item) {
1203
-            EEH_Line_Item::set_line_items_taxable(
1204
-                $child_line_item,
1205
-                $taxable,
1206
-                $code_substring_for_whitelist
1207
-            );
1208
-        }
1209
-    }
1210
-
1211
-
1212
-    /**
1213
-     * Gets all descendants that are event subtotals
1214
-     *
1215
-     * @uses  EEH_Line_Item::get_subtotals_of_object_type()
1216
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1217
-     * @return EE_Line_Item[]
1218
-     * @throws EE_Error
1219
-     */
1220
-    public static function get_event_subtotals(EE_Line_Item $parent_line_item)
1221
-    {
1222
-        return self::get_subtotals_of_object_type($parent_line_item, EEM_Line_Item::OBJ_TYPE_EVENT);
1223
-    }
1224
-
1225
-
1226
-    /**
1227
-     * Gets all descendants subtotals that match the supplied object type
1228
-     *
1229
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1230
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1231
-     * @param string       $obj_type
1232
-     * @return EE_Line_Item[]
1233
-     * @throws EE_Error
1234
-     */
1235
-    public static function get_subtotals_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1236
-    {
1237
-        return self::_get_descendants_by_type_and_object_type(
1238
-            $parent_line_item,
1239
-            EEM_Line_Item::type_sub_total,
1240
-            $obj_type
1241
-        );
1242
-    }
1243
-
1244
-
1245
-    /**
1246
-     * Gets all descendants that are tickets
1247
-     *
1248
-     * @uses  EEH_Line_Item::get_line_items_of_object_type()
1249
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1250
-     * @return EE_Line_Item[]
1251
-     * @throws EE_Error
1252
-     */
1253
-    public static function get_ticket_line_items(EE_Line_Item $parent_line_item)
1254
-    {
1255
-        return self::get_line_items_of_object_type(
1256
-            $parent_line_item,
1257
-            EEM_Line_Item::OBJ_TYPE_TICKET
1258
-        );
1259
-    }
1260
-
1261
-
1262
-    /**
1263
-     * Gets all descendants subtotals that match the supplied object type
1264
-     *
1265
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1266
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1267
-     * @param string       $obj_type
1268
-     * @return EE_Line_Item[]
1269
-     * @throws EE_Error
1270
-     */
1271
-    public static function get_line_items_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1272
-    {
1273
-        return self::_get_descendants_by_type_and_object_type(
1274
-            $parent_line_item,
1275
-            EEM_Line_Item::type_line_item,
1276
-            $obj_type
1277
-        );
1278
-    }
1279
-
1280
-
1281
-    /**
1282
-     * Gets all the descendants (ie, children or children of children etc) that are of the type 'tax'
1283
-     *
1284
-     * @uses  EEH_Line_Item::get_descendants_of_type()
1285
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1286
-     * @return EE_Line_Item[]
1287
-     * @throws EE_Error
1288
-     */
1289
-    public static function get_tax_descendants(EE_Line_Item $parent_line_item)
1290
-    {
1291
-        return EEH_Line_Item::get_descendants_of_type(
1292
-            $parent_line_item,
1293
-            EEM_Line_Item::type_tax
1294
-        );
1295
-    }
1296
-
1297
-
1298
-    /**
1299
-     * Gets all the real items purchased which are children of this item
1300
-     *
1301
-     * @uses  EEH_Line_Item::get_descendants_of_type()
1302
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1303
-     * @return EE_Line_Item[]
1304
-     * @throws EE_Error
1305
-     */
1306
-    public static function get_line_item_descendants(EE_Line_Item $parent_line_item)
1307
-    {
1308
-        return EEH_Line_Item::get_descendants_of_type(
1309
-            $parent_line_item,
1310
-            EEM_Line_Item::type_line_item
1311
-        );
1312
-    }
1313
-
1314
-
1315
-    /**
1316
-     * Gets all descendants of supplied line item that match the supplied line item type
1317
-     *
1318
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1319
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1320
-     * @param string       $line_item_type   one of the EEM_Line_Item constants
1321
-     * @return EE_Line_Item[]
1322
-     * @throws EE_Error
1323
-     */
1324
-    public static function get_descendants_of_type(EE_Line_Item $parent_line_item, $line_item_type)
1325
-    {
1326
-        return self::_get_descendants_by_type_and_object_type(
1327
-            $parent_line_item,
1328
-            $line_item_type,
1329
-            null
1330
-        );
1331
-    }
1332
-
1333
-
1334
-    /**
1335
-     * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1336
-     * as well
1337
-     *
1338
-     * @param EE_Line_Item  $parent_line_item - the line item to find descendants of
1339
-     * @param string        $line_item_type   one of the EEM_Line_Item constants
1340
-     * @param string | NULL $obj_type         object model class name (minus prefix) or NULL to ignore object type when
1341
-     *                                        searching
1342
-     * @return EE_Line_Item[]
1343
-     * @throws EE_Error
1344
-     */
1345
-    protected static function _get_descendants_by_type_and_object_type(
1346
-        EE_Line_Item $parent_line_item,
1347
-        $line_item_type,
1348
-        $obj_type = null
1349
-    ) {
1350
-        $objects = array();
1351
-        foreach ($parent_line_item->children() as $child_line_item) {
1352
-            if ($child_line_item instanceof EE_Line_Item) {
1353
-                if ($child_line_item->type() === $line_item_type
1354
-                    && (
1355
-                        $child_line_item->OBJ_type() === $obj_type || $obj_type === null
1356
-                    )
1357
-                ) {
1358
-                    $objects[] = $child_line_item;
1359
-                } else {
1360
-                    // go-through-all-its children looking for more matches
1361
-                    $objects = array_merge(
1362
-                        $objects,
1363
-                        self::_get_descendants_by_type_and_object_type(
1364
-                            $child_line_item,
1365
-                            $line_item_type,
1366
-                            $obj_type
1367
-                        )
1368
-                    );
1369
-                }
1370
-            }
1371
-        }
1372
-        return $objects;
1373
-    }
1374
-
1375
-
1376
-    /**
1377
-     * Gets all descendants subtotals that match the supplied object type
1378
-     *
1379
-     * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1380
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1381
-     * @param string       $OBJ_type         object type (like Event)
1382
-     * @param array        $OBJ_IDs          array of OBJ_IDs
1383
-     * @return EE_Line_Item[]
1384
-     * @throws EE_Error
1385
-     */
1386
-    public static function get_line_items_by_object_type_and_IDs(
1387
-        EE_Line_Item $parent_line_item,
1388
-        $OBJ_type = '',
1389
-        $OBJ_IDs = array()
1390
-    ) {
1391
-        return self::_get_descendants_by_object_type_and_object_ID(
1392
-            $parent_line_item,
1393
-            $OBJ_type,
1394
-            $OBJ_IDs
1395
-        );
1396
-    }
1397
-
1398
-
1399
-    /**
1400
-     * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1401
-     * as well
1402
-     *
1403
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1404
-     * @param string       $OBJ_type         object type (like Event)
1405
-     * @param array        $OBJ_IDs          array of OBJ_IDs
1406
-     * @return EE_Line_Item[]
1407
-     * @throws EE_Error
1408
-     */
1409
-    protected static function _get_descendants_by_object_type_and_object_ID(
1410
-        EE_Line_Item $parent_line_item,
1411
-        $OBJ_type,
1412
-        $OBJ_IDs
1413
-    ) {
1414
-        $objects = array();
1415
-        foreach ($parent_line_item->children() as $child_line_item) {
1416
-            if ($child_line_item instanceof EE_Line_Item) {
1417
-                if ($child_line_item->OBJ_type() === $OBJ_type
1418
-                    && is_array($OBJ_IDs)
1419
-                    && in_array($child_line_item->OBJ_ID(), $OBJ_IDs)
1420
-                ) {
1421
-                    $objects[] = $child_line_item;
1422
-                } else {
1423
-                    // go-through-all-its children looking for more matches
1424
-                    $objects = array_merge(
1425
-                        $objects,
1426
-                        self::_get_descendants_by_object_type_and_object_ID(
1427
-                            $child_line_item,
1428
-                            $OBJ_type,
1429
-                            $OBJ_IDs
1430
-                        )
1431
-                    );
1432
-                }
1433
-            }
1434
-        }
1435
-        return $objects;
1436
-    }
1437
-
1438
-
1439
-    /**
1440
-     * Uses a breadth-first-search in order to find the nearest descendant of
1441
-     * the specified type and returns it, else NULL
1442
-     *
1443
-     * @uses  EEH_Line_Item::_get_nearest_descendant()
1444
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1445
-     * @param string       $type             like one of the EEM_Line_Item::type_*
1446
-     * @return EE_Line_Item
1447
-     * @throws EE_Error
1448
-     * @throws InvalidArgumentException
1449
-     * @throws InvalidDataTypeException
1450
-     * @throws InvalidInterfaceException
1451
-     * @throws ReflectionException
1452
-     */
1453
-    public static function get_nearest_descendant_of_type(EE_Line_Item $parent_line_item, $type)
1454
-    {
1455
-        return self::_get_nearest_descendant($parent_line_item, 'LIN_type', $type);
1456
-    }
1457
-
1458
-
1459
-    /**
1460
-     * Uses a breadth-first-search in order to find the nearest descendant
1461
-     * having the specified LIN_code and returns it, else NULL
1462
-     *
1463
-     * @uses  EEH_Line_Item::_get_nearest_descendant()
1464
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1465
-     * @param string       $code             any value used for LIN_code
1466
-     * @return EE_Line_Item
1467
-     * @throws EE_Error
1468
-     * @throws InvalidArgumentException
1469
-     * @throws InvalidDataTypeException
1470
-     * @throws InvalidInterfaceException
1471
-     * @throws ReflectionException
1472
-     */
1473
-    public static function get_nearest_descendant_having_code(EE_Line_Item $parent_line_item, $code)
1474
-    {
1475
-        return self::_get_nearest_descendant($parent_line_item, 'LIN_code', $code);
1476
-    }
1477
-
1478
-
1479
-    /**
1480
-     * Uses a breadth-first-search in order to find the nearest descendant
1481
-     * having the specified LIN_code and returns it, else NULL
1482
-     *
1483
-     * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1484
-     * @param string       $search_field     name of EE_Line_Item property
1485
-     * @param string       $value            any value stored in $search_field
1486
-     * @return EE_Line_Item
1487
-     * @throws EE_Error
1488
-     * @throws InvalidArgumentException
1489
-     * @throws InvalidDataTypeException
1490
-     * @throws InvalidInterfaceException
1491
-     * @throws ReflectionException
1492
-     */
1493
-    protected static function _get_nearest_descendant(EE_Line_Item $parent_line_item, $search_field, $value)
1494
-    {
1495
-        foreach ($parent_line_item->children() as $child) {
1496
-            if ($child->get($search_field) == $value) {
1497
-                return $child;
1498
-            }
1499
-        }
1500
-        foreach ($parent_line_item->children() as $child) {
1501
-            $descendant_found = self::_get_nearest_descendant(
1502
-                $child,
1503
-                $search_field,
1504
-                $value
1505
-            );
1506
-            if ($descendant_found) {
1507
-                return $descendant_found;
1508
-            }
1509
-        }
1510
-        return null;
1511
-    }
1512
-
1513
-
1514
-    /**
1515
-     * if passed line item has a TXN ID, uses that to jump directly to the grand total line item for the transaction,
1516
-     * else recursively walks up the line item tree until a parent of type total is found,
1517
-     *
1518
-     * @param EE_Line_Item $line_item
1519
-     * @return EE_Line_Item
1520
-     * @throws EE_Error
1521
-     */
1522
-    public static function find_transaction_grand_total_for_line_item(EE_Line_Item $line_item)
1523
-    {
1524
-        if ($line_item->TXN_ID()) {
1525
-            $total_line_item = $line_item->transaction()->total_line_item(false);
1526
-            if ($total_line_item instanceof EE_Line_Item) {
1527
-                return $total_line_item;
1528
-            }
1529
-        } else {
1530
-            $line_item_parent = $line_item->parent();
1531
-            if ($line_item_parent instanceof EE_Line_Item) {
1532
-                if ($line_item_parent->is_total()) {
1533
-                    return $line_item_parent;
1534
-                }
1535
-                return EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item_parent);
1536
-            }
1537
-        }
1538
-        throw new EE_Error(
1539
-            sprintf(
1540
-                esc_html__(
1541
-                    'A valid grand total for line item %1$d was not found.',
1542
-                    'event_espresso'
1543
-                ),
1544
-                $line_item->ID()
1545
-            )
1546
-        );
1547
-    }
1548
-
1549
-
1550
-    /**
1551
-     * Prints out a representation of the line item tree
1552
-     *
1553
-     * @param EE_Line_Item $line_item
1554
-     * @param int          $indentation
1555
-     * @return void
1556
-     * @throws EE_Error
1557
-     */
1558
-    public static function visualize(EE_Line_Item $line_item, $indentation = 0)
1559
-    {
1560
-        echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1561
-        if (! $indentation) {
1562
-            echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1563
-        }
1564
-        for ($i = 0; $i < $indentation; $i++) {
1565
-            echo '. ';
1566
-        }
1567
-        $breakdown = '';
1568
-        if ($line_item->is_line_item()) {
1569
-            if ($line_item->is_percent()) {
1570
-                $breakdown = "{$line_item->percent()}%";
1571
-            } else {
1572
-                $breakdown = '$' . "{$line_item->unit_price()} x {$line_item->quantity()}";
1573
-            }
1574
-        }
1575
-        echo $line_item->name();
1576
-        echo " [ ID:{$line_item->ID()} | qty:{$line_item->quantity()} ] {$line_item->type()} : ";
1577
-        echo '$' . (string) $line_item->total();
1578
-        if ($breakdown) {
1579
-            echo " ( {$breakdown} )";
1580
-        }
1581
-        if ($line_item->is_taxable()) {
1582
-            echo '  * taxable';
1583
-        }
1584
-        if ($line_item->children()) {
1585
-            foreach ($line_item->children() as $child) {
1586
-                self::visualize($child, $indentation + 1);
1587
-            }
1588
-        }
1589
-    }
1590
-
1591
-
1592
-    /**
1593
-     * Calculates the registration's final price, taking into account that they
1594
-     * need to not only help pay for their OWN ticket, but also any transaction-wide surcharges and taxes,
1595
-     * and receive a portion of any transaction-wide discounts.
1596
-     * eg1, if I buy a $1 ticket and brent buys a $9 ticket, and we receive a $5 discount
1597
-     * then I'll get 1/10 of that $5 discount, which is $0.50, and brent will get
1598
-     * 9/10ths of that $5 discount, which is $4.50. So my final price should be $0.50
1599
-     * and brent's final price should be $5.50.
1600
-     * In order to do this, we basically need to traverse the line item tree calculating
1601
-     * the running totals (just as if we were recalculating the total), but when we identify
1602
-     * regular line items, we need to keep track of their share of the grand total.
1603
-     * Also, we need to keep track of the TAXABLE total for each ticket purchase, so
1604
-     * we can know how to apply taxes to it. (Note: "taxable total" does not equal the "pretax total"
1605
-     * when there are non-taxable items; otherwise they would be the same)
1606
-     *
1607
-     * @param EE_Line_Item $line_item
1608
-     * @param array        $billable_ticket_quantities  array of EE_Ticket IDs and their corresponding quantity that
1609
-     *                                                  can be included in price calculations at this moment
1610
-     * @return array        keys are line items for tickets IDs and values are their share of the running total,
1611
-     *                                                  plus the key 'total', and 'taxable' which also has keys of all
1612
-     *                                                  the ticket IDs.
1613
-     *                                                  Eg array(
1614
-     *                                                      12 => 4.3
1615
-     *                                                      23 => 8.0
1616
-     *                                                      'total' => 16.6,
1617
-     *                                                      'taxable' => array(
1618
-     *                                                          12 => 10,
1619
-     *                                                          23 => 4
1620
-     *                                                      ).
1621
-     *                                                  So to find which registrations have which final price, we need
1622
-     *                                                  to find which line item is theirs, which can be done with
1623
-     *                                                  `EEM_Line_Item::instance()->get_line_item_for_registration(
1624
-     *                                                  $registration );`
1625
-     * @throws EE_Error
1626
-     * @throws InvalidArgumentException
1627
-     * @throws InvalidDataTypeException
1628
-     * @throws InvalidInterfaceException
1629
-     * @throws ReflectionException
1630
-     */
1631
-    public static function calculate_reg_final_prices_per_line_item(
1632
-        EE_Line_Item $line_item,
1633
-        $billable_ticket_quantities = array()
1634
-    ) {
1635
-        $running_totals = [
1636
-            'total'   => 0,
1637
-            'taxable' => ['total' => 0]
1638
-        ];
1639
-        foreach ($line_item->children() as $child_line_item) {
1640
-            switch ($child_line_item->type()) {
1641
-                case EEM_Line_Item::type_sub_total:
1642
-                    $running_totals_from_subtotal = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1643
-                        $child_line_item,
1644
-                        $billable_ticket_quantities
1645
-                    );
1646
-                    // combine arrays but preserve numeric keys
1647
-                    $running_totals = array_replace_recursive($running_totals_from_subtotal, $running_totals);
1648
-                    $running_totals['total'] += $running_totals_from_subtotal['total'];
1649
-                    $running_totals['taxable']['total'] += $running_totals_from_subtotal['taxable']['total'];
1650
-                    break;
1651
-
1652
-                case EEM_Line_Item::type_tax_sub_total:
1653
-                    // find how much the taxes percentage is
1654
-                    if ($child_line_item->percent() !== 0) {
1655
-                        $tax_percent_decimal = $child_line_item->percent() / 100;
1656
-                    } else {
1657
-                        $tax_percent_decimal = EE_Taxes::get_total_taxes_percentage() / 100;
1658
-                    }
1659
-                    // and apply to all the taxable totals, and add to the pretax totals
1660
-                    foreach ($running_totals as $line_item_id => $this_running_total) {
1661
-                        // "total" and "taxable" array key is an exception
1662
-                        if ($line_item_id === 'taxable') {
1663
-                            continue;
1664
-                        }
1665
-                        $taxable_total = $running_totals['taxable'][ $line_item_id ];
1666
-                        $running_totals[ $line_item_id ] += ($taxable_total * $tax_percent_decimal);
1667
-                    }
1668
-                    break;
1669
-
1670
-                case EEM_Line_Item::type_line_item:
1671
-                    // ticket line items or ????
1672
-                    if ($child_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
1673
-                        // kk it's a ticket
1674
-                        if (isset($running_totals[ $child_line_item->ID() ])) {
1675
-                            // huh? that shouldn't happen.
1676
-                            $running_totals['total'] += $child_line_item->total();
1677
-                        } else {
1678
-                            // its not in our running totals yet. great.
1679
-                            if ($child_line_item->is_taxable()) {
1680
-                                $taxable_amount = $child_line_item->unit_price();
1681
-                            } else {
1682
-                                $taxable_amount = 0;
1683
-                            }
1684
-                            // are we only calculating totals for some tickets?
1685
-                            if (isset($billable_ticket_quantities[ $child_line_item->OBJ_ID() ])) {
1686
-                                $quantity = $billable_ticket_quantities[ $child_line_item->OBJ_ID() ];
1687
-                                $running_totals[ $child_line_item->ID() ] = $quantity
1688
-                                    ? $child_line_item->unit_price()
1689
-                                    : 0;
1690
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $quantity
1691
-                                    ? $taxable_amount
1692
-                                    : 0;
1693
-                            } else {
1694
-                                $quantity = $child_line_item->quantity();
1695
-                                $running_totals[ $child_line_item->ID() ] = $child_line_item->unit_price();
1696
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $taxable_amount;
1697
-                            }
1698
-                            $running_totals['taxable']['total'] += $taxable_amount * $quantity;
1699
-                            $running_totals['total'] += $child_line_item->unit_price() * $quantity;
1700
-                        }
1701
-                    } else {
1702
-                        // it's some other type of item added to the cart
1703
-                        // it should affect the running totals
1704
-                        // basically we want to convert it into a PERCENT modifier. Because
1705
-                        // more clearly affect all registration's final price equally
1706
-                        $line_items_percent_of_running_total = $running_totals['total'] > 0
1707
-                            ? ($child_line_item->total() / $running_totals['total']) + 1
1708
-                            : 1;
1709
-                        foreach ($running_totals as $line_item_id => $this_running_total) {
1710
-                            // the "taxable" array key is an exception
1711
-                            if ($line_item_id === 'taxable') {
1712
-                                continue;
1713
-                            }
1714
-                            // update the running totals
1715
-                            // yes this actually even works for the running grand total!
1716
-                            $running_totals[ $line_item_id ] =
1717
-                                $line_items_percent_of_running_total * $this_running_total;
1718
-
1719
-                            if ($child_line_item->is_taxable()) {
1720
-                                $running_totals['taxable'][ $line_item_id ] =
1721
-                                    $line_items_percent_of_running_total * $running_totals['taxable'][ $line_item_id ];
1722
-                            }
1723
-                        }
1724
-                    }
1725
-                    break;
1726
-            }
1727
-        }
1728
-        return $running_totals;
1729
-    }
1730
-
1731
-
1732
-    /**
1733
-     * @param EE_Line_Item $total_line_item
1734
-     * @param EE_Line_Item $ticket_line_item
1735
-     * @return float | null
1736
-     * @throws EE_Error
1737
-     * @throws InvalidArgumentException
1738
-     * @throws InvalidDataTypeException
1739
-     * @throws InvalidInterfaceException
1740
-     * @throws OutOfRangeException
1741
-     * @throws ReflectionException
1742
-     */
1743
-    public static function calculate_final_price_for_ticket_line_item(
1744
-        EE_Line_Item $total_line_item,
1745
-        EE_Line_Item $ticket_line_item
1746
-    ) {
1747
-        static $final_prices_per_ticket_line_item = array();
1748
-        if (empty($final_prices_per_ticket_line_item) || empty($final_prices_per_ticket_line_item[ $total_line_item->ID() ])) {
1749
-            $final_prices_per_ticket_line_item[ $total_line_item->ID() ] = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1750
-                $total_line_item
1751
-            );
1752
-        }
1753
-        // ok now find this new registration's final price
1754
-        if (isset($final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ])) {
1755
-            return $final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ];
1756
-        }
1757
-        $message = sprintf(
1758
-            esc_html__(
1759
-                'The final price for the ticket line item (ID:%1$d) could not be calculated.',
1760
-                'event_espresso'
1761
-            ),
1762
-            $ticket_line_item->ID()
1763
-        );
1764
-        if (WP_DEBUG) {
1765
-            $message .= '<br>' . print_r($final_prices_per_ticket_line_item, true);
1766
-            throw new OutOfRangeException($message);
1767
-        }
1768
-        EE_Log::instance()->log(__CLASS__, __FUNCTION__, $message);
1769
-        return null;
1770
-    }
1771
-
1772
-
1773
-    /**
1774
-     * Creates a duplicate of the line item tree, except only includes billable items
1775
-     * and the portion of line items attributed to billable things
1776
-     *
1777
-     * @param EE_Line_Item      $line_item
1778
-     * @param EE_Registration[] $registrations
1779
-     * @return EE_Line_Item
1780
-     * @throws EE_Error
1781
-     * @throws InvalidArgumentException
1782
-     * @throws InvalidDataTypeException
1783
-     * @throws InvalidInterfaceException
1784
-     * @throws ReflectionException
1785
-     */
1786
-    public static function billable_line_item_tree(EE_Line_Item $line_item, $registrations)
1787
-    {
1788
-        $copy_li = EEH_Line_Item::billable_line_item($line_item, $registrations);
1789
-        foreach ($line_item->children() as $child_li) {
1790
-            $copy_li->add_child_line_item(
1791
-                EEH_Line_Item::billable_line_item_tree($child_li, $registrations)
1792
-            );
1793
-        }
1794
-        // if this is the grand total line item, make sure the totals all add up
1795
-        // (we could have duplicated this logic AS we copied the line items, but
1796
-        // it seems DRYer this way)
1797
-        if ($copy_li->type() === EEM_Line_Item::type_total) {
1798
-            $copy_li->recalculate_total_including_taxes();
1799
-        }
1800
-        return $copy_li;
1801
-    }
1802
-
1803
-
1804
-    /**
1805
-     * Creates a new, unsaved line item from $line_item that factors in the
1806
-     * number of billable registrations on $registrations.
1807
-     *
1808
-     * @param EE_Line_Item      $line_item
1809
-     * @param EE_Registration[] $registrations
1810
-     * @return EE_Line_Item
1811
-     * @throws EE_Error
1812
-     * @throws InvalidArgumentException
1813
-     * @throws InvalidDataTypeException
1814
-     * @throws InvalidInterfaceException
1815
-     * @throws ReflectionException
1816
-     */
1817
-    public static function billable_line_item(EE_Line_Item $line_item, $registrations)
1818
-    {
1819
-        $new_li_fields = $line_item->model_field_array();
1820
-        if ($line_item->type() === EEM_Line_Item::type_line_item &&
1821
-            $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1822
-        ) {
1823
-            $count = 0;
1824
-            foreach ($registrations as $registration) {
1825
-                if ($line_item->OBJ_ID() === $registration->ticket_ID() &&
1826
-                    in_array(
1827
-                        $registration->status_ID(),
1828
-                        EEM_Registration::reg_statuses_that_allow_payment(),
1829
-                        true
1830
-                    )
1831
-                ) {
1832
-                    $count++;
1833
-                }
1834
-            }
1835
-            $new_li_fields['LIN_quantity'] = $count;
1836
-        }
1837
-        // don't set the total. We'll leave that up to the code that calculates it
1838
-        unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent'], $new_li_fields['LIN_total']);
1839
-        return EE_Line_Item::new_instance($new_li_fields);
1840
-    }
1841
-
1842
-
1843
-    /**
1844
-     * Returns a modified line item tree where all the subtotals which have a total of 0
1845
-     * are removed, and line items with a quantity of 0
1846
-     *
1847
-     * @param EE_Line_Item $line_item |null
1848
-     * @return EE_Line_Item|null
1849
-     * @throws EE_Error
1850
-     * @throws InvalidArgumentException
1851
-     * @throws InvalidDataTypeException
1852
-     * @throws InvalidInterfaceException
1853
-     * @throws ReflectionException
1854
-     */
1855
-    public static function non_empty_line_items(EE_Line_Item $line_item)
1856
-    {
1857
-        $copied_li = EEH_Line_Item::non_empty_line_item($line_item);
1858
-        if ($copied_li === null) {
1859
-            return null;
1860
-        }
1861
-        // if this is an event subtotal, we want to only include it if it
1862
-        // has a non-zero total and at least one ticket line item child
1863
-        $ticket_children = 0;
1864
-        foreach ($line_item->children() as $child_li) {
1865
-            $child_li_copy = EEH_Line_Item::non_empty_line_items($child_li);
1866
-            if ($child_li_copy !== null) {
1867
-                $copied_li->add_child_line_item($child_li_copy);
1868
-                if ($child_li_copy->type() === EEM_Line_Item::type_line_item &&
1869
-                    $child_li_copy->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1870
-                ) {
1871
-                    $ticket_children++;
1872
-                }
1873
-            }
1874
-        }
1875
-        // if this is an event subtotal with NO ticket children
1876
-        // we basically want to ignore it
1877
-        if ($ticket_children === 0
1878
-            && $line_item->type() === EEM_Line_Item::type_sub_total
1879
-            && $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
1880
-            && $line_item->total() === 0
1881
-        ) {
1882
-            return null;
1883
-        }
1884
-        return $copied_li;
1885
-    }
1886
-
1887
-
1888
-    /**
1889
-     * Creates a new, unsaved line item, but if it's a ticket line item
1890
-     * with a total of 0, or a subtotal of 0, returns null instead
1891
-     *
1892
-     * @param EE_Line_Item $line_item
1893
-     * @return EE_Line_Item
1894
-     * @throws EE_Error
1895
-     * @throws InvalidArgumentException
1896
-     * @throws InvalidDataTypeException
1897
-     * @throws InvalidInterfaceException
1898
-     * @throws ReflectionException
1899
-     */
1900
-    public static function non_empty_line_item(EE_Line_Item $line_item)
1901
-    {
1902
-        if ($line_item->type() === EEM_Line_Item::type_line_item
1903
-            && $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1904
-            && $line_item->quantity() === 0
1905
-        ) {
1906
-            return null;
1907
-        }
1908
-        $new_li_fields = $line_item->model_field_array();
1909
-        // don't set the total. We'll leave that up to the code that calculates it
1910
-        unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent']);
1911
-        return EE_Line_Item::new_instance($new_li_fields);
1912
-    }
1913
-
1914
-
1915
-    /**
1916
-     * Cycles through all of the ticket line items for the supplied total line item
1917
-     * and ensures that the line item's "is_taxable" field matches that of its corresponding ticket
1918
-     *
1919
-     * @param EE_Line_Item $total_line_item
1920
-     * @since 4.9.79.p
1921
-     * @throws EE_Error
1922
-     * @throws InvalidArgumentException
1923
-     * @throws InvalidDataTypeException
1924
-     * @throws InvalidInterfaceException
1925
-     * @throws ReflectionException
1926
-     */
1927
-    public static function resetIsTaxableForTickets(EE_Line_Item $total_line_item)
1928
-    {
1929
-        $ticket_line_items = self::get_ticket_line_items($total_line_item);
1930
-        foreach ($ticket_line_items as $ticket_line_item) {
1931
-            if ($ticket_line_item instanceof EE_Line_Item
1932
-                && $ticket_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1933
-            ) {
1934
-                $ticket = $ticket_line_item->ticket();
1935
-                if ($ticket instanceof EE_Ticket && $ticket->taxable() !== $ticket_line_item->is_taxable()) {
1936
-                    $ticket_line_item->set_is_taxable($ticket->taxable());
1937
-                    $ticket_line_item->save();
1938
-                }
1939
-            }
1940
-        }
1941
-    }
1942
-
1943
-
1944
-
1945
-    /**************************************** @DEPRECATED METHODS *************************************** */
1946
-    /**
1947
-     * @deprecated
1948
-     * @param EE_Line_Item $total_line_item
1949
-     * @return EE_Line_Item
1950
-     * @throws EE_Error
1951
-     * @throws InvalidArgumentException
1952
-     * @throws InvalidDataTypeException
1953
-     * @throws InvalidInterfaceException
1954
-     * @throws ReflectionException
1955
-     */
1956
-    public static function get_items_subtotal(EE_Line_Item $total_line_item)
1957
-    {
1958
-        EE_Error::doing_it_wrong(
1959
-            'EEH_Line_Item::get_items_subtotal()',
1960
-            sprintf(
1961
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
1962
-                'EEH_Line_Item::get_pre_tax_subtotal()'
1963
-            ),
1964
-            '4.6.0'
1965
-        );
1966
-        return self::get_pre_tax_subtotal($total_line_item);
1967
-    }
1968
-
1969
-
1970
-    /**
1971
-     * @deprecated
1972
-     * @param EE_Transaction $transaction
1973
-     * @return EE_Line_Item
1974
-     * @throws EE_Error
1975
-     * @throws InvalidArgumentException
1976
-     * @throws InvalidDataTypeException
1977
-     * @throws InvalidInterfaceException
1978
-     * @throws ReflectionException
1979
-     */
1980
-    public static function create_default_total_line_item($transaction = null)
1981
-    {
1982
-        EE_Error::doing_it_wrong(
1983
-            'EEH_Line_Item::create_default_total_line_item()',
1984
-            sprintf(
1985
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
1986
-                'EEH_Line_Item::create_total_line_item()'
1987
-            ),
1988
-            '4.6.0'
1989
-        );
1990
-        return self::create_total_line_item($transaction);
1991
-    }
1992
-
1993
-
1994
-    /**
1995
-     * @deprecated
1996
-     * @param EE_Line_Item   $total_line_item
1997
-     * @param EE_Transaction $transaction
1998
-     * @return EE_Line_Item
1999
-     * @throws EE_Error
2000
-     * @throws InvalidArgumentException
2001
-     * @throws InvalidDataTypeException
2002
-     * @throws InvalidInterfaceException
2003
-     * @throws ReflectionException
2004
-     */
2005
-    public static function create_default_tickets_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2006
-    {
2007
-        EE_Error::doing_it_wrong(
2008
-            'EEH_Line_Item::create_default_tickets_subtotal()',
2009
-            sprintf(
2010
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2011
-                'EEH_Line_Item::create_pre_tax_subtotal()'
2012
-            ),
2013
-            '4.6.0'
2014
-        );
2015
-        return self::create_pre_tax_subtotal($total_line_item, $transaction);
2016
-    }
2017
-
2018
-
2019
-    /**
2020
-     * @deprecated
2021
-     * @param EE_Line_Item   $total_line_item
2022
-     * @param EE_Transaction $transaction
2023
-     * @return EE_Line_Item
2024
-     * @throws EE_Error
2025
-     * @throws InvalidArgumentException
2026
-     * @throws InvalidDataTypeException
2027
-     * @throws InvalidInterfaceException
2028
-     * @throws ReflectionException
2029
-     */
2030
-    public static function create_default_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2031
-    {
2032
-        EE_Error::doing_it_wrong(
2033
-            'EEH_Line_Item::create_default_taxes_subtotal()',
2034
-            sprintf(
2035
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2036
-                'EEH_Line_Item::create_taxes_subtotal()'
2037
-            ),
2038
-            '4.6.0'
2039
-        );
2040
-        return self::create_taxes_subtotal($total_line_item, $transaction);
2041
-    }
2042
-
2043
-
2044
-    /**
2045
-     * @deprecated
2046
-     * @param EE_Line_Item   $total_line_item
2047
-     * @param EE_Transaction $transaction
2048
-     * @return EE_Line_Item
2049
-     * @throws EE_Error
2050
-     * @throws InvalidArgumentException
2051
-     * @throws InvalidDataTypeException
2052
-     * @throws InvalidInterfaceException
2053
-     * @throws ReflectionException
2054
-     */
2055
-    public static function create_default_event_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2056
-    {
2057
-        EE_Error::doing_it_wrong(
2058
-            'EEH_Line_Item::create_default_event_subtotal()',
2059
-            sprintf(
2060
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
2061
-                'EEH_Line_Item::create_event_subtotal()'
2062
-            ),
2063
-            '4.6.0'
2064
-        );
2065
-        return self::create_event_subtotal($total_line_item, $transaction);
2066
-    }
24
+	/**
25
+	 * Adds a simple item (unrelated to any other model object) to the provided PARENT line item.
26
+	 * Does NOT automatically re-calculate the line item totals or update the related transaction.
27
+	 * You should call recalculate_total_including_taxes() on the grant total line item after this
28
+	 * to update the subtotals, and EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
29
+	 * to keep the registration final prices in-sync with the transaction's total.
30
+	 *
31
+	 * @param EE_Line_Item $parent_line_item
32
+	 * @param string       $name
33
+	 * @param float        $unit_price
34
+	 * @param string       $description
35
+	 * @param int          $quantity
36
+	 * @param boolean      $taxable
37
+	 * @param boolean      $code if set to a value, ensures there is only one line item with that code
38
+	 * @return boolean success
39
+	 * @throws EE_Error
40
+	 * @throws InvalidArgumentException
41
+	 * @throws InvalidDataTypeException
42
+	 * @throws InvalidInterfaceException
43
+	 * @throws ReflectionException
44
+	 */
45
+	public static function add_unrelated_item(
46
+		EE_Line_Item $parent_line_item,
47
+		$name,
48
+		$unit_price,
49
+		$description = '',
50
+		$quantity = 1,
51
+		$taxable = false,
52
+		$code = null
53
+	) {
54
+		$items_subtotal = self::get_pre_tax_subtotal($parent_line_item);
55
+		$line_item = EE_Line_Item::new_instance(array(
56
+			'LIN_name'       => $name,
57
+			'LIN_desc'       => $description,
58
+			'LIN_unit_price' => $unit_price,
59
+			'LIN_quantity'   => $quantity,
60
+			'LIN_percent'    => null,
61
+			'LIN_is_taxable' => $taxable,
62
+			'LIN_order'      => $items_subtotal instanceof EE_Line_Item ? count($items_subtotal->children()) : 0,
63
+			'LIN_total'      => (float) $unit_price * (int) $quantity,
64
+			'LIN_type'       => EEM_Line_Item::type_line_item,
65
+			'LIN_code'       => $code,
66
+		));
67
+		$line_item = apply_filters(
68
+			'FHEE__EEH_Line_Item__add_unrelated_item__line_item',
69
+			$line_item,
70
+			$parent_line_item
71
+		);
72
+		return self::add_item($parent_line_item, $line_item);
73
+	}
74
+
75
+
76
+	/**
77
+	 * Adds a simple item ( unrelated to any other model object) to the total line item,
78
+	 * in the correct spot in the line item tree. Does not automatically
79
+	 * re-calculate the line item totals, nor update the related transaction, nor upgrade the transaction's
80
+	 * registrations' final prices (which should probably change because of this).
81
+	 * You should call recalculate_total_including_taxes() on the grand total line item, then
82
+	 * update the transaction's total, and EE_Registration_Processor::update_registration_final_prices()
83
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
84
+	 *
85
+	 * @param EE_Line_Item $parent_line_item
86
+	 * @param string       $name
87
+	 * @param float        $percentage_amount
88
+	 * @param string       $description
89
+	 * @param boolean      $taxable
90
+	 * @return boolean success
91
+	 * @throws EE_Error
92
+	 */
93
+	public static function add_percentage_based_item(
94
+		EE_Line_Item $parent_line_item,
95
+		$name,
96
+		$percentage_amount,
97
+		$description = '',
98
+		$taxable = false
99
+	) {
100
+		$line_item = EE_Line_Item::new_instance(array(
101
+			'LIN_name'       => $name,
102
+			'LIN_desc'       => $description,
103
+			'LIN_unit_price' => 0,
104
+			'LIN_percent'    => $percentage_amount,
105
+			'LIN_quantity'   => 1,
106
+			'LIN_is_taxable' => $taxable,
107
+			'LIN_total'      => (float) ($percentage_amount * ($parent_line_item->total() / 100)),
108
+			'LIN_type'       => EEM_Line_Item::type_line_item,
109
+			'LIN_parent'     => $parent_line_item->ID(),
110
+		));
111
+		$line_item = apply_filters(
112
+			'FHEE__EEH_Line_Item__add_percentage_based_item__line_item',
113
+			$line_item
114
+		);
115
+		return $parent_line_item->add_child_line_item($line_item, false);
116
+	}
117
+
118
+
119
+	/**
120
+	 * Returns the new line item created by adding a purchase of the ticket
121
+	 * ensures that ticket line item is saved, and that cart total has been recalculated.
122
+	 * If this ticket has already been purchased, just increments its count.
123
+	 * Automatically re-calculates the line item totals and updates the related transaction. But
124
+	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
125
+	 * should probably change because of this).
126
+	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
127
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
128
+	 *
129
+	 * @param EE_Line_Item $total_line_item grand total line item of type EEM_Line_Item::type_total
130
+	 * @param EE_Ticket    $ticket
131
+	 * @param int          $qty
132
+	 * @return EE_Line_Item
133
+	 * @throws EE_Error
134
+	 * @throws InvalidArgumentException
135
+	 * @throws InvalidDataTypeException
136
+	 * @throws InvalidInterfaceException
137
+	 * @throws ReflectionException
138
+	 */
139
+	public static function add_ticket_purchase(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
140
+	{
141
+		if (! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
142
+			throw new EE_Error(
143
+				sprintf(
144
+					esc_html__(
145
+						'A valid line item total is required in order to add tickets. A line item of type "%s" was passed.',
146
+						'event_espresso'
147
+					),
148
+					$ticket->ID(),
149
+					$total_line_item->ID()
150
+				)
151
+			);
152
+		}
153
+		// either increment the qty for an existing ticket
154
+		$line_item = self::increment_ticket_qty_if_already_in_cart($total_line_item, $ticket, $qty);
155
+		// or add a new one
156
+		if (! $line_item instanceof EE_Line_Item) {
157
+			$line_item = self::create_ticket_line_item($total_line_item, $ticket, $qty);
158
+		}
159
+		$total_line_item->recalculate_total_including_taxes();
160
+		return $line_item;
161
+	}
162
+
163
+
164
+	/**
165
+	 * Returns the new line item created by adding a purchase of the ticket
166
+	 *
167
+	 * @param EE_Line_Item $total_line_item
168
+	 * @param EE_Ticket    $ticket
169
+	 * @param int          $qty
170
+	 * @return EE_Line_Item
171
+	 * @throws EE_Error
172
+	 * @throws InvalidArgumentException
173
+	 * @throws InvalidDataTypeException
174
+	 * @throws InvalidInterfaceException
175
+	 * @throws ReflectionException
176
+	 */
177
+	public static function increment_ticket_qty_if_already_in_cart(
178
+		EE_Line_Item $total_line_item,
179
+		EE_Ticket $ticket,
180
+		$qty = 1
181
+	) {
182
+		$line_item = null;
183
+		if ($total_line_item instanceof EE_Line_Item && $total_line_item->is_total()) {
184
+			$ticket_line_items = EEH_Line_Item::get_ticket_line_items($total_line_item);
185
+			foreach ((array) $ticket_line_items as $ticket_line_item) {
186
+				if ($ticket_line_item instanceof EE_Line_Item
187
+					&& (int) $ticket_line_item->OBJ_ID() === (int) $ticket->ID()
188
+				) {
189
+					$line_item = $ticket_line_item;
190
+					break;
191
+				}
192
+			}
193
+		}
194
+		if ($line_item instanceof EE_Line_Item) {
195
+			EEH_Line_Item::increment_quantity($line_item, $qty);
196
+			return $line_item;
197
+		}
198
+		return null;
199
+	}
200
+
201
+
202
+	/**
203
+	 * Increments the line item and all its children's quantity by $qty (but percent line items are unaffected).
204
+	 * Does NOT save or recalculate other line items totals
205
+	 *
206
+	 * @param EE_Line_Item $line_item
207
+	 * @param int          $qty
208
+	 * @return void
209
+	 * @throws EE_Error
210
+	 * @throws InvalidArgumentException
211
+	 * @throws InvalidDataTypeException
212
+	 * @throws InvalidInterfaceException
213
+	 * @throws ReflectionException
214
+	 */
215
+	public static function increment_quantity(EE_Line_Item $line_item, $qty = 1)
216
+	{
217
+		if (! $line_item->is_percent()) {
218
+			$qty += $line_item->quantity();
219
+			$line_item->set_quantity($qty);
220
+			$line_item->set_total($line_item->unit_price() * $qty);
221
+			$line_item->save();
222
+		}
223
+		foreach ($line_item->children() as $child) {
224
+			if ($child->is_sub_line_item()) {
225
+				EEH_Line_Item::update_quantity($child, $qty);
226
+			}
227
+		}
228
+	}
229
+
230
+
231
+	/**
232
+	 * Decrements the line item and all its children's quantity by $qty (but percent line items are unaffected).
233
+	 * Does NOT save or recalculate other line items totals
234
+	 *
235
+	 * @param EE_Line_Item $line_item
236
+	 * @param int          $qty
237
+	 * @return void
238
+	 * @throws EE_Error
239
+	 * @throws InvalidArgumentException
240
+	 * @throws InvalidDataTypeException
241
+	 * @throws InvalidInterfaceException
242
+	 * @throws ReflectionException
243
+	 */
244
+	public static function decrement_quantity(EE_Line_Item $line_item, $qty = 1)
245
+	{
246
+		if (! $line_item->is_percent()) {
247
+			$qty = $line_item->quantity() - $qty;
248
+			$qty = max($qty, 0);
249
+			$line_item->set_quantity($qty);
250
+			$line_item->set_total($line_item->unit_price() * $qty);
251
+			$line_item->save();
252
+		}
253
+		foreach ($line_item->children() as $child) {
254
+			if ($child->is_sub_line_item()) {
255
+				EEH_Line_Item::update_quantity($child, $qty);
256
+			}
257
+		}
258
+	}
259
+
260
+
261
+	/**
262
+	 * Updates the line item and its children's quantities to the specified number.
263
+	 * Does NOT save them or recalculate totals.
264
+	 *
265
+	 * @param EE_Line_Item $line_item
266
+	 * @param int          $new_quantity
267
+	 * @throws EE_Error
268
+	 * @throws InvalidArgumentException
269
+	 * @throws InvalidDataTypeException
270
+	 * @throws InvalidInterfaceException
271
+	 * @throws ReflectionException
272
+	 */
273
+	public static function update_quantity(EE_Line_Item $line_item, $new_quantity)
274
+	{
275
+		if (! $line_item->is_percent()) {
276
+			$line_item->set_quantity($new_quantity);
277
+			$line_item->set_total($line_item->unit_price() * $new_quantity);
278
+			$line_item->save();
279
+		}
280
+		foreach ($line_item->children() as $child) {
281
+			if ($child->is_sub_line_item()) {
282
+				EEH_Line_Item::update_quantity($child, $new_quantity);
283
+			}
284
+		}
285
+	}
286
+
287
+
288
+	/**
289
+	 * Returns the new line item created by adding a purchase of the ticket
290
+	 *
291
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
292
+	 * @param EE_Ticket    $ticket
293
+	 * @param int          $qty
294
+	 * @return EE_Line_Item
295
+	 * @throws EE_Error
296
+	 * @throws InvalidArgumentException
297
+	 * @throws InvalidDataTypeException
298
+	 * @throws InvalidInterfaceException
299
+	 * @throws ReflectionException
300
+	 */
301
+	public static function create_ticket_line_item(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
302
+	{
303
+		$datetimes = $ticket->datetimes();
304
+		$first_datetime = reset($datetimes);
305
+		$first_datetime_name = esc_html__('Event', 'event_espresso');
306
+		if ($first_datetime instanceof EE_Datetime && $first_datetime->event() instanceof EE_Event) {
307
+			$first_datetime_name = $first_datetime->event()->name();
308
+		}
309
+		$event = sprintf(_x('(For %1$s)', '(For Event Name)', 'event_espresso'), $first_datetime_name);
310
+		// get event subtotal line
311
+		$events_sub_total = self::get_event_line_item_for_ticket($total_line_item, $ticket);
312
+		$taxes = $ticket->tax_price_modifiers();
313
+		$is_taxable = empty($taxes) ? $ticket->taxable() : false;
314
+		// add $ticket to cart
315
+		$line_item = EE_Line_Item::new_instance(array(
316
+			'LIN_name'       => $ticket->name(),
317
+			'LIN_desc'       => $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
318
+			'LIN_unit_price' => $ticket->price(),
319
+			'LIN_quantity'   => $qty,
320
+			'LIN_is_taxable' => $is_taxable,
321
+			'LIN_order'      => count($events_sub_total->children()),
322
+			'LIN_total'      => $ticket->price() * $qty,
323
+			'LIN_type'       => EEM_Line_Item::type_line_item,
324
+			'OBJ_ID'         => $ticket->ID(),
325
+			'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_TICKET,
326
+		));
327
+		$line_item = apply_filters(
328
+			'FHEE__EEH_Line_Item__create_ticket_line_item__line_item',
329
+			$line_item
330
+		);
331
+		$events_sub_total->add_child_line_item($line_item);
332
+		// now add the sub-line items
333
+		$running_total_for_ticket = 0;
334
+		foreach ($ticket->prices(array('order_by' => array('PRC_order' => 'ASC'))) as $price) {
335
+			$sign = $price->is_discount() ? -1 : 1;
336
+			$price_total = $price->is_percent()
337
+				? $running_total_for_ticket * $price->amount() / 100
338
+				: $price->amount() * $qty;
339
+			$sub_line_item = EE_Line_Item::new_instance(array(
340
+				'LIN_name'       => $price->name(),
341
+				'LIN_desc'       => $price->desc(),
342
+				'LIN_quantity'   => $price->is_percent() ? null : $qty,
343
+				'LIN_is_taxable' => false,
344
+				'LIN_order'      => $price->order(),
345
+				'LIN_total'      => $sign * $price_total,
346
+				'LIN_type'       => EEM_Line_Item::type_sub_line_item,
347
+				'OBJ_ID'         => $price->ID(),
348
+				'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
349
+			));
350
+			$sub_line_item = apply_filters(
351
+				'FHEE__EEH_Line_Item__create_ticket_line_item__sub_line_item',
352
+				$sub_line_item
353
+			);
354
+			if ($price->is_percent()) {
355
+				$sub_line_item->set_percent($sign * $price->amount());
356
+			} else {
357
+				$sub_line_item->set_unit_price($sign * $price->amount());
358
+			}
359
+			$running_total_for_ticket += $price_total;
360
+			$line_item->add_child_line_item($sub_line_item);
361
+		}
362
+		return $line_item;
363
+	}
364
+
365
+
366
+	/**
367
+	 * Adds the specified item under the pre-tax-sub-total line item. Automatically
368
+	 * re-calculates the line item totals and updates the related transaction. But
369
+	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
370
+	 * should probably change because of this).
371
+	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
372
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
373
+	 *
374
+	 * @param EE_Line_Item $total_line_item
375
+	 * @param EE_Line_Item $item to be added
376
+	 * @return boolean
377
+	 * @throws EE_Error
378
+	 * @throws InvalidArgumentException
379
+	 * @throws InvalidDataTypeException
380
+	 * @throws InvalidInterfaceException
381
+	 * @throws ReflectionException
382
+	 */
383
+	public static function add_item(EE_Line_Item $total_line_item, EE_Line_Item $item)
384
+	{
385
+		$pre_tax_subtotal = self::get_pre_tax_subtotal($total_line_item);
386
+		if ($pre_tax_subtotal instanceof EE_Line_Item) {
387
+			$success = $pre_tax_subtotal->add_child_line_item($item);
388
+		} else {
389
+			return false;
390
+		}
391
+		$total_line_item->recalculate_total_including_taxes();
392
+		return $success;
393
+	}
394
+
395
+
396
+	/**
397
+	 * cancels an existing ticket line item,
398
+	 * by decrementing it's quantity by 1 and adding a new "type_cancellation" sub-line-item.
399
+	 * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
400
+	 *
401
+	 * @param EE_Line_Item $ticket_line_item
402
+	 * @param int          $qty
403
+	 * @return bool success
404
+	 * @throws EE_Error
405
+	 * @throws InvalidArgumentException
406
+	 * @throws InvalidDataTypeException
407
+	 * @throws InvalidInterfaceException
408
+	 * @throws ReflectionException
409
+	 */
410
+	public static function cancel_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
411
+	{
412
+		// validate incoming line_item
413
+		if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
414
+			throw new EE_Error(
415
+				sprintf(
416
+					esc_html__(
417
+						'The supplied line item must have an Object Type of "Ticket", not %1$s.',
418
+						'event_espresso'
419
+					),
420
+					$ticket_line_item->type()
421
+				)
422
+			);
423
+		}
424
+		if ($ticket_line_item->quantity() < $qty) {
425
+			throw new EE_Error(
426
+				sprintf(
427
+					esc_html__(
428
+						'Can not cancel %1$d ticket(s) because the supplied line item has a quantity of %2$d.',
429
+						'event_espresso'
430
+					),
431
+					$qty,
432
+					$ticket_line_item->quantity()
433
+				)
434
+			);
435
+		}
436
+		// decrement ticket quantity; don't rely on auto-fixing when recalculating totals to do this
437
+		$ticket_line_item->set_quantity($ticket_line_item->quantity() - $qty);
438
+		foreach ($ticket_line_item->children() as $child_line_item) {
439
+			if ($child_line_item->is_sub_line_item()
440
+				&& ! $child_line_item->is_percent()
441
+				&& ! $child_line_item->is_cancellation()
442
+			) {
443
+				$child_line_item->set_quantity($child_line_item->quantity() - $qty);
444
+			}
445
+		}
446
+		// get cancellation sub line item
447
+		$cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
448
+			$ticket_line_item,
449
+			EEM_Line_Item::type_cancellation
450
+		);
451
+		$cancellation_line_item = reset($cancellation_line_item);
452
+		// verify that this ticket was indeed previously cancelled
453
+		if ($cancellation_line_item instanceof EE_Line_Item) {
454
+			// increment cancelled quantity
455
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() + $qty);
456
+		} else {
457
+			// create cancellation sub line item
458
+			$cancellation_line_item = EE_Line_Item::new_instance(array(
459
+				'LIN_name'       => esc_html__('Cancellation', 'event_espresso'),
460
+				'LIN_desc'       => sprintf(
461
+					esc_html_x(
462
+						'Cancelled %1$s : %2$s',
463
+						'Cancelled Ticket Name : 2015-01-01 11:11',
464
+						'event_espresso'
465
+					),
466
+					$ticket_line_item->name(),
467
+					current_time(get_option('date_format') . ' ' . get_option('time_format'))
468
+				),
469
+				'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
470
+				'LIN_quantity'   => $qty,
471
+				'LIN_is_taxable' => $ticket_line_item->is_taxable(),
472
+				'LIN_order'      => count($ticket_line_item->children()),
473
+				'LIN_total'      => 0, // $ticket_line_item->unit_price()
474
+				'LIN_type'       => EEM_Line_Item::type_cancellation,
475
+			));
476
+			$ticket_line_item->add_child_line_item($cancellation_line_item);
477
+		}
478
+		if ($ticket_line_item->save_this_and_descendants() > 0) {
479
+			// decrement parent line item quantity
480
+			$event_line_item = $ticket_line_item->parent();
481
+			if ($event_line_item instanceof EE_Line_Item
482
+				&& $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
483
+			) {
484
+				$event_line_item->set_quantity($event_line_item->quantity() - $qty);
485
+				$event_line_item->save();
486
+			}
487
+			EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
488
+			return true;
489
+		}
490
+		return false;
491
+	}
492
+
493
+
494
+	/**
495
+	 * reinstates (un-cancels?) a previously canceled ticket line item,
496
+	 * by incrementing it's quantity by 1, and decrementing it's "type_cancellation" sub-line-item.
497
+	 * ALL totals and subtotals will NEED TO BE UPDATED after performing this action
498
+	 *
499
+	 * @param EE_Line_Item $ticket_line_item
500
+	 * @param int          $qty
501
+	 * @return bool success
502
+	 * @throws EE_Error
503
+	 * @throws InvalidArgumentException
504
+	 * @throws InvalidDataTypeException
505
+	 * @throws InvalidInterfaceException
506
+	 * @throws ReflectionException
507
+	 */
508
+	public static function reinstate_canceled_ticket_line_item(EE_Line_Item $ticket_line_item, $qty = 1)
509
+	{
510
+		// validate incoming line_item
511
+		if ($ticket_line_item->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_TICKET) {
512
+			throw new EE_Error(
513
+				sprintf(
514
+					esc_html__(
515
+						'The supplied line item must have an Object Type of "Ticket", not %1$s.',
516
+						'event_espresso'
517
+					),
518
+					$ticket_line_item->type()
519
+				)
520
+			);
521
+		}
522
+		// get cancellation sub line item
523
+		$cancellation_line_item = EEH_Line_Item::get_descendants_of_type(
524
+			$ticket_line_item,
525
+			EEM_Line_Item::type_cancellation
526
+		);
527
+		$cancellation_line_item = reset($cancellation_line_item);
528
+		// verify that this ticket was indeed previously cancelled
529
+		if (! $cancellation_line_item instanceof EE_Line_Item) {
530
+			return false;
531
+		}
532
+		if ($cancellation_line_item->quantity() > $qty) {
533
+			// decrement cancelled quantity
534
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
535
+		} elseif ($cancellation_line_item->quantity() === $qty) {
536
+			// decrement cancelled quantity in case anyone still has the object kicking around
537
+			$cancellation_line_item->set_quantity($cancellation_line_item->quantity() - $qty);
538
+			// delete because quantity will end up as 0
539
+			$cancellation_line_item->delete();
540
+			// and attempt to destroy the object,
541
+			// even though PHP won't actually destroy it until it needs the memory
542
+			unset($cancellation_line_item);
543
+		} else {
544
+			// what ?!?! negative quantity ?!?!
545
+			throw new EE_Error(
546
+				sprintf(
547
+					esc_html__(
548
+						'Can not reinstate %1$d cancelled ticket(s) because the cancelled ticket quantity is only %2$d.',
549
+						'event_espresso'
550
+					),
551
+					$qty,
552
+					$cancellation_line_item->quantity()
553
+				)
554
+			);
555
+		}
556
+		// increment ticket quantity
557
+		$ticket_line_item->set_quantity($ticket_line_item->quantity() + $qty);
558
+		if ($ticket_line_item->save_this_and_descendants() > 0) {
559
+			// increment parent line item quantity
560
+			$event_line_item = $ticket_line_item->parent();
561
+			if ($event_line_item instanceof EE_Line_Item
562
+				&& $event_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
563
+			) {
564
+				$event_line_item->set_quantity($event_line_item->quantity() + $qty);
565
+			}
566
+			EEH_Line_Item::get_grand_total_and_recalculate_everything($ticket_line_item);
567
+			return true;
568
+		}
569
+		return false;
570
+	}
571
+
572
+
573
+	/**
574
+	 * calls EEH_Line_Item::find_transaction_grand_total_for_line_item()
575
+	 * then EE_Line_Item::recalculate_total_including_taxes() on the result
576
+	 *
577
+	 * @param EE_Line_Item $line_item
578
+	 * @return float
579
+	 * @throws EE_Error
580
+	 * @throws InvalidArgumentException
581
+	 * @throws InvalidDataTypeException
582
+	 * @throws InvalidInterfaceException
583
+	 * @throws ReflectionException
584
+	 */
585
+	public static function get_grand_total_and_recalculate_everything(EE_Line_Item $line_item)
586
+	{
587
+		$grand_total_line_item = EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item);
588
+		return $grand_total_line_item->recalculate_total_including_taxes();
589
+	}
590
+
591
+
592
+	/**
593
+	 * Gets the line item which contains the subtotal of all the items
594
+	 *
595
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
596
+	 * @return EE_Line_Item
597
+	 * @throws EE_Error
598
+	 * @throws InvalidArgumentException
599
+	 * @throws InvalidDataTypeException
600
+	 * @throws InvalidInterfaceException
601
+	 * @throws ReflectionException
602
+	 */
603
+	public static function get_pre_tax_subtotal(EE_Line_Item $total_line_item)
604
+	{
605
+		$pre_tax_subtotal = $total_line_item->get_child_line_item('pre-tax-subtotal');
606
+		return $pre_tax_subtotal instanceof EE_Line_Item
607
+			? $pre_tax_subtotal
608
+			: self::create_pre_tax_subtotal($total_line_item);
609
+	}
610
+
611
+
612
+	/**
613
+	 * Gets the line item for the taxes subtotal
614
+	 *
615
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
616
+	 * @return EE_Line_Item
617
+	 * @throws EE_Error
618
+	 * @throws InvalidArgumentException
619
+	 * @throws InvalidDataTypeException
620
+	 * @throws InvalidInterfaceException
621
+	 * @throws ReflectionException
622
+	 */
623
+	public static function get_taxes_subtotal(EE_Line_Item $total_line_item)
624
+	{
625
+		$taxes = $total_line_item->get_child_line_item('taxes');
626
+		return $taxes ? $taxes : self::create_taxes_subtotal($total_line_item);
627
+	}
628
+
629
+
630
+	/**
631
+	 * sets the TXN ID on an EE_Line_Item if passed a valid EE_Transaction object
632
+	 *
633
+	 * @param EE_Line_Item   $line_item
634
+	 * @param EE_Transaction $transaction
635
+	 * @return void
636
+	 * @throws EE_Error
637
+	 * @throws InvalidArgumentException
638
+	 * @throws InvalidDataTypeException
639
+	 * @throws InvalidInterfaceException
640
+	 * @throws ReflectionException
641
+	 */
642
+	public static function set_TXN_ID(EE_Line_Item $line_item, $transaction = null)
643
+	{
644
+		if ($transaction) {
645
+			/** @type EEM_Transaction $EEM_Transaction */
646
+			$EEM_Transaction = EE_Registry::instance()->load_model('Transaction');
647
+			$TXN_ID = $EEM_Transaction->ensure_is_ID($transaction);
648
+			$line_item->set_TXN_ID($TXN_ID);
649
+		}
650
+	}
651
+
652
+
653
+	/**
654
+	 * Creates a new default total line item for the transaction,
655
+	 * and its tickets subtotal and taxes subtotal line items (and adds the
656
+	 * existing taxes as children of the taxes subtotal line item)
657
+	 *
658
+	 * @param EE_Transaction $transaction
659
+	 * @return EE_Line_Item of type total
660
+	 * @throws EE_Error
661
+	 * @throws InvalidArgumentException
662
+	 * @throws InvalidDataTypeException
663
+	 * @throws InvalidInterfaceException
664
+	 * @throws ReflectionException
665
+	 */
666
+	public static function create_total_line_item($transaction = null)
667
+	{
668
+		$total_line_item = EE_Line_Item::new_instance(array(
669
+			'LIN_code' => 'total',
670
+			'LIN_name' => esc_html__('Grand Total', 'event_espresso'),
671
+			'LIN_type' => EEM_Line_Item::type_total,
672
+			'OBJ_type' => EEM_Line_Item::OBJ_TYPE_TRANSACTION,
673
+		));
674
+		$total_line_item = apply_filters(
675
+			'FHEE__EEH_Line_Item__create_total_line_item__total_line_item',
676
+			$total_line_item
677
+		);
678
+		self::set_TXN_ID($total_line_item, $transaction);
679
+		self::create_pre_tax_subtotal($total_line_item, $transaction);
680
+		self::create_taxes_subtotal($total_line_item, $transaction);
681
+		return $total_line_item;
682
+	}
683
+
684
+
685
+	/**
686
+	 * Creates a default items subtotal line item
687
+	 *
688
+	 * @param EE_Line_Item   $total_line_item
689
+	 * @param EE_Transaction $transaction
690
+	 * @return EE_Line_Item
691
+	 * @throws EE_Error
692
+	 * @throws InvalidArgumentException
693
+	 * @throws InvalidDataTypeException
694
+	 * @throws InvalidInterfaceException
695
+	 * @throws ReflectionException
696
+	 */
697
+	protected static function create_pre_tax_subtotal(EE_Line_Item $total_line_item, $transaction = null)
698
+	{
699
+		$pre_tax_line_item = EE_Line_Item::new_instance(array(
700
+			'LIN_code' => 'pre-tax-subtotal',
701
+			'LIN_name' => esc_html__('Pre-Tax Subtotal', 'event_espresso'),
702
+			'LIN_type' => EEM_Line_Item::type_sub_total,
703
+		));
704
+		$pre_tax_line_item = apply_filters(
705
+			'FHEE__EEH_Line_Item__create_pre_tax_subtotal__pre_tax_line_item',
706
+			$pre_tax_line_item
707
+		);
708
+		self::set_TXN_ID($pre_tax_line_item, $transaction);
709
+		$total_line_item->add_child_line_item($pre_tax_line_item);
710
+		self::create_event_subtotal($pre_tax_line_item, $transaction);
711
+		return $pre_tax_line_item;
712
+	}
713
+
714
+
715
+	/**
716
+	 * Creates a line item for the taxes subtotal and finds all the tax prices
717
+	 * and applies taxes to it
718
+	 *
719
+	 * @param EE_Line_Item   $total_line_item of type EEM_Line_Item::type_total
720
+	 * @param EE_Transaction $transaction
721
+	 * @return EE_Line_Item
722
+	 * @throws EE_Error
723
+	 * @throws InvalidArgumentException
724
+	 * @throws InvalidDataTypeException
725
+	 * @throws InvalidInterfaceException
726
+	 * @throws ReflectionException
727
+	 */
728
+	protected static function create_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
729
+	{
730
+		$tax_line_item = EE_Line_Item::new_instance(array(
731
+			'LIN_code'  => 'taxes',
732
+			'LIN_name'  => esc_html__('Taxes', 'event_espresso'),
733
+			'LIN_type'  => EEM_Line_Item::type_tax_sub_total,
734
+			'LIN_order' => 1000,// this should always come last
735
+		));
736
+		$tax_line_item = apply_filters(
737
+			'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
738
+			$tax_line_item
739
+		);
740
+		self::set_TXN_ID($tax_line_item, $transaction);
741
+		$total_line_item->add_child_line_item($tax_line_item);
742
+		// and lastly, add the actual taxes
743
+		self::apply_taxes($total_line_item);
744
+		return $tax_line_item;
745
+	}
746
+
747
+
748
+	/**
749
+	 * Creates a default items subtotal line item
750
+	 *
751
+	 * @param EE_Line_Item   $pre_tax_line_item
752
+	 * @param EE_Transaction $transaction
753
+	 * @param EE_Event       $event
754
+	 * @return EE_Line_Item
755
+	 * @throws EE_Error
756
+	 * @throws InvalidArgumentException
757
+	 * @throws InvalidDataTypeException
758
+	 * @throws InvalidInterfaceException
759
+	 * @throws ReflectionException
760
+	 */
761
+	public static function create_event_subtotal(EE_Line_Item $pre_tax_line_item, $transaction = null, $event = null)
762
+	{
763
+		$event_line_item = EE_Line_Item::new_instance(array(
764
+			'LIN_code' => self::get_event_code($event),
765
+			'LIN_name' => self::get_event_name($event),
766
+			'LIN_desc' => self::get_event_desc($event),
767
+			'LIN_type' => EEM_Line_Item::type_sub_total,
768
+			'OBJ_type' => EEM_Line_Item::OBJ_TYPE_EVENT,
769
+			'OBJ_ID'   => $event instanceof EE_Event ? $event->ID() : 0,
770
+		));
771
+		$event_line_item = apply_filters(
772
+			'FHEE__EEH_Line_Item__create_event_subtotal__event_line_item',
773
+			$event_line_item
774
+		);
775
+		self::set_TXN_ID($event_line_item, $transaction);
776
+		$pre_tax_line_item->add_child_line_item($event_line_item);
777
+		return $event_line_item;
778
+	}
779
+
780
+
781
+	/**
782
+	 * Gets what the event ticket's code SHOULD be
783
+	 *
784
+	 * @param EE_Event $event
785
+	 * @return string
786
+	 * @throws EE_Error
787
+	 */
788
+	public static function get_event_code($event)
789
+	{
790
+		return 'event-' . ($event instanceof EE_Event ? $event->ID() : '0');
791
+	}
792
+
793
+
794
+	/**
795
+	 * Gets the event name
796
+	 *
797
+	 * @param EE_Event $event
798
+	 * @return string
799
+	 * @throws EE_Error
800
+	 */
801
+	public static function get_event_name($event)
802
+	{
803
+		return $event instanceof EE_Event
804
+			? mb_substr($event->name(), 0, 245)
805
+			: esc_html__('Event', 'event_espresso');
806
+	}
807
+
808
+
809
+	/**
810
+	 * Gets the event excerpt
811
+	 *
812
+	 * @param EE_Event $event
813
+	 * @return string
814
+	 * @throws EE_Error
815
+	 */
816
+	public static function get_event_desc($event)
817
+	{
818
+		return $event instanceof EE_Event ? $event->short_description() : '';
819
+	}
820
+
821
+
822
+	/**
823
+	 * Given the grand total line item and a ticket, finds the event sub-total
824
+	 * line item the ticket's purchase should be added onto
825
+	 *
826
+	 * @access public
827
+	 * @param EE_Line_Item $grand_total the grand total line item
828
+	 * @param EE_Ticket    $ticket
829
+	 * @return EE_Line_Item
830
+	 * @throws EE_Error
831
+	 * @throws InvalidArgumentException
832
+	 * @throws InvalidDataTypeException
833
+	 * @throws InvalidInterfaceException
834
+	 * @throws ReflectionException
835
+	 */
836
+	public static function get_event_line_item_for_ticket(EE_Line_Item $grand_total, EE_Ticket $ticket)
837
+	{
838
+		$first_datetime = $ticket->first_datetime();
839
+		if (! $first_datetime instanceof EE_Datetime) {
840
+			throw new EE_Error(
841
+				sprintf(
842
+					__('The supplied ticket (ID %d) has no datetimes', 'event_espresso'),
843
+					$ticket->ID()
844
+				)
845
+			);
846
+		}
847
+		$event = $first_datetime->event();
848
+		if (! $event instanceof EE_Event) {
849
+			throw new EE_Error(
850
+				sprintf(
851
+					esc_html__(
852
+						'The supplied ticket (ID %d) has no event data associated with it.',
853
+						'event_espresso'
854
+					),
855
+					$ticket->ID()
856
+				)
857
+			);
858
+		}
859
+		$events_sub_total = EEH_Line_Item::get_event_line_item($grand_total, $event);
860
+		if (! $events_sub_total instanceof EE_Line_Item) {
861
+			throw new EE_Error(
862
+				sprintf(
863
+					esc_html__(
864
+						'There is no events sub-total for ticket %s on total line item %d',
865
+						'event_espresso'
866
+					),
867
+					$ticket->ID(),
868
+					$grand_total->ID()
869
+				)
870
+			);
871
+		}
872
+		return $events_sub_total;
873
+	}
874
+
875
+
876
+	/**
877
+	 * Gets the event line item
878
+	 *
879
+	 * @param EE_Line_Item $grand_total
880
+	 * @param EE_Event     $event
881
+	 * @return EE_Line_Item for the event subtotal which is a child of $grand_total
882
+	 * @throws EE_Error
883
+	 * @throws InvalidArgumentException
884
+	 * @throws InvalidDataTypeException
885
+	 * @throws InvalidInterfaceException
886
+	 * @throws ReflectionException
887
+	 */
888
+	public static function get_event_line_item(EE_Line_Item $grand_total, $event)
889
+	{
890
+		/** @type EE_Event $event */
891
+		$event = EEM_Event::instance()->ensure_is_obj($event, true);
892
+		$event_line_item = null;
893
+		$found = false;
894
+		foreach (EEH_Line_Item::get_event_subtotals($grand_total) as $event_line_item) {
895
+			// default event subtotal, we should only ever find this the first time this method is called
896
+			if (! $event_line_item->OBJ_ID()) {
897
+				// let's use this! but first... set the event details
898
+				EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
899
+				$found = true;
900
+				break;
901
+			}
902
+			if ($event_line_item->OBJ_ID() === $event->ID()) {
903
+				// found existing line item for this event in the cart, so break out of loop and use this one
904
+				$found = true;
905
+				break;
906
+			}
907
+		}
908
+		if (! $found) {
909
+			// there is no event sub-total yet, so add it
910
+			$pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal($grand_total);
911
+			// create a new "event" subtotal below that
912
+			$event_line_item = EEH_Line_Item::create_event_subtotal($pre_tax_subtotal, null, $event);
913
+			// and set the event details
914
+			EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
915
+		}
916
+		return $event_line_item;
917
+	}
918
+
919
+
920
+	/**
921
+	 * Creates a default items subtotal line item
922
+	 *
923
+	 * @param EE_Line_Item   $event_line_item
924
+	 * @param EE_Event       $event
925
+	 * @param EE_Transaction $transaction
926
+	 * @return void
927
+	 * @throws EE_Error
928
+	 * @throws InvalidArgumentException
929
+	 * @throws InvalidDataTypeException
930
+	 * @throws InvalidInterfaceException
931
+	 * @throws ReflectionException
932
+	 */
933
+	public static function set_event_subtotal_details(
934
+		EE_Line_Item $event_line_item,
935
+		EE_Event $event,
936
+		$transaction = null
937
+	) {
938
+		if ($event instanceof EE_Event) {
939
+			$event_line_item->set_code(self::get_event_code($event));
940
+			$event_line_item->set_name(self::get_event_name($event));
941
+			$event_line_item->set_desc(self::get_event_desc($event));
942
+			$event_line_item->set_OBJ_ID($event->ID());
943
+		}
944
+		self::set_TXN_ID($event_line_item, $transaction);
945
+	}
946
+
947
+
948
+	/**
949
+	 * Finds what taxes should apply, adds them as tax line items under the taxes sub-total,
950
+	 * and recalculates the taxes sub-total and the grand total. Resets the taxes, so
951
+	 * any old taxes are removed
952
+	 *
953
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
954
+	 * @param bool         $update_txn_status
955
+	 * @return bool
956
+	 * @throws EE_Error
957
+	 * @throws InvalidArgumentException
958
+	 * @throws InvalidDataTypeException
959
+	 * @throws InvalidInterfaceException
960
+	 * @throws ReflectionException
961
+	 * @throws RuntimeException
962
+	 */
963
+	public static function apply_taxes(EE_Line_Item $total_line_item, $update_txn_status = false)
964
+	{
965
+		/** @type EEM_Price $EEM_Price */
966
+		$EEM_Price = EE_Registry::instance()->load_model('Price');
967
+		// get array of taxes via Price Model
968
+		$ordered_taxes = $EEM_Price->get_all_prices_that_are_taxes();
969
+		ksort($ordered_taxes);
970
+		$taxes_line_item = self::get_taxes_subtotal($total_line_item);
971
+		// just to be safe, remove its old tax line items
972
+		$deleted = $taxes_line_item->delete_children_line_items();
973
+		$updates = false;
974
+		// loop thru taxes
975
+		foreach ($ordered_taxes as $order => $taxes) {
976
+			foreach ($taxes as $tax) {
977
+				if ($tax instanceof EE_Price) {
978
+					$tax_line_item = EE_Line_Item::new_instance(
979
+						array(
980
+							'LIN_name'       => $tax->name(),
981
+							'LIN_desc'       => $tax->desc(),
982
+							'LIN_percent'    => $tax->amount(),
983
+							'LIN_is_taxable' => false,
984
+							'LIN_order'      => $order,
985
+							'LIN_total'      => 0,
986
+							'LIN_type'       => EEM_Line_Item::type_tax,
987
+							'OBJ_type'       => EEM_Line_Item::OBJ_TYPE_PRICE,
988
+							'OBJ_ID'         => $tax->ID(),
989
+						)
990
+					);
991
+					$tax_line_item = apply_filters(
992
+						'FHEE__EEH_Line_Item__apply_taxes__tax_line_item',
993
+						$tax_line_item
994
+					);
995
+					$updates = $taxes_line_item->add_child_line_item($tax_line_item) ?
996
+						true :
997
+						$updates;
998
+				}
999
+			}
1000
+		}
1001
+		// only recalculate totals if something changed
1002
+		if ($deleted || $updates) {
1003
+			$total_line_item->recalculate_total_including_taxes($update_txn_status);
1004
+			return true;
1005
+		}
1006
+		return false;
1007
+	}
1008
+
1009
+
1010
+	/**
1011
+	 * Ensures that taxes have been applied to the order, if not applies them.
1012
+	 * Returns the total amount of tax
1013
+	 *
1014
+	 * @param EE_Line_Item $total_line_item of type EEM_Line_Item::type_total
1015
+	 * @return float
1016
+	 * @throws EE_Error
1017
+	 * @throws InvalidArgumentException
1018
+	 * @throws InvalidDataTypeException
1019
+	 * @throws InvalidInterfaceException
1020
+	 * @throws ReflectionException
1021
+	 */
1022
+	public static function ensure_taxes_applied($total_line_item)
1023
+	{
1024
+		$taxes_subtotal = self::get_taxes_subtotal($total_line_item);
1025
+		if (! $taxes_subtotal->children()) {
1026
+			self::apply_taxes($total_line_item);
1027
+		}
1028
+		return $taxes_subtotal->total();
1029
+	}
1030
+
1031
+
1032
+	/**
1033
+	 * Deletes ALL children of the passed line item
1034
+	 *
1035
+	 * @param EE_Line_Item $parent_line_item
1036
+	 * @return bool
1037
+	 * @throws EE_Error
1038
+	 * @throws InvalidArgumentException
1039
+	 * @throws InvalidDataTypeException
1040
+	 * @throws InvalidInterfaceException
1041
+	 * @throws ReflectionException
1042
+	 */
1043
+	public static function delete_all_child_items(EE_Line_Item $parent_line_item)
1044
+	{
1045
+		$deleted = 0;
1046
+		foreach ($parent_line_item->children() as $child_line_item) {
1047
+			if ($child_line_item instanceof EE_Line_Item) {
1048
+				$deleted += EEH_Line_Item::delete_all_child_items($child_line_item);
1049
+				if ($child_line_item->ID()) {
1050
+					$child_line_item->delete();
1051
+					unset($child_line_item);
1052
+				} else {
1053
+					$parent_line_item->delete_child_line_item($child_line_item->code());
1054
+				}
1055
+				$deleted++;
1056
+			}
1057
+		}
1058
+		return $deleted;
1059
+	}
1060
+
1061
+
1062
+	/**
1063
+	 * Deletes the line items as indicated by the line item code(s) provided,
1064
+	 * regardless of where they're found in the line item tree. Automatically
1065
+	 * re-calculates the line item totals and updates the related transaction. But
1066
+	 * DOES NOT automatically upgrade the transaction's registrations' final prices (which
1067
+	 * should probably change because of this).
1068
+	 * You should call EE_Registration_Processor::calculate_reg_final_prices_per_line_item()
1069
+	 * after using this, to keep the registration final prices in-sync with the transaction's total.
1070
+	 *
1071
+	 * @param EE_Line_Item      $total_line_item of type EEM_Line_Item::type_total
1072
+	 * @param array|bool|string $line_item_codes
1073
+	 * @return int number of items successfully removed
1074
+	 * @throws EE_Error
1075
+	 */
1076
+	public static function delete_items(EE_Line_Item $total_line_item, $line_item_codes = false)
1077
+	{
1078
+
1079
+		if ($total_line_item->type() !== EEM_Line_Item::type_total) {
1080
+			EE_Error::doing_it_wrong(
1081
+				'EEH_Line_Item::delete_items',
1082
+				esc_html__(
1083
+					'This static method should only be called with a TOTAL line item, otherwise we won\'t recalculate the totals correctly',
1084
+					'event_espresso'
1085
+				),
1086
+				'4.6.18'
1087
+			);
1088
+		}
1089
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1090
+
1091
+		// check if only a single line_item_id was passed
1092
+		if (! empty($line_item_codes) && ! is_array($line_item_codes)) {
1093
+			// place single line_item_id in an array to appear as multiple line_item_ids
1094
+			$line_item_codes = array($line_item_codes);
1095
+		}
1096
+		$removals = 0;
1097
+		// cycle thru line_item_ids
1098
+		foreach ($line_item_codes as $line_item_id) {
1099
+			$removals += $total_line_item->delete_child_line_item($line_item_id);
1100
+		}
1101
+
1102
+		if ($removals > 0) {
1103
+			$total_line_item->recalculate_taxes_and_tax_total();
1104
+			return $removals;
1105
+		} else {
1106
+			return false;
1107
+		}
1108
+	}
1109
+
1110
+
1111
+	/**
1112
+	 * Overwrites the previous tax by clearing out the old taxes, and creates a new
1113
+	 * tax and updates the total line item accordingly
1114
+	 *
1115
+	 * @param EE_Line_Item $total_line_item
1116
+	 * @param float        $amount
1117
+	 * @param string       $name
1118
+	 * @param string       $description
1119
+	 * @param string       $code
1120
+	 * @param boolean      $add_to_existing_line_item
1121
+	 *                          if true, and a duplicate line item with the same code is found,
1122
+	 *                          $amount will be added onto it; otherwise will simply set the taxes to match $amount
1123
+	 * @return EE_Line_Item the new tax line item created
1124
+	 * @throws EE_Error
1125
+	 * @throws InvalidArgumentException
1126
+	 * @throws InvalidDataTypeException
1127
+	 * @throws InvalidInterfaceException
1128
+	 * @throws ReflectionException
1129
+	 */
1130
+	public static function set_total_tax_to(
1131
+		EE_Line_Item $total_line_item,
1132
+		$amount,
1133
+		$name = null,
1134
+		$description = null,
1135
+		$code = null,
1136
+		$add_to_existing_line_item = false
1137
+	) {
1138
+		$tax_subtotal = self::get_taxes_subtotal($total_line_item);
1139
+		$taxable_total = $total_line_item->taxable_total();
1140
+
1141
+		if ($add_to_existing_line_item) {
1142
+			$new_tax = $tax_subtotal->get_child_line_item($code);
1143
+			EEM_Line_Item::instance()->delete(
1144
+				array(array('LIN_code' => array('!=', $code), 'LIN_parent' => $tax_subtotal->ID()))
1145
+			);
1146
+		} else {
1147
+			$new_tax = null;
1148
+			$tax_subtotal->delete_children_line_items();
1149
+		}
1150
+		if ($new_tax) {
1151
+			$new_tax->set_total($new_tax->total() + $amount);
1152
+			$new_tax->set_percent($taxable_total ? $new_tax->total() / $taxable_total * 100 : 0);
1153
+		} else {
1154
+			// no existing tax item. Create it
1155
+			$new_tax = EE_Line_Item::new_instance(array(
1156
+				'TXN_ID'      => $total_line_item->TXN_ID(),
1157
+				'LIN_name'    => $name ? $name : esc_html__('Tax', 'event_espresso'),
1158
+				'LIN_desc'    => $description ? $description : '',
1159
+				'LIN_percent' => $taxable_total ? ($amount / $taxable_total * 100) : 0,
1160
+				'LIN_total'   => $amount,
1161
+				'LIN_parent'  => $tax_subtotal->ID(),
1162
+				'LIN_type'    => EEM_Line_Item::type_tax,
1163
+				'LIN_code'    => $code,
1164
+			));
1165
+		}
1166
+
1167
+		$new_tax = apply_filters(
1168
+			'FHEE__EEH_Line_Item__set_total_tax_to__new_tax_subtotal',
1169
+			$new_tax,
1170
+			$total_line_item
1171
+		);
1172
+		$new_tax->save();
1173
+		$tax_subtotal->set_total($new_tax->total());
1174
+		$tax_subtotal->save();
1175
+		$total_line_item->recalculate_total_including_taxes();
1176
+		return $new_tax;
1177
+	}
1178
+
1179
+
1180
+	/**
1181
+	 * Makes all the line items which are children of $line_item taxable (or not).
1182
+	 * Does NOT save the line items
1183
+	 *
1184
+	 * @param EE_Line_Item $line_item
1185
+	 * @param boolean      $taxable
1186
+	 * @param string       $code_substring_for_whitelist if this string is part of the line item's code
1187
+	 *                                                   it will be whitelisted (ie, except from becoming taxable)
1188
+	 * @throws EE_Error
1189
+	 */
1190
+	public static function set_line_items_taxable(
1191
+		EE_Line_Item $line_item,
1192
+		$taxable = true,
1193
+		$code_substring_for_whitelist = null
1194
+	) {
1195
+		$whitelisted = false;
1196
+		if ($code_substring_for_whitelist !== null) {
1197
+			$whitelisted = strpos($line_item->code(), $code_substring_for_whitelist) !== false;
1198
+		}
1199
+		if (! $whitelisted && $line_item->is_line_item()) {
1200
+			$line_item->set_is_taxable($taxable);
1201
+		}
1202
+		foreach ($line_item->children() as $child_line_item) {
1203
+			EEH_Line_Item::set_line_items_taxable(
1204
+				$child_line_item,
1205
+				$taxable,
1206
+				$code_substring_for_whitelist
1207
+			);
1208
+		}
1209
+	}
1210
+
1211
+
1212
+	/**
1213
+	 * Gets all descendants that are event subtotals
1214
+	 *
1215
+	 * @uses  EEH_Line_Item::get_subtotals_of_object_type()
1216
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1217
+	 * @return EE_Line_Item[]
1218
+	 * @throws EE_Error
1219
+	 */
1220
+	public static function get_event_subtotals(EE_Line_Item $parent_line_item)
1221
+	{
1222
+		return self::get_subtotals_of_object_type($parent_line_item, EEM_Line_Item::OBJ_TYPE_EVENT);
1223
+	}
1224
+
1225
+
1226
+	/**
1227
+	 * Gets all descendants subtotals that match the supplied object type
1228
+	 *
1229
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1230
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1231
+	 * @param string       $obj_type
1232
+	 * @return EE_Line_Item[]
1233
+	 * @throws EE_Error
1234
+	 */
1235
+	public static function get_subtotals_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1236
+	{
1237
+		return self::_get_descendants_by_type_and_object_type(
1238
+			$parent_line_item,
1239
+			EEM_Line_Item::type_sub_total,
1240
+			$obj_type
1241
+		);
1242
+	}
1243
+
1244
+
1245
+	/**
1246
+	 * Gets all descendants that are tickets
1247
+	 *
1248
+	 * @uses  EEH_Line_Item::get_line_items_of_object_type()
1249
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1250
+	 * @return EE_Line_Item[]
1251
+	 * @throws EE_Error
1252
+	 */
1253
+	public static function get_ticket_line_items(EE_Line_Item $parent_line_item)
1254
+	{
1255
+		return self::get_line_items_of_object_type(
1256
+			$parent_line_item,
1257
+			EEM_Line_Item::OBJ_TYPE_TICKET
1258
+		);
1259
+	}
1260
+
1261
+
1262
+	/**
1263
+	 * Gets all descendants subtotals that match the supplied object type
1264
+	 *
1265
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1266
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1267
+	 * @param string       $obj_type
1268
+	 * @return EE_Line_Item[]
1269
+	 * @throws EE_Error
1270
+	 */
1271
+	public static function get_line_items_of_object_type(EE_Line_Item $parent_line_item, $obj_type = '')
1272
+	{
1273
+		return self::_get_descendants_by_type_and_object_type(
1274
+			$parent_line_item,
1275
+			EEM_Line_Item::type_line_item,
1276
+			$obj_type
1277
+		);
1278
+	}
1279
+
1280
+
1281
+	/**
1282
+	 * Gets all the descendants (ie, children or children of children etc) that are of the type 'tax'
1283
+	 *
1284
+	 * @uses  EEH_Line_Item::get_descendants_of_type()
1285
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1286
+	 * @return EE_Line_Item[]
1287
+	 * @throws EE_Error
1288
+	 */
1289
+	public static function get_tax_descendants(EE_Line_Item $parent_line_item)
1290
+	{
1291
+		return EEH_Line_Item::get_descendants_of_type(
1292
+			$parent_line_item,
1293
+			EEM_Line_Item::type_tax
1294
+		);
1295
+	}
1296
+
1297
+
1298
+	/**
1299
+	 * Gets all the real items purchased which are children of this item
1300
+	 *
1301
+	 * @uses  EEH_Line_Item::get_descendants_of_type()
1302
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1303
+	 * @return EE_Line_Item[]
1304
+	 * @throws EE_Error
1305
+	 */
1306
+	public static function get_line_item_descendants(EE_Line_Item $parent_line_item)
1307
+	{
1308
+		return EEH_Line_Item::get_descendants_of_type(
1309
+			$parent_line_item,
1310
+			EEM_Line_Item::type_line_item
1311
+		);
1312
+	}
1313
+
1314
+
1315
+	/**
1316
+	 * Gets all descendants of supplied line item that match the supplied line item type
1317
+	 *
1318
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1319
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1320
+	 * @param string       $line_item_type   one of the EEM_Line_Item constants
1321
+	 * @return EE_Line_Item[]
1322
+	 * @throws EE_Error
1323
+	 */
1324
+	public static function get_descendants_of_type(EE_Line_Item $parent_line_item, $line_item_type)
1325
+	{
1326
+		return self::_get_descendants_by_type_and_object_type(
1327
+			$parent_line_item,
1328
+			$line_item_type,
1329
+			null
1330
+		);
1331
+	}
1332
+
1333
+
1334
+	/**
1335
+	 * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1336
+	 * as well
1337
+	 *
1338
+	 * @param EE_Line_Item  $parent_line_item - the line item to find descendants of
1339
+	 * @param string        $line_item_type   one of the EEM_Line_Item constants
1340
+	 * @param string | NULL $obj_type         object model class name (minus prefix) or NULL to ignore object type when
1341
+	 *                                        searching
1342
+	 * @return EE_Line_Item[]
1343
+	 * @throws EE_Error
1344
+	 */
1345
+	protected static function _get_descendants_by_type_and_object_type(
1346
+		EE_Line_Item $parent_line_item,
1347
+		$line_item_type,
1348
+		$obj_type = null
1349
+	) {
1350
+		$objects = array();
1351
+		foreach ($parent_line_item->children() as $child_line_item) {
1352
+			if ($child_line_item instanceof EE_Line_Item) {
1353
+				if ($child_line_item->type() === $line_item_type
1354
+					&& (
1355
+						$child_line_item->OBJ_type() === $obj_type || $obj_type === null
1356
+					)
1357
+				) {
1358
+					$objects[] = $child_line_item;
1359
+				} else {
1360
+					// go-through-all-its children looking for more matches
1361
+					$objects = array_merge(
1362
+						$objects,
1363
+						self::_get_descendants_by_type_and_object_type(
1364
+							$child_line_item,
1365
+							$line_item_type,
1366
+							$obj_type
1367
+						)
1368
+					);
1369
+				}
1370
+			}
1371
+		}
1372
+		return $objects;
1373
+	}
1374
+
1375
+
1376
+	/**
1377
+	 * Gets all descendants subtotals that match the supplied object type
1378
+	 *
1379
+	 * @uses  EEH_Line_Item::_get_descendants_by_type_and_object_type()
1380
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1381
+	 * @param string       $OBJ_type         object type (like Event)
1382
+	 * @param array        $OBJ_IDs          array of OBJ_IDs
1383
+	 * @return EE_Line_Item[]
1384
+	 * @throws EE_Error
1385
+	 */
1386
+	public static function get_line_items_by_object_type_and_IDs(
1387
+		EE_Line_Item $parent_line_item,
1388
+		$OBJ_type = '',
1389
+		$OBJ_IDs = array()
1390
+	) {
1391
+		return self::_get_descendants_by_object_type_and_object_ID(
1392
+			$parent_line_item,
1393
+			$OBJ_type,
1394
+			$OBJ_IDs
1395
+		);
1396
+	}
1397
+
1398
+
1399
+	/**
1400
+	 * Gets all descendants of supplied line item that match the supplied line item type and possibly the object type
1401
+	 * as well
1402
+	 *
1403
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1404
+	 * @param string       $OBJ_type         object type (like Event)
1405
+	 * @param array        $OBJ_IDs          array of OBJ_IDs
1406
+	 * @return EE_Line_Item[]
1407
+	 * @throws EE_Error
1408
+	 */
1409
+	protected static function _get_descendants_by_object_type_and_object_ID(
1410
+		EE_Line_Item $parent_line_item,
1411
+		$OBJ_type,
1412
+		$OBJ_IDs
1413
+	) {
1414
+		$objects = array();
1415
+		foreach ($parent_line_item->children() as $child_line_item) {
1416
+			if ($child_line_item instanceof EE_Line_Item) {
1417
+				if ($child_line_item->OBJ_type() === $OBJ_type
1418
+					&& is_array($OBJ_IDs)
1419
+					&& in_array($child_line_item->OBJ_ID(), $OBJ_IDs)
1420
+				) {
1421
+					$objects[] = $child_line_item;
1422
+				} else {
1423
+					// go-through-all-its children looking for more matches
1424
+					$objects = array_merge(
1425
+						$objects,
1426
+						self::_get_descendants_by_object_type_and_object_ID(
1427
+							$child_line_item,
1428
+							$OBJ_type,
1429
+							$OBJ_IDs
1430
+						)
1431
+					);
1432
+				}
1433
+			}
1434
+		}
1435
+		return $objects;
1436
+	}
1437
+
1438
+
1439
+	/**
1440
+	 * Uses a breadth-first-search in order to find the nearest descendant of
1441
+	 * the specified type and returns it, else NULL
1442
+	 *
1443
+	 * @uses  EEH_Line_Item::_get_nearest_descendant()
1444
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1445
+	 * @param string       $type             like one of the EEM_Line_Item::type_*
1446
+	 * @return EE_Line_Item
1447
+	 * @throws EE_Error
1448
+	 * @throws InvalidArgumentException
1449
+	 * @throws InvalidDataTypeException
1450
+	 * @throws InvalidInterfaceException
1451
+	 * @throws ReflectionException
1452
+	 */
1453
+	public static function get_nearest_descendant_of_type(EE_Line_Item $parent_line_item, $type)
1454
+	{
1455
+		return self::_get_nearest_descendant($parent_line_item, 'LIN_type', $type);
1456
+	}
1457
+
1458
+
1459
+	/**
1460
+	 * Uses a breadth-first-search in order to find the nearest descendant
1461
+	 * having the specified LIN_code and returns it, else NULL
1462
+	 *
1463
+	 * @uses  EEH_Line_Item::_get_nearest_descendant()
1464
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1465
+	 * @param string       $code             any value used for LIN_code
1466
+	 * @return EE_Line_Item
1467
+	 * @throws EE_Error
1468
+	 * @throws InvalidArgumentException
1469
+	 * @throws InvalidDataTypeException
1470
+	 * @throws InvalidInterfaceException
1471
+	 * @throws ReflectionException
1472
+	 */
1473
+	public static function get_nearest_descendant_having_code(EE_Line_Item $parent_line_item, $code)
1474
+	{
1475
+		return self::_get_nearest_descendant($parent_line_item, 'LIN_code', $code);
1476
+	}
1477
+
1478
+
1479
+	/**
1480
+	 * Uses a breadth-first-search in order to find the nearest descendant
1481
+	 * having the specified LIN_code and returns it, else NULL
1482
+	 *
1483
+	 * @param EE_Line_Item $parent_line_item - the line item to find descendants of
1484
+	 * @param string       $search_field     name of EE_Line_Item property
1485
+	 * @param string       $value            any value stored in $search_field
1486
+	 * @return EE_Line_Item
1487
+	 * @throws EE_Error
1488
+	 * @throws InvalidArgumentException
1489
+	 * @throws InvalidDataTypeException
1490
+	 * @throws InvalidInterfaceException
1491
+	 * @throws ReflectionException
1492
+	 */
1493
+	protected static function _get_nearest_descendant(EE_Line_Item $parent_line_item, $search_field, $value)
1494
+	{
1495
+		foreach ($parent_line_item->children() as $child) {
1496
+			if ($child->get($search_field) == $value) {
1497
+				return $child;
1498
+			}
1499
+		}
1500
+		foreach ($parent_line_item->children() as $child) {
1501
+			$descendant_found = self::_get_nearest_descendant(
1502
+				$child,
1503
+				$search_field,
1504
+				$value
1505
+			);
1506
+			if ($descendant_found) {
1507
+				return $descendant_found;
1508
+			}
1509
+		}
1510
+		return null;
1511
+	}
1512
+
1513
+
1514
+	/**
1515
+	 * if passed line item has a TXN ID, uses that to jump directly to the grand total line item for the transaction,
1516
+	 * else recursively walks up the line item tree until a parent of type total is found,
1517
+	 *
1518
+	 * @param EE_Line_Item $line_item
1519
+	 * @return EE_Line_Item
1520
+	 * @throws EE_Error
1521
+	 */
1522
+	public static function find_transaction_grand_total_for_line_item(EE_Line_Item $line_item)
1523
+	{
1524
+		if ($line_item->TXN_ID()) {
1525
+			$total_line_item = $line_item->transaction()->total_line_item(false);
1526
+			if ($total_line_item instanceof EE_Line_Item) {
1527
+				return $total_line_item;
1528
+			}
1529
+		} else {
1530
+			$line_item_parent = $line_item->parent();
1531
+			if ($line_item_parent instanceof EE_Line_Item) {
1532
+				if ($line_item_parent->is_total()) {
1533
+					return $line_item_parent;
1534
+				}
1535
+				return EEH_Line_Item::find_transaction_grand_total_for_line_item($line_item_parent);
1536
+			}
1537
+		}
1538
+		throw new EE_Error(
1539
+			sprintf(
1540
+				esc_html__(
1541
+					'A valid grand total for line item %1$d was not found.',
1542
+					'event_espresso'
1543
+				),
1544
+				$line_item->ID()
1545
+			)
1546
+		);
1547
+	}
1548
+
1549
+
1550
+	/**
1551
+	 * Prints out a representation of the line item tree
1552
+	 *
1553
+	 * @param EE_Line_Item $line_item
1554
+	 * @param int          $indentation
1555
+	 * @return void
1556
+	 * @throws EE_Error
1557
+	 */
1558
+	public static function visualize(EE_Line_Item $line_item, $indentation = 0)
1559
+	{
1560
+		echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1561
+		if (! $indentation) {
1562
+			echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1563
+		}
1564
+		for ($i = 0; $i < $indentation; $i++) {
1565
+			echo '. ';
1566
+		}
1567
+		$breakdown = '';
1568
+		if ($line_item->is_line_item()) {
1569
+			if ($line_item->is_percent()) {
1570
+				$breakdown = "{$line_item->percent()}%";
1571
+			} else {
1572
+				$breakdown = '$' . "{$line_item->unit_price()} x {$line_item->quantity()}";
1573
+			}
1574
+		}
1575
+		echo $line_item->name();
1576
+		echo " [ ID:{$line_item->ID()} | qty:{$line_item->quantity()} ] {$line_item->type()} : ";
1577
+		echo '$' . (string) $line_item->total();
1578
+		if ($breakdown) {
1579
+			echo " ( {$breakdown} )";
1580
+		}
1581
+		if ($line_item->is_taxable()) {
1582
+			echo '  * taxable';
1583
+		}
1584
+		if ($line_item->children()) {
1585
+			foreach ($line_item->children() as $child) {
1586
+				self::visualize($child, $indentation + 1);
1587
+			}
1588
+		}
1589
+	}
1590
+
1591
+
1592
+	/**
1593
+	 * Calculates the registration's final price, taking into account that they
1594
+	 * need to not only help pay for their OWN ticket, but also any transaction-wide surcharges and taxes,
1595
+	 * and receive a portion of any transaction-wide discounts.
1596
+	 * eg1, if I buy a $1 ticket and brent buys a $9 ticket, and we receive a $5 discount
1597
+	 * then I'll get 1/10 of that $5 discount, which is $0.50, and brent will get
1598
+	 * 9/10ths of that $5 discount, which is $4.50. So my final price should be $0.50
1599
+	 * and brent's final price should be $5.50.
1600
+	 * In order to do this, we basically need to traverse the line item tree calculating
1601
+	 * the running totals (just as if we were recalculating the total), but when we identify
1602
+	 * regular line items, we need to keep track of their share of the grand total.
1603
+	 * Also, we need to keep track of the TAXABLE total for each ticket purchase, so
1604
+	 * we can know how to apply taxes to it. (Note: "taxable total" does not equal the "pretax total"
1605
+	 * when there are non-taxable items; otherwise they would be the same)
1606
+	 *
1607
+	 * @param EE_Line_Item $line_item
1608
+	 * @param array        $billable_ticket_quantities  array of EE_Ticket IDs and their corresponding quantity that
1609
+	 *                                                  can be included in price calculations at this moment
1610
+	 * @return array        keys are line items for tickets IDs and values are their share of the running total,
1611
+	 *                                                  plus the key 'total', and 'taxable' which also has keys of all
1612
+	 *                                                  the ticket IDs.
1613
+	 *                                                  Eg array(
1614
+	 *                                                      12 => 4.3
1615
+	 *                                                      23 => 8.0
1616
+	 *                                                      'total' => 16.6,
1617
+	 *                                                      'taxable' => array(
1618
+	 *                                                          12 => 10,
1619
+	 *                                                          23 => 4
1620
+	 *                                                      ).
1621
+	 *                                                  So to find which registrations have which final price, we need
1622
+	 *                                                  to find which line item is theirs, which can be done with
1623
+	 *                                                  `EEM_Line_Item::instance()->get_line_item_for_registration(
1624
+	 *                                                  $registration );`
1625
+	 * @throws EE_Error
1626
+	 * @throws InvalidArgumentException
1627
+	 * @throws InvalidDataTypeException
1628
+	 * @throws InvalidInterfaceException
1629
+	 * @throws ReflectionException
1630
+	 */
1631
+	public static function calculate_reg_final_prices_per_line_item(
1632
+		EE_Line_Item $line_item,
1633
+		$billable_ticket_quantities = array()
1634
+	) {
1635
+		$running_totals = [
1636
+			'total'   => 0,
1637
+			'taxable' => ['total' => 0]
1638
+		];
1639
+		foreach ($line_item->children() as $child_line_item) {
1640
+			switch ($child_line_item->type()) {
1641
+				case EEM_Line_Item::type_sub_total:
1642
+					$running_totals_from_subtotal = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1643
+						$child_line_item,
1644
+						$billable_ticket_quantities
1645
+					);
1646
+					// combine arrays but preserve numeric keys
1647
+					$running_totals = array_replace_recursive($running_totals_from_subtotal, $running_totals);
1648
+					$running_totals['total'] += $running_totals_from_subtotal['total'];
1649
+					$running_totals['taxable']['total'] += $running_totals_from_subtotal['taxable']['total'];
1650
+					break;
1651
+
1652
+				case EEM_Line_Item::type_tax_sub_total:
1653
+					// find how much the taxes percentage is
1654
+					if ($child_line_item->percent() !== 0) {
1655
+						$tax_percent_decimal = $child_line_item->percent() / 100;
1656
+					} else {
1657
+						$tax_percent_decimal = EE_Taxes::get_total_taxes_percentage() / 100;
1658
+					}
1659
+					// and apply to all the taxable totals, and add to the pretax totals
1660
+					foreach ($running_totals as $line_item_id => $this_running_total) {
1661
+						// "total" and "taxable" array key is an exception
1662
+						if ($line_item_id === 'taxable') {
1663
+							continue;
1664
+						}
1665
+						$taxable_total = $running_totals['taxable'][ $line_item_id ];
1666
+						$running_totals[ $line_item_id ] += ($taxable_total * $tax_percent_decimal);
1667
+					}
1668
+					break;
1669
+
1670
+				case EEM_Line_Item::type_line_item:
1671
+					// ticket line items or ????
1672
+					if ($child_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
1673
+						// kk it's a ticket
1674
+						if (isset($running_totals[ $child_line_item->ID() ])) {
1675
+							// huh? that shouldn't happen.
1676
+							$running_totals['total'] += $child_line_item->total();
1677
+						} else {
1678
+							// its not in our running totals yet. great.
1679
+							if ($child_line_item->is_taxable()) {
1680
+								$taxable_amount = $child_line_item->unit_price();
1681
+							} else {
1682
+								$taxable_amount = 0;
1683
+							}
1684
+							// are we only calculating totals for some tickets?
1685
+							if (isset($billable_ticket_quantities[ $child_line_item->OBJ_ID() ])) {
1686
+								$quantity = $billable_ticket_quantities[ $child_line_item->OBJ_ID() ];
1687
+								$running_totals[ $child_line_item->ID() ] = $quantity
1688
+									? $child_line_item->unit_price()
1689
+									: 0;
1690
+								$running_totals['taxable'][ $child_line_item->ID() ] = $quantity
1691
+									? $taxable_amount
1692
+									: 0;
1693
+							} else {
1694
+								$quantity = $child_line_item->quantity();
1695
+								$running_totals[ $child_line_item->ID() ] = $child_line_item->unit_price();
1696
+								$running_totals['taxable'][ $child_line_item->ID() ] = $taxable_amount;
1697
+							}
1698
+							$running_totals['taxable']['total'] += $taxable_amount * $quantity;
1699
+							$running_totals['total'] += $child_line_item->unit_price() * $quantity;
1700
+						}
1701
+					} else {
1702
+						// it's some other type of item added to the cart
1703
+						// it should affect the running totals
1704
+						// basically we want to convert it into a PERCENT modifier. Because
1705
+						// more clearly affect all registration's final price equally
1706
+						$line_items_percent_of_running_total = $running_totals['total'] > 0
1707
+							? ($child_line_item->total() / $running_totals['total']) + 1
1708
+							: 1;
1709
+						foreach ($running_totals as $line_item_id => $this_running_total) {
1710
+							// the "taxable" array key is an exception
1711
+							if ($line_item_id === 'taxable') {
1712
+								continue;
1713
+							}
1714
+							// update the running totals
1715
+							// yes this actually even works for the running grand total!
1716
+							$running_totals[ $line_item_id ] =
1717
+								$line_items_percent_of_running_total * $this_running_total;
1718
+
1719
+							if ($child_line_item->is_taxable()) {
1720
+								$running_totals['taxable'][ $line_item_id ] =
1721
+									$line_items_percent_of_running_total * $running_totals['taxable'][ $line_item_id ];
1722
+							}
1723
+						}
1724
+					}
1725
+					break;
1726
+			}
1727
+		}
1728
+		return $running_totals;
1729
+	}
1730
+
1731
+
1732
+	/**
1733
+	 * @param EE_Line_Item $total_line_item
1734
+	 * @param EE_Line_Item $ticket_line_item
1735
+	 * @return float | null
1736
+	 * @throws EE_Error
1737
+	 * @throws InvalidArgumentException
1738
+	 * @throws InvalidDataTypeException
1739
+	 * @throws InvalidInterfaceException
1740
+	 * @throws OutOfRangeException
1741
+	 * @throws ReflectionException
1742
+	 */
1743
+	public static function calculate_final_price_for_ticket_line_item(
1744
+		EE_Line_Item $total_line_item,
1745
+		EE_Line_Item $ticket_line_item
1746
+	) {
1747
+		static $final_prices_per_ticket_line_item = array();
1748
+		if (empty($final_prices_per_ticket_line_item) || empty($final_prices_per_ticket_line_item[ $total_line_item->ID() ])) {
1749
+			$final_prices_per_ticket_line_item[ $total_line_item->ID() ] = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1750
+				$total_line_item
1751
+			);
1752
+		}
1753
+		// ok now find this new registration's final price
1754
+		if (isset($final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ])) {
1755
+			return $final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ];
1756
+		}
1757
+		$message = sprintf(
1758
+			esc_html__(
1759
+				'The final price for the ticket line item (ID:%1$d) could not be calculated.',
1760
+				'event_espresso'
1761
+			),
1762
+			$ticket_line_item->ID()
1763
+		);
1764
+		if (WP_DEBUG) {
1765
+			$message .= '<br>' . print_r($final_prices_per_ticket_line_item, true);
1766
+			throw new OutOfRangeException($message);
1767
+		}
1768
+		EE_Log::instance()->log(__CLASS__, __FUNCTION__, $message);
1769
+		return null;
1770
+	}
1771
+
1772
+
1773
+	/**
1774
+	 * Creates a duplicate of the line item tree, except only includes billable items
1775
+	 * and the portion of line items attributed to billable things
1776
+	 *
1777
+	 * @param EE_Line_Item      $line_item
1778
+	 * @param EE_Registration[] $registrations
1779
+	 * @return EE_Line_Item
1780
+	 * @throws EE_Error
1781
+	 * @throws InvalidArgumentException
1782
+	 * @throws InvalidDataTypeException
1783
+	 * @throws InvalidInterfaceException
1784
+	 * @throws ReflectionException
1785
+	 */
1786
+	public static function billable_line_item_tree(EE_Line_Item $line_item, $registrations)
1787
+	{
1788
+		$copy_li = EEH_Line_Item::billable_line_item($line_item, $registrations);
1789
+		foreach ($line_item->children() as $child_li) {
1790
+			$copy_li->add_child_line_item(
1791
+				EEH_Line_Item::billable_line_item_tree($child_li, $registrations)
1792
+			);
1793
+		}
1794
+		// if this is the grand total line item, make sure the totals all add up
1795
+		// (we could have duplicated this logic AS we copied the line items, but
1796
+		// it seems DRYer this way)
1797
+		if ($copy_li->type() === EEM_Line_Item::type_total) {
1798
+			$copy_li->recalculate_total_including_taxes();
1799
+		}
1800
+		return $copy_li;
1801
+	}
1802
+
1803
+
1804
+	/**
1805
+	 * Creates a new, unsaved line item from $line_item that factors in the
1806
+	 * number of billable registrations on $registrations.
1807
+	 *
1808
+	 * @param EE_Line_Item      $line_item
1809
+	 * @param EE_Registration[] $registrations
1810
+	 * @return EE_Line_Item
1811
+	 * @throws EE_Error
1812
+	 * @throws InvalidArgumentException
1813
+	 * @throws InvalidDataTypeException
1814
+	 * @throws InvalidInterfaceException
1815
+	 * @throws ReflectionException
1816
+	 */
1817
+	public static function billable_line_item(EE_Line_Item $line_item, $registrations)
1818
+	{
1819
+		$new_li_fields = $line_item->model_field_array();
1820
+		if ($line_item->type() === EEM_Line_Item::type_line_item &&
1821
+			$line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1822
+		) {
1823
+			$count = 0;
1824
+			foreach ($registrations as $registration) {
1825
+				if ($line_item->OBJ_ID() === $registration->ticket_ID() &&
1826
+					in_array(
1827
+						$registration->status_ID(),
1828
+						EEM_Registration::reg_statuses_that_allow_payment(),
1829
+						true
1830
+					)
1831
+				) {
1832
+					$count++;
1833
+				}
1834
+			}
1835
+			$new_li_fields['LIN_quantity'] = $count;
1836
+		}
1837
+		// don't set the total. We'll leave that up to the code that calculates it
1838
+		unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent'], $new_li_fields['LIN_total']);
1839
+		return EE_Line_Item::new_instance($new_li_fields);
1840
+	}
1841
+
1842
+
1843
+	/**
1844
+	 * Returns a modified line item tree where all the subtotals which have a total of 0
1845
+	 * are removed, and line items with a quantity of 0
1846
+	 *
1847
+	 * @param EE_Line_Item $line_item |null
1848
+	 * @return EE_Line_Item|null
1849
+	 * @throws EE_Error
1850
+	 * @throws InvalidArgumentException
1851
+	 * @throws InvalidDataTypeException
1852
+	 * @throws InvalidInterfaceException
1853
+	 * @throws ReflectionException
1854
+	 */
1855
+	public static function non_empty_line_items(EE_Line_Item $line_item)
1856
+	{
1857
+		$copied_li = EEH_Line_Item::non_empty_line_item($line_item);
1858
+		if ($copied_li === null) {
1859
+			return null;
1860
+		}
1861
+		// if this is an event subtotal, we want to only include it if it
1862
+		// has a non-zero total and at least one ticket line item child
1863
+		$ticket_children = 0;
1864
+		foreach ($line_item->children() as $child_li) {
1865
+			$child_li_copy = EEH_Line_Item::non_empty_line_items($child_li);
1866
+			if ($child_li_copy !== null) {
1867
+				$copied_li->add_child_line_item($child_li_copy);
1868
+				if ($child_li_copy->type() === EEM_Line_Item::type_line_item &&
1869
+					$child_li_copy->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1870
+				) {
1871
+					$ticket_children++;
1872
+				}
1873
+			}
1874
+		}
1875
+		// if this is an event subtotal with NO ticket children
1876
+		// we basically want to ignore it
1877
+		if ($ticket_children === 0
1878
+			&& $line_item->type() === EEM_Line_Item::type_sub_total
1879
+			&& $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_EVENT
1880
+			&& $line_item->total() === 0
1881
+		) {
1882
+			return null;
1883
+		}
1884
+		return $copied_li;
1885
+	}
1886
+
1887
+
1888
+	/**
1889
+	 * Creates a new, unsaved line item, but if it's a ticket line item
1890
+	 * with a total of 0, or a subtotal of 0, returns null instead
1891
+	 *
1892
+	 * @param EE_Line_Item $line_item
1893
+	 * @return EE_Line_Item
1894
+	 * @throws EE_Error
1895
+	 * @throws InvalidArgumentException
1896
+	 * @throws InvalidDataTypeException
1897
+	 * @throws InvalidInterfaceException
1898
+	 * @throws ReflectionException
1899
+	 */
1900
+	public static function non_empty_line_item(EE_Line_Item $line_item)
1901
+	{
1902
+		if ($line_item->type() === EEM_Line_Item::type_line_item
1903
+			&& $line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1904
+			&& $line_item->quantity() === 0
1905
+		) {
1906
+			return null;
1907
+		}
1908
+		$new_li_fields = $line_item->model_field_array();
1909
+		// don't set the total. We'll leave that up to the code that calculates it
1910
+		unset($new_li_fields['LIN_ID'], $new_li_fields['LIN_parent']);
1911
+		return EE_Line_Item::new_instance($new_li_fields);
1912
+	}
1913
+
1914
+
1915
+	/**
1916
+	 * Cycles through all of the ticket line items for the supplied total line item
1917
+	 * and ensures that the line item's "is_taxable" field matches that of its corresponding ticket
1918
+	 *
1919
+	 * @param EE_Line_Item $total_line_item
1920
+	 * @since 4.9.79.p
1921
+	 * @throws EE_Error
1922
+	 * @throws InvalidArgumentException
1923
+	 * @throws InvalidDataTypeException
1924
+	 * @throws InvalidInterfaceException
1925
+	 * @throws ReflectionException
1926
+	 */
1927
+	public static function resetIsTaxableForTickets(EE_Line_Item $total_line_item)
1928
+	{
1929
+		$ticket_line_items = self::get_ticket_line_items($total_line_item);
1930
+		foreach ($ticket_line_items as $ticket_line_item) {
1931
+			if ($ticket_line_item instanceof EE_Line_Item
1932
+				&& $ticket_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET
1933
+			) {
1934
+				$ticket = $ticket_line_item->ticket();
1935
+				if ($ticket instanceof EE_Ticket && $ticket->taxable() !== $ticket_line_item->is_taxable()) {
1936
+					$ticket_line_item->set_is_taxable($ticket->taxable());
1937
+					$ticket_line_item->save();
1938
+				}
1939
+			}
1940
+		}
1941
+	}
1942
+
1943
+
1944
+
1945
+	/**************************************** @DEPRECATED METHODS *************************************** */
1946
+	/**
1947
+	 * @deprecated
1948
+	 * @param EE_Line_Item $total_line_item
1949
+	 * @return EE_Line_Item
1950
+	 * @throws EE_Error
1951
+	 * @throws InvalidArgumentException
1952
+	 * @throws InvalidDataTypeException
1953
+	 * @throws InvalidInterfaceException
1954
+	 * @throws ReflectionException
1955
+	 */
1956
+	public static function get_items_subtotal(EE_Line_Item $total_line_item)
1957
+	{
1958
+		EE_Error::doing_it_wrong(
1959
+			'EEH_Line_Item::get_items_subtotal()',
1960
+			sprintf(
1961
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
1962
+				'EEH_Line_Item::get_pre_tax_subtotal()'
1963
+			),
1964
+			'4.6.0'
1965
+		);
1966
+		return self::get_pre_tax_subtotal($total_line_item);
1967
+	}
1968
+
1969
+
1970
+	/**
1971
+	 * @deprecated
1972
+	 * @param EE_Transaction $transaction
1973
+	 * @return EE_Line_Item
1974
+	 * @throws EE_Error
1975
+	 * @throws InvalidArgumentException
1976
+	 * @throws InvalidDataTypeException
1977
+	 * @throws InvalidInterfaceException
1978
+	 * @throws ReflectionException
1979
+	 */
1980
+	public static function create_default_total_line_item($transaction = null)
1981
+	{
1982
+		EE_Error::doing_it_wrong(
1983
+			'EEH_Line_Item::create_default_total_line_item()',
1984
+			sprintf(
1985
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
1986
+				'EEH_Line_Item::create_total_line_item()'
1987
+			),
1988
+			'4.6.0'
1989
+		);
1990
+		return self::create_total_line_item($transaction);
1991
+	}
1992
+
1993
+
1994
+	/**
1995
+	 * @deprecated
1996
+	 * @param EE_Line_Item   $total_line_item
1997
+	 * @param EE_Transaction $transaction
1998
+	 * @return EE_Line_Item
1999
+	 * @throws EE_Error
2000
+	 * @throws InvalidArgumentException
2001
+	 * @throws InvalidDataTypeException
2002
+	 * @throws InvalidInterfaceException
2003
+	 * @throws ReflectionException
2004
+	 */
2005
+	public static function create_default_tickets_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2006
+	{
2007
+		EE_Error::doing_it_wrong(
2008
+			'EEH_Line_Item::create_default_tickets_subtotal()',
2009
+			sprintf(
2010
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2011
+				'EEH_Line_Item::create_pre_tax_subtotal()'
2012
+			),
2013
+			'4.6.0'
2014
+		);
2015
+		return self::create_pre_tax_subtotal($total_line_item, $transaction);
2016
+	}
2017
+
2018
+
2019
+	/**
2020
+	 * @deprecated
2021
+	 * @param EE_Line_Item   $total_line_item
2022
+	 * @param EE_Transaction $transaction
2023
+	 * @return EE_Line_Item
2024
+	 * @throws EE_Error
2025
+	 * @throws InvalidArgumentException
2026
+	 * @throws InvalidDataTypeException
2027
+	 * @throws InvalidInterfaceException
2028
+	 * @throws ReflectionException
2029
+	 */
2030
+	public static function create_default_taxes_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2031
+	{
2032
+		EE_Error::doing_it_wrong(
2033
+			'EEH_Line_Item::create_default_taxes_subtotal()',
2034
+			sprintf(
2035
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2036
+				'EEH_Line_Item::create_taxes_subtotal()'
2037
+			),
2038
+			'4.6.0'
2039
+		);
2040
+		return self::create_taxes_subtotal($total_line_item, $transaction);
2041
+	}
2042
+
2043
+
2044
+	/**
2045
+	 * @deprecated
2046
+	 * @param EE_Line_Item   $total_line_item
2047
+	 * @param EE_Transaction $transaction
2048
+	 * @return EE_Line_Item
2049
+	 * @throws EE_Error
2050
+	 * @throws InvalidArgumentException
2051
+	 * @throws InvalidDataTypeException
2052
+	 * @throws InvalidInterfaceException
2053
+	 * @throws ReflectionException
2054
+	 */
2055
+	public static function create_default_event_subtotal(EE_Line_Item $total_line_item, $transaction = null)
2056
+	{
2057
+		EE_Error::doing_it_wrong(
2058
+			'EEH_Line_Item::create_default_event_subtotal()',
2059
+			sprintf(
2060
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
2061
+				'EEH_Line_Item::create_event_subtotal()'
2062
+			),
2063
+			'4.6.0'
2064
+		);
2065
+		return self::create_event_subtotal($total_line_item, $transaction);
2066
+	}
2067 2067
 }
Please login to merge, or discard this patch.
Spacing   +38 added lines, -38 removed lines patch added patch discarded remove patch
@@ -138,7 +138,7 @@  discard block
 block discarded – undo
138 138
      */
139 139
     public static function add_ticket_purchase(EE_Line_Item $total_line_item, EE_Ticket $ticket, $qty = 1)
140 140
     {
141
-        if (! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
141
+        if ( ! $total_line_item instanceof EE_Line_Item || ! $total_line_item->is_total()) {
142 142
             throw new EE_Error(
143 143
                 sprintf(
144 144
                     esc_html__(
@@ -153,7 +153,7 @@  discard block
 block discarded – undo
153 153
         // either increment the qty for an existing ticket
154 154
         $line_item = self::increment_ticket_qty_if_already_in_cart($total_line_item, $ticket, $qty);
155 155
         // or add a new one
156
-        if (! $line_item instanceof EE_Line_Item) {
156
+        if ( ! $line_item instanceof EE_Line_Item) {
157 157
             $line_item = self::create_ticket_line_item($total_line_item, $ticket, $qty);
158 158
         }
159 159
         $total_line_item->recalculate_total_including_taxes();
@@ -214,7 +214,7 @@  discard block
 block discarded – undo
214 214
      */
215 215
     public static function increment_quantity(EE_Line_Item $line_item, $qty = 1)
216 216
     {
217
-        if (! $line_item->is_percent()) {
217
+        if ( ! $line_item->is_percent()) {
218 218
             $qty += $line_item->quantity();
219 219
             $line_item->set_quantity($qty);
220 220
             $line_item->set_total($line_item->unit_price() * $qty);
@@ -243,7 +243,7 @@  discard block
 block discarded – undo
243 243
      */
244 244
     public static function decrement_quantity(EE_Line_Item $line_item, $qty = 1)
245 245
     {
246
-        if (! $line_item->is_percent()) {
246
+        if ( ! $line_item->is_percent()) {
247 247
             $qty = $line_item->quantity() - $qty;
248 248
             $qty = max($qty, 0);
249 249
             $line_item->set_quantity($qty);
@@ -272,7 +272,7 @@  discard block
 block discarded – undo
272 272
      */
273 273
     public static function update_quantity(EE_Line_Item $line_item, $new_quantity)
274 274
     {
275
-        if (! $line_item->is_percent()) {
275
+        if ( ! $line_item->is_percent()) {
276 276
             $line_item->set_quantity($new_quantity);
277 277
             $line_item->set_total($line_item->unit_price() * $new_quantity);
278 278
             $line_item->save();
@@ -314,7 +314,7 @@  discard block
 block discarded – undo
314 314
         // add $ticket to cart
315 315
         $line_item = EE_Line_Item::new_instance(array(
316 316
             'LIN_name'       => $ticket->name(),
317
-            'LIN_desc'       => $ticket->description() !== '' ? $ticket->description() . ' ' . $event : $event,
317
+            'LIN_desc'       => $ticket->description() !== '' ? $ticket->description().' '.$event : $event,
318 318
             'LIN_unit_price' => $ticket->price(),
319 319
             'LIN_quantity'   => $qty,
320 320
             'LIN_is_taxable' => $is_taxable,
@@ -464,7 +464,7 @@  discard block
 block discarded – undo
464 464
                         'event_espresso'
465 465
                     ),
466 466
                     $ticket_line_item->name(),
467
-                    current_time(get_option('date_format') . ' ' . get_option('time_format'))
467
+                    current_time(get_option('date_format').' '.get_option('time_format'))
468 468
                 ),
469 469
                 'LIN_unit_price' => 0, // $ticket_line_item->unit_price()
470 470
                 'LIN_quantity'   => $qty,
@@ -526,7 +526,7 @@  discard block
 block discarded – undo
526 526
         );
527 527
         $cancellation_line_item = reset($cancellation_line_item);
528 528
         // verify that this ticket was indeed previously cancelled
529
-        if (! $cancellation_line_item instanceof EE_Line_Item) {
529
+        if ( ! $cancellation_line_item instanceof EE_Line_Item) {
530 530
             return false;
531 531
         }
532 532
         if ($cancellation_line_item->quantity() > $qty) {
@@ -731,7 +731,7 @@  discard block
 block discarded – undo
731 731
             'LIN_code'  => 'taxes',
732 732
             'LIN_name'  => esc_html__('Taxes', 'event_espresso'),
733 733
             'LIN_type'  => EEM_Line_Item::type_tax_sub_total,
734
-            'LIN_order' => 1000,// this should always come last
734
+            'LIN_order' => 1000, // this should always come last
735 735
         ));
736 736
         $tax_line_item = apply_filters(
737 737
             'FHEE__EEH_Line_Item__create_taxes_subtotal__tax_line_item',
@@ -787,7 +787,7 @@  discard block
 block discarded – undo
787 787
      */
788 788
     public static function get_event_code($event)
789 789
     {
790
-        return 'event-' . ($event instanceof EE_Event ? $event->ID() : '0');
790
+        return 'event-'.($event instanceof EE_Event ? $event->ID() : '0');
791 791
     }
792 792
 
793 793
 
@@ -836,7 +836,7 @@  discard block
 block discarded – undo
836 836
     public static function get_event_line_item_for_ticket(EE_Line_Item $grand_total, EE_Ticket $ticket)
837 837
     {
838 838
         $first_datetime = $ticket->first_datetime();
839
-        if (! $first_datetime instanceof EE_Datetime) {
839
+        if ( ! $first_datetime instanceof EE_Datetime) {
840 840
             throw new EE_Error(
841 841
                 sprintf(
842 842
                     __('The supplied ticket (ID %d) has no datetimes', 'event_espresso'),
@@ -845,7 +845,7 @@  discard block
 block discarded – undo
845 845
             );
846 846
         }
847 847
         $event = $first_datetime->event();
848
-        if (! $event instanceof EE_Event) {
848
+        if ( ! $event instanceof EE_Event) {
849 849
             throw new EE_Error(
850 850
                 sprintf(
851 851
                     esc_html__(
@@ -857,7 +857,7 @@  discard block
 block discarded – undo
857 857
             );
858 858
         }
859 859
         $events_sub_total = EEH_Line_Item::get_event_line_item($grand_total, $event);
860
-        if (! $events_sub_total instanceof EE_Line_Item) {
860
+        if ( ! $events_sub_total instanceof EE_Line_Item) {
861 861
             throw new EE_Error(
862 862
                 sprintf(
863 863
                     esc_html__(
@@ -893,7 +893,7 @@  discard block
 block discarded – undo
893 893
         $found = false;
894 894
         foreach (EEH_Line_Item::get_event_subtotals($grand_total) as $event_line_item) {
895 895
             // default event subtotal, we should only ever find this the first time this method is called
896
-            if (! $event_line_item->OBJ_ID()) {
896
+            if ( ! $event_line_item->OBJ_ID()) {
897 897
                 // let's use this! but first... set the event details
898 898
                 EEH_Line_Item::set_event_subtotal_details($event_line_item, $event);
899 899
                 $found = true;
@@ -905,7 +905,7 @@  discard block
 block discarded – undo
905 905
                 break;
906 906
             }
907 907
         }
908
-        if (! $found) {
908
+        if ( ! $found) {
909 909
             // there is no event sub-total yet, so add it
910 910
             $pre_tax_subtotal = EEH_Line_Item::get_pre_tax_subtotal($grand_total);
911 911
             // create a new "event" subtotal below that
@@ -1022,7 +1022,7 @@  discard block
 block discarded – undo
1022 1022
     public static function ensure_taxes_applied($total_line_item)
1023 1023
     {
1024 1024
         $taxes_subtotal = self::get_taxes_subtotal($total_line_item);
1025
-        if (! $taxes_subtotal->children()) {
1025
+        if ( ! $taxes_subtotal->children()) {
1026 1026
             self::apply_taxes($total_line_item);
1027 1027
         }
1028 1028
         return $taxes_subtotal->total();
@@ -1089,7 +1089,7 @@  discard block
 block discarded – undo
1089 1089
         do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1090 1090
 
1091 1091
         // check if only a single line_item_id was passed
1092
-        if (! empty($line_item_codes) && ! is_array($line_item_codes)) {
1092
+        if ( ! empty($line_item_codes) && ! is_array($line_item_codes)) {
1093 1093
             // place single line_item_id in an array to appear as multiple line_item_ids
1094 1094
             $line_item_codes = array($line_item_codes);
1095 1095
         }
@@ -1196,7 +1196,7 @@  discard block
 block discarded – undo
1196 1196
         if ($code_substring_for_whitelist !== null) {
1197 1197
             $whitelisted = strpos($line_item->code(), $code_substring_for_whitelist) !== false;
1198 1198
         }
1199
-        if (! $whitelisted && $line_item->is_line_item()) {
1199
+        if ( ! $whitelisted && $line_item->is_line_item()) {
1200 1200
             $line_item->set_is_taxable($taxable);
1201 1201
         }
1202 1202
         foreach ($line_item->children() as $child_line_item) {
@@ -1558,7 +1558,7 @@  discard block
 block discarded – undo
1558 1558
     public static function visualize(EE_Line_Item $line_item, $indentation = 0)
1559 1559
     {
1560 1560
         echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1561
-        if (! $indentation) {
1561
+        if ( ! $indentation) {
1562 1562
             echo defined('EE_TESTS_DIR') ? "\n" : '<br />';
1563 1563
         }
1564 1564
         for ($i = 0; $i < $indentation; $i++) {
@@ -1569,12 +1569,12 @@  discard block
 block discarded – undo
1569 1569
             if ($line_item->is_percent()) {
1570 1570
                 $breakdown = "{$line_item->percent()}%";
1571 1571
             } else {
1572
-                $breakdown = '$' . "{$line_item->unit_price()} x {$line_item->quantity()}";
1572
+                $breakdown = '$'."{$line_item->unit_price()} x {$line_item->quantity()}";
1573 1573
             }
1574 1574
         }
1575 1575
         echo $line_item->name();
1576 1576
         echo " [ ID:{$line_item->ID()} | qty:{$line_item->quantity()} ] {$line_item->type()} : ";
1577
-        echo '$' . (string) $line_item->total();
1577
+        echo '$'.(string) $line_item->total();
1578 1578
         if ($breakdown) {
1579 1579
             echo " ( {$breakdown} )";
1580 1580
         }
@@ -1662,8 +1662,8 @@  discard block
 block discarded – undo
1662 1662
                         if ($line_item_id === 'taxable') {
1663 1663
                             continue;
1664 1664
                         }
1665
-                        $taxable_total = $running_totals['taxable'][ $line_item_id ];
1666
-                        $running_totals[ $line_item_id ] += ($taxable_total * $tax_percent_decimal);
1665
+                        $taxable_total = $running_totals['taxable'][$line_item_id];
1666
+                        $running_totals[$line_item_id] += ($taxable_total * $tax_percent_decimal);
1667 1667
                     }
1668 1668
                     break;
1669 1669
 
@@ -1671,7 +1671,7 @@  discard block
 block discarded – undo
1671 1671
                     // ticket line items or ????
1672 1672
                     if ($child_line_item->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
1673 1673
                         // kk it's a ticket
1674
-                        if (isset($running_totals[ $child_line_item->ID() ])) {
1674
+                        if (isset($running_totals[$child_line_item->ID()])) {
1675 1675
                             // huh? that shouldn't happen.
1676 1676
                             $running_totals['total'] += $child_line_item->total();
1677 1677
                         } else {
@@ -1682,18 +1682,18 @@  discard block
 block discarded – undo
1682 1682
                                 $taxable_amount = 0;
1683 1683
                             }
1684 1684
                             // are we only calculating totals for some tickets?
1685
-                            if (isset($billable_ticket_quantities[ $child_line_item->OBJ_ID() ])) {
1686
-                                $quantity = $billable_ticket_quantities[ $child_line_item->OBJ_ID() ];
1687
-                                $running_totals[ $child_line_item->ID() ] = $quantity
1685
+                            if (isset($billable_ticket_quantities[$child_line_item->OBJ_ID()])) {
1686
+                                $quantity = $billable_ticket_quantities[$child_line_item->OBJ_ID()];
1687
+                                $running_totals[$child_line_item->ID()] = $quantity
1688 1688
                                     ? $child_line_item->unit_price()
1689 1689
                                     : 0;
1690
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $quantity
1690
+                                $running_totals['taxable'][$child_line_item->ID()] = $quantity
1691 1691
                                     ? $taxable_amount
1692 1692
                                     : 0;
1693 1693
                             } else {
1694 1694
                                 $quantity = $child_line_item->quantity();
1695
-                                $running_totals[ $child_line_item->ID() ] = $child_line_item->unit_price();
1696
-                                $running_totals['taxable'][ $child_line_item->ID() ] = $taxable_amount;
1695
+                                $running_totals[$child_line_item->ID()] = $child_line_item->unit_price();
1696
+                                $running_totals['taxable'][$child_line_item->ID()] = $taxable_amount;
1697 1697
                             }
1698 1698
                             $running_totals['taxable']['total'] += $taxable_amount * $quantity;
1699 1699
                             $running_totals['total'] += $child_line_item->unit_price() * $quantity;
@@ -1713,12 +1713,12 @@  discard block
 block discarded – undo
1713 1713
                             }
1714 1714
                             // update the running totals
1715 1715
                             // yes this actually even works for the running grand total!
1716
-                            $running_totals[ $line_item_id ] =
1716
+                            $running_totals[$line_item_id] =
1717 1717
                                 $line_items_percent_of_running_total * $this_running_total;
1718 1718
 
1719 1719
                             if ($child_line_item->is_taxable()) {
1720
-                                $running_totals['taxable'][ $line_item_id ] =
1721
-                                    $line_items_percent_of_running_total * $running_totals['taxable'][ $line_item_id ];
1720
+                                $running_totals['taxable'][$line_item_id] =
1721
+                                    $line_items_percent_of_running_total * $running_totals['taxable'][$line_item_id];
1722 1722
                             }
1723 1723
                         }
1724 1724
                     }
@@ -1745,14 +1745,14 @@  discard block
 block discarded – undo
1745 1745
         EE_Line_Item $ticket_line_item
1746 1746
     ) {
1747 1747
         static $final_prices_per_ticket_line_item = array();
1748
-        if (empty($final_prices_per_ticket_line_item) || empty($final_prices_per_ticket_line_item[ $total_line_item->ID() ])) {
1749
-            $final_prices_per_ticket_line_item[ $total_line_item->ID() ] = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1748
+        if (empty($final_prices_per_ticket_line_item) || empty($final_prices_per_ticket_line_item[$total_line_item->ID()])) {
1749
+            $final_prices_per_ticket_line_item[$total_line_item->ID()] = EEH_Line_Item::calculate_reg_final_prices_per_line_item(
1750 1750
                 $total_line_item
1751 1751
             );
1752 1752
         }
1753 1753
         // ok now find this new registration's final price
1754
-        if (isset($final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ])) {
1755
-            return $final_prices_per_ticket_line_item[ $total_line_item->ID() ][ $ticket_line_item->ID() ];
1754
+        if (isset($final_prices_per_ticket_line_item[$total_line_item->ID()][$ticket_line_item->ID()])) {
1755
+            return $final_prices_per_ticket_line_item[$total_line_item->ID()][$ticket_line_item->ID()];
1756 1756
         }
1757 1757
         $message = sprintf(
1758 1758
             esc_html__(
@@ -1762,7 +1762,7 @@  discard block
 block discarded – undo
1762 1762
             $ticket_line_item->ID()
1763 1763
         );
1764 1764
         if (WP_DEBUG) {
1765
-            $message .= '<br>' . print_r($final_prices_per_ticket_line_item, true);
1765
+            $message .= '<br>'.print_r($final_prices_per_ticket_line_item, true);
1766 1766
             throw new OutOfRangeException($message);
1767 1767
         }
1768 1768
         EE_Log::instance()->log(__CLASS__, __FUNCTION__, $message);
Please login to merge, or discard this patch.
core/db_classes/EE_Line_Item.class.php 2 patches
Indentation   +1742 added lines, -1742 removed lines patch added patch discarded remove patch
@@ -14,1746 +14,1746 @@
 block discarded – undo
14 14
 class EE_Line_Item extends EE_Base_Class implements EEI_Line_Item
15 15
 {
16 16
 
17
-    /**
18
-     * for children line items (currently not a normal relation)
19
-     *
20
-     * @type EE_Line_Item[]
21
-     */
22
-    protected $_children = array();
23
-
24
-    /**
25
-     * for the parent line item
26
-     *
27
-     * @var EE_Line_Item
28
-     */
29
-    protected $_parent;
30
-
31
-
32
-    /**
33
-     * @param array  $props_n_values          incoming values
34
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
35
-     *                                        used.)
36
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
37
-     *                                        date_format and the second value is the time format
38
-     * @return EE_Line_Item
39
-     * @throws EE_Error
40
-     * @throws InvalidArgumentException
41
-     * @throws InvalidDataTypeException
42
-     * @throws InvalidInterfaceException
43
-     * @throws ReflectionException
44
-     */
45
-    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
46
-    {
47
-        $has_object = parent::_check_for_object(
48
-            $props_n_values,
49
-            __CLASS__,
50
-            $timezone,
51
-            $date_formats
52
-        );
53
-        return $has_object
54
-            ? $has_object
55
-            : new self($props_n_values, false, $timezone);
56
-    }
57
-
58
-
59
-    /**
60
-     * @param array  $props_n_values  incoming values from the database
61
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
62
-     *                                the website will be used.
63
-     * @return EE_Line_Item
64
-     * @throws EE_Error
65
-     * @throws InvalidArgumentException
66
-     * @throws InvalidDataTypeException
67
-     * @throws InvalidInterfaceException
68
-     * @throws ReflectionException
69
-     */
70
-    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
71
-    {
72
-        return new self($props_n_values, true, $timezone);
73
-    }
74
-
75
-
76
-    /**
77
-     * Adds some defaults if they're not specified
78
-     *
79
-     * @param array  $fieldValues
80
-     * @param bool   $bydb
81
-     * @param string $timezone
82
-     * @throws EE_Error
83
-     * @throws InvalidArgumentException
84
-     * @throws InvalidDataTypeException
85
-     * @throws InvalidInterfaceException
86
-     * @throws ReflectionException
87
-     */
88
-    protected function __construct($fieldValues = array(), $bydb = false, $timezone = '')
89
-    {
90
-        parent::__construct($fieldValues, $bydb, $timezone);
91
-        if (! $this->get('LIN_code')) {
92
-            $this->set_code($this->generate_code());
93
-        }
94
-    }
95
-
96
-
97
-    /**
98
-     * Gets ID
99
-     *
100
-     * @return int
101
-     * @throws EE_Error
102
-     * @throws InvalidArgumentException
103
-     * @throws InvalidDataTypeException
104
-     * @throws InvalidInterfaceException
105
-     * @throws ReflectionException
106
-     */
107
-    public function ID()
108
-    {
109
-        return $this->get('LIN_ID');
110
-    }
111
-
112
-
113
-    /**
114
-     * Gets TXN_ID
115
-     *
116
-     * @return int
117
-     * @throws EE_Error
118
-     * @throws InvalidArgumentException
119
-     * @throws InvalidDataTypeException
120
-     * @throws InvalidInterfaceException
121
-     * @throws ReflectionException
122
-     */
123
-    public function TXN_ID()
124
-    {
125
-        return $this->get('TXN_ID');
126
-    }
127
-
128
-
129
-    /**
130
-     * Sets TXN_ID
131
-     *
132
-     * @param int $TXN_ID
133
-     * @throws EE_Error
134
-     * @throws InvalidArgumentException
135
-     * @throws InvalidDataTypeException
136
-     * @throws InvalidInterfaceException
137
-     * @throws ReflectionException
138
-     */
139
-    public function set_TXN_ID($TXN_ID)
140
-    {
141
-        $this->set('TXN_ID', $TXN_ID);
142
-    }
143
-
144
-
145
-    /**
146
-     * Gets name
147
-     *
148
-     * @return string
149
-     * @throws EE_Error
150
-     * @throws InvalidArgumentException
151
-     * @throws InvalidDataTypeException
152
-     * @throws InvalidInterfaceException
153
-     * @throws ReflectionException
154
-     */
155
-    public function name()
156
-    {
157
-        $name = $this->get('LIN_name');
158
-        if (! $name) {
159
-            $name = ucwords(str_replace('-', ' ', $this->type()));
160
-        }
161
-        return $name;
162
-    }
163
-
164
-
165
-    /**
166
-     * Sets name
167
-     *
168
-     * @param string $name
169
-     * @throws EE_Error
170
-     * @throws InvalidArgumentException
171
-     * @throws InvalidDataTypeException
172
-     * @throws InvalidInterfaceException
173
-     * @throws ReflectionException
174
-     */
175
-    public function set_name($name)
176
-    {
177
-        $this->set('LIN_name', $name);
178
-    }
179
-
180
-
181
-    /**
182
-     * Gets desc
183
-     *
184
-     * @return string
185
-     * @throws EE_Error
186
-     * @throws InvalidArgumentException
187
-     * @throws InvalidDataTypeException
188
-     * @throws InvalidInterfaceException
189
-     * @throws ReflectionException
190
-     */
191
-    public function desc()
192
-    {
193
-        return $this->get('LIN_desc');
194
-    }
195
-
196
-
197
-    /**
198
-     * Sets desc
199
-     *
200
-     * @param string $desc
201
-     * @throws EE_Error
202
-     * @throws InvalidArgumentException
203
-     * @throws InvalidDataTypeException
204
-     * @throws InvalidInterfaceException
205
-     * @throws ReflectionException
206
-     */
207
-    public function set_desc($desc)
208
-    {
209
-        $this->set('LIN_desc', $desc);
210
-    }
211
-
212
-
213
-    /**
214
-     * Gets quantity
215
-     *
216
-     * @return int
217
-     * @throws EE_Error
218
-     * @throws InvalidArgumentException
219
-     * @throws InvalidDataTypeException
220
-     * @throws InvalidInterfaceException
221
-     * @throws ReflectionException
222
-     */
223
-    public function quantity()
224
-    {
225
-        return $this->get('LIN_quantity');
226
-    }
227
-
228
-
229
-    /**
230
-     * Sets quantity
231
-     *
232
-     * @param int $quantity
233
-     * @throws EE_Error
234
-     * @throws InvalidArgumentException
235
-     * @throws InvalidDataTypeException
236
-     * @throws InvalidInterfaceException
237
-     * @throws ReflectionException
238
-     */
239
-    public function set_quantity($quantity)
240
-    {
241
-        $this->set('LIN_quantity', max($quantity, 0));
242
-    }
243
-
244
-
245
-    /**
246
-     * Gets item_id
247
-     *
248
-     * @return string
249
-     * @throws EE_Error
250
-     * @throws InvalidArgumentException
251
-     * @throws InvalidDataTypeException
252
-     * @throws InvalidInterfaceException
253
-     * @throws ReflectionException
254
-     */
255
-    public function OBJ_ID()
256
-    {
257
-        return $this->get('OBJ_ID');
258
-    }
259
-
260
-
261
-    /**
262
-     * Sets item_id
263
-     *
264
-     * @param string $item_id
265
-     * @throws EE_Error
266
-     * @throws InvalidArgumentException
267
-     * @throws InvalidDataTypeException
268
-     * @throws InvalidInterfaceException
269
-     * @throws ReflectionException
270
-     */
271
-    public function set_OBJ_ID($item_id)
272
-    {
273
-        $this->set('OBJ_ID', $item_id);
274
-    }
275
-
276
-
277
-    /**
278
-     * Gets item_type
279
-     *
280
-     * @return string
281
-     * @throws EE_Error
282
-     * @throws InvalidArgumentException
283
-     * @throws InvalidDataTypeException
284
-     * @throws InvalidInterfaceException
285
-     * @throws ReflectionException
286
-     */
287
-    public function OBJ_type()
288
-    {
289
-        return $this->get('OBJ_type');
290
-    }
291
-
292
-
293
-    /**
294
-     * Gets item_type
295
-     *
296
-     * @return string
297
-     * @throws EE_Error
298
-     * @throws InvalidArgumentException
299
-     * @throws InvalidDataTypeException
300
-     * @throws InvalidInterfaceException
301
-     * @throws ReflectionException
302
-     */
303
-    public function OBJ_type_i18n()
304
-    {
305
-        $obj_type = $this->OBJ_type();
306
-        switch ($obj_type) {
307
-            case EEM_Line_Item::OBJ_TYPE_EVENT:
308
-                $obj_type = esc_html__('Event', 'event_espresso');
309
-                break;
310
-            case EEM_Line_Item::OBJ_TYPE_PRICE:
311
-                $obj_type = esc_html__('Price', 'event_espresso');
312
-                break;
313
-            case EEM_Line_Item::OBJ_TYPE_PROMOTION:
314
-                $obj_type = esc_html__('Promotion', 'event_espresso');
315
-                break;
316
-            case EEM_Line_Item::OBJ_TYPE_TICKET:
317
-                $obj_type = esc_html__('Ticket', 'event_espresso');
318
-                break;
319
-            case EEM_Line_Item::OBJ_TYPE_TRANSACTION:
320
-                $obj_type = esc_html__('Transaction', 'event_espresso');
321
-                break;
322
-        }
323
-        return apply_filters('FHEE__EE_Line_Item__OBJ_type_i18n', $obj_type, $this);
324
-    }
325
-
326
-
327
-    /**
328
-     * Sets item_type
329
-     *
330
-     * @param string $OBJ_type
331
-     * @throws EE_Error
332
-     * @throws InvalidArgumentException
333
-     * @throws InvalidDataTypeException
334
-     * @throws InvalidInterfaceException
335
-     * @throws ReflectionException
336
-     */
337
-    public function set_OBJ_type($OBJ_type)
338
-    {
339
-        $this->set('OBJ_type', $OBJ_type);
340
-    }
341
-
342
-
343
-    /**
344
-     * Gets unit_price
345
-     *
346
-     * @return float
347
-     * @throws EE_Error
348
-     * @throws InvalidArgumentException
349
-     * @throws InvalidDataTypeException
350
-     * @throws InvalidInterfaceException
351
-     * @throws ReflectionException
352
-     */
353
-    public function unit_price()
354
-    {
355
-        return $this->get('LIN_unit_price');
356
-    }
357
-
358
-
359
-    /**
360
-     * Sets unit_price
361
-     *
362
-     * @param float $unit_price
363
-     * @throws EE_Error
364
-     * @throws InvalidArgumentException
365
-     * @throws InvalidDataTypeException
366
-     * @throws InvalidInterfaceException
367
-     * @throws ReflectionException
368
-     */
369
-    public function set_unit_price($unit_price)
370
-    {
371
-        $this->set('LIN_unit_price', $unit_price);
372
-    }
373
-
374
-
375
-    /**
376
-     * Checks if this item is a percentage modifier or not
377
-     *
378
-     * @return boolean
379
-     * @throws EE_Error
380
-     * @throws InvalidArgumentException
381
-     * @throws InvalidDataTypeException
382
-     * @throws InvalidInterfaceException
383
-     * @throws ReflectionException
384
-     */
385
-    public function is_percent()
386
-    {
387
-        if ($this->is_tax_sub_total()) {
388
-            // tax subtotals HAVE a percent on them, that percentage only applies
389
-            // to taxable items, so its' an exception. Treat it like a flat line item
390
-            return false;
391
-        }
392
-        $unit_price = abs($this->get('LIN_unit_price'));
393
-        $percent = abs($this->get('LIN_percent'));
394
-        if ($unit_price < .001 && $percent) {
395
-            return true;
396
-        }
397
-        if ($unit_price >= .001 && ! $percent) {
398
-            return false;
399
-        }
400
-        if ($unit_price >= .001 && $percent) {
401
-            throw new EE_Error(
402
-                sprintf(
403
-                    esc_html__(
404
-                        'A Line Item can not have a unit price of (%s) AND a percent (%s)!',
405
-                        'event_espresso'
406
-                    ),
407
-                    $unit_price,
408
-                    $percent
409
-                )
410
-            );
411
-        }
412
-        // if they're both 0, assume its not a percent item
413
-        return false;
414
-    }
415
-
416
-
417
-    /**
418
-     * Gets percent (between 100-.001)
419
-     *
420
-     * @return float
421
-     * @throws EE_Error
422
-     * @throws InvalidArgumentException
423
-     * @throws InvalidDataTypeException
424
-     * @throws InvalidInterfaceException
425
-     * @throws ReflectionException
426
-     */
427
-    public function percent()
428
-    {
429
-        return $this->get('LIN_percent');
430
-    }
431
-
432
-
433
-    /**
434
-     * Sets percent (between 100-0.01)
435
-     *
436
-     * @param float $percent
437
-     * @throws EE_Error
438
-     * @throws InvalidArgumentException
439
-     * @throws InvalidDataTypeException
440
-     * @throws InvalidInterfaceException
441
-     * @throws ReflectionException
442
-     */
443
-    public function set_percent($percent)
444
-    {
445
-        $this->set('LIN_percent', $percent);
446
-    }
447
-
448
-
449
-    /**
450
-     * Gets total
451
-     *
452
-     * @return float
453
-     * @throws EE_Error
454
-     * @throws InvalidArgumentException
455
-     * @throws InvalidDataTypeException
456
-     * @throws InvalidInterfaceException
457
-     * @throws ReflectionException
458
-     */
459
-    public function total()
460
-    {
461
-        return $this->get('LIN_total');
462
-    }
463
-
464
-
465
-    /**
466
-     * Sets total
467
-     *
468
-     * @param float $total
469
-     * @throws EE_Error
470
-     * @throws InvalidArgumentException
471
-     * @throws InvalidDataTypeException
472
-     * @throws InvalidInterfaceException
473
-     * @throws ReflectionException
474
-     */
475
-    public function set_total($total)
476
-    {
477
-        $this->set('LIN_total', $total);
478
-    }
479
-
480
-
481
-    /**
482
-     * Gets order
483
-     *
484
-     * @return int
485
-     * @throws EE_Error
486
-     * @throws InvalidArgumentException
487
-     * @throws InvalidDataTypeException
488
-     * @throws InvalidInterfaceException
489
-     * @throws ReflectionException
490
-     */
491
-    public function order()
492
-    {
493
-        return $this->get('LIN_order');
494
-    }
495
-
496
-
497
-    /**
498
-     * Sets order
499
-     *
500
-     * @param int $order
501
-     * @throws EE_Error
502
-     * @throws InvalidArgumentException
503
-     * @throws InvalidDataTypeException
504
-     * @throws InvalidInterfaceException
505
-     * @throws ReflectionException
506
-     */
507
-    public function set_order($order)
508
-    {
509
-        $this->set('LIN_order', $order);
510
-    }
511
-
512
-
513
-    /**
514
-     * Gets parent
515
-     *
516
-     * @return int
517
-     * @throws EE_Error
518
-     * @throws InvalidArgumentException
519
-     * @throws InvalidDataTypeException
520
-     * @throws InvalidInterfaceException
521
-     * @throws ReflectionException
522
-     */
523
-    public function parent_ID()
524
-    {
525
-        return $this->get('LIN_parent');
526
-    }
527
-
528
-
529
-    /**
530
-     * Sets parent
531
-     *
532
-     * @param int $parent
533
-     * @throws EE_Error
534
-     * @throws InvalidArgumentException
535
-     * @throws InvalidDataTypeException
536
-     * @throws InvalidInterfaceException
537
-     * @throws ReflectionException
538
-     */
539
-    public function set_parent_ID($parent)
540
-    {
541
-        $this->set('LIN_parent', $parent);
542
-    }
543
-
544
-
545
-    /**
546
-     * Gets type
547
-     *
548
-     * @return string
549
-     * @throws EE_Error
550
-     * @throws InvalidArgumentException
551
-     * @throws InvalidDataTypeException
552
-     * @throws InvalidInterfaceException
553
-     * @throws ReflectionException
554
-     */
555
-    public function type()
556
-    {
557
-        return $this->get('LIN_type');
558
-    }
559
-
560
-
561
-    /**
562
-     * Sets type
563
-     *
564
-     * @param string $type
565
-     * @throws EE_Error
566
-     * @throws InvalidArgumentException
567
-     * @throws InvalidDataTypeException
568
-     * @throws InvalidInterfaceException
569
-     * @throws ReflectionException
570
-     */
571
-    public function set_type($type)
572
-    {
573
-        $this->set('LIN_type', $type);
574
-    }
575
-
576
-
577
-    /**
578
-     * Gets the line item of which this item is a composite. Eg, if this is a subtotal, the parent might be a total\
579
-     * If this line item is saved to the DB, fetches the parent from the DB. However, if this line item isn't in the DB
580
-     * it uses its cached reference to its parent line item (which would have been set by `EE_Line_Item::set_parent()`
581
-     * or indirectly by `EE_Line_item::add_child_line_item()`)
582
-     *
583
-     * @return EE_Base_Class|EE_Line_Item
584
-     * @throws EE_Error
585
-     * @throws InvalidArgumentException
586
-     * @throws InvalidDataTypeException
587
-     * @throws InvalidInterfaceException
588
-     * @throws ReflectionException
589
-     */
590
-    public function parent()
591
-    {
592
-        return $this->ID()
593
-            ? $this->get_model()->get_one_by_ID($this->parent_ID())
594
-            : $this->_parent;
595
-    }
596
-
597
-
598
-    /**
599
-     * Gets ALL the children of this line item (ie, all the parts that contribute towards this total).
600
-     *
601
-     * @return EE_Base_Class[]|EE_Line_Item[]
602
-     * @throws EE_Error
603
-     * @throws InvalidArgumentException
604
-     * @throws InvalidDataTypeException
605
-     * @throws InvalidInterfaceException
606
-     * @throws ReflectionException
607
-     */
608
-    public function children()
609
-    {
610
-        if ($this->ID()) {
611
-            return $this->get_model()->get_all(
612
-                array(
613
-                    array('LIN_parent' => $this->ID()),
614
-                    'order_by' => array('LIN_order' => 'ASC'),
615
-                )
616
-            );
617
-        }
618
-        if (! is_array($this->_children)) {
619
-            $this->_children = array();
620
-        }
621
-        return $this->_children;
622
-    }
623
-
624
-
625
-    /**
626
-     * Gets code
627
-     *
628
-     * @return string
629
-     * @throws EE_Error
630
-     * @throws InvalidArgumentException
631
-     * @throws InvalidDataTypeException
632
-     * @throws InvalidInterfaceException
633
-     * @throws ReflectionException
634
-     */
635
-    public function code()
636
-    {
637
-        return $this->get('LIN_code');
638
-    }
639
-
640
-
641
-    /**
642
-     * Sets code
643
-     *
644
-     * @param string $code
645
-     * @throws EE_Error
646
-     * @throws InvalidArgumentException
647
-     * @throws InvalidDataTypeException
648
-     * @throws InvalidInterfaceException
649
-     * @throws ReflectionException
650
-     */
651
-    public function set_code($code)
652
-    {
653
-        $this->set('LIN_code', $code);
654
-    }
655
-
656
-
657
-    /**
658
-     * Gets is_taxable
659
-     *
660
-     * @return boolean
661
-     * @throws EE_Error
662
-     * @throws InvalidArgumentException
663
-     * @throws InvalidDataTypeException
664
-     * @throws InvalidInterfaceException
665
-     * @throws ReflectionException
666
-     */
667
-    public function is_taxable()
668
-    {
669
-        return $this->get('LIN_is_taxable');
670
-    }
671
-
672
-
673
-    /**
674
-     * Sets is_taxable
675
-     *
676
-     * @param boolean $is_taxable
677
-     * @throws EE_Error
678
-     * @throws InvalidArgumentException
679
-     * @throws InvalidDataTypeException
680
-     * @throws InvalidInterfaceException
681
-     * @throws ReflectionException
682
-     */
683
-    public function set_is_taxable($is_taxable)
684
-    {
685
-        $this->set('LIN_is_taxable', $is_taxable);
686
-    }
687
-
688
-
689
-    /**
690
-     * Gets the object that this model-joins-to.
691
-     * returns one of the model objects that the field OBJ_ID can point to... see the 'OBJ_ID' field on
692
-     * EEM_Promotion_Object
693
-     *        Eg, if this line item join model object is for a ticket, this will return the EE_Ticket object
694
-     *
695
-     * @return EE_Base_Class | NULL
696
-     * @throws EE_Error
697
-     * @throws InvalidArgumentException
698
-     * @throws InvalidDataTypeException
699
-     * @throws InvalidInterfaceException
700
-     * @throws ReflectionException
701
-     */
702
-    public function get_object()
703
-    {
704
-        $model_name_of_related_obj = $this->OBJ_type();
705
-        return $this->get_model()->has_relation($model_name_of_related_obj)
706
-            ? $this->get_first_related($model_name_of_related_obj)
707
-            : null;
708
-    }
709
-
710
-
711
-    /**
712
-     * Like EE_Line_Item::get_object(), but can only ever actually return an EE_Ticket.
713
-     * (IE, if this line item is for a price or something else, will return NULL)
714
-     *
715
-     * @param array $query_params
716
-     * @return EE_Base_Class|EE_Ticket
717
-     * @throws EE_Error
718
-     * @throws InvalidArgumentException
719
-     * @throws InvalidDataTypeException
720
-     * @throws InvalidInterfaceException
721
-     * @throws ReflectionException
722
-     */
723
-    public function ticket($query_params = array())
724
-    {
725
-        // we're going to assume that when this method is called
726
-        // we always want to receive the attached ticket EVEN if that ticket is archived.
727
-        // This can be overridden via the incoming $query_params argument
728
-        $remove_defaults = array('default_where_conditions' => 'none');
729
-        $query_params = array_merge($remove_defaults, $query_params);
730
-        return $this->get_first_related(EEM_Line_Item::OBJ_TYPE_TICKET, $query_params);
731
-    }
732
-
733
-
734
-    /**
735
-     * Gets the EE_Datetime that's related to the ticket, IF this is for a ticket
736
-     *
737
-     * @return EE_Datetime | NULL
738
-     * @throws EE_Error
739
-     * @throws InvalidArgumentException
740
-     * @throws InvalidDataTypeException
741
-     * @throws InvalidInterfaceException
742
-     * @throws ReflectionException
743
-     */
744
-    public function get_ticket_datetime()
745
-    {
746
-        if ($this->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
747
-            $ticket = $this->ticket();
748
-            if ($ticket instanceof EE_Ticket) {
749
-                $datetime = $ticket->first_datetime();
750
-                if ($datetime instanceof EE_Datetime) {
751
-                    return $datetime;
752
-                }
753
-            }
754
-        }
755
-        return null;
756
-    }
757
-
758
-
759
-    /**
760
-     * Gets the event's name that's related to the ticket, if this is for
761
-     * a ticket
762
-     *
763
-     * @return string
764
-     * @throws EE_Error
765
-     * @throws InvalidArgumentException
766
-     * @throws InvalidDataTypeException
767
-     * @throws InvalidInterfaceException
768
-     * @throws ReflectionException
769
-     */
770
-    public function ticket_event_name()
771
-    {
772
-        $event_name = esc_html__('Unknown', 'event_espresso');
773
-        $event = $this->ticket_event();
774
-        if ($event instanceof EE_Event) {
775
-            $event_name = $event->name();
776
-        }
777
-        return $event_name;
778
-    }
779
-
780
-
781
-    /**
782
-     * Gets the event that's related to the ticket, if this line item represents a ticket.
783
-     *
784
-     * @return EE_Event|null
785
-     * @throws EE_Error
786
-     * @throws InvalidArgumentException
787
-     * @throws InvalidDataTypeException
788
-     * @throws InvalidInterfaceException
789
-     * @throws ReflectionException
790
-     */
791
-    public function ticket_event()
792
-    {
793
-        $event = null;
794
-        $ticket = $this->ticket();
795
-        if ($ticket instanceof EE_Ticket) {
796
-            $datetime = $ticket->first_datetime();
797
-            if ($datetime instanceof EE_Datetime) {
798
-                $event = $datetime->event();
799
-            }
800
-        }
801
-        return $event;
802
-    }
803
-
804
-
805
-    /**
806
-     * Gets the first datetime for this lien item, assuming it's for a ticket
807
-     *
808
-     * @param string $date_format
809
-     * @param string $time_format
810
-     * @return string
811
-     * @throws EE_Error
812
-     * @throws InvalidArgumentException
813
-     * @throws InvalidDataTypeException
814
-     * @throws InvalidInterfaceException
815
-     * @throws ReflectionException
816
-     */
817
-    public function ticket_datetime_start($date_format = '', $time_format = '')
818
-    {
819
-        $first_datetime_string = esc_html__('Unknown', 'event_espresso');
820
-        $datetime = $this->get_ticket_datetime();
821
-        if ($datetime) {
822
-            $first_datetime_string = $datetime->start_date_and_time($date_format, $time_format);
823
-        }
824
-        return $first_datetime_string;
825
-    }
826
-
827
-
828
-    /**
829
-     * Adds the line item as a child to this line item. If there is another child line
830
-     * item with the same LIN_code, it is overwritten by this new one
831
-     *
832
-     * @param EEI_Line_Item $line_item
833
-     * @param bool          $set_order
834
-     * @return bool success
835
-     * @throws EE_Error
836
-     * @throws InvalidArgumentException
837
-     * @throws InvalidDataTypeException
838
-     * @throws InvalidInterfaceException
839
-     * @throws ReflectionException
840
-     */
841
-    public function add_child_line_item(EEI_Line_Item $line_item, $set_order = true)
842
-    {
843
-        // should we calculate the LIN_order for this line item ?
844
-        if ($set_order || $line_item->order() === null) {
845
-            $line_item->set_order(count($this->children()));
846
-        }
847
-        if ($this->ID()) {
848
-            // check for any duplicate line items (with the same code), if so, this replaces it
849
-            $line_item_with_same_code = $this->get_child_line_item($line_item->code());
850
-            if ($line_item_with_same_code instanceof EE_Line_Item && $line_item_with_same_code !== $line_item) {
851
-                $this->delete_child_line_item($line_item_with_same_code->code());
852
-            }
853
-            $line_item->set_parent_ID($this->ID());
854
-            if ($this->TXN_ID()) {
855
-                $line_item->set_TXN_ID($this->TXN_ID());
856
-            }
857
-            return $line_item->save();
858
-        }
859
-        $this->_children[ $line_item->code() ] = $line_item;
860
-        if ($line_item->parent() !== $this) {
861
-            $line_item->set_parent($this);
862
-        }
863
-        return true;
864
-    }
865
-
866
-
867
-    /**
868
-     * Similar to EE_Base_Class::_add_relation_to, except this isn't a normal relation.
869
-     * If this line item is saved to the DB, this is just a wrapper for set_parent_ID() and save()
870
-     * However, if this line item is NOT saved to the DB, this just caches the parent on
871
-     * the EE_Line_Item::_parent property.
872
-     *
873
-     * @param EE_Line_Item $line_item
874
-     * @throws EE_Error
875
-     * @throws InvalidArgumentException
876
-     * @throws InvalidDataTypeException
877
-     * @throws InvalidInterfaceException
878
-     * @throws ReflectionException
879
-     */
880
-    public function set_parent($line_item)
881
-    {
882
-        if ($this->ID()) {
883
-            if (! $line_item->ID()) {
884
-                $line_item->save();
885
-            }
886
-            $this->set_parent_ID($line_item->ID());
887
-            $this->save();
888
-        } else {
889
-            $this->_parent = $line_item;
890
-            $this->set_parent_ID($line_item->ID());
891
-        }
892
-    }
893
-
894
-
895
-    /**
896
-     * Gets the child line item as specified by its code. Because this returns an object (by reference)
897
-     * you can modify this child line item and the parent (this object) can know about them
898
-     * because it also has a reference to that line item
899
-     *
900
-     * @param string $code
901
-     * @return EE_Base_Class|EE_Line_Item|EE_Soft_Delete_Base_Class|NULL
902
-     * @throws EE_Error
903
-     * @throws InvalidArgumentException
904
-     * @throws InvalidDataTypeException
905
-     * @throws InvalidInterfaceException
906
-     * @throws ReflectionException
907
-     */
908
-    public function get_child_line_item($code)
909
-    {
910
-        if ($this->ID()) {
911
-            return $this->get_model()->get_one(
912
-                array(array('LIN_parent' => $this->ID(), 'LIN_code' => $code))
913
-            );
914
-        }
915
-        return isset($this->_children[ $code ])
916
-            ? $this->_children[ $code ]
917
-            : null;
918
-    }
919
-
920
-
921
-    /**
922
-     * Returns how many items are deleted (or, if this item has not been saved ot the DB yet, just how many it HAD
923
-     * cached on it)
924
-     *
925
-     * @return int
926
-     * @throws EE_Error
927
-     * @throws InvalidArgumentException
928
-     * @throws InvalidDataTypeException
929
-     * @throws InvalidInterfaceException
930
-     * @throws ReflectionException
931
-     */
932
-    public function delete_children_line_items()
933
-    {
934
-        if ($this->ID()) {
935
-            return $this->get_model()->delete(array(array('LIN_parent' => $this->ID())));
936
-        }
937
-        $count = count($this->_children);
938
-        $this->_children = array();
939
-        return $count;
940
-    }
941
-
942
-
943
-    /**
944
-     * If this line item has been saved to the DB, deletes its child with LIN_code == $code. If this line
945
-     * HAS NOT been saved to the DB, removes the child line item with index $code.
946
-     * Also searches through the child's children for a matching line item. However, once a line item has been found
947
-     * and deleted, stops searching (so if there are line items with duplicate codes, only the first one found will be
948
-     * deleted)
949
-     *
950
-     * @param string $code
951
-     * @param bool   $stop_search_once_found
952
-     * @return int count of items deleted (or simply removed from the line item's cache, if not has not been saved to
953
-     *             the DB yet)
954
-     * @throws EE_Error
955
-     * @throws InvalidArgumentException
956
-     * @throws InvalidDataTypeException
957
-     * @throws InvalidInterfaceException
958
-     * @throws ReflectionException
959
-     */
960
-    public function delete_child_line_item($code, $stop_search_once_found = true)
961
-    {
962
-        if ($this->ID()) {
963
-            $items_deleted = 0;
964
-            if ($this->code() === $code) {
965
-                $items_deleted += EEH_Line_Item::delete_all_child_items($this);
966
-                $items_deleted += (int) $this->delete();
967
-                if ($stop_search_once_found) {
968
-                    return $items_deleted;
969
-                }
970
-            }
971
-            foreach ($this->children() as $child_line_item) {
972
-                $items_deleted += $child_line_item->delete_child_line_item($code, $stop_search_once_found);
973
-            }
974
-            return $items_deleted;
975
-        }
976
-        if (isset($this->_children[ $code ])) {
977
-            unset($this->_children[ $code ]);
978
-            return 1;
979
-        }
980
-        return 0;
981
-    }
982
-
983
-
984
-    /**
985
-     * If this line item is in the database, is of the type subtotal, and
986
-     * has no children, why do we have it? It should be deleted so this function
987
-     * does that
988
-     *
989
-     * @return boolean
990
-     * @throws EE_Error
991
-     * @throws InvalidArgumentException
992
-     * @throws InvalidDataTypeException
993
-     * @throws InvalidInterfaceException
994
-     * @throws ReflectionException
995
-     */
996
-    public function delete_if_childless_subtotal()
997
-    {
998
-        if ($this->ID() && $this->type() === EEM_Line_Item::type_sub_total && ! $this->children()) {
999
-            return $this->delete();
1000
-        }
1001
-        return false;
1002
-    }
1003
-
1004
-
1005
-    /**
1006
-     * Creates a code and returns a string. doesn't assign the code to this model object
1007
-     *
1008
-     * @return string
1009
-     * @throws EE_Error
1010
-     * @throws InvalidArgumentException
1011
-     * @throws InvalidDataTypeException
1012
-     * @throws InvalidInterfaceException
1013
-     * @throws ReflectionException
1014
-     */
1015
-    public function generate_code()
1016
-    {
1017
-        // each line item in the cart requires a unique identifier
1018
-        return md5($this->get('OBJ_type') . $this->get('OBJ_ID') . microtime());
1019
-    }
1020
-
1021
-
1022
-    /**
1023
-     * @return bool
1024
-     * @throws EE_Error
1025
-     * @throws InvalidArgumentException
1026
-     * @throws InvalidDataTypeException
1027
-     * @throws InvalidInterfaceException
1028
-     * @throws ReflectionException
1029
-     */
1030
-    public function is_tax()
1031
-    {
1032
-        return $this->type() === EEM_Line_Item::type_tax;
1033
-    }
1034
-
1035
-
1036
-    /**
1037
-     * @return bool
1038
-     * @throws EE_Error
1039
-     * @throws InvalidArgumentException
1040
-     * @throws InvalidDataTypeException
1041
-     * @throws InvalidInterfaceException
1042
-     * @throws ReflectionException
1043
-     */
1044
-    public function is_tax_sub_total()
1045
-    {
1046
-        return $this->type() === EEM_Line_Item::type_tax_sub_total;
1047
-    }
1048
-
1049
-
1050
-    /**
1051
-     * @return bool
1052
-     * @throws EE_Error
1053
-     * @throws InvalidArgumentException
1054
-     * @throws InvalidDataTypeException
1055
-     * @throws InvalidInterfaceException
1056
-     * @throws ReflectionException
1057
-     */
1058
-    public function is_line_item()
1059
-    {
1060
-        return $this->type() === EEM_Line_Item::type_line_item;
1061
-    }
1062
-
1063
-
1064
-    /**
1065
-     * @return bool
1066
-     * @throws EE_Error
1067
-     * @throws InvalidArgumentException
1068
-     * @throws InvalidDataTypeException
1069
-     * @throws InvalidInterfaceException
1070
-     * @throws ReflectionException
1071
-     */
1072
-    public function is_sub_line_item()
1073
-    {
1074
-        return $this->type() === EEM_Line_Item::type_sub_line_item;
1075
-    }
1076
-
1077
-
1078
-    /**
1079
-     * @return bool
1080
-     * @throws EE_Error
1081
-     * @throws InvalidArgumentException
1082
-     * @throws InvalidDataTypeException
1083
-     * @throws InvalidInterfaceException
1084
-     * @throws ReflectionException
1085
-     */
1086
-    public function is_sub_total()
1087
-    {
1088
-        return $this->type() === EEM_Line_Item::type_sub_total;
1089
-    }
1090
-
1091
-
1092
-    /**
1093
-     * Whether or not this line item is a cancellation line item
1094
-     *
1095
-     * @return boolean
1096
-     * @throws EE_Error
1097
-     * @throws InvalidArgumentException
1098
-     * @throws InvalidDataTypeException
1099
-     * @throws InvalidInterfaceException
1100
-     * @throws ReflectionException
1101
-     */
1102
-    public function is_cancellation()
1103
-    {
1104
-        return EEM_Line_Item::type_cancellation === $this->type();
1105
-    }
1106
-
1107
-
1108
-    /**
1109
-     * @return bool
1110
-     * @throws EE_Error
1111
-     * @throws InvalidArgumentException
1112
-     * @throws InvalidDataTypeException
1113
-     * @throws InvalidInterfaceException
1114
-     * @throws ReflectionException
1115
-     */
1116
-    public function is_total()
1117
-    {
1118
-        return $this->type() === EEM_Line_Item::type_total;
1119
-    }
1120
-
1121
-
1122
-    /**
1123
-     * @return bool
1124
-     * @throws EE_Error
1125
-     * @throws InvalidArgumentException
1126
-     * @throws InvalidDataTypeException
1127
-     * @throws InvalidInterfaceException
1128
-     * @throws ReflectionException
1129
-     */
1130
-    public function is_cancelled()
1131
-    {
1132
-        return $this->type() === EEM_Line_Item::type_cancellation;
1133
-    }
1134
-
1135
-
1136
-    /**
1137
-     * @return string like '2, 004.00', formatted according to the localized currency
1138
-     * @throws EE_Error
1139
-     * @throws InvalidArgumentException
1140
-     * @throws InvalidDataTypeException
1141
-     * @throws InvalidInterfaceException
1142
-     * @throws ReflectionException
1143
-     */
1144
-    public function unit_price_no_code()
1145
-    {
1146
-        return $this->get_pretty('LIN_unit_price', 'no_currency_code');
1147
-    }
1148
-
1149
-
1150
-    /**
1151
-     * @return string like '2, 004.00', formatted according to the localized currency
1152
-     * @throws EE_Error
1153
-     * @throws InvalidArgumentException
1154
-     * @throws InvalidDataTypeException
1155
-     * @throws InvalidInterfaceException
1156
-     * @throws ReflectionException
1157
-     */
1158
-    public function total_no_code()
1159
-    {
1160
-        return $this->get_pretty('LIN_total', 'no_currency_code');
1161
-    }
1162
-
1163
-
1164
-    /**
1165
-     * Gets the final total on this item, taking taxes into account.
1166
-     * Has the side-effect of setting the sub-total as it was just calculated.
1167
-     * If this is used on a grand-total line item, also updates the transaction's
1168
-     * TXN_total (provided this line item is allowed to persist, otherwise we don't
1169
-     * want to change a persistable transaction with info from a non-persistent line item)
1170
-     *
1171
-     * @param bool $update_txn_status
1172
-     * @return float
1173
-     * @throws EE_Error
1174
-     * @throws InvalidArgumentException
1175
-     * @throws InvalidDataTypeException
1176
-     * @throws InvalidInterfaceException
1177
-     * @throws ReflectionException
1178
-     * @throws RuntimeException
1179
-     */
1180
-    public function recalculate_total_including_taxes($update_txn_status = false)
1181
-    {
1182
-        $pre_tax_total = $this->recalculate_pre_tax_total();
1183
-        $tax_total = $this->recalculate_taxes_and_tax_total();
1184
-        $total = $pre_tax_total + $tax_total;
1185
-        // no negative totals plz
1186
-        $total = max($total, 0);
1187
-        $this->set_total($total);
1188
-        // only update the related transaction's total
1189
-        // if we intend to save this line item and its a grand total
1190
-        if ($this->allow_persist() && $this->type() === EEM_Line_Item::type_total
1191
-            && $this->transaction()
1192
-               instanceof
1193
-               EE_Transaction
1194
-        ) {
1195
-            $this->transaction()->set_total($total);
1196
-            if ($update_txn_status) {
1197
-                // don't save the TXN because that will be done below
1198
-                // and the following method only saves if the status changes
1199
-                $this->transaction()->update_status_based_on_total_paid(false);
1200
-            }
1201
-            if ($this->transaction()->ID()) {
1202
-                $this->transaction()->save();
1203
-            }
1204
-        }
1205
-        $this->maybe_save();
1206
-        return $total;
1207
-    }
1208
-
1209
-
1210
-    /**
1211
-     * Recursively goes through all the children and recalculates sub-totals EXCEPT for
1212
-     * tax-sub-totals (they're a an odd beast). Updates the 'total' on each line item according to either its
1213
-     * unit price * quantity or the total of all its children EXCEPT when we're only calculating the taxable total and
1214
-     * when this is called on the grand total
1215
-     *
1216
-     * @return float
1217
-     * @throws EE_Error
1218
-     * @throws InvalidArgumentException
1219
-     * @throws InvalidDataTypeException
1220
-     * @throws InvalidInterfaceException
1221
-     * @throws ReflectionException
1222
-     */
1223
-    public function recalculate_pre_tax_total()
1224
-    {
1225
-        $total = 0;
1226
-        $my_children = $this->children();
1227
-        $has_children = ! empty($my_children);
1228
-        if ($has_children && $this->is_line_item()) {
1229
-            $total = $this->_recalculate_pretax_total_for_line_item($total, $my_children);
1230
-        } elseif (! $has_children && ($this->is_sub_line_item() || $this->is_line_item())) {
1231
-            $total = $this->unit_price() * $this->quantity();
1232
-        } elseif ($this->is_sub_total() || $this->is_total()) {
1233
-            $total = $this->_recalculate_pretax_total_for_subtotal($total, $my_children);
1234
-        } elseif ($this->is_tax_sub_total() || $this->is_tax() || $this->is_cancelled()) {
1235
-            // completely ignore tax totals, tax sub-totals, and cancelled line items, when calculating the pre-tax-total
1236
-            return 0;
1237
-        }
1238
-        // ensure all non-line items and non-sub-line-items have a quantity of 1 (except for Events)
1239
-        if (! $this->is_line_item() && ! $this->is_sub_line_item() && ! $this->is_cancellation()) {
1240
-            if ($this->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_EVENT) {
1241
-                $this->set_quantity(1);
1242
-            }
1243
-            if (! $this->is_percent()) {
1244
-                $this->set_unit_price($total);
1245
-            }
1246
-        }
1247
-        // we don't want to bother saving grand totals, because that needs to factor in taxes anyways
1248
-        // so it ought to be
1249
-        if (! $this->is_total()) {
1250
-            $this->set_total($total);
1251
-            // if not a percent line item, make sure we keep the unit price in sync
1252
-            if ($has_children
1253
-                && $this->is_line_item()
1254
-                && ! $this->is_percent()
1255
-            ) {
1256
-                if ($this->quantity() === 0) {
1257
-                    $new_unit_price = 0;
1258
-                } else {
1259
-                    $new_unit_price = $this->total() / $this->quantity();
1260
-                }
1261
-                $this->set_unit_price($new_unit_price);
1262
-            }
1263
-            $this->maybe_save();
1264
-        }
1265
-        return $total;
1266
-    }
1267
-
1268
-
1269
-    /**
1270
-     * Calculates the pretax total when this line item is a subtotal or total line item.
1271
-     * Basically does a sum-then-round approach (ie, any percent line item that are children
1272
-     * will calculate their total based on the un-rounded total we're working with so far, and
1273
-     * THEN round the result; instead of rounding as we go like with sub-line-items)
1274
-     *
1275
-     * @param float          $calculated_total_so_far
1276
-     * @param EE_Line_Item[] $my_children
1277
-     * @return float
1278
-     * @throws EE_Error
1279
-     * @throws InvalidArgumentException
1280
-     * @throws InvalidDataTypeException
1281
-     * @throws InvalidInterfaceException
1282
-     * @throws ReflectionException
1283
-     */
1284
-    protected function _recalculate_pretax_total_for_subtotal($calculated_total_so_far, $my_children = null)
1285
-    {
1286
-        if ($my_children === null) {
1287
-            $my_children = $this->children();
1288
-        }
1289
-        $subtotal_quantity = 0;
1290
-        // get the total of all its children
1291
-        foreach ($my_children as $child_line_item) {
1292
-            if ($child_line_item instanceof EE_Line_Item) {
1293
-                // skip line item if it is cancelled or is a tax
1294
-                if ($child_line_item->is_cancellation() || $child_line_item->is_tax()) {
1295
-                    continue;
1296
-                }
1297
-                // percentage line items are based on total so far
1298
-                if ($child_line_item->is_percent()) {
1299
-                    // round as we go so that the line items add up ok
1300
-                    $percent_total = round(
1301
-                        $calculated_total_so_far * $child_line_item->percent() / 100,
1302
-                        EE_Registry::instance()->CFG->currency->dec_plc
1303
-                    );
1304
-                    $child_line_item->set_total($percent_total);
1305
-                    // so far all percent line items should have a quantity of 1
1306
-                    // (ie, no double percent discounts. Although that might be requested someday)
1307
-                    $child_line_item->set_quantity(1);
1308
-                    $child_line_item->maybe_save();
1309
-                    $calculated_total_so_far += $percent_total;
1310
-                } else {
1311
-                    // verify flat sub-line-item quantities match their parent
1312
-                    if ($child_line_item->is_sub_line_item()) {
1313
-                        $child_line_item->set_quantity($this->quantity());
1314
-                    }
1315
-                    $calculated_total_so_far += $child_line_item->recalculate_pre_tax_total();
1316
-                    $subtotal_quantity += $child_line_item->quantity();
1317
-                }
1318
-            }
1319
-        }
1320
-        if ($this->is_sub_total()) {
1321
-            // no negative totals plz
1322
-            $calculated_total_so_far = max($calculated_total_so_far, 0);
1323
-            $subtotal_quantity = $subtotal_quantity > 0 ? 1 : 0;
1324
-            $this->set_quantity($subtotal_quantity);
1325
-            $this->maybe_save();
1326
-        }
1327
-        return $calculated_total_so_far;
1328
-    }
1329
-
1330
-
1331
-    /**
1332
-     * Calculates the pretax total for a normal line item, in a round-then-sum approach
1333
-     * (where each sub-line-item is applied to the base price for the line item
1334
-     * and the result is immediately rounded, rather than summing all the sub-line-items
1335
-     * then rounding, like we do when recalculating pretax totals on totals and subtotals).
1336
-     *
1337
-     * @param float          $calculated_total_so_far
1338
-     * @param EE_Line_Item[] $my_children
1339
-     * @return float
1340
-     * @throws EE_Error
1341
-     * @throws InvalidArgumentException
1342
-     * @throws InvalidDataTypeException
1343
-     * @throws InvalidInterfaceException
1344
-     * @throws ReflectionException
1345
-     */
1346
-    protected function _recalculate_pretax_total_for_line_item($calculated_total_so_far, $my_children = null)
1347
-    {
1348
-        if ($my_children === null) {
1349
-            $my_children = $this->children();
1350
-        }
1351
-        // we need to keep track of the running total for a single item,
1352
-        // because we need to round as we go
1353
-        $unit_price_for_total = 0;
1354
-        $quantity_for_total = 1;
1355
-        // get the total of all its children
1356
-        foreach ($my_children as $child_line_item) {
1357
-            if ($child_line_item instanceof EE_Line_Item) {
1358
-                // skip line item if it is cancelled or is a tax
1359
-                if ($child_line_item->is_cancellation() || $child_line_item->is_tax()) {
1360
-                    continue;
1361
-                }
1362
-                if ($child_line_item->is_percent()) {
1363
-                    // it should be the unit-price-so-far multiplied by teh percent multiplied by the quantity
1364
-                    // not total multiplied by percent, because that ignores rounding along-the-way
1365
-                    $percent_unit_price = round(
1366
-                        $unit_price_for_total * $child_line_item->percent() / 100,
1367
-                        EE_Registry::instance()->CFG->currency->dec_plc
1368
-                    );
1369
-                    $percent_total = $percent_unit_price * $quantity_for_total;
1370
-                    $child_line_item->set_total($percent_total);
1371
-                    // so far all percent line items should have a quantity of 1
1372
-                    // (ie, no double percent discounts. Although that might be requested someday)
1373
-                    $child_line_item->set_quantity(1);
1374
-                    $child_line_item->maybe_save();
1375
-                    $calculated_total_so_far += $percent_total;
1376
-                    $unit_price_for_total += $percent_unit_price;
1377
-                } else {
1378
-                    // verify flat sub-line-item quantities match their parent
1379
-                    if ($child_line_item->is_sub_line_item()) {
1380
-                        $child_line_item->set_quantity($this->quantity());
1381
-                    }
1382
-                    $quantity_for_total = $child_line_item->quantity();
1383
-                    $calculated_total_so_far += $child_line_item->recalculate_pre_tax_total();
1384
-                    $unit_price_for_total += $child_line_item->unit_price();
1385
-                }
1386
-            }
1387
-        }
1388
-        return $calculated_total_so_far;
1389
-    }
1390
-
1391
-
1392
-    /**
1393
-     * Recalculates the total on each individual tax (based on a recalculation of the pre-tax total), sets
1394
-     * the totals on each tax calculated, and returns the final tax total. Re-saves tax line items
1395
-     * and tax sub-total if already in the DB
1396
-     *
1397
-     * @return float
1398
-     * @throws EE_Error
1399
-     * @throws InvalidArgumentException
1400
-     * @throws InvalidDataTypeException
1401
-     * @throws InvalidInterfaceException
1402
-     * @throws ReflectionException
1403
-     */
1404
-    public function recalculate_taxes_and_tax_total()
1405
-    {
1406
-        // get all taxes
1407
-        $taxes = $this->tax_descendants();
1408
-        // calculate the pretax total
1409
-        $taxable_total = $this->taxable_total();
1410
-        $tax_total = 0;
1411
-        foreach ($taxes as $tax) {
1412
-            $total_on_this_tax = $taxable_total * $tax->percent() / 100;
1413
-            // remember the total on this line item
1414
-            $tax->set_total($total_on_this_tax);
1415
-            $tax->maybe_save();
1416
-            $tax_total += $tax->total();
1417
-        }
1418
-        $this->_recalculate_tax_sub_total();
1419
-        return $tax_total;
1420
-    }
1421
-
1422
-
1423
-    /**
1424
-     * Simply forces all the tax-sub-totals to recalculate. Assumes the taxes have been calculated
1425
-     *
1426
-     * @return void
1427
-     * @throws EE_Error
1428
-     * @throws InvalidArgumentException
1429
-     * @throws InvalidDataTypeException
1430
-     * @throws InvalidInterfaceException
1431
-     * @throws ReflectionException
1432
-     */
1433
-    private function _recalculate_tax_sub_total()
1434
-    {
1435
-        if ($this->is_tax_sub_total()) {
1436
-            $total = 0;
1437
-            $total_percent = 0;
1438
-            // simply loop through all its children (which should be taxes) and sum their total
1439
-            foreach ($this->children() as $child_tax) {
1440
-                if ($child_tax instanceof EE_Line_Item) {
1441
-                    $total += $child_tax->total();
1442
-                    $total_percent += $child_tax->percent();
1443
-                }
1444
-            }
1445
-            $this->set_total($total);
1446
-            $this->set_percent($total_percent);
1447
-            $this->maybe_save();
1448
-        } elseif ($this->is_total()) {
1449
-            foreach ($this->children() as $maybe_tax_subtotal) {
1450
-                if ($maybe_tax_subtotal instanceof EE_Line_Item) {
1451
-                    $maybe_tax_subtotal->_recalculate_tax_sub_total();
1452
-                }
1453
-            }
1454
-        }
1455
-    }
1456
-
1457
-
1458
-    /**
1459
-     * Gets the total tax on this line item. Assumes taxes have already been calculated using
1460
-     * recalculate_taxes_and_total
1461
-     *
1462
-     * @return float
1463
-     * @throws EE_Error
1464
-     * @throws InvalidArgumentException
1465
-     * @throws InvalidDataTypeException
1466
-     * @throws InvalidInterfaceException
1467
-     * @throws ReflectionException
1468
-     */
1469
-    public function get_total_tax()
1470
-    {
1471
-        $this->_recalculate_tax_sub_total();
1472
-        $total = 0;
1473
-        foreach ($this->tax_descendants() as $tax_line_item) {
1474
-            if ($tax_line_item instanceof EE_Line_Item) {
1475
-                $total += $tax_line_item->total();
1476
-            }
1477
-        }
1478
-        return $total;
1479
-    }
1480
-
1481
-
1482
-    /**
1483
-     * Gets the total for all the items purchased only
1484
-     *
1485
-     * @return float
1486
-     * @throws EE_Error
1487
-     * @throws InvalidArgumentException
1488
-     * @throws InvalidDataTypeException
1489
-     * @throws InvalidInterfaceException
1490
-     * @throws ReflectionException
1491
-     */
1492
-    public function get_items_total()
1493
-    {
1494
-        // by default, let's make sure we're consistent with the existing line item
1495
-        if ($this->is_total()) {
1496
-            $pretax_subtotal_li = EEH_Line_Item::get_pre_tax_subtotal($this);
1497
-            if ($pretax_subtotal_li instanceof EE_Line_Item) {
1498
-                return $pretax_subtotal_li->total();
1499
-            }
1500
-        }
1501
-        $total = 0;
1502
-        foreach ($this->get_items() as $item) {
1503
-            if ($item instanceof EE_Line_Item) {
1504
-                $total += $item->total();
1505
-            }
1506
-        }
1507
-        return $total;
1508
-    }
1509
-
1510
-
1511
-    /**
1512
-     * Gets all the descendants (ie, children or children of children etc) that
1513
-     * are of the type 'tax'
1514
-     *
1515
-     * @return EE_Line_Item[]
1516
-     * @throws EE_Error
1517
-     */
1518
-    public function tax_descendants()
1519
-    {
1520
-        return EEH_Line_Item::get_tax_descendants($this);
1521
-    }
1522
-
1523
-
1524
-    /**
1525
-     * Gets all the real items purchased which are children of this item
1526
-     *
1527
-     * @return EE_Line_Item[]
1528
-     * @throws EE_Error
1529
-     */
1530
-    public function get_items()
1531
-    {
1532
-        return EEH_Line_Item::get_line_item_descendants($this);
1533
-    }
1534
-
1535
-
1536
-    /**
1537
-     * Returns the amount taxable among this line item's children (or if it has no children,
1538
-     * how much of it is taxable). Does not recalculate totals or subtotals.
1539
-     * If the taxable total is negative, (eg, if none of the tickets were taxable,
1540
-     * but there is a "Taxable" discount), returns 0.
1541
-     *
1542
-     * @return float
1543
-     * @throws EE_Error
1544
-     * @throws InvalidArgumentException
1545
-     * @throws InvalidDataTypeException
1546
-     * @throws InvalidInterfaceException
1547
-     * @throws ReflectionException
1548
-     */
1549
-    public function taxable_total()
1550
-    {
1551
-        $total = 0;
1552
-        if ($this->children()) {
1553
-            foreach ($this->children() as $child_line_item) {
1554
-                if ($child_line_item->type() === EEM_Line_Item::type_line_item && $child_line_item->is_taxable()) {
1555
-                    // if it's a percent item, only take into account the percent
1556
-                    // that's taxable too (the taxable total so far)
1557
-                    if ($child_line_item->is_percent()) {
1558
-                        $total += ($total * $child_line_item->percent() / 100);
1559
-                    } else {
1560
-                        $total += $child_line_item->total();
1561
-                    }
1562
-                } elseif ($child_line_item->type() === EEM_Line_Item::type_sub_total) {
1563
-                    $total += $child_line_item->taxable_total();
1564
-                }
1565
-            }
1566
-        }
1567
-        return max($total, 0);
1568
-    }
1569
-
1570
-
1571
-    /**
1572
-     * Gets the transaction for this line item
1573
-     *
1574
-     * @return EE_Base_Class|EE_Transaction
1575
-     * @throws EE_Error
1576
-     * @throws InvalidArgumentException
1577
-     * @throws InvalidDataTypeException
1578
-     * @throws InvalidInterfaceException
1579
-     * @throws ReflectionException
1580
-     */
1581
-    public function transaction()
1582
-    {
1583
-        return $this->get_first_related(EEM_Line_Item::OBJ_TYPE_TRANSACTION);
1584
-    }
1585
-
1586
-
1587
-    /**
1588
-     * Saves this line item to the DB, and recursively saves its descendants.
1589
-     * Because there currently is no proper parent-child relation on the model,
1590
-     * save_this_and_cached() will NOT save the descendants.
1591
-     * Also sets the transaction on this line item and all its descendants before saving
1592
-     *
1593
-     * @param int $txn_id if none is provided, assumes $this->TXN_ID()
1594
-     * @return int count of items saved
1595
-     * @throws EE_Error
1596
-     * @throws InvalidArgumentException
1597
-     * @throws InvalidDataTypeException
1598
-     * @throws InvalidInterfaceException
1599
-     * @throws ReflectionException
1600
-     */
1601
-    public function save_this_and_descendants_to_txn($txn_id = null)
1602
-    {
1603
-        $count = 0;
1604
-        if (! $txn_id) {
1605
-            $txn_id = $this->TXN_ID();
1606
-        }
1607
-        $this->set_TXN_ID($txn_id);
1608
-        $children = $this->children();
1609
-        $count += $this->save()
1610
-            ? 1
1611
-            : 0;
1612
-        foreach ($children as $child_line_item) {
1613
-            if ($child_line_item instanceof EE_Line_Item) {
1614
-                $child_line_item->set_parent_ID($this->ID());
1615
-                $count += $child_line_item->save_this_and_descendants_to_txn($txn_id);
1616
-            }
1617
-        }
1618
-        return $count;
1619
-    }
1620
-
1621
-
1622
-    /**
1623
-     * Saves this line item to the DB, and recursively saves its descendants.
1624
-     *
1625
-     * @return int count of items saved
1626
-     * @throws EE_Error
1627
-     * @throws InvalidArgumentException
1628
-     * @throws InvalidDataTypeException
1629
-     * @throws InvalidInterfaceException
1630
-     * @throws ReflectionException
1631
-     */
1632
-    public function save_this_and_descendants()
1633
-    {
1634
-        $count = 0;
1635
-        $children = $this->children();
1636
-        $count += $this->save()
1637
-            ? 1
1638
-            : 0;
1639
-        foreach ($children as $child_line_item) {
1640
-            if ($child_line_item instanceof EE_Line_Item) {
1641
-                $child_line_item->set_parent_ID($this->ID());
1642
-                $count += $child_line_item->save_this_and_descendants();
1643
-            }
1644
-        }
1645
-        return $count;
1646
-    }
1647
-
1648
-
1649
-    /**
1650
-     * returns the cancellation line item if this item was cancelled
1651
-     *
1652
-     * @return EE_Line_Item[]
1653
-     * @throws InvalidArgumentException
1654
-     * @throws InvalidInterfaceException
1655
-     * @throws InvalidDataTypeException
1656
-     * @throws ReflectionException
1657
-     * @throws EE_Error
1658
-     */
1659
-    public function get_cancellations()
1660
-    {
1661
-        EE_Registry::instance()->load_helper('Line_Item');
1662
-        return EEH_Line_Item::get_descendants_of_type($this, EEM_Line_Item::type_cancellation);
1663
-    }
1664
-
1665
-
1666
-    /**
1667
-     * If this item has an ID, then this saves it again to update the db
1668
-     *
1669
-     * @return int count of items saved
1670
-     * @throws EE_Error
1671
-     * @throws InvalidArgumentException
1672
-     * @throws InvalidDataTypeException
1673
-     * @throws InvalidInterfaceException
1674
-     * @throws ReflectionException
1675
-     */
1676
-    public function maybe_save()
1677
-    {
1678
-        if ($this->ID()) {
1679
-            return $this->save();
1680
-        }
1681
-        return false;
1682
-    }
1683
-
1684
-
1685
-    /**
1686
-     * clears the cached children and parent from the line item
1687
-     *
1688
-     * @return void
1689
-     */
1690
-    public function clear_related_line_item_cache()
1691
-    {
1692
-        $this->_children = array();
1693
-        $this->_parent = null;
1694
-    }
1695
-
1696
-
1697
-    /**
1698
-     * @param bool $raw
1699
-     * @return int
1700
-     * @throws EE_Error
1701
-     * @throws InvalidArgumentException
1702
-     * @throws InvalidDataTypeException
1703
-     * @throws InvalidInterfaceException
1704
-     * @throws ReflectionException
1705
-     */
1706
-    public function timestamp($raw = false)
1707
-    {
1708
-        return $raw
1709
-            ? $this->get_raw('LIN_timestamp')
1710
-            : $this->get('LIN_timestamp');
1711
-    }
1712
-
1713
-
1714
-
1715
-
1716
-    /************************* DEPRECATED *************************/
1717
-    /**
1718
-     * @deprecated 4.6.0
1719
-     * @param string $type one of the constants on EEM_Line_Item
1720
-     * @return EE_Line_Item[]
1721
-     * @throws EE_Error
1722
-     */
1723
-    protected function _get_descendants_of_type($type)
1724
-    {
1725
-        EE_Error::doing_it_wrong(
1726
-            'EE_Line_Item::_get_descendants_of_type()',
1727
-            sprintf(
1728
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
1729
-                'EEH_Line_Item::get_descendants_of_type()'
1730
-            ),
1731
-            '4.6.0'
1732
-        );
1733
-        return EEH_Line_Item::get_descendants_of_type($this, $type);
1734
-    }
1735
-
1736
-
1737
-    /**
1738
-     * @deprecated 4.6.0
1739
-     * @param string $type like one of the EEM_Line_Item::type_*
1740
-     * @return EE_Line_Item
1741
-     * @throws EE_Error
1742
-     * @throws InvalidArgumentException
1743
-     * @throws InvalidDataTypeException
1744
-     * @throws InvalidInterfaceException
1745
-     * @throws ReflectionException
1746
-     */
1747
-    public function get_nearest_descendant_of_type($type)
1748
-    {
1749
-        EE_Error::doing_it_wrong(
1750
-            'EE_Line_Item::get_nearest_descendant_of_type()',
1751
-            sprintf(
1752
-                esc_html__('Method replaced with %1$s', 'event_espresso'),
1753
-                'EEH_Line_Item::get_nearest_descendant_of_type()'
1754
-            ),
1755
-            '4.6.0'
1756
-        );
1757
-        return EEH_Line_Item::get_nearest_descendant_of_type($this, $type);
1758
-    }
17
+	/**
18
+	 * for children line items (currently not a normal relation)
19
+	 *
20
+	 * @type EE_Line_Item[]
21
+	 */
22
+	protected $_children = array();
23
+
24
+	/**
25
+	 * for the parent line item
26
+	 *
27
+	 * @var EE_Line_Item
28
+	 */
29
+	protected $_parent;
30
+
31
+
32
+	/**
33
+	 * @param array  $props_n_values          incoming values
34
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
35
+	 *                                        used.)
36
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
37
+	 *                                        date_format and the second value is the time format
38
+	 * @return EE_Line_Item
39
+	 * @throws EE_Error
40
+	 * @throws InvalidArgumentException
41
+	 * @throws InvalidDataTypeException
42
+	 * @throws InvalidInterfaceException
43
+	 * @throws ReflectionException
44
+	 */
45
+	public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
46
+	{
47
+		$has_object = parent::_check_for_object(
48
+			$props_n_values,
49
+			__CLASS__,
50
+			$timezone,
51
+			$date_formats
52
+		);
53
+		return $has_object
54
+			? $has_object
55
+			: new self($props_n_values, false, $timezone);
56
+	}
57
+
58
+
59
+	/**
60
+	 * @param array  $props_n_values  incoming values from the database
61
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
62
+	 *                                the website will be used.
63
+	 * @return EE_Line_Item
64
+	 * @throws EE_Error
65
+	 * @throws InvalidArgumentException
66
+	 * @throws InvalidDataTypeException
67
+	 * @throws InvalidInterfaceException
68
+	 * @throws ReflectionException
69
+	 */
70
+	public static function new_instance_from_db($props_n_values = array(), $timezone = null)
71
+	{
72
+		return new self($props_n_values, true, $timezone);
73
+	}
74
+
75
+
76
+	/**
77
+	 * Adds some defaults if they're not specified
78
+	 *
79
+	 * @param array  $fieldValues
80
+	 * @param bool   $bydb
81
+	 * @param string $timezone
82
+	 * @throws EE_Error
83
+	 * @throws InvalidArgumentException
84
+	 * @throws InvalidDataTypeException
85
+	 * @throws InvalidInterfaceException
86
+	 * @throws ReflectionException
87
+	 */
88
+	protected function __construct($fieldValues = array(), $bydb = false, $timezone = '')
89
+	{
90
+		parent::__construct($fieldValues, $bydb, $timezone);
91
+		if (! $this->get('LIN_code')) {
92
+			$this->set_code($this->generate_code());
93
+		}
94
+	}
95
+
96
+
97
+	/**
98
+	 * Gets ID
99
+	 *
100
+	 * @return int
101
+	 * @throws EE_Error
102
+	 * @throws InvalidArgumentException
103
+	 * @throws InvalidDataTypeException
104
+	 * @throws InvalidInterfaceException
105
+	 * @throws ReflectionException
106
+	 */
107
+	public function ID()
108
+	{
109
+		return $this->get('LIN_ID');
110
+	}
111
+
112
+
113
+	/**
114
+	 * Gets TXN_ID
115
+	 *
116
+	 * @return int
117
+	 * @throws EE_Error
118
+	 * @throws InvalidArgumentException
119
+	 * @throws InvalidDataTypeException
120
+	 * @throws InvalidInterfaceException
121
+	 * @throws ReflectionException
122
+	 */
123
+	public function TXN_ID()
124
+	{
125
+		return $this->get('TXN_ID');
126
+	}
127
+
128
+
129
+	/**
130
+	 * Sets TXN_ID
131
+	 *
132
+	 * @param int $TXN_ID
133
+	 * @throws EE_Error
134
+	 * @throws InvalidArgumentException
135
+	 * @throws InvalidDataTypeException
136
+	 * @throws InvalidInterfaceException
137
+	 * @throws ReflectionException
138
+	 */
139
+	public function set_TXN_ID($TXN_ID)
140
+	{
141
+		$this->set('TXN_ID', $TXN_ID);
142
+	}
143
+
144
+
145
+	/**
146
+	 * Gets name
147
+	 *
148
+	 * @return string
149
+	 * @throws EE_Error
150
+	 * @throws InvalidArgumentException
151
+	 * @throws InvalidDataTypeException
152
+	 * @throws InvalidInterfaceException
153
+	 * @throws ReflectionException
154
+	 */
155
+	public function name()
156
+	{
157
+		$name = $this->get('LIN_name');
158
+		if (! $name) {
159
+			$name = ucwords(str_replace('-', ' ', $this->type()));
160
+		}
161
+		return $name;
162
+	}
163
+
164
+
165
+	/**
166
+	 * Sets name
167
+	 *
168
+	 * @param string $name
169
+	 * @throws EE_Error
170
+	 * @throws InvalidArgumentException
171
+	 * @throws InvalidDataTypeException
172
+	 * @throws InvalidInterfaceException
173
+	 * @throws ReflectionException
174
+	 */
175
+	public function set_name($name)
176
+	{
177
+		$this->set('LIN_name', $name);
178
+	}
179
+
180
+
181
+	/**
182
+	 * Gets desc
183
+	 *
184
+	 * @return string
185
+	 * @throws EE_Error
186
+	 * @throws InvalidArgumentException
187
+	 * @throws InvalidDataTypeException
188
+	 * @throws InvalidInterfaceException
189
+	 * @throws ReflectionException
190
+	 */
191
+	public function desc()
192
+	{
193
+		return $this->get('LIN_desc');
194
+	}
195
+
196
+
197
+	/**
198
+	 * Sets desc
199
+	 *
200
+	 * @param string $desc
201
+	 * @throws EE_Error
202
+	 * @throws InvalidArgumentException
203
+	 * @throws InvalidDataTypeException
204
+	 * @throws InvalidInterfaceException
205
+	 * @throws ReflectionException
206
+	 */
207
+	public function set_desc($desc)
208
+	{
209
+		$this->set('LIN_desc', $desc);
210
+	}
211
+
212
+
213
+	/**
214
+	 * Gets quantity
215
+	 *
216
+	 * @return int
217
+	 * @throws EE_Error
218
+	 * @throws InvalidArgumentException
219
+	 * @throws InvalidDataTypeException
220
+	 * @throws InvalidInterfaceException
221
+	 * @throws ReflectionException
222
+	 */
223
+	public function quantity()
224
+	{
225
+		return $this->get('LIN_quantity');
226
+	}
227
+
228
+
229
+	/**
230
+	 * Sets quantity
231
+	 *
232
+	 * @param int $quantity
233
+	 * @throws EE_Error
234
+	 * @throws InvalidArgumentException
235
+	 * @throws InvalidDataTypeException
236
+	 * @throws InvalidInterfaceException
237
+	 * @throws ReflectionException
238
+	 */
239
+	public function set_quantity($quantity)
240
+	{
241
+		$this->set('LIN_quantity', max($quantity, 0));
242
+	}
243
+
244
+
245
+	/**
246
+	 * Gets item_id
247
+	 *
248
+	 * @return string
249
+	 * @throws EE_Error
250
+	 * @throws InvalidArgumentException
251
+	 * @throws InvalidDataTypeException
252
+	 * @throws InvalidInterfaceException
253
+	 * @throws ReflectionException
254
+	 */
255
+	public function OBJ_ID()
256
+	{
257
+		return $this->get('OBJ_ID');
258
+	}
259
+
260
+
261
+	/**
262
+	 * Sets item_id
263
+	 *
264
+	 * @param string $item_id
265
+	 * @throws EE_Error
266
+	 * @throws InvalidArgumentException
267
+	 * @throws InvalidDataTypeException
268
+	 * @throws InvalidInterfaceException
269
+	 * @throws ReflectionException
270
+	 */
271
+	public function set_OBJ_ID($item_id)
272
+	{
273
+		$this->set('OBJ_ID', $item_id);
274
+	}
275
+
276
+
277
+	/**
278
+	 * Gets item_type
279
+	 *
280
+	 * @return string
281
+	 * @throws EE_Error
282
+	 * @throws InvalidArgumentException
283
+	 * @throws InvalidDataTypeException
284
+	 * @throws InvalidInterfaceException
285
+	 * @throws ReflectionException
286
+	 */
287
+	public function OBJ_type()
288
+	{
289
+		return $this->get('OBJ_type');
290
+	}
291
+
292
+
293
+	/**
294
+	 * Gets item_type
295
+	 *
296
+	 * @return string
297
+	 * @throws EE_Error
298
+	 * @throws InvalidArgumentException
299
+	 * @throws InvalidDataTypeException
300
+	 * @throws InvalidInterfaceException
301
+	 * @throws ReflectionException
302
+	 */
303
+	public function OBJ_type_i18n()
304
+	{
305
+		$obj_type = $this->OBJ_type();
306
+		switch ($obj_type) {
307
+			case EEM_Line_Item::OBJ_TYPE_EVENT:
308
+				$obj_type = esc_html__('Event', 'event_espresso');
309
+				break;
310
+			case EEM_Line_Item::OBJ_TYPE_PRICE:
311
+				$obj_type = esc_html__('Price', 'event_espresso');
312
+				break;
313
+			case EEM_Line_Item::OBJ_TYPE_PROMOTION:
314
+				$obj_type = esc_html__('Promotion', 'event_espresso');
315
+				break;
316
+			case EEM_Line_Item::OBJ_TYPE_TICKET:
317
+				$obj_type = esc_html__('Ticket', 'event_espresso');
318
+				break;
319
+			case EEM_Line_Item::OBJ_TYPE_TRANSACTION:
320
+				$obj_type = esc_html__('Transaction', 'event_espresso');
321
+				break;
322
+		}
323
+		return apply_filters('FHEE__EE_Line_Item__OBJ_type_i18n', $obj_type, $this);
324
+	}
325
+
326
+
327
+	/**
328
+	 * Sets item_type
329
+	 *
330
+	 * @param string $OBJ_type
331
+	 * @throws EE_Error
332
+	 * @throws InvalidArgumentException
333
+	 * @throws InvalidDataTypeException
334
+	 * @throws InvalidInterfaceException
335
+	 * @throws ReflectionException
336
+	 */
337
+	public function set_OBJ_type($OBJ_type)
338
+	{
339
+		$this->set('OBJ_type', $OBJ_type);
340
+	}
341
+
342
+
343
+	/**
344
+	 * Gets unit_price
345
+	 *
346
+	 * @return float
347
+	 * @throws EE_Error
348
+	 * @throws InvalidArgumentException
349
+	 * @throws InvalidDataTypeException
350
+	 * @throws InvalidInterfaceException
351
+	 * @throws ReflectionException
352
+	 */
353
+	public function unit_price()
354
+	{
355
+		return $this->get('LIN_unit_price');
356
+	}
357
+
358
+
359
+	/**
360
+	 * Sets unit_price
361
+	 *
362
+	 * @param float $unit_price
363
+	 * @throws EE_Error
364
+	 * @throws InvalidArgumentException
365
+	 * @throws InvalidDataTypeException
366
+	 * @throws InvalidInterfaceException
367
+	 * @throws ReflectionException
368
+	 */
369
+	public function set_unit_price($unit_price)
370
+	{
371
+		$this->set('LIN_unit_price', $unit_price);
372
+	}
373
+
374
+
375
+	/**
376
+	 * Checks if this item is a percentage modifier or not
377
+	 *
378
+	 * @return boolean
379
+	 * @throws EE_Error
380
+	 * @throws InvalidArgumentException
381
+	 * @throws InvalidDataTypeException
382
+	 * @throws InvalidInterfaceException
383
+	 * @throws ReflectionException
384
+	 */
385
+	public function is_percent()
386
+	{
387
+		if ($this->is_tax_sub_total()) {
388
+			// tax subtotals HAVE a percent on them, that percentage only applies
389
+			// to taxable items, so its' an exception. Treat it like a flat line item
390
+			return false;
391
+		}
392
+		$unit_price = abs($this->get('LIN_unit_price'));
393
+		$percent = abs($this->get('LIN_percent'));
394
+		if ($unit_price < .001 && $percent) {
395
+			return true;
396
+		}
397
+		if ($unit_price >= .001 && ! $percent) {
398
+			return false;
399
+		}
400
+		if ($unit_price >= .001 && $percent) {
401
+			throw new EE_Error(
402
+				sprintf(
403
+					esc_html__(
404
+						'A Line Item can not have a unit price of (%s) AND a percent (%s)!',
405
+						'event_espresso'
406
+					),
407
+					$unit_price,
408
+					$percent
409
+				)
410
+			);
411
+		}
412
+		// if they're both 0, assume its not a percent item
413
+		return false;
414
+	}
415
+
416
+
417
+	/**
418
+	 * Gets percent (between 100-.001)
419
+	 *
420
+	 * @return float
421
+	 * @throws EE_Error
422
+	 * @throws InvalidArgumentException
423
+	 * @throws InvalidDataTypeException
424
+	 * @throws InvalidInterfaceException
425
+	 * @throws ReflectionException
426
+	 */
427
+	public function percent()
428
+	{
429
+		return $this->get('LIN_percent');
430
+	}
431
+
432
+
433
+	/**
434
+	 * Sets percent (between 100-0.01)
435
+	 *
436
+	 * @param float $percent
437
+	 * @throws EE_Error
438
+	 * @throws InvalidArgumentException
439
+	 * @throws InvalidDataTypeException
440
+	 * @throws InvalidInterfaceException
441
+	 * @throws ReflectionException
442
+	 */
443
+	public function set_percent($percent)
444
+	{
445
+		$this->set('LIN_percent', $percent);
446
+	}
447
+
448
+
449
+	/**
450
+	 * Gets total
451
+	 *
452
+	 * @return float
453
+	 * @throws EE_Error
454
+	 * @throws InvalidArgumentException
455
+	 * @throws InvalidDataTypeException
456
+	 * @throws InvalidInterfaceException
457
+	 * @throws ReflectionException
458
+	 */
459
+	public function total()
460
+	{
461
+		return $this->get('LIN_total');
462
+	}
463
+
464
+
465
+	/**
466
+	 * Sets total
467
+	 *
468
+	 * @param float $total
469
+	 * @throws EE_Error
470
+	 * @throws InvalidArgumentException
471
+	 * @throws InvalidDataTypeException
472
+	 * @throws InvalidInterfaceException
473
+	 * @throws ReflectionException
474
+	 */
475
+	public function set_total($total)
476
+	{
477
+		$this->set('LIN_total', $total);
478
+	}
479
+
480
+
481
+	/**
482
+	 * Gets order
483
+	 *
484
+	 * @return int
485
+	 * @throws EE_Error
486
+	 * @throws InvalidArgumentException
487
+	 * @throws InvalidDataTypeException
488
+	 * @throws InvalidInterfaceException
489
+	 * @throws ReflectionException
490
+	 */
491
+	public function order()
492
+	{
493
+		return $this->get('LIN_order');
494
+	}
495
+
496
+
497
+	/**
498
+	 * Sets order
499
+	 *
500
+	 * @param int $order
501
+	 * @throws EE_Error
502
+	 * @throws InvalidArgumentException
503
+	 * @throws InvalidDataTypeException
504
+	 * @throws InvalidInterfaceException
505
+	 * @throws ReflectionException
506
+	 */
507
+	public function set_order($order)
508
+	{
509
+		$this->set('LIN_order', $order);
510
+	}
511
+
512
+
513
+	/**
514
+	 * Gets parent
515
+	 *
516
+	 * @return int
517
+	 * @throws EE_Error
518
+	 * @throws InvalidArgumentException
519
+	 * @throws InvalidDataTypeException
520
+	 * @throws InvalidInterfaceException
521
+	 * @throws ReflectionException
522
+	 */
523
+	public function parent_ID()
524
+	{
525
+		return $this->get('LIN_parent');
526
+	}
527
+
528
+
529
+	/**
530
+	 * Sets parent
531
+	 *
532
+	 * @param int $parent
533
+	 * @throws EE_Error
534
+	 * @throws InvalidArgumentException
535
+	 * @throws InvalidDataTypeException
536
+	 * @throws InvalidInterfaceException
537
+	 * @throws ReflectionException
538
+	 */
539
+	public function set_parent_ID($parent)
540
+	{
541
+		$this->set('LIN_parent', $parent);
542
+	}
543
+
544
+
545
+	/**
546
+	 * Gets type
547
+	 *
548
+	 * @return string
549
+	 * @throws EE_Error
550
+	 * @throws InvalidArgumentException
551
+	 * @throws InvalidDataTypeException
552
+	 * @throws InvalidInterfaceException
553
+	 * @throws ReflectionException
554
+	 */
555
+	public function type()
556
+	{
557
+		return $this->get('LIN_type');
558
+	}
559
+
560
+
561
+	/**
562
+	 * Sets type
563
+	 *
564
+	 * @param string $type
565
+	 * @throws EE_Error
566
+	 * @throws InvalidArgumentException
567
+	 * @throws InvalidDataTypeException
568
+	 * @throws InvalidInterfaceException
569
+	 * @throws ReflectionException
570
+	 */
571
+	public function set_type($type)
572
+	{
573
+		$this->set('LIN_type', $type);
574
+	}
575
+
576
+
577
+	/**
578
+	 * Gets the line item of which this item is a composite. Eg, if this is a subtotal, the parent might be a total\
579
+	 * If this line item is saved to the DB, fetches the parent from the DB. However, if this line item isn't in the DB
580
+	 * it uses its cached reference to its parent line item (which would have been set by `EE_Line_Item::set_parent()`
581
+	 * or indirectly by `EE_Line_item::add_child_line_item()`)
582
+	 *
583
+	 * @return EE_Base_Class|EE_Line_Item
584
+	 * @throws EE_Error
585
+	 * @throws InvalidArgumentException
586
+	 * @throws InvalidDataTypeException
587
+	 * @throws InvalidInterfaceException
588
+	 * @throws ReflectionException
589
+	 */
590
+	public function parent()
591
+	{
592
+		return $this->ID()
593
+			? $this->get_model()->get_one_by_ID($this->parent_ID())
594
+			: $this->_parent;
595
+	}
596
+
597
+
598
+	/**
599
+	 * Gets ALL the children of this line item (ie, all the parts that contribute towards this total).
600
+	 *
601
+	 * @return EE_Base_Class[]|EE_Line_Item[]
602
+	 * @throws EE_Error
603
+	 * @throws InvalidArgumentException
604
+	 * @throws InvalidDataTypeException
605
+	 * @throws InvalidInterfaceException
606
+	 * @throws ReflectionException
607
+	 */
608
+	public function children()
609
+	{
610
+		if ($this->ID()) {
611
+			return $this->get_model()->get_all(
612
+				array(
613
+					array('LIN_parent' => $this->ID()),
614
+					'order_by' => array('LIN_order' => 'ASC'),
615
+				)
616
+			);
617
+		}
618
+		if (! is_array($this->_children)) {
619
+			$this->_children = array();
620
+		}
621
+		return $this->_children;
622
+	}
623
+
624
+
625
+	/**
626
+	 * Gets code
627
+	 *
628
+	 * @return string
629
+	 * @throws EE_Error
630
+	 * @throws InvalidArgumentException
631
+	 * @throws InvalidDataTypeException
632
+	 * @throws InvalidInterfaceException
633
+	 * @throws ReflectionException
634
+	 */
635
+	public function code()
636
+	{
637
+		return $this->get('LIN_code');
638
+	}
639
+
640
+
641
+	/**
642
+	 * Sets code
643
+	 *
644
+	 * @param string $code
645
+	 * @throws EE_Error
646
+	 * @throws InvalidArgumentException
647
+	 * @throws InvalidDataTypeException
648
+	 * @throws InvalidInterfaceException
649
+	 * @throws ReflectionException
650
+	 */
651
+	public function set_code($code)
652
+	{
653
+		$this->set('LIN_code', $code);
654
+	}
655
+
656
+
657
+	/**
658
+	 * Gets is_taxable
659
+	 *
660
+	 * @return boolean
661
+	 * @throws EE_Error
662
+	 * @throws InvalidArgumentException
663
+	 * @throws InvalidDataTypeException
664
+	 * @throws InvalidInterfaceException
665
+	 * @throws ReflectionException
666
+	 */
667
+	public function is_taxable()
668
+	{
669
+		return $this->get('LIN_is_taxable');
670
+	}
671
+
672
+
673
+	/**
674
+	 * Sets is_taxable
675
+	 *
676
+	 * @param boolean $is_taxable
677
+	 * @throws EE_Error
678
+	 * @throws InvalidArgumentException
679
+	 * @throws InvalidDataTypeException
680
+	 * @throws InvalidInterfaceException
681
+	 * @throws ReflectionException
682
+	 */
683
+	public function set_is_taxable($is_taxable)
684
+	{
685
+		$this->set('LIN_is_taxable', $is_taxable);
686
+	}
687
+
688
+
689
+	/**
690
+	 * Gets the object that this model-joins-to.
691
+	 * returns one of the model objects that the field OBJ_ID can point to... see the 'OBJ_ID' field on
692
+	 * EEM_Promotion_Object
693
+	 *        Eg, if this line item join model object is for a ticket, this will return the EE_Ticket object
694
+	 *
695
+	 * @return EE_Base_Class | NULL
696
+	 * @throws EE_Error
697
+	 * @throws InvalidArgumentException
698
+	 * @throws InvalidDataTypeException
699
+	 * @throws InvalidInterfaceException
700
+	 * @throws ReflectionException
701
+	 */
702
+	public function get_object()
703
+	{
704
+		$model_name_of_related_obj = $this->OBJ_type();
705
+		return $this->get_model()->has_relation($model_name_of_related_obj)
706
+			? $this->get_first_related($model_name_of_related_obj)
707
+			: null;
708
+	}
709
+
710
+
711
+	/**
712
+	 * Like EE_Line_Item::get_object(), but can only ever actually return an EE_Ticket.
713
+	 * (IE, if this line item is for a price or something else, will return NULL)
714
+	 *
715
+	 * @param array $query_params
716
+	 * @return EE_Base_Class|EE_Ticket
717
+	 * @throws EE_Error
718
+	 * @throws InvalidArgumentException
719
+	 * @throws InvalidDataTypeException
720
+	 * @throws InvalidInterfaceException
721
+	 * @throws ReflectionException
722
+	 */
723
+	public function ticket($query_params = array())
724
+	{
725
+		// we're going to assume that when this method is called
726
+		// we always want to receive the attached ticket EVEN if that ticket is archived.
727
+		// This can be overridden via the incoming $query_params argument
728
+		$remove_defaults = array('default_where_conditions' => 'none');
729
+		$query_params = array_merge($remove_defaults, $query_params);
730
+		return $this->get_first_related(EEM_Line_Item::OBJ_TYPE_TICKET, $query_params);
731
+	}
732
+
733
+
734
+	/**
735
+	 * Gets the EE_Datetime that's related to the ticket, IF this is for a ticket
736
+	 *
737
+	 * @return EE_Datetime | NULL
738
+	 * @throws EE_Error
739
+	 * @throws InvalidArgumentException
740
+	 * @throws InvalidDataTypeException
741
+	 * @throws InvalidInterfaceException
742
+	 * @throws ReflectionException
743
+	 */
744
+	public function get_ticket_datetime()
745
+	{
746
+		if ($this->OBJ_type() === EEM_Line_Item::OBJ_TYPE_TICKET) {
747
+			$ticket = $this->ticket();
748
+			if ($ticket instanceof EE_Ticket) {
749
+				$datetime = $ticket->first_datetime();
750
+				if ($datetime instanceof EE_Datetime) {
751
+					return $datetime;
752
+				}
753
+			}
754
+		}
755
+		return null;
756
+	}
757
+
758
+
759
+	/**
760
+	 * Gets the event's name that's related to the ticket, if this is for
761
+	 * a ticket
762
+	 *
763
+	 * @return string
764
+	 * @throws EE_Error
765
+	 * @throws InvalidArgumentException
766
+	 * @throws InvalidDataTypeException
767
+	 * @throws InvalidInterfaceException
768
+	 * @throws ReflectionException
769
+	 */
770
+	public function ticket_event_name()
771
+	{
772
+		$event_name = esc_html__('Unknown', 'event_espresso');
773
+		$event = $this->ticket_event();
774
+		if ($event instanceof EE_Event) {
775
+			$event_name = $event->name();
776
+		}
777
+		return $event_name;
778
+	}
779
+
780
+
781
+	/**
782
+	 * Gets the event that's related to the ticket, if this line item represents a ticket.
783
+	 *
784
+	 * @return EE_Event|null
785
+	 * @throws EE_Error
786
+	 * @throws InvalidArgumentException
787
+	 * @throws InvalidDataTypeException
788
+	 * @throws InvalidInterfaceException
789
+	 * @throws ReflectionException
790
+	 */
791
+	public function ticket_event()
792
+	{
793
+		$event = null;
794
+		$ticket = $this->ticket();
795
+		if ($ticket instanceof EE_Ticket) {
796
+			$datetime = $ticket->first_datetime();
797
+			if ($datetime instanceof EE_Datetime) {
798
+				$event = $datetime->event();
799
+			}
800
+		}
801
+		return $event;
802
+	}
803
+
804
+
805
+	/**
806
+	 * Gets the first datetime for this lien item, assuming it's for a ticket
807
+	 *
808
+	 * @param string $date_format
809
+	 * @param string $time_format
810
+	 * @return string
811
+	 * @throws EE_Error
812
+	 * @throws InvalidArgumentException
813
+	 * @throws InvalidDataTypeException
814
+	 * @throws InvalidInterfaceException
815
+	 * @throws ReflectionException
816
+	 */
817
+	public function ticket_datetime_start($date_format = '', $time_format = '')
818
+	{
819
+		$first_datetime_string = esc_html__('Unknown', 'event_espresso');
820
+		$datetime = $this->get_ticket_datetime();
821
+		if ($datetime) {
822
+			$first_datetime_string = $datetime->start_date_and_time($date_format, $time_format);
823
+		}
824
+		return $first_datetime_string;
825
+	}
826
+
827
+
828
+	/**
829
+	 * Adds the line item as a child to this line item. If there is another child line
830
+	 * item with the same LIN_code, it is overwritten by this new one
831
+	 *
832
+	 * @param EEI_Line_Item $line_item
833
+	 * @param bool          $set_order
834
+	 * @return bool success
835
+	 * @throws EE_Error
836
+	 * @throws InvalidArgumentException
837
+	 * @throws InvalidDataTypeException
838
+	 * @throws InvalidInterfaceException
839
+	 * @throws ReflectionException
840
+	 */
841
+	public function add_child_line_item(EEI_Line_Item $line_item, $set_order = true)
842
+	{
843
+		// should we calculate the LIN_order for this line item ?
844
+		if ($set_order || $line_item->order() === null) {
845
+			$line_item->set_order(count($this->children()));
846
+		}
847
+		if ($this->ID()) {
848
+			// check for any duplicate line items (with the same code), if so, this replaces it
849
+			$line_item_with_same_code = $this->get_child_line_item($line_item->code());
850
+			if ($line_item_with_same_code instanceof EE_Line_Item && $line_item_with_same_code !== $line_item) {
851
+				$this->delete_child_line_item($line_item_with_same_code->code());
852
+			}
853
+			$line_item->set_parent_ID($this->ID());
854
+			if ($this->TXN_ID()) {
855
+				$line_item->set_TXN_ID($this->TXN_ID());
856
+			}
857
+			return $line_item->save();
858
+		}
859
+		$this->_children[ $line_item->code() ] = $line_item;
860
+		if ($line_item->parent() !== $this) {
861
+			$line_item->set_parent($this);
862
+		}
863
+		return true;
864
+	}
865
+
866
+
867
+	/**
868
+	 * Similar to EE_Base_Class::_add_relation_to, except this isn't a normal relation.
869
+	 * If this line item is saved to the DB, this is just a wrapper for set_parent_ID() and save()
870
+	 * However, if this line item is NOT saved to the DB, this just caches the parent on
871
+	 * the EE_Line_Item::_parent property.
872
+	 *
873
+	 * @param EE_Line_Item $line_item
874
+	 * @throws EE_Error
875
+	 * @throws InvalidArgumentException
876
+	 * @throws InvalidDataTypeException
877
+	 * @throws InvalidInterfaceException
878
+	 * @throws ReflectionException
879
+	 */
880
+	public function set_parent($line_item)
881
+	{
882
+		if ($this->ID()) {
883
+			if (! $line_item->ID()) {
884
+				$line_item->save();
885
+			}
886
+			$this->set_parent_ID($line_item->ID());
887
+			$this->save();
888
+		} else {
889
+			$this->_parent = $line_item;
890
+			$this->set_parent_ID($line_item->ID());
891
+		}
892
+	}
893
+
894
+
895
+	/**
896
+	 * Gets the child line item as specified by its code. Because this returns an object (by reference)
897
+	 * you can modify this child line item and the parent (this object) can know about them
898
+	 * because it also has a reference to that line item
899
+	 *
900
+	 * @param string $code
901
+	 * @return EE_Base_Class|EE_Line_Item|EE_Soft_Delete_Base_Class|NULL
902
+	 * @throws EE_Error
903
+	 * @throws InvalidArgumentException
904
+	 * @throws InvalidDataTypeException
905
+	 * @throws InvalidInterfaceException
906
+	 * @throws ReflectionException
907
+	 */
908
+	public function get_child_line_item($code)
909
+	{
910
+		if ($this->ID()) {
911
+			return $this->get_model()->get_one(
912
+				array(array('LIN_parent' => $this->ID(), 'LIN_code' => $code))
913
+			);
914
+		}
915
+		return isset($this->_children[ $code ])
916
+			? $this->_children[ $code ]
917
+			: null;
918
+	}
919
+
920
+
921
+	/**
922
+	 * Returns how many items are deleted (or, if this item has not been saved ot the DB yet, just how many it HAD
923
+	 * cached on it)
924
+	 *
925
+	 * @return int
926
+	 * @throws EE_Error
927
+	 * @throws InvalidArgumentException
928
+	 * @throws InvalidDataTypeException
929
+	 * @throws InvalidInterfaceException
930
+	 * @throws ReflectionException
931
+	 */
932
+	public function delete_children_line_items()
933
+	{
934
+		if ($this->ID()) {
935
+			return $this->get_model()->delete(array(array('LIN_parent' => $this->ID())));
936
+		}
937
+		$count = count($this->_children);
938
+		$this->_children = array();
939
+		return $count;
940
+	}
941
+
942
+
943
+	/**
944
+	 * If this line item has been saved to the DB, deletes its child with LIN_code == $code. If this line
945
+	 * HAS NOT been saved to the DB, removes the child line item with index $code.
946
+	 * Also searches through the child's children for a matching line item. However, once a line item has been found
947
+	 * and deleted, stops searching (so if there are line items with duplicate codes, only the first one found will be
948
+	 * deleted)
949
+	 *
950
+	 * @param string $code
951
+	 * @param bool   $stop_search_once_found
952
+	 * @return int count of items deleted (or simply removed from the line item's cache, if not has not been saved to
953
+	 *             the DB yet)
954
+	 * @throws EE_Error
955
+	 * @throws InvalidArgumentException
956
+	 * @throws InvalidDataTypeException
957
+	 * @throws InvalidInterfaceException
958
+	 * @throws ReflectionException
959
+	 */
960
+	public function delete_child_line_item($code, $stop_search_once_found = true)
961
+	{
962
+		if ($this->ID()) {
963
+			$items_deleted = 0;
964
+			if ($this->code() === $code) {
965
+				$items_deleted += EEH_Line_Item::delete_all_child_items($this);
966
+				$items_deleted += (int) $this->delete();
967
+				if ($stop_search_once_found) {
968
+					return $items_deleted;
969
+				}
970
+			}
971
+			foreach ($this->children() as $child_line_item) {
972
+				$items_deleted += $child_line_item->delete_child_line_item($code, $stop_search_once_found);
973
+			}
974
+			return $items_deleted;
975
+		}
976
+		if (isset($this->_children[ $code ])) {
977
+			unset($this->_children[ $code ]);
978
+			return 1;
979
+		}
980
+		return 0;
981
+	}
982
+
983
+
984
+	/**
985
+	 * If this line item is in the database, is of the type subtotal, and
986
+	 * has no children, why do we have it? It should be deleted so this function
987
+	 * does that
988
+	 *
989
+	 * @return boolean
990
+	 * @throws EE_Error
991
+	 * @throws InvalidArgumentException
992
+	 * @throws InvalidDataTypeException
993
+	 * @throws InvalidInterfaceException
994
+	 * @throws ReflectionException
995
+	 */
996
+	public function delete_if_childless_subtotal()
997
+	{
998
+		if ($this->ID() && $this->type() === EEM_Line_Item::type_sub_total && ! $this->children()) {
999
+			return $this->delete();
1000
+		}
1001
+		return false;
1002
+	}
1003
+
1004
+
1005
+	/**
1006
+	 * Creates a code and returns a string. doesn't assign the code to this model object
1007
+	 *
1008
+	 * @return string
1009
+	 * @throws EE_Error
1010
+	 * @throws InvalidArgumentException
1011
+	 * @throws InvalidDataTypeException
1012
+	 * @throws InvalidInterfaceException
1013
+	 * @throws ReflectionException
1014
+	 */
1015
+	public function generate_code()
1016
+	{
1017
+		// each line item in the cart requires a unique identifier
1018
+		return md5($this->get('OBJ_type') . $this->get('OBJ_ID') . microtime());
1019
+	}
1020
+
1021
+
1022
+	/**
1023
+	 * @return bool
1024
+	 * @throws EE_Error
1025
+	 * @throws InvalidArgumentException
1026
+	 * @throws InvalidDataTypeException
1027
+	 * @throws InvalidInterfaceException
1028
+	 * @throws ReflectionException
1029
+	 */
1030
+	public function is_tax()
1031
+	{
1032
+		return $this->type() === EEM_Line_Item::type_tax;
1033
+	}
1034
+
1035
+
1036
+	/**
1037
+	 * @return bool
1038
+	 * @throws EE_Error
1039
+	 * @throws InvalidArgumentException
1040
+	 * @throws InvalidDataTypeException
1041
+	 * @throws InvalidInterfaceException
1042
+	 * @throws ReflectionException
1043
+	 */
1044
+	public function is_tax_sub_total()
1045
+	{
1046
+		return $this->type() === EEM_Line_Item::type_tax_sub_total;
1047
+	}
1048
+
1049
+
1050
+	/**
1051
+	 * @return bool
1052
+	 * @throws EE_Error
1053
+	 * @throws InvalidArgumentException
1054
+	 * @throws InvalidDataTypeException
1055
+	 * @throws InvalidInterfaceException
1056
+	 * @throws ReflectionException
1057
+	 */
1058
+	public function is_line_item()
1059
+	{
1060
+		return $this->type() === EEM_Line_Item::type_line_item;
1061
+	}
1062
+
1063
+
1064
+	/**
1065
+	 * @return bool
1066
+	 * @throws EE_Error
1067
+	 * @throws InvalidArgumentException
1068
+	 * @throws InvalidDataTypeException
1069
+	 * @throws InvalidInterfaceException
1070
+	 * @throws ReflectionException
1071
+	 */
1072
+	public function is_sub_line_item()
1073
+	{
1074
+		return $this->type() === EEM_Line_Item::type_sub_line_item;
1075
+	}
1076
+
1077
+
1078
+	/**
1079
+	 * @return bool
1080
+	 * @throws EE_Error
1081
+	 * @throws InvalidArgumentException
1082
+	 * @throws InvalidDataTypeException
1083
+	 * @throws InvalidInterfaceException
1084
+	 * @throws ReflectionException
1085
+	 */
1086
+	public function is_sub_total()
1087
+	{
1088
+		return $this->type() === EEM_Line_Item::type_sub_total;
1089
+	}
1090
+
1091
+
1092
+	/**
1093
+	 * Whether or not this line item is a cancellation line item
1094
+	 *
1095
+	 * @return boolean
1096
+	 * @throws EE_Error
1097
+	 * @throws InvalidArgumentException
1098
+	 * @throws InvalidDataTypeException
1099
+	 * @throws InvalidInterfaceException
1100
+	 * @throws ReflectionException
1101
+	 */
1102
+	public function is_cancellation()
1103
+	{
1104
+		return EEM_Line_Item::type_cancellation === $this->type();
1105
+	}
1106
+
1107
+
1108
+	/**
1109
+	 * @return bool
1110
+	 * @throws EE_Error
1111
+	 * @throws InvalidArgumentException
1112
+	 * @throws InvalidDataTypeException
1113
+	 * @throws InvalidInterfaceException
1114
+	 * @throws ReflectionException
1115
+	 */
1116
+	public function is_total()
1117
+	{
1118
+		return $this->type() === EEM_Line_Item::type_total;
1119
+	}
1120
+
1121
+
1122
+	/**
1123
+	 * @return bool
1124
+	 * @throws EE_Error
1125
+	 * @throws InvalidArgumentException
1126
+	 * @throws InvalidDataTypeException
1127
+	 * @throws InvalidInterfaceException
1128
+	 * @throws ReflectionException
1129
+	 */
1130
+	public function is_cancelled()
1131
+	{
1132
+		return $this->type() === EEM_Line_Item::type_cancellation;
1133
+	}
1134
+
1135
+
1136
+	/**
1137
+	 * @return string like '2, 004.00', formatted according to the localized currency
1138
+	 * @throws EE_Error
1139
+	 * @throws InvalidArgumentException
1140
+	 * @throws InvalidDataTypeException
1141
+	 * @throws InvalidInterfaceException
1142
+	 * @throws ReflectionException
1143
+	 */
1144
+	public function unit_price_no_code()
1145
+	{
1146
+		return $this->get_pretty('LIN_unit_price', 'no_currency_code');
1147
+	}
1148
+
1149
+
1150
+	/**
1151
+	 * @return string like '2, 004.00', formatted according to the localized currency
1152
+	 * @throws EE_Error
1153
+	 * @throws InvalidArgumentException
1154
+	 * @throws InvalidDataTypeException
1155
+	 * @throws InvalidInterfaceException
1156
+	 * @throws ReflectionException
1157
+	 */
1158
+	public function total_no_code()
1159
+	{
1160
+		return $this->get_pretty('LIN_total', 'no_currency_code');
1161
+	}
1162
+
1163
+
1164
+	/**
1165
+	 * Gets the final total on this item, taking taxes into account.
1166
+	 * Has the side-effect of setting the sub-total as it was just calculated.
1167
+	 * If this is used on a grand-total line item, also updates the transaction's
1168
+	 * TXN_total (provided this line item is allowed to persist, otherwise we don't
1169
+	 * want to change a persistable transaction with info from a non-persistent line item)
1170
+	 *
1171
+	 * @param bool $update_txn_status
1172
+	 * @return float
1173
+	 * @throws EE_Error
1174
+	 * @throws InvalidArgumentException
1175
+	 * @throws InvalidDataTypeException
1176
+	 * @throws InvalidInterfaceException
1177
+	 * @throws ReflectionException
1178
+	 * @throws RuntimeException
1179
+	 */
1180
+	public function recalculate_total_including_taxes($update_txn_status = false)
1181
+	{
1182
+		$pre_tax_total = $this->recalculate_pre_tax_total();
1183
+		$tax_total = $this->recalculate_taxes_and_tax_total();
1184
+		$total = $pre_tax_total + $tax_total;
1185
+		// no negative totals plz
1186
+		$total = max($total, 0);
1187
+		$this->set_total($total);
1188
+		// only update the related transaction's total
1189
+		// if we intend to save this line item and its a grand total
1190
+		if ($this->allow_persist() && $this->type() === EEM_Line_Item::type_total
1191
+			&& $this->transaction()
1192
+			   instanceof
1193
+			   EE_Transaction
1194
+		) {
1195
+			$this->transaction()->set_total($total);
1196
+			if ($update_txn_status) {
1197
+				// don't save the TXN because that will be done below
1198
+				// and the following method only saves if the status changes
1199
+				$this->transaction()->update_status_based_on_total_paid(false);
1200
+			}
1201
+			if ($this->transaction()->ID()) {
1202
+				$this->transaction()->save();
1203
+			}
1204
+		}
1205
+		$this->maybe_save();
1206
+		return $total;
1207
+	}
1208
+
1209
+
1210
+	/**
1211
+	 * Recursively goes through all the children and recalculates sub-totals EXCEPT for
1212
+	 * tax-sub-totals (they're a an odd beast). Updates the 'total' on each line item according to either its
1213
+	 * unit price * quantity or the total of all its children EXCEPT when we're only calculating the taxable total and
1214
+	 * when this is called on the grand total
1215
+	 *
1216
+	 * @return float
1217
+	 * @throws EE_Error
1218
+	 * @throws InvalidArgumentException
1219
+	 * @throws InvalidDataTypeException
1220
+	 * @throws InvalidInterfaceException
1221
+	 * @throws ReflectionException
1222
+	 */
1223
+	public function recalculate_pre_tax_total()
1224
+	{
1225
+		$total = 0;
1226
+		$my_children = $this->children();
1227
+		$has_children = ! empty($my_children);
1228
+		if ($has_children && $this->is_line_item()) {
1229
+			$total = $this->_recalculate_pretax_total_for_line_item($total, $my_children);
1230
+		} elseif (! $has_children && ($this->is_sub_line_item() || $this->is_line_item())) {
1231
+			$total = $this->unit_price() * $this->quantity();
1232
+		} elseif ($this->is_sub_total() || $this->is_total()) {
1233
+			$total = $this->_recalculate_pretax_total_for_subtotal($total, $my_children);
1234
+		} elseif ($this->is_tax_sub_total() || $this->is_tax() || $this->is_cancelled()) {
1235
+			// completely ignore tax totals, tax sub-totals, and cancelled line items, when calculating the pre-tax-total
1236
+			return 0;
1237
+		}
1238
+		// ensure all non-line items and non-sub-line-items have a quantity of 1 (except for Events)
1239
+		if (! $this->is_line_item() && ! $this->is_sub_line_item() && ! $this->is_cancellation()) {
1240
+			if ($this->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_EVENT) {
1241
+				$this->set_quantity(1);
1242
+			}
1243
+			if (! $this->is_percent()) {
1244
+				$this->set_unit_price($total);
1245
+			}
1246
+		}
1247
+		// we don't want to bother saving grand totals, because that needs to factor in taxes anyways
1248
+		// so it ought to be
1249
+		if (! $this->is_total()) {
1250
+			$this->set_total($total);
1251
+			// if not a percent line item, make sure we keep the unit price in sync
1252
+			if ($has_children
1253
+				&& $this->is_line_item()
1254
+				&& ! $this->is_percent()
1255
+			) {
1256
+				if ($this->quantity() === 0) {
1257
+					$new_unit_price = 0;
1258
+				} else {
1259
+					$new_unit_price = $this->total() / $this->quantity();
1260
+				}
1261
+				$this->set_unit_price($new_unit_price);
1262
+			}
1263
+			$this->maybe_save();
1264
+		}
1265
+		return $total;
1266
+	}
1267
+
1268
+
1269
+	/**
1270
+	 * Calculates the pretax total when this line item is a subtotal or total line item.
1271
+	 * Basically does a sum-then-round approach (ie, any percent line item that are children
1272
+	 * will calculate their total based on the un-rounded total we're working with so far, and
1273
+	 * THEN round the result; instead of rounding as we go like with sub-line-items)
1274
+	 *
1275
+	 * @param float          $calculated_total_so_far
1276
+	 * @param EE_Line_Item[] $my_children
1277
+	 * @return float
1278
+	 * @throws EE_Error
1279
+	 * @throws InvalidArgumentException
1280
+	 * @throws InvalidDataTypeException
1281
+	 * @throws InvalidInterfaceException
1282
+	 * @throws ReflectionException
1283
+	 */
1284
+	protected function _recalculate_pretax_total_for_subtotal($calculated_total_so_far, $my_children = null)
1285
+	{
1286
+		if ($my_children === null) {
1287
+			$my_children = $this->children();
1288
+		}
1289
+		$subtotal_quantity = 0;
1290
+		// get the total of all its children
1291
+		foreach ($my_children as $child_line_item) {
1292
+			if ($child_line_item instanceof EE_Line_Item) {
1293
+				// skip line item if it is cancelled or is a tax
1294
+				if ($child_line_item->is_cancellation() || $child_line_item->is_tax()) {
1295
+					continue;
1296
+				}
1297
+				// percentage line items are based on total so far
1298
+				if ($child_line_item->is_percent()) {
1299
+					// round as we go so that the line items add up ok
1300
+					$percent_total = round(
1301
+						$calculated_total_so_far * $child_line_item->percent() / 100,
1302
+						EE_Registry::instance()->CFG->currency->dec_plc
1303
+					);
1304
+					$child_line_item->set_total($percent_total);
1305
+					// so far all percent line items should have a quantity of 1
1306
+					// (ie, no double percent discounts. Although that might be requested someday)
1307
+					$child_line_item->set_quantity(1);
1308
+					$child_line_item->maybe_save();
1309
+					$calculated_total_so_far += $percent_total;
1310
+				} else {
1311
+					// verify flat sub-line-item quantities match their parent
1312
+					if ($child_line_item->is_sub_line_item()) {
1313
+						$child_line_item->set_quantity($this->quantity());
1314
+					}
1315
+					$calculated_total_so_far += $child_line_item->recalculate_pre_tax_total();
1316
+					$subtotal_quantity += $child_line_item->quantity();
1317
+				}
1318
+			}
1319
+		}
1320
+		if ($this->is_sub_total()) {
1321
+			// no negative totals plz
1322
+			$calculated_total_so_far = max($calculated_total_so_far, 0);
1323
+			$subtotal_quantity = $subtotal_quantity > 0 ? 1 : 0;
1324
+			$this->set_quantity($subtotal_quantity);
1325
+			$this->maybe_save();
1326
+		}
1327
+		return $calculated_total_so_far;
1328
+	}
1329
+
1330
+
1331
+	/**
1332
+	 * Calculates the pretax total for a normal line item, in a round-then-sum approach
1333
+	 * (where each sub-line-item is applied to the base price for the line item
1334
+	 * and the result is immediately rounded, rather than summing all the sub-line-items
1335
+	 * then rounding, like we do when recalculating pretax totals on totals and subtotals).
1336
+	 *
1337
+	 * @param float          $calculated_total_so_far
1338
+	 * @param EE_Line_Item[] $my_children
1339
+	 * @return float
1340
+	 * @throws EE_Error
1341
+	 * @throws InvalidArgumentException
1342
+	 * @throws InvalidDataTypeException
1343
+	 * @throws InvalidInterfaceException
1344
+	 * @throws ReflectionException
1345
+	 */
1346
+	protected function _recalculate_pretax_total_for_line_item($calculated_total_so_far, $my_children = null)
1347
+	{
1348
+		if ($my_children === null) {
1349
+			$my_children = $this->children();
1350
+		}
1351
+		// we need to keep track of the running total for a single item,
1352
+		// because we need to round as we go
1353
+		$unit_price_for_total = 0;
1354
+		$quantity_for_total = 1;
1355
+		// get the total of all its children
1356
+		foreach ($my_children as $child_line_item) {
1357
+			if ($child_line_item instanceof EE_Line_Item) {
1358
+				// skip line item if it is cancelled or is a tax
1359
+				if ($child_line_item->is_cancellation() || $child_line_item->is_tax()) {
1360
+					continue;
1361
+				}
1362
+				if ($child_line_item->is_percent()) {
1363
+					// it should be the unit-price-so-far multiplied by teh percent multiplied by the quantity
1364
+					// not total multiplied by percent, because that ignores rounding along-the-way
1365
+					$percent_unit_price = round(
1366
+						$unit_price_for_total * $child_line_item->percent() / 100,
1367
+						EE_Registry::instance()->CFG->currency->dec_plc
1368
+					);
1369
+					$percent_total = $percent_unit_price * $quantity_for_total;
1370
+					$child_line_item->set_total($percent_total);
1371
+					// so far all percent line items should have a quantity of 1
1372
+					// (ie, no double percent discounts. Although that might be requested someday)
1373
+					$child_line_item->set_quantity(1);
1374
+					$child_line_item->maybe_save();
1375
+					$calculated_total_so_far += $percent_total;
1376
+					$unit_price_for_total += $percent_unit_price;
1377
+				} else {
1378
+					// verify flat sub-line-item quantities match their parent
1379
+					if ($child_line_item->is_sub_line_item()) {
1380
+						$child_line_item->set_quantity($this->quantity());
1381
+					}
1382
+					$quantity_for_total = $child_line_item->quantity();
1383
+					$calculated_total_so_far += $child_line_item->recalculate_pre_tax_total();
1384
+					$unit_price_for_total += $child_line_item->unit_price();
1385
+				}
1386
+			}
1387
+		}
1388
+		return $calculated_total_so_far;
1389
+	}
1390
+
1391
+
1392
+	/**
1393
+	 * Recalculates the total on each individual tax (based on a recalculation of the pre-tax total), sets
1394
+	 * the totals on each tax calculated, and returns the final tax total. Re-saves tax line items
1395
+	 * and tax sub-total if already in the DB
1396
+	 *
1397
+	 * @return float
1398
+	 * @throws EE_Error
1399
+	 * @throws InvalidArgumentException
1400
+	 * @throws InvalidDataTypeException
1401
+	 * @throws InvalidInterfaceException
1402
+	 * @throws ReflectionException
1403
+	 */
1404
+	public function recalculate_taxes_and_tax_total()
1405
+	{
1406
+		// get all taxes
1407
+		$taxes = $this->tax_descendants();
1408
+		// calculate the pretax total
1409
+		$taxable_total = $this->taxable_total();
1410
+		$tax_total = 0;
1411
+		foreach ($taxes as $tax) {
1412
+			$total_on_this_tax = $taxable_total * $tax->percent() / 100;
1413
+			// remember the total on this line item
1414
+			$tax->set_total($total_on_this_tax);
1415
+			$tax->maybe_save();
1416
+			$tax_total += $tax->total();
1417
+		}
1418
+		$this->_recalculate_tax_sub_total();
1419
+		return $tax_total;
1420
+	}
1421
+
1422
+
1423
+	/**
1424
+	 * Simply forces all the tax-sub-totals to recalculate. Assumes the taxes have been calculated
1425
+	 *
1426
+	 * @return void
1427
+	 * @throws EE_Error
1428
+	 * @throws InvalidArgumentException
1429
+	 * @throws InvalidDataTypeException
1430
+	 * @throws InvalidInterfaceException
1431
+	 * @throws ReflectionException
1432
+	 */
1433
+	private function _recalculate_tax_sub_total()
1434
+	{
1435
+		if ($this->is_tax_sub_total()) {
1436
+			$total = 0;
1437
+			$total_percent = 0;
1438
+			// simply loop through all its children (which should be taxes) and sum their total
1439
+			foreach ($this->children() as $child_tax) {
1440
+				if ($child_tax instanceof EE_Line_Item) {
1441
+					$total += $child_tax->total();
1442
+					$total_percent += $child_tax->percent();
1443
+				}
1444
+			}
1445
+			$this->set_total($total);
1446
+			$this->set_percent($total_percent);
1447
+			$this->maybe_save();
1448
+		} elseif ($this->is_total()) {
1449
+			foreach ($this->children() as $maybe_tax_subtotal) {
1450
+				if ($maybe_tax_subtotal instanceof EE_Line_Item) {
1451
+					$maybe_tax_subtotal->_recalculate_tax_sub_total();
1452
+				}
1453
+			}
1454
+		}
1455
+	}
1456
+
1457
+
1458
+	/**
1459
+	 * Gets the total tax on this line item. Assumes taxes have already been calculated using
1460
+	 * recalculate_taxes_and_total
1461
+	 *
1462
+	 * @return float
1463
+	 * @throws EE_Error
1464
+	 * @throws InvalidArgumentException
1465
+	 * @throws InvalidDataTypeException
1466
+	 * @throws InvalidInterfaceException
1467
+	 * @throws ReflectionException
1468
+	 */
1469
+	public function get_total_tax()
1470
+	{
1471
+		$this->_recalculate_tax_sub_total();
1472
+		$total = 0;
1473
+		foreach ($this->tax_descendants() as $tax_line_item) {
1474
+			if ($tax_line_item instanceof EE_Line_Item) {
1475
+				$total += $tax_line_item->total();
1476
+			}
1477
+		}
1478
+		return $total;
1479
+	}
1480
+
1481
+
1482
+	/**
1483
+	 * Gets the total for all the items purchased only
1484
+	 *
1485
+	 * @return float
1486
+	 * @throws EE_Error
1487
+	 * @throws InvalidArgumentException
1488
+	 * @throws InvalidDataTypeException
1489
+	 * @throws InvalidInterfaceException
1490
+	 * @throws ReflectionException
1491
+	 */
1492
+	public function get_items_total()
1493
+	{
1494
+		// by default, let's make sure we're consistent with the existing line item
1495
+		if ($this->is_total()) {
1496
+			$pretax_subtotal_li = EEH_Line_Item::get_pre_tax_subtotal($this);
1497
+			if ($pretax_subtotal_li instanceof EE_Line_Item) {
1498
+				return $pretax_subtotal_li->total();
1499
+			}
1500
+		}
1501
+		$total = 0;
1502
+		foreach ($this->get_items() as $item) {
1503
+			if ($item instanceof EE_Line_Item) {
1504
+				$total += $item->total();
1505
+			}
1506
+		}
1507
+		return $total;
1508
+	}
1509
+
1510
+
1511
+	/**
1512
+	 * Gets all the descendants (ie, children or children of children etc) that
1513
+	 * are of the type 'tax'
1514
+	 *
1515
+	 * @return EE_Line_Item[]
1516
+	 * @throws EE_Error
1517
+	 */
1518
+	public function tax_descendants()
1519
+	{
1520
+		return EEH_Line_Item::get_tax_descendants($this);
1521
+	}
1522
+
1523
+
1524
+	/**
1525
+	 * Gets all the real items purchased which are children of this item
1526
+	 *
1527
+	 * @return EE_Line_Item[]
1528
+	 * @throws EE_Error
1529
+	 */
1530
+	public function get_items()
1531
+	{
1532
+		return EEH_Line_Item::get_line_item_descendants($this);
1533
+	}
1534
+
1535
+
1536
+	/**
1537
+	 * Returns the amount taxable among this line item's children (or if it has no children,
1538
+	 * how much of it is taxable). Does not recalculate totals or subtotals.
1539
+	 * If the taxable total is negative, (eg, if none of the tickets were taxable,
1540
+	 * but there is a "Taxable" discount), returns 0.
1541
+	 *
1542
+	 * @return float
1543
+	 * @throws EE_Error
1544
+	 * @throws InvalidArgumentException
1545
+	 * @throws InvalidDataTypeException
1546
+	 * @throws InvalidInterfaceException
1547
+	 * @throws ReflectionException
1548
+	 */
1549
+	public function taxable_total()
1550
+	{
1551
+		$total = 0;
1552
+		if ($this->children()) {
1553
+			foreach ($this->children() as $child_line_item) {
1554
+				if ($child_line_item->type() === EEM_Line_Item::type_line_item && $child_line_item->is_taxable()) {
1555
+					// if it's a percent item, only take into account the percent
1556
+					// that's taxable too (the taxable total so far)
1557
+					if ($child_line_item->is_percent()) {
1558
+						$total += ($total * $child_line_item->percent() / 100);
1559
+					} else {
1560
+						$total += $child_line_item->total();
1561
+					}
1562
+				} elseif ($child_line_item->type() === EEM_Line_Item::type_sub_total) {
1563
+					$total += $child_line_item->taxable_total();
1564
+				}
1565
+			}
1566
+		}
1567
+		return max($total, 0);
1568
+	}
1569
+
1570
+
1571
+	/**
1572
+	 * Gets the transaction for this line item
1573
+	 *
1574
+	 * @return EE_Base_Class|EE_Transaction
1575
+	 * @throws EE_Error
1576
+	 * @throws InvalidArgumentException
1577
+	 * @throws InvalidDataTypeException
1578
+	 * @throws InvalidInterfaceException
1579
+	 * @throws ReflectionException
1580
+	 */
1581
+	public function transaction()
1582
+	{
1583
+		return $this->get_first_related(EEM_Line_Item::OBJ_TYPE_TRANSACTION);
1584
+	}
1585
+
1586
+
1587
+	/**
1588
+	 * Saves this line item to the DB, and recursively saves its descendants.
1589
+	 * Because there currently is no proper parent-child relation on the model,
1590
+	 * save_this_and_cached() will NOT save the descendants.
1591
+	 * Also sets the transaction on this line item and all its descendants before saving
1592
+	 *
1593
+	 * @param int $txn_id if none is provided, assumes $this->TXN_ID()
1594
+	 * @return int count of items saved
1595
+	 * @throws EE_Error
1596
+	 * @throws InvalidArgumentException
1597
+	 * @throws InvalidDataTypeException
1598
+	 * @throws InvalidInterfaceException
1599
+	 * @throws ReflectionException
1600
+	 */
1601
+	public function save_this_and_descendants_to_txn($txn_id = null)
1602
+	{
1603
+		$count = 0;
1604
+		if (! $txn_id) {
1605
+			$txn_id = $this->TXN_ID();
1606
+		}
1607
+		$this->set_TXN_ID($txn_id);
1608
+		$children = $this->children();
1609
+		$count += $this->save()
1610
+			? 1
1611
+			: 0;
1612
+		foreach ($children as $child_line_item) {
1613
+			if ($child_line_item instanceof EE_Line_Item) {
1614
+				$child_line_item->set_parent_ID($this->ID());
1615
+				$count += $child_line_item->save_this_and_descendants_to_txn($txn_id);
1616
+			}
1617
+		}
1618
+		return $count;
1619
+	}
1620
+
1621
+
1622
+	/**
1623
+	 * Saves this line item to the DB, and recursively saves its descendants.
1624
+	 *
1625
+	 * @return int count of items saved
1626
+	 * @throws EE_Error
1627
+	 * @throws InvalidArgumentException
1628
+	 * @throws InvalidDataTypeException
1629
+	 * @throws InvalidInterfaceException
1630
+	 * @throws ReflectionException
1631
+	 */
1632
+	public function save_this_and_descendants()
1633
+	{
1634
+		$count = 0;
1635
+		$children = $this->children();
1636
+		$count += $this->save()
1637
+			? 1
1638
+			: 0;
1639
+		foreach ($children as $child_line_item) {
1640
+			if ($child_line_item instanceof EE_Line_Item) {
1641
+				$child_line_item->set_parent_ID($this->ID());
1642
+				$count += $child_line_item->save_this_and_descendants();
1643
+			}
1644
+		}
1645
+		return $count;
1646
+	}
1647
+
1648
+
1649
+	/**
1650
+	 * returns the cancellation line item if this item was cancelled
1651
+	 *
1652
+	 * @return EE_Line_Item[]
1653
+	 * @throws InvalidArgumentException
1654
+	 * @throws InvalidInterfaceException
1655
+	 * @throws InvalidDataTypeException
1656
+	 * @throws ReflectionException
1657
+	 * @throws EE_Error
1658
+	 */
1659
+	public function get_cancellations()
1660
+	{
1661
+		EE_Registry::instance()->load_helper('Line_Item');
1662
+		return EEH_Line_Item::get_descendants_of_type($this, EEM_Line_Item::type_cancellation);
1663
+	}
1664
+
1665
+
1666
+	/**
1667
+	 * If this item has an ID, then this saves it again to update the db
1668
+	 *
1669
+	 * @return int count of items saved
1670
+	 * @throws EE_Error
1671
+	 * @throws InvalidArgumentException
1672
+	 * @throws InvalidDataTypeException
1673
+	 * @throws InvalidInterfaceException
1674
+	 * @throws ReflectionException
1675
+	 */
1676
+	public function maybe_save()
1677
+	{
1678
+		if ($this->ID()) {
1679
+			return $this->save();
1680
+		}
1681
+		return false;
1682
+	}
1683
+
1684
+
1685
+	/**
1686
+	 * clears the cached children and parent from the line item
1687
+	 *
1688
+	 * @return void
1689
+	 */
1690
+	public function clear_related_line_item_cache()
1691
+	{
1692
+		$this->_children = array();
1693
+		$this->_parent = null;
1694
+	}
1695
+
1696
+
1697
+	/**
1698
+	 * @param bool $raw
1699
+	 * @return int
1700
+	 * @throws EE_Error
1701
+	 * @throws InvalidArgumentException
1702
+	 * @throws InvalidDataTypeException
1703
+	 * @throws InvalidInterfaceException
1704
+	 * @throws ReflectionException
1705
+	 */
1706
+	public function timestamp($raw = false)
1707
+	{
1708
+		return $raw
1709
+			? $this->get_raw('LIN_timestamp')
1710
+			: $this->get('LIN_timestamp');
1711
+	}
1712
+
1713
+
1714
+
1715
+
1716
+	/************************* DEPRECATED *************************/
1717
+	/**
1718
+	 * @deprecated 4.6.0
1719
+	 * @param string $type one of the constants on EEM_Line_Item
1720
+	 * @return EE_Line_Item[]
1721
+	 * @throws EE_Error
1722
+	 */
1723
+	protected function _get_descendants_of_type($type)
1724
+	{
1725
+		EE_Error::doing_it_wrong(
1726
+			'EE_Line_Item::_get_descendants_of_type()',
1727
+			sprintf(
1728
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
1729
+				'EEH_Line_Item::get_descendants_of_type()'
1730
+			),
1731
+			'4.6.0'
1732
+		);
1733
+		return EEH_Line_Item::get_descendants_of_type($this, $type);
1734
+	}
1735
+
1736
+
1737
+	/**
1738
+	 * @deprecated 4.6.0
1739
+	 * @param string $type like one of the EEM_Line_Item::type_*
1740
+	 * @return EE_Line_Item
1741
+	 * @throws EE_Error
1742
+	 * @throws InvalidArgumentException
1743
+	 * @throws InvalidDataTypeException
1744
+	 * @throws InvalidInterfaceException
1745
+	 * @throws ReflectionException
1746
+	 */
1747
+	public function get_nearest_descendant_of_type($type)
1748
+	{
1749
+		EE_Error::doing_it_wrong(
1750
+			'EE_Line_Item::get_nearest_descendant_of_type()',
1751
+			sprintf(
1752
+				esc_html__('Method replaced with %1$s', 'event_espresso'),
1753
+				'EEH_Line_Item::get_nearest_descendant_of_type()'
1754
+			),
1755
+			'4.6.0'
1756
+		);
1757
+		return EEH_Line_Item::get_nearest_descendant_of_type($this, $type);
1758
+	}
1759 1759
 }
Please login to merge, or discard this patch.
Spacing   +15 added lines, -15 removed lines patch added patch discarded remove patch
@@ -88,7 +88,7 @@  discard block
 block discarded – undo
88 88
     protected function __construct($fieldValues = array(), $bydb = false, $timezone = '')
89 89
     {
90 90
         parent::__construct($fieldValues, $bydb, $timezone);
91
-        if (! $this->get('LIN_code')) {
91
+        if ( ! $this->get('LIN_code')) {
92 92
             $this->set_code($this->generate_code());
93 93
         }
94 94
     }
@@ -155,7 +155,7 @@  discard block
 block discarded – undo
155 155
     public function name()
156 156
     {
157 157
         $name = $this->get('LIN_name');
158
-        if (! $name) {
158
+        if ( ! $name) {
159 159
             $name = ucwords(str_replace('-', ' ', $this->type()));
160 160
         }
161 161
         return $name;
@@ -615,7 +615,7 @@  discard block
 block discarded – undo
615 615
                 )
616 616
             );
617 617
         }
618
-        if (! is_array($this->_children)) {
618
+        if ( ! is_array($this->_children)) {
619 619
             $this->_children = array();
620 620
         }
621 621
         return $this->_children;
@@ -856,7 +856,7 @@  discard block
 block discarded – undo
856 856
             }
857 857
             return $line_item->save();
858 858
         }
859
-        $this->_children[ $line_item->code() ] = $line_item;
859
+        $this->_children[$line_item->code()] = $line_item;
860 860
         if ($line_item->parent() !== $this) {
861 861
             $line_item->set_parent($this);
862 862
         }
@@ -880,7 +880,7 @@  discard block
 block discarded – undo
880 880
     public function set_parent($line_item)
881 881
     {
882 882
         if ($this->ID()) {
883
-            if (! $line_item->ID()) {
883
+            if ( ! $line_item->ID()) {
884 884
                 $line_item->save();
885 885
             }
886 886
             $this->set_parent_ID($line_item->ID());
@@ -912,8 +912,8 @@  discard block
 block discarded – undo
912 912
                 array(array('LIN_parent' => $this->ID(), 'LIN_code' => $code))
913 913
             );
914 914
         }
915
-        return isset($this->_children[ $code ])
916
-            ? $this->_children[ $code ]
915
+        return isset($this->_children[$code])
916
+            ? $this->_children[$code]
917 917
             : null;
918 918
     }
919 919
 
@@ -973,8 +973,8 @@  discard block
 block discarded – undo
973 973
             }
974 974
             return $items_deleted;
975 975
         }
976
-        if (isset($this->_children[ $code ])) {
977
-            unset($this->_children[ $code ]);
976
+        if (isset($this->_children[$code])) {
977
+            unset($this->_children[$code]);
978 978
             return 1;
979 979
         }
980 980
         return 0;
@@ -1015,7 +1015,7 @@  discard block
 block discarded – undo
1015 1015
     public function generate_code()
1016 1016
     {
1017 1017
         // each line item in the cart requires a unique identifier
1018
-        return md5($this->get('OBJ_type') . $this->get('OBJ_ID') . microtime());
1018
+        return md5($this->get('OBJ_type').$this->get('OBJ_ID').microtime());
1019 1019
     }
1020 1020
 
1021 1021
 
@@ -1227,7 +1227,7 @@  discard block
 block discarded – undo
1227 1227
         $has_children = ! empty($my_children);
1228 1228
         if ($has_children && $this->is_line_item()) {
1229 1229
             $total = $this->_recalculate_pretax_total_for_line_item($total, $my_children);
1230
-        } elseif (! $has_children && ($this->is_sub_line_item() || $this->is_line_item())) {
1230
+        } elseif ( ! $has_children && ($this->is_sub_line_item() || $this->is_line_item())) {
1231 1231
             $total = $this->unit_price() * $this->quantity();
1232 1232
         } elseif ($this->is_sub_total() || $this->is_total()) {
1233 1233
             $total = $this->_recalculate_pretax_total_for_subtotal($total, $my_children);
@@ -1236,17 +1236,17 @@  discard block
 block discarded – undo
1236 1236
             return 0;
1237 1237
         }
1238 1238
         // ensure all non-line items and non-sub-line-items have a quantity of 1 (except for Events)
1239
-        if (! $this->is_line_item() && ! $this->is_sub_line_item() && ! $this->is_cancellation()) {
1239
+        if ( ! $this->is_line_item() && ! $this->is_sub_line_item() && ! $this->is_cancellation()) {
1240 1240
             if ($this->OBJ_type() !== EEM_Line_Item::OBJ_TYPE_EVENT) {
1241 1241
                 $this->set_quantity(1);
1242 1242
             }
1243
-            if (! $this->is_percent()) {
1243
+            if ( ! $this->is_percent()) {
1244 1244
                 $this->set_unit_price($total);
1245 1245
             }
1246 1246
         }
1247 1247
         // we don't want to bother saving grand totals, because that needs to factor in taxes anyways
1248 1248
         // so it ought to be
1249
-        if (! $this->is_total()) {
1249
+        if ( ! $this->is_total()) {
1250 1250
             $this->set_total($total);
1251 1251
             // if not a percent line item, make sure we keep the unit price in sync
1252 1252
             if ($has_children
@@ -1601,7 +1601,7 @@  discard block
 block discarded – undo
1601 1601
     public function save_this_and_descendants_to_txn($txn_id = null)
1602 1602
     {
1603 1603
         $count = 0;
1604
-        if (! $txn_id) {
1604
+        if ( ! $txn_id) {
1605 1605
             $txn_id = $this->TXN_ID();
1606 1606
         }
1607 1607
         $this->set_TXN_ID($txn_id);
Please login to merge, or discard this patch.