Completed
Branch Gutenberg/event-attendees-bloc... (e27df5)
by
unknown
42:51 queued 28:10
created

EEM_Line_Item   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 443
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 23

Importance

Changes 0
Metric Value
dl 0
loc 443
rs 10
c 0
b 0
f 0
wmc 23
lcom 2
cbo 23

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 47 1
A get_all_of_type_for_transaction() 0 8 1
A get_all_non_ticket_line_items_for_transaction() 0 11 1
A delete_line_items_with_no_transaction() 0 18 1
A get_line_item_for_transaction_object() 0 8 1
A get_object_line_items_for_transaction() 0 12 4
A get_all_ticket_line_items_for_transaction() 0 9 1
A get_ticket_line_item_for_transaction() 0 10 1
A get_existing_promotion_line_item() 0 11 1
A get_all_promotion_line_items() 0 10 1
A get_line_item_for_registration() 0 4 1
A line_item_for_registration_query_params() 0 10 1
A get_total_line_items_with_no_transaction() 0 4 1
A get_total_line_items_for_active_carts() 0 4 1
A get_total_line_items_for_expired_carts() 0 4 1
A get_total_line_items_for_carts() 0 18 3
A getTicketLineItemsForExpiredCarts() 0 19 2
1
<?php
2
3
use EventEspresso\core\exceptions\InvalidDataTypeException;
4
use EventEspresso\core\exceptions\InvalidInterfaceException;
5
use EventEspresso\core\services\loaders\LoaderFactory;
6
7
/**
8
 * Line Item Model. Mostly used for storing a snapshot of all the items in a transaction
9
 * as they were recorded at the time of being added to the cart.
10
 * There are different 'types' of line items: item, sub-item, tax, sub-total, and total.
11
 * Note that line items can be nested. For example, a total line item should have one-or-more
12
 * children sub-totals. Likewise, sub-totals should have one-or-more nested items or taxes
13
 * (or maybe promotions or products?). Also, items can have nested sub-items (eg. an item could be a
14
 * ticket, which has many sub-item prices which together make up the price of that ticket).
15
 * Note that line items should point to real model objects using OBJ_ID and OBJ_type (note:
16
 * there is a current limitation that they can only point to models with INT primary keys),
17
 * but this is NOT required. And in fact, the items they are related to CAN be deleted, but
18
 * the line item should still exist (in this case it merely shows that there was ONCE a model
19
 * object the line item was based off of).
20
 *
21
 * In usage, Line Items are first stored on the EE_Cart, but not saved until a user's registration is
22
 * finalized (like how the EE_Transaction is stored in the session until it is confirmed).
23
 * Many of their methods (like
24
 *
25
 *
26
 * @package            Event Espresso
27
 * @subpackage        includes/models/EEM_Line_Item.model.php
28
 * @author            Mike Nelson
29
 *
30
 * ------------------------------------------------------------------------
31
 */
