Completed
Branch BUG-10878-event-spaces-remaini... (17f80a)
by
unknown
13:41
created

EventSpacesCalculator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 2
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace EventEspresso\core\domain\services\event;
4
5
use EE_Datetime;
6
use EE_Error;
7
use EE_Event;
8
use EE_Ticket;
9
use EEM_Ticket;
10
11
defined('EVENT_ESPRESSO_VERSION') || exit;
12
13
14
/**
15
 * Class EventSpacesCalculator
16
 * Calculates total available spaces for an event with no regard for sold tickets,
17
 * or spaces remaining based on "saleable" tickets
18
 *
19
 * @package EventEspresso\core\domain\services\event
20
 * @author  Brent Christensen
21
 * @since   $VID:$
22
 */
23
class EventSpacesCalculator
24
{
25
26
    /**
27
     * @var EE_Event $event
28
     */
29
    private $event;
30
31
    /**
32
     * @var array $datetime_query_params
33
     */
34
    private $datetime_query_params;
35
36
    /**
37
     * @var EE_Ticket[] $active_tickets
38
     */
39
    private $active_tickets = array();
40
41
    /**
42
     * @var EE_Datetime[] $datetimes
43
     */
44
    private $datetimes = array();
45
46
    /**
47
     * Array of Ticket IDs grouped by Datetime
48
     *
49
     * @var array $datetimes
50
     */
51
    private $datetime_tickets = array();
52
53
    /**
54
     * Max spaces for each Datetime (reg limit - previous sold)
55
     *
56
     * @var array $datetime_spaces
57
     */
58
    private $datetime_spaces = array();
59
60
    /**
61
     * Array of Datetime IDs grouped by Ticket
62
     *
63
     * @var array $ticket_datetimes
64
     */
65
    private $ticket_datetimes = array();
66
67
    /**
68
     * maximum ticket quantities for each ticket (adjusted for reg limit)
69
     *
70
     * @var array $ticket_quantities
71
     */
72
    private $ticket_quantities = array();
73
74
    /**
75
     * total quantity of sold and reserved for each ticket
76
     *
77
     * @var array $tickets_sold
78
     */
79
    private $tickets_sold = array();
80
81
    /**
82
     * total spaces available across all datetimes
83
     *
84
     * @var array $total_spaces
85
     */
86
    private $total_spaces = array();
87
88
    /**
89
     * @var boolean $debug
90
     */
91
    private $debug = false;
92
93
94
95
    /**
96
     * EventSpacesCalculator constructor.
97
     *
98
     * @param EE_Event $event
99
     * @param array    $datetime_query_params
100
     * @throws EE_Error
101
     */
102
    public function __construct(EE_Event $event, array $datetime_query_params = array())
103
    {
104
        $this->event = $event;
105
        $this->datetime_query_params = $datetime_query_params + array('order_by' => array('DTT_reg_limit' => 'ASC'));
106
    }
107
108
109
110
    /**
111
     * @return EE_Ticket[]
112
     * @throws EE_Error
113
     */
114
    public function getActiveTickets()
115
    {
116
        if(empty($this->active_tickets)) {
117
            $this->active_tickets = $this->event->tickets(
118
                array(
119
                    array(
120
                        'TKT_end_date' => array('>=', EEM_Ticket::instance()->current_time_for_query('TKT_end_date')),
121
                        'TKT_deleted'  => false,
122
                    ),
123
                    'order_by' => array('TKT_qty' => 'ASC')
124
                )
125
            );
126
        }
127
        return $this->active_tickets;
128
    }
129
130
131
132
    /**
133
     * @param EE_Ticket[] $active_tickets
134
     * @throws EE_Error
135
     */
136
    public function setActiveTickets(array $active_tickets)
137
    {
138
        // sort incoming array by ticket quantity (asc)
139
        usort(
140
            $active_tickets,
141
            function (EE_Ticket $a, EE_Ticket $b) {
142
                if ($a->qty() === $b->qty()) {
143
                    return 0;
144
                }
145
                return ($a->qty() < $b->qty()) ? -1 : 1;
146
            }
147
        );
148
        $this->active_tickets = $active_tickets;
149
    }
150
151
152
153
    /**
154
     * @return EE_Datetime[]
155
     */
156
    public function getDatetimes()
157
    {
158
        return $this->datetimes;
159
    }
160
161
162
163
    /**
164
     * @param EE_Datetime $datetime
165
     * @throws EE_Error
166
     *
167
     */
168
    public function setDatetime(EE_Datetime $datetime)
169
    {
170
        $this->datetimes[$datetime->ID()] = $datetime;
171
    }
172
173
174
175
    /**
176
     * calculate spaces remaining based on "saleable" tickets
177
     *
178
     * @return int|float
179
     * @throws EE_Error
180
     */
181
    public function spacesRemaining()
182
    {
183
        $this->initialize();
184
        return $this->calculate();
185
    }
186
187
188
189
    /**
190
     * calculates total available spaces for an event with no regard for sold tickets
191
     *
192
     * @return int|float
193
     * @throws EE_Error
194
     */
195
    public function totalSpacesAvailable()
196
    {
197
        $this->initialize();
198
        return $this->calculate(false);
199
    }
200
201
202
203
    /**
204
     * Loops through the active tickets for the event
205
     * and builds a series of data arrays that will be used for calculating
206
     * the total maximum available spaces, as well as the spaces remaining.
207
     * Because ticket quantities affect datetime spaces and vice versa,
208
     * we need to be constantly updating these data arrays as things change,
209
     * which is the entire reason for their existence.
210
     *
211
     * @throws EE_Error
212
     */
213
    private function initialize()
214
    {
215
        if ($this->debug) {
216
            echo "\n\n" . __LINE__ . ') ' . strtoupper(__METHOD__) . '()';
217
        }
218
        $this->datetime_tickets = array();
219
        $this->datetime_spaces = array();
220
        $this->ticket_datetimes = array();
221
        $this->ticket_quantities = array();
222
        $this->tickets_sold = array();
223
        $this->total_spaces = array();
224
        $active_tickets = $this->getActiveTickets();
225
        if (! empty($active_tickets)) {
226
            foreach ($active_tickets as $ticket) {
227
                if (! $ticket instanceof EE_Ticket) {
228
                    continue;
229
                }
230
                $TKT_ID = $ticket->name();
231
                // to start, we'll just consider the raw qty to be the maximum availability for this ticket
232
                $max_tickets = $ticket->qty();
233
                // but we'll adjust that after looping over each datetime for the ticket and checking reg limits
234
                $ticket_datetimes = $ticket->datetimes($this->datetime_query_params);
235
                foreach ($ticket_datetimes as $datetime) {
236
                    if (! $datetime instanceof EE_Datetime) {
237
                        continue;
238
                    }
239
                    // save all datetimes
240
                    $this->setDatetime($datetime);
241
                    $DTT_ID = $datetime->name();
242
                    $reg_limit = $datetime->reg_limit();
243
                    // ticket quantity can not exceed datetime reg limit
244
                    $max_tickets = min($max_tickets, $reg_limit);
245
                    // as described earlier, because we need to be able to constantly adjust numbers for things,
246
                    // we are going to move all of our data into the following arrays:
247
                    // datetime spaces initially represents the reg limit for each datetime,
248
                    // but this will get adjusted as tickets are accounted for
249
                    $this->datetime_spaces[$DTT_ID] = $reg_limit;
250
                    // just an array of ticket IDs grouped by datetime
251
                    $this->datetime_tickets[$DTT_ID][] = $TKT_ID;
252
                    // and an array of datetime IDs grouped by ticket
253
                    $this->ticket_datetimes[$TKT_ID][] = $DTT_ID;
254
                }
255
                // total quantity of sold and reserved for each ticket
256
                $this->tickets_sold[$TKT_ID] = $ticket->sold() + $ticket->reserved();
257
                // and the maximum ticket quantities for each ticket (adjusted for reg limit)
258
                $this->ticket_quantities[$TKT_ID] = $max_tickets;
259
            }
260
        }
261
        // sort datetime spaces by reg limit, but maintain our string indexes
262
        asort($this->datetime_spaces, SORT_NUMERIC);
263
        // datetime tickets need to be sorted in the SAME order as the above array...
264
        // so we'll just use array_merge() to take the structure of datetime_spaces
265
        // but overwrite all of the data with that from datetime_tickets
266
        $this->datetime_tickets = array_merge(
267
            $this->datetime_spaces,
268
            $this->datetime_tickets
269
        );
270
        if ($this->debug) {
271
            \EEH_Debug_Tools::printr($this->datetime_spaces, 'datetime_spaces', __FILE__, __LINE__);
272
            \EEH_Debug_Tools::printr($this->datetime_tickets, 'datetime_tickets', __FILE__, __LINE__);
273
            \EEH_Debug_Tools::printr($this->ticket_quantities, 'ticket_quantities', __FILE__, __LINE__);
274
        }
275
    }
276
277
278
279
    /**
280
     * performs calculations on initialized data
281
     *
282
     * @param bool $consider_sold
283
     * @return int|float
284
     */
285
    private function calculate($consider_sold = true)
286
    {
287
        if ($this->debug) {
288
            echo "\n\n" . __LINE__ . ') ' . strtoupper(__METHOD__) . '()';
289
        }
290
        foreach ($this->datetime_tickets as $DTT_ID => $tickets) {
291
            $this->trackAvailableSpacesForDatetimes($DTT_ID, $tickets);
292
        }
293
        // total spaces available is just the sum of the spaces available for each datetime
294
        $spaces_remaining = array_sum($this->total_spaces);
295
        if($consider_sold) {
296
            // less the sum of all tickets sold for these datetimes
297
            $spaces_remaining -= array_sum($this->tickets_sold);
298
        }
299 View Code Duplication
        if ($this->debug) {
300
            \EEH_Debug_Tools::printr($this->total_spaces, '$this->total_spaces', __FILE__, __LINE__);
301
            \EEH_Debug_Tools::printr($this->tickets_sold, '$this->tickets_sold', __FILE__, __LINE__);
302
            \EEH_Debug_Tools::printr($spaces_remaining, '$spaces_remaining', __FILE__, __LINE__);
303
        }
304
        return $spaces_remaining;
305
    }
306
307
308
309
    /**
310
     * @param string $DTT_ID
311
     * @param array  $tickets
312
     */
313
    private function trackAvailableSpacesForDatetimes($DTT_ID, array $tickets)
314
    {
315
        // make sure a reg limit is set for the datetime
316
        $reg_limit = isset($this->datetime_spaces[$DTT_ID])
317
            ? $this->datetime_spaces[$DTT_ID]
318
            : 0;
319
        // and bail if it is not
320
        if (! $reg_limit) {
321
            if ($this->debug) {
322
                echo "\n . {$DTT_ID} AT CAPACITY";
323
            }
324
            return;
325
        }
326
        if ($this->debug) {
327
            echo "\n\n{$DTT_ID}";
328
            echo "\n . " . 'REG LIMIT: ' . $reg_limit;
329
        }
330
        // set default number of available spaces
331
        $spaces = 0;
332
        $this->total_spaces[$DTT_ID] = 0;
333
        foreach ($tickets as $TKT_ID) {
334
            $spaces = $this->calculateAvailableSpacesForTicket(
335
                $DTT_ID,
336
                $reg_limit,
337
                $TKT_ID,
338
                $spaces
339
            );
340
        }
341
        // spaces can't be negative
342
        $spaces = max($spaces, 0);
343
        if ($spaces) {
344
            // track any non-zero values
345
            $this->total_spaces[$DTT_ID] += $spaces;
346
            if ($this->debug) {
347
                echo "\n . spaces: {$spaces}";
348
            }
349
        } else {
350
            if ($this->debug) {
351
                echo "\n . NO TICKETS AVAILABLE FOR DATETIME";
352
            }
353
        }
354 View Code Duplication
        if ($this->debug) {
355
            \EEH_Debug_Tools::printr($this->total_spaces[$DTT_ID], '$spaces_remaining', __FILE__, __LINE__);
356
            \EEH_Debug_Tools::printr($this->ticket_quantities, '$ticket_quantities', __FILE__, __LINE__);
357
            \EEH_Debug_Tools::printr($this->datetime_spaces, 'datetime_spaces', __FILE__, __LINE__);
358
        }
359
    }
360
361
362
363
    /**
364
     * @param string $DTT_ID
365
     * @param int    $reg_limit
366
     * @param string $TKT_ID
367
     * @param int    $spaces
368
     * @return int
369
     */
370
    private function calculateAvailableSpacesForTicket($DTT_ID, $reg_limit,$TKT_ID, $spaces)
371
    {
372
        if ($this->debug) {
373
            echo "\n . {$TKT_ID}";
374
        }
375
        // make sure ticket quantity is set
376
        $ticket_quantity = isset($this->ticket_quantities[$TKT_ID])
377
            ? $this->ticket_quantities[$TKT_ID]
378
            : 0;
379
        if ($ticket_quantity) {
380
            if ($this->debug) {
381
                echo "\n . . spaces ({$spaces}) <= reg_limit ({$reg_limit}) = ";
382
                echo ($spaces <= $reg_limit)
383
                    ? 'true'
384
                    : 'false';
385
            }
386
            // if the datetime is NOT at full capacity yet
387
            if ($spaces <= $reg_limit) {
388
                // then the maximum ticket quantity we can allocate is the lowest value of either:
389
                //  the number of remaining spaces for the datetime, which is the limit - spaces already taken
390
                //  or the maximum ticket quantity
391
                $ticket_quantity = min(($reg_limit - $spaces), $ticket_quantity);
392
                // adjust the available quantity in our tracking array
393
                $this->ticket_quantities[$TKT_ID] -= $ticket_quantity;
394
                // and increment spaces allocated for this datetime
395
                $spaces += $ticket_quantity;
396
                if ($this->debug) {
397
                    echo "\n . . ticket quantity: {$ticket_quantity} ({$TKT_ID})";
398
                    echo "\n . . . allocate {$ticket_quantity} tickets ({$TKT_ID})";
399
                    if ($spaces >= $reg_limit) {
400
                        echo "\n . {$DTT_ID} AT CAPACITY";
401
                    }
402
                }
403
                // now adjust all other datetimes that allow access to this ticket
404
                $this->adjustDatetimes(
405
                    $DTT_ID,
406
                    $spaces,
407
                    $reg_limit,
408
                    $TKT_ID,
409
                    $ticket_quantity
410
                );
411
            }
412
        }
413
        return $spaces;
414
    }
415
416
417
418
    /**
419
     * subtracts ticket amounts from all datetime reg limits
420
     * that allow access to the ticket specified,
421
     * because that ticket could be used
422
     * to attend any of the datetimes it has access to
423
     *
424
     * @param string $DTT_ID
425
     * @param int    $spaces
426
     * @param int    $reg_limit
427
     * @param string $TKT_ID
428
     * @param int    $ticket_quantity
429
     */
430
    private function adjustDatetimes($DTT_ID, $spaces, $reg_limit, $TKT_ID, $ticket_quantity)
431
    {
432
        foreach ($this->datetime_tickets as $datetime_ID => $datetime_tickets) {
433
            // if the supplied ticket has access to this datetime
434
            if (in_array($TKT_ID, $datetime_tickets, true)) {
435
                // and datetime has spaces available
436
                if (isset($this->datetime_spaces[$datetime_ID])) {
437
                    // then decrement the available spaces for the datetime
438
                    $this->datetime_spaces[$datetime_ID] -= $ticket_quantity;
439
                    // but don't let quantities go below zero
440
                    $this->datetime_spaces[$datetime_ID] = max(
441
                        $this->datetime_spaces[$datetime_ID],
442
                        0
443
                    );
444
                    if ($this->debug) {
445
                        echo "\n . . . " . $datetime_ID . " capacity reduced by {$ticket_quantity}";
446
                        echo " because it allows access to {$TKT_ID}";
447
                    }
448
                }
449
                // if this datetime is at full capacity
450
                if ($datetime_ID === $DTT_ID && $spaces >= $reg_limit) {
451
                    // then all of it's tickets are now unavailable
452
                    foreach ($datetime_tickets as $datetime_ticket) {
453
                        // so  set any tracked available quantities to zero
454
                        if (isset($this->ticket_quantities[$datetime_ticket])) {
455
                            $this->ticket_quantities[$datetime_ticket] = 0;
456
                        }
457
                        if ($this->debug) {
458
                            echo "\n . . . " . $datetime_ticket . ' unavailable: ';
459
                        }
460
                    }
461
                }
462
            }
463
        }
464
    }
465
466
}
467
// Location: EventSpacesCalculator.php
468