32
class EEM_Line_Item extends EEM_Base
33
{
34
35
    /**
36
     * Tax sub-total is just the total of all the taxes, which should be children
37
     * of this line item. There should only ever be one tax sub-total, and it should
38
     * be a direct child of. Its quantity and LIN_unit_price = 1.
39
     */
40
    const type_tax_sub_total = 'tax-sub-total';
41
42
    /**
43
     * Tax line items indicate a tax applied to all the taxable line items.
44
     * Should not have any children line items. Its LIN_unit_price = 0. Its LIN_percent is a percent, not a decimal
45
     * (eg 10% tax = 10, not 0.1). Its LIN_total = LIN_unit_price * pre-tax-total. Quantity = 1.
46
     */
47
    const type_tax = 'tax';
48
49
    /**
50
     * Indicating individual items purchased, or discounts or surcharges.
51
     * The sum of all the regular line items  plus the tax items should equal
52
     * the grand total.
53
     * Possible children are sub-line-items and cancellations.
54
     * For flat items, LIN_unit_price * LIN_quantity = LIN_total. Its LIN_total is the sum of all the children
55
     * LIN_totals. Its LIN_percent = 0.
56
     * For percent items, its LIN_unit_price = 0. Its LIN_percent is a percent, not a decimal (eg 10% = 10, not 0.1).
57
     * Its LIN_total is LIN_percent / 100 * sum of lower-priority sibling line items. Quantity = 1.
58
     */
59
    const type_line_item = 'line-item';
60
61
    /**
62
     * Line item indicating all the factors that make a single line item.
63
     * Sub-line items should have NO children line items.
64
     * For flat sub-items, their quantity should match their parent item, their LIN_unit_price should be this sub-item's
65
     * contribution towards the price of ONE of their parent items, and its LIN_total should be
66
     *  = LIN_quantity * LIN_unit_price. Its LIN_percent = 0.
67
     * For percent sub-items, the quantity should be 1, LIN_unit_price should be 0, and its LIN_total should
68
     * = LIN_percent / 100 * sum of lower-priority sibling line items..
69
     */
70
    const type_sub_line_item = 'sub-item';
71
72
    /**
73
     * Line item indicating a sub-total (eg total for an event, or pre-tax subtotal).
74
     * Direct children should be event subtotals.
75
     * Should have quantity of 1, and a LIN_total and LIN_unit_price of the sum of all its sub-items' LIN_totals.
76
     *
77
     */
78
    const type_sub_total = 'sub-total';
79
80
    /**
81
     * Line item for the grand total of an order. Its direct children
82
     * should be tax subtotals and (pre-tax) subtotals, and possibly a regular line item
83
     * indicating a transaction-wide discount/surcharge. Should have a quantity of 1, a LIN_total and LIN_unit_price of
84
     * the entire order's mount.
85
     */
86
    const type_total = 'total';
87
88
    /**
89
     * When a line item is cancelled, a sub-line-item of type 'cancellation'
90
     * should be created, indicating the quantity that were cancelled
91
     * (because a line item could have a quantity of 1, and its cancellation item
92
     * could be for 3, indicating that originally 4 were purchased, but 3 have been
93
     * cancelled, and only one remains).
94
     * When items are refunded, a cancellation line item should be made, which points
95
     * to teh payment model object which actually refunded the payment.
96
     * Cancellations should NOT have any children line items; the should NOT affect
97
     * any calculations, and are only meant as a record that cancellations have occurred.
98
     * Their LIN_percent should be 0.
99
     */
100
    const type_cancellation = 'cancellation';
101
102
    // private instance of the EEM_Line_Item object
103
    protected static $_instance = null;
104
105
106
    /**
107
     *        private constructor to prevent direct creation
108
     * @Constructor
109
     * @access protected
110
     * @param string $timezone string representing the timezone we want to set for returned Date Time Strings (and any incoming timezone data that gets saved).  Note this just sends the timezone info to the date time model field objects.  Default is NULL (and will be assumed using the set timezone in the 'timezone_string' wp option)
111
     * @return \EEM_Line_Item
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
112
     */
113
    protected function __construct($timezone)
114
    {
115
        $this->singular_item = __('Line Item', 'event_espresso');
116
        $this->plural_item = __('Line Items', 'event_espresso');
117
118
        $this->_tables = array(
119
            'Line_Item' => new EE_Primary_Table('esp_line_item', 'LIN_ID')
120
        );
121
        $line_items_can_be_for = apply_filters('FHEE__EEM_Line_Item__line_items_can_be_for', array('Ticket', 'Price', 'Event'));
122
        $this->_fields = array(
123
            'Line_Item' => array(
124
                'LIN_ID' => new EE_Primary_Key_Int_Field('LIN_ID', __("ID", "event_espresso")),
125
                'LIN_code' => new EE_Slug_Field('LIN_code', __("Code for index into Cart", "event_espresso"), true),
126
                'TXN_ID' => new EE_Foreign_Key_Int_Field('TXN_ID', __("Transaction ID", "event_espresso"), true, null, 'Transaction'),
127
                'LIN_name' => new EE_Full_HTML_Field('LIN_name', __("Line Item Name", "event_espresso"), false, ''),
128
                'LIN_desc' => new EE_Full_HTML_Field('LIN_desc', __("Line Item Description", "event_espresso"), true),
129
                'LIN_unit_price' => new EE_Money_Field('LIN_unit_price', __("Unit Price", "event_espresso"), false, 0),
130
                'LIN_percent' => new EE_Float_Field('LIN_percent', __("Percent", "event_espresso"), false, 0),
131
                'LIN_is_taxable' => new EE_Boolean_Field('LIN_is_taxable', __("Taxable", "event_espresso"), false, false),
132
                'LIN_order' => new EE_Integer_Field('LIN_order', __("Order of Application towards total of parent", "event_espresso"), false, 1),
133
                'LIN_total' => new EE_Money_Field('LIN_total', __("Total (unit price x quantity)", "event_espresso"), false, 0),
134
                'LIN_quantity' => new EE_Integer_Field('LIN_quantity', __("Quantity", "event_espresso"), true, 1),
135
                'LIN_parent' => new EE_Integer_Field('LIN_parent', __("Parent ID (this item goes towards that Line Item's total)", "event_espresso"), true, null),
136
                'LIN_type' => new EE_Enum_Text_Field('LIN_type', __("Type", "event_espresso"), false, 'line-item', array(
137
                        self::type_line_item => __("Line Item", "event_espresso"),
138
                        self::type_sub_line_item => __("Sub-Item", "event_espresso"),
139
                        self::type_sub_total => __("Subtotal", "event_espresso"),
140
                        self::type_tax_sub_total => __("Tax Subtotal", "event_espresso"),
141
                        self::type_tax => __("Tax", "event_espresso"),
142
                        self::type_total => __("Total", "event_espresso"),
143
                        self::type_cancellation => __('Cancellation', 'event_espresso')
144
                    )),
145
                'OBJ_ID' => new EE_Foreign_Key_Int_Field('OBJ_ID', __('ID of Item purchased.', 'event_espresso'), true, null, $line_items_can_be_for),
146
                'OBJ_type' => new EE_Any_Foreign_Model_Name_Field('OBJ_type', __("Model Name this Line Item is for", "event_espresso"), true, null, $line_items_can_be_for),
147
                'LIN_timestamp' => new EE_Datetime_Field('LIN_timestamp', __('When the line item was created', 'event_espresso'), false, EE_Datetime_Field::now, $timezone),
148
            )
149
        );
150
        $this->_model_relations = array(
151
            'Transaction' => new EE_Belongs_To_Relation(),
152
            'Ticket' => new EE_Belongs_To_Any_Relation(),
153
            'Price' => new EE_Belongs_To_Any_Relation(),
154
            'Event' => new EE_Belongs_To_Any_Relation()
155
        );
156
        $this->_model_chain_to_wp_user = 'Transaction.Registration.Event';
157
        $this->_caps_slug = 'transactions';
158
        parent::__construct($timezone);
159
    }
160
161
162
    /**
163
     * Gets all the line items for this transaction of the given type
164
     * @param string $line_item_type like one of EEM_Line_Item::type_*
165
     * @param EE_Transaction|int $transaction
166
     * @return EE_Line_Item[]
167
     */
168
    public function get_all_of_type_for_transaction($line_item_type, $transaction)
169
    {
170
        $transaction = EEM_Transaction::instance()->ensure_is_ID($transaction);
171
        return $this->get_all(array(array(
172
            'LIN_type' => $line_item_type,
173
            'TXN_ID' => $transaction
174
        )));
175
    }
176
177
178
    /**
179
     * Gets all line items unrelated to tickets that are normal line items
180
     * (eg shipping, promotions, and miscellaneous other stuff should probably fit in this category)
181
     * @param EE_Transaction|int $transaction
182
     * @return EE_Line_Item[]
183
     */
184
    public function get_all_non_ticket_line_items_for_transaction($transaction)
185
    {
186
        $transaction = EEM_Transaction::instance()->ensure_is_ID($transaction);
187
        return $this->get_all(array(array(
188
            'LIN_type' => self::type_line_item,
189
            'TXN_ID' => $transaction,
190
            'OR' => array(
191
                'OBJ_type*notticket' => array('!=', 'Ticket'),
192
                'OBJ_type*null' => array('IS_NULL'))
193
        )));
194
    }
195
196
    /**
197
     * Deletes line items with no transaction who have passed the transaction cutoff time.
198
     * This needs to be very efficient
199
     * because if there are spam bots afoot there will be LOTS of line items
200
     * @return int count of how many deleted
201
     */
202
    public function delete_line_items_with_no_transaction()
203
    {
204
        /** @type WPDB $wpdb */
205
        global $wpdb;
206
        $time_to_leave_alone = apply_filters(
207
            'FHEE__EEM_Line_Item__delete_line_items_with_no_transaction__time_to_leave_alone',
208
            WEEK_IN_SECONDS
209
        );
210
        $query = $wpdb->prepare(
211
            'DELETE li
212
				FROM ' . $this->table() . ' li
213
				LEFT JOIN ' . EEM_Transaction::instance()->table() . ' t ON li.TXN_ID = t.TXN_ID
214
				WHERE t.TXN_ID IS NULL AND li.LIN_timestamp < %s',
215
            // use GMT time because that's what TXN_timestamps are in
216
            date('Y-m-d H:i:s', time() - $time_to_leave_alone)
217
        );
218
        return $wpdb->query($query);
219
    }
220
221
222
    /**
223
     * get_line_item_for_transaction_object
224
     * Gets a transaction's line item record for a specific object such as a EE_Event or EE_Ticket
225
     *
226
     * @param int $TXN_ID
227
     * @param \EE_Base_Class $object
228
     * @return EE_Line_Item[]
229
     */
230
    public function get_line_item_for_transaction_object($TXN_ID, EE_Base_Class $object)
231
    {
232
        return $this->get_all(array(array(
233
            'TXN_ID' => $TXN_ID,
234
            'OBJ_type' => str_replace('EE_', '', get_class($object)),
235
            'OBJ_ID' => $object->ID()
236
        )));
237
    }
238
239
240
    /**
241
     * get_object_line_items_for_transaction
242
     * Gets all of the the object line items for a transaction, based on an object type plus an array of object IDs
243
     *
244
     * @param int $TXN_ID
245
     * @param string $OBJ_type
246
     * @param array $OBJ_IDs
247
     * @return EE_Line_Item[]
248
     */
249
    public function get_object_line_items_for_transaction($TXN_ID, $OBJ_type = 'Event', $OBJ_IDs = array())
250
    {
251
        $query_params = array(
252
            'OBJ_type' => $OBJ_type,
253
            // if incoming $OBJ_IDs is an array, then make sure it is formatted correctly for the query
254
            'OBJ_ID' => is_array($OBJ_IDs) && !isset($OBJ_IDs['IN']) ? array('IN', $OBJ_IDs) : $OBJ_IDs
255
        );
256
        if ($TXN_ID) {
257
            $query_params['TXN_ID'] = $TXN_ID;
258
        }
259
        return $this->get_all(array($query_params));
260
    }
261
262
263
    /**
264
     * get_all_ticket_line_items_for_transaction
265
     *
266
     * @param EE_Transaction $transaction
267
     * @return EE_Line_Item[]
268
     */
269
    public function get_all_ticket_line_items_for_transaction(EE_Transaction $transaction)
270
    {
271
        return $this->get_all(array(
272
            array(
273
                'TXN_ID' => $transaction->ID(),
274
                'OBJ_type' => 'Ticket',
275
            )
276
        ));
277
    }
278
279
280
    /**
281
     * get_ticket_line_item_for_transaction
282
     *
283
     * @param int $TXN_ID
284
     * @param int $TKT_ID
285
     * @return \EE_Line_Item
286
     */
287
    public function get_ticket_line_item_for_transaction($TXN_ID, $TKT_ID)
288
    {
289
        return $this->get_one(array(
290
            array(
291
                'TXN_ID' => EEM_Transaction::instance()->ensure_is_ID($TXN_ID),
292
                'OBJ_ID' => $TKT_ID,
293
                'OBJ_type' => 'Ticket',
294
            )
295
        ));
296
    }
297
298
299
    /**
300
     * get_existing_promotion_line_item
301
     * searches the cart for existing line items for the specified promotion
302
     *
303
     * @since   1.0.0
304
     *
305
     * @param EE_Line_Item $parent_line_item
306
     * @param EE_Promotion $promotion
307
     * @return EE_Line_Item
308
     */
309
    public function get_existing_promotion_line_item(EE_Line_Item $parent_line_item, EE_Promotion $promotion)
310
    {
311
        return $this->get_one(array(
312
            array(
313
                'TXN_ID' => $parent_line_item->TXN_ID(),
314
                'LIN_parent' => $parent_line_item->ID(),
315
                'OBJ_type' => 'Promotion',
316
                'OBJ_ID' => $promotion->ID()
317
            )
318
        ));
319
    }
320
321
322
    /**
323
     * get_all_promotion_line_items
324
     * searches the cart for any and all existing promotion line items
325
     *
326
     * @since   1.0.0
327
     *
328
     * @param EE_Line_Item $parent_line_item
329
     * @return EE_Line_Item[]
330
     */
331
    public function get_all_promotion_line_items(EE_Line_Item $parent_line_item)
332
    {
333
        return $this->get_all(array(
334
            array(
335
                'TXN_ID' => $parent_line_item->TXN_ID(),
336
                'LIN_parent' => $parent_line_item->ID(),
337
                'OBJ_type' => 'Promotion'
338
            )
339
        ));
340
    }
341
342
    /**
343
     * Gets the registration's corresponding line item.
344
     * Note: basically does NOT support having multiple line items for a single ticket,
345
     * which would happen if some of the registrations had a price modifier while others didn't.
346
     * In order to support that, we'd probably need a LIN_ID on registrations or something.
347
     * @param EE_Registration $registration
348
     * @return EE_Line_ITem
349
     */
350
    public function get_line_item_for_registration(EE_Registration $registration)
351
    {
352
        return $this->get_one($this->line_item_for_registration_query_params($registration));
353
    }
354
355
    /**
356
     * Gets the query params used to retrieve a specific line item for the given registration
357
     * @param EE_Registration $registration
358
     * @param array $original_query_params any extra query params you'd like to be merged with
359
     * @return array like EEM_Base::get_all()'s $query_params
360
     */
361
    public function line_item_for_registration_query_params(EE_Registration $registration, $original_query_params = array())
362
    {
363
        return array_replace_recursive($original_query_params, array(
364
            array(
365
                'OBJ_ID' => $registration->ticket_ID(),
366
                'OBJ_type' => 'Ticket',
367
                'TXN_ID' => $registration->transaction_ID()
368
            )
369
        ));
370
    }
371
372
373
    /**
374
     * @return EE_Base_Class[]|EE_Line_Item[]
375
     * @throws InvalidInterfaceException
376
     * @throws InvalidDataTypeException
377
     * @throws EE_Error
378
     * @throws InvalidArgumentException
379
     */
380
    public function get_total_line_items_with_no_transaction()
381
    {
382
        return $this->get_total_line_items_for_carts();
383
    }
384
385
386
    /**
387
     * @return EE_Base_Class[]|EE_Line_Item[]
388
     * @throws InvalidInterfaceException
389
     * @throws InvalidDataTypeException
390
     * @throws EE_Error
391
     * @throws InvalidArgumentException
392
     */
393
    public function get_total_line_items_for_active_carts()
394
    {
395
        return $this->get_total_line_items_for_carts(false);
396
    }
397
398
399
    /**
400
     * @return EE_Base_Class[]|EE_Line_Item[]
401
     * @throws InvalidInterfaceException
402
     * @throws InvalidDataTypeException
403
     * @throws EE_Error
404
     * @throws InvalidArgumentException
405
     */
406
    public function get_total_line_items_for_expired_carts()
407
    {
408
        return $this->get_total_line_items_for_carts(true);
409
    }
410
411
412
    /**
413
     * Returns an array of grand total line items where the TXN_ID is 0.
414
     * If $expired is set to true, then only line items for expired sessions will be returned.
415
     * If $expired is set to false, then only line items for active sessions will be returned.
416
     *
417
     * @param null $expired
418
     * @return EE_Base_Class[]|EE_Line_Item[]
419
     * @throws EE_Error
420
     * @throws InvalidArgumentException
421
     * @throws InvalidDataTypeException
422
     * @throws InvalidInterfaceException
423
     */
424
    private function get_total_line_items_for_carts($expired = null)
425
    {
426
        $where_params = array(
427
            'TXN_ID' => 0,
428
            'LIN_type' => 'total',
429
        );
430
        if ($expired !== null) {
431
            /** @var EventEspresso\core\domain\values\session\SessionLifespan $session_lifespan */
432
            $session_lifespan = LoaderFactory::getLoader()->getShared(
433
                'EventEspresso\core\domain\values\session\SessionLifespan'
434
            );
435
            $where_params['LIN_timestamp'] = array(
436
                $expired ? '<=' : '>',
437
                $session_lifespan->expiration(),
438
            );
439
        }
440
        return $this->get_all(array($where_params));
441
    }
442
443
444
    /**
445
     * Returns an array of ticket total line items where the TXN_ID is 0
446
     * AND the timestamp is older than the session lifespan.
447
     *
448
     * @param int $timestamp
449
     * @return EE_Base_Class[]|EE_Line_Item[]
450
     * @throws EE_Error
451
     * @throws InvalidArgumentException
452
     * @throws InvalidDataTypeException
453
     * @throws InvalidInterfaceException
454
     */
455
    public function getTicketLineItemsForExpiredCarts($timestamp = 0)
456
    {
457
        if (! absint($timestamp)) {
458
            /** @var EventEspresso\core\domain\values\session\SessionLifespan $session_lifespan */
459
            $session_lifespan = LoaderFactory::getLoader()->getShared(
460
                'EventEspresso\core\domain\values\session\SessionLifespan'
461
            );
462
            $timestamp = $session_lifespan->expiration();
463
        }
464
        return $this->get_all(
465
            array(
466
                array(
467
                    'TXN_ID'        => 0,
468
                    'OBJ_type'      => 'Ticket',
469
                    'LIN_timestamp' => array('<=', $timestamp),
470
                )
471
            )
472
        );
473
    }
474
}
475