1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* @file |
5
|
|
|
* Class AbstractCalendar |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
namespace Roomify\Bat\Calendar; |
9
|
|
|
|
10
|
|
|
use Roomify\Bat\Event\Event; |
11
|
|
|
use Roomify\Bat\Unit\Unit; |
12
|
|
|
use Roomify\Bat\Calendar\CalendarInterface; |
13
|
|
|
use Roomify\Bat\Calendar\CalendarResponse; |
14
|
|
|
use Roomify\Bat\Event\EventItemizer; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* Handles querying and updating state stores |
18
|
|
|
*/ |
19
|
|
|
abstract class AbstractCalendar implements CalendarInterface { |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* The units we are dealing with. If no unit ids set the calendar will return |
23
|
|
|
* results for date range and all units within that range. |
24
|
|
|
* |
25
|
|
|
* @var array |
26
|
|
|
*/ |
27
|
|
|
protected $units; |
28
|
|
|
|
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* The class that will access the actual event store where event data is held. |
32
|
|
|
* |
33
|
|
|
* @var \Roomify\Bat\Store\StoreInterface |
34
|
|
|
*/ |
35
|
|
|
protected $store; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* The default value for events. In the event store this is represented by 0 which is then |
39
|
|
|
* replaced by the default value provided in the constructor. |
40
|
|
|
* |
41
|
|
|
* @var |
42
|
|
|
*/ |
43
|
|
|
protected $default_value; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Stores itemized events allowing us to perform searches over them without having to pull |
47
|
|
|
* them out of storage (i.e. reducing DB calls) |
48
|
|
|
* |
49
|
|
|
* @var array |
50
|
|
|
*/ |
51
|
|
|
protected $itemized_events; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* {@inheritdoc} |
55
|
|
|
*/ |
56
|
|
|
public function addEvents($events, $granularity) { |
57
|
|
|
|
58
|
|
|
$added = TRUE; |
59
|
|
|
|
60
|
|
|
foreach ($events as $event) { |
61
|
|
|
// Events save themselves so here we cycle through each and return true if all events |
62
|
|
|
// were saved |
63
|
|
|
|
64
|
|
|
$check = $event->saveEvent($this->store, $granularity); |
65
|
|
|
|
66
|
|
|
if ($check == FALSE) { |
67
|
|
|
$added = FALSE; |
68
|
|
|
break; |
69
|
|
|
} |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
return $added; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Given a start and end time will retrieve events from the defined store. |
77
|
|
|
* |
78
|
|
|
* If unit_ids where defined it will filter for those unit ids. |
79
|
|
|
* |
80
|
|
|
* @param \DateTime $start_date |
81
|
|
|
* @param \DateTime $end_date |
82
|
|
|
* @param $reset - if set to TRUE we will always refer to the Store to retrieve events |
83
|
|
|
* |
84
|
|
|
* @return array |
85
|
|
|
*/ |
86
|
|
|
public function getEvents(\DateTime $start_date, \DateTime $end_date, $reset = TRUE) { |
87
|
|
|
if ($reset || empty($this->itemized_events)) { |
88
|
|
|
// We first get events in the itemized format |
89
|
|
|
$this->itemized_events = $this->getEventsItemized($start_date, $end_date); |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
// We then normalize those events to create Events that get added to an array |
93
|
|
|
return $this->getEventsNormalized($start_date, $end_date, $this->itemized_events); |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Given a start and end time this will return the states units find themselves in for that range. |
98
|
|
|
* |
99
|
|
|
* @param \DateTime $start_date |
100
|
|
|
* @param \DateTime $end_date |
101
|
|
|
* @param $reset - if set to TRUE we will refer to the Store to retrieve events |
102
|
|
|
* |
103
|
|
|
* @return array |
104
|
|
|
* An array of states keyed by unit |
105
|
|
|
*/ |
106
|
|
|
public function getStates(\DateTime $start_date, \DateTime $end_date, $reset = TRUE) { |
107
|
|
|
$events = $this->getEvents($start_date, $end_date, $reset); |
108
|
|
|
|
109
|
|
|
$states = array(); |
110
|
|
|
foreach ($events as $unit => $unit_events) { |
111
|
|
|
foreach ($unit_events as $event) { |
112
|
|
|
$states[$unit][$event->getValue()] = $event->getValue(); |
113
|
|
|
} |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
return $states; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Given a date range and a set of valid states it will return all units within the |
121
|
|
|
* set of valid states. |
122
|
|
|
* If intersect is set to TRUE a unit will report as matched as long as within the time |
123
|
|
|
* period requested it finds itself at least once within a valid state. |
124
|
|
|
* Alternatively units will match ONLY if they find themselves withing the valid states and |
125
|
|
|
* no other state. |
126
|
|
|
* |
127
|
|
|
* @param \DateTime $start_date |
128
|
|
|
* @param \DateTime $end_date |
129
|
|
|
* @param $valid_states |
130
|
|
|
* @param $constraints |
131
|
|
|
* @param $intersect - performs an intersect rather than a diff on valid states |
132
|
|
|
* @param $reset - if set to true we refer to the Store to retrieve events |
133
|
|
|
* |
134
|
|
|
* @return CalendarResponse |
135
|
|
|
*/ |
136
|
|
|
public function getMatchingUnits(\DateTime $start_date, \DateTime $end_date, $valid_states, $constraints = array(), $intersect = FALSE, $reset = TRUE) { |
137
|
|
|
$units = array(); |
138
|
|
|
$response = new CalendarResponse($start_date, $end_date, $valid_states); |
139
|
|
|
$keyed_units = $this->keyUnitsById(); |
140
|
|
|
|
141
|
|
|
$states = $this->getStates($start_date, $end_date, $reset); |
142
|
|
|
foreach ($states as $unit => $unit_states) { |
143
|
|
|
// Create an array with just the states |
144
|
|
|
$current_states = array_keys($unit_states); |
145
|
|
|
|
146
|
|
|
// Compare the current states with the set of valid states |
147
|
|
|
if ($intersect) { |
148
|
|
|
$remaining_states = array_intersect($current_states, $valid_states); |
149
|
|
|
} |
150
|
|
|
else { |
151
|
|
|
$remaining_states = array_diff($current_states, $valid_states); |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
if ((count($remaining_states) == 0 && !$intersect) || (count($remaining_states) > 0 && $intersect)) { |
155
|
|
|
// Unit is in a state that is within the set of valid states so add to result set |
156
|
|
|
$units[$unit] = $unit; |
157
|
|
|
$response->addMatch($keyed_units[$unit], CalendarResponse::VALID_STATE); |
158
|
|
|
} |
159
|
|
|
else { |
160
|
|
|
$response->addMiss($keyed_units[$unit], CalendarResponse::INVALID_STATE); |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
$unit_constraints = $keyed_units[$unit]->getConstraints(); |
164
|
|
|
$response->applyConstraints($unit_constraints); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
$response->applyConstraints($constraints); |
168
|
|
|
|
169
|
|
|
return $response; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* Provides an itemized array of events keyed by the unit_id and divided by day, |
174
|
|
|
* hour and minute. |
175
|
|
|
* |
176
|
|
|
* @param \DateTime $start_date |
177
|
|
|
* @param \DateTime $end_date |
178
|
|
|
* @param String $granularity |
179
|
|
|
* |
180
|
|
|
* @return array |
181
|
|
|
*/ |
182
|
|
|
public function getEventsItemized(\DateTime $start_date, \DateTime $end_date, $granularity = Event::BAT_HOURLY) { |
183
|
|
|
// The final events we will return |
184
|
|
|
$events = array(); |
185
|
|
|
|
186
|
|
|
$keyed_units = $this->keyUnitsById(); |
187
|
|
|
|
188
|
|
|
$db_events = $this->store->getEventData($start_date, $end_date, array_keys($keyed_units)); |
189
|
|
|
|
190
|
|
|
// Create a mock itemized event for the period in question - since event data is either |
191
|
|
|
// in the database or the default value we first create a mock event and then fill it in |
192
|
|
|
// accordingly |
193
|
|
|
$mock_event = new Event($start_date, $end_date, new Unit(0,0), $this->default_value); |
194
|
|
|
$itemized = $mock_event->itemize(new EventItemizer($mock_event, $granularity)); |
195
|
|
|
|
196
|
|
|
// Cycle through each unit retrieved and provide it with a fully configured itemized mock event |
197
|
|
|
foreach ($db_events as $unit => $event) { |
198
|
|
|
// Add the mock event |
199
|
|
|
$events[$unit] = $itemized; |
200
|
|
|
|
201
|
|
|
$events[$unit][Event::BAT_DAY] = $this->itemizeDays($db_events, $itemized, $unit, $keyed_units); |
202
|
|
|
|
203
|
|
|
// Handle hours |
204
|
|
View Code Duplication |
if (isset($itemized[Event::BAT_HOUR]) || isset($db_events[$unit][Event::BAT_HOUR])) { |
|
|
|
|
205
|
|
|
$events[$unit][Event::BAT_HOUR] = $this->itemizeHours($db_events, $itemized, $unit, $keyed_units); |
206
|
|
|
} else { |
207
|
|
|
// No hours - set an empty array |
208
|
|
|
$events[$unit][Event::BAT_HOUR] = array(); |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
// Handle minutes |
212
|
|
View Code Duplication |
if (isset($itemized[Event::BAT_MINUTE]) || isset($db_events[$unit][Event::BAT_MINUTE])) { |
|
|
|
|
213
|
|
|
$events[$unit][Event::BAT_MINUTE] = $this->itemizeMinutes($db_events, $itemized, $unit, $keyed_units); |
214
|
|
|
} else { |
215
|
|
|
// No minutes - set an empty array |
216
|
|
|
$events[$unit][Event::BAT_MINUTE] = array(); |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
// Check to see if any events came back from the db |
222
|
|
|
foreach ($keyed_units as $id => $unit) { |
223
|
|
|
// If we don't have any db events add mock events (itemized) |
224
|
|
|
if ((isset($events[$id]) && count($events[$id]) == 0) || !isset($events[$id])) { |
225
|
|
|
$empty_event = new Event($start_date, $end_date, $unit, $unit->getDefaultValue()); |
226
|
|
|
$events[$id] = $empty_event->itemize(new EventItemizer($empty_event, $granularity)); |
227
|
|
|
} |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
return $events; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* Helper function that cycles through db results and setups the BAT_DAY itemized array |
235
|
|
|
* |
236
|
|
|
* @param $db_events |
237
|
|
|
* @param $itemized |
238
|
|
|
* @param $unit |
239
|
|
|
* @param $keyed_units |
240
|
|
|
* |
241
|
|
|
* @return array |
242
|
|
|
*/ |
243
|
|
|
private function itemizeDays($db_events, $itemized, $unit, $keyed_units) { |
244
|
|
|
$result = array(); |
245
|
|
|
|
246
|
|
|
foreach ($itemized[Event::BAT_DAY] as $year => $months) { |
247
|
|
|
foreach ($months as $month => $days) { |
248
|
|
|
// Check if month is defined in DB otherwise set to default value |
249
|
|
|
if (isset($db_events[$unit][Event::BAT_DAY][$year][$month])) { |
250
|
|
|
foreach ($days as $day => $value) { |
251
|
|
|
$result[$year][$month][$day] = ((int)$db_events[$unit][Event::BAT_DAY][$year][$month][$day] == 0 ? $keyed_units[$unit]->getDefaultValue() : (int)$db_events[$unit][Event::BAT_DAY][$year][$month][$day]); |
252
|
|
|
} |
253
|
|
|
} |
254
|
|
View Code Duplication |
else { |
|
|
|
|
255
|
|
|
foreach ($days as $day => $value) { |
256
|
|
|
$result[$year][$month][$day] = $keyed_units[$unit]->getDefaultValue(); |
257
|
|
|
} |
258
|
|
|
} |
259
|
|
|
} |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
return $result; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Helper function that cycles through db results and setups the BAT_HOUR itemized array |
267
|
|
|
* @param $db_events |
268
|
|
|
* @param $itemized |
269
|
|
|
* @param $unit |
270
|
|
|
* @param $keyed_units |
271
|
|
|
* |
272
|
|
|
* @return array |
273
|
|
|
*/ |
274
|
|
|
private function itemizeHours($db_events, $itemized, $unit, $keyed_units) { |
275
|
|
|
|
276
|
|
|
$result = array(); |
277
|
|
|
|
278
|
|
|
if (isset($itemized[Event::BAT_HOUR])) { |
279
|
|
|
foreach ($itemized[Event::BAT_HOUR] as $year => $months) { |
280
|
|
|
foreach ($months as $month => $days) { |
281
|
|
|
foreach ($days as $day => $hours) { |
282
|
|
|
foreach ($hours as $hour => $value) { |
283
|
|
|
if (isset($db_events[$unit][Event::BAT_HOUR][$year][$month][$day][$hour])) { |
284
|
|
|
$result[$year][$month][$day][$hour] = ((int) $db_events[$unit][Event::BAT_HOUR][$year][$month][$day][$hour] == 0 ? $keyed_units[$unit]->getDefaultValue() : (int) $db_events[$unit][Event::BAT_HOUR][$year][$month][$day][$hour]); |
285
|
|
|
} |
286
|
|
View Code Duplication |
else { |
|
|
|
|
287
|
|
|
// If nothing from db - then revert to the defaults |
288
|
|
|
$result[$year][$month][$day][$hour] = (int) $keyed_units[$unit]->getDefaultValue(); |
289
|
|
|
} |
290
|
|
|
} |
291
|
|
|
} |
292
|
|
|
} |
293
|
|
|
} |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
// Now fill in hour data coming from the database which the mock event did *not* cater for in the data structure |
297
|
|
|
if (isset($db_events[$unit][Event::BAT_HOUR])) { |
298
|
|
View Code Duplication |
foreach ($db_events[$unit][Event::BAT_HOUR] as $year => $months) { |
|
|
|
|
299
|
|
|
foreach ($months as $month => $days) { |
300
|
|
|
foreach ($days as $day => $hours) { |
301
|
|
|
foreach ($hours as $hour => $value) { |
302
|
|
|
$result[$year][$month][$day][$hour] = ((int) $value == 0 ? $keyed_units[$unit]->getDefaultValue() : (int) $value); |
303
|
|
|
} |
304
|
|
|
ksort($result[$year][$month][$day], SORT_NATURAL); |
305
|
|
|
} |
306
|
|
|
} |
307
|
|
|
} |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
return $result; |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
/** |
314
|
|
|
* Helper function that cycles through db results and setups the BAT_MINUTE itemized array |
315
|
|
|
* |
316
|
|
|
* @param $db_events |
317
|
|
|
* @param $itemized |
318
|
|
|
* @param $unit |
319
|
|
|
* @param $keyed_units |
320
|
|
|
* |
321
|
|
|
* @return array |
322
|
|
|
*/ |
323
|
|
|
private function itemizeMinutes($db_events, $itemized, $unit, $keyed_units) { |
324
|
|
|
$result = array(); |
325
|
|
|
|
326
|
|
|
if (isset($itemized[Event::BAT_MINUTE])) { |
327
|
|
|
foreach ($itemized[Event::BAT_MINUTE] as $year => $months) { |
328
|
|
|
foreach ($months as $month => $days) { |
329
|
|
|
foreach ($days as $day => $hours) { |
330
|
|
|
foreach ($hours as $hour => $minutes) { |
331
|
|
|
foreach ($minutes as $minute => $value) { |
332
|
|
|
if (isset($db_events[$unit][Event::BAT_MINUTE][$year][$month][$day][$hour][$minute])) { |
333
|
|
|
$result[$year][$month][$day][$hour][$minute] = ((int) $db_events[$unit][Event::BAT_MINUTE][$year][$month][$day][$hour][$minute] == 0 ? $keyed_units[$unit]->getDefaultValue() : (int) $db_events[$unit][Event::BAT_MINUTE][$year][$month][$day][$hour][$minute]); |
334
|
|
|
} |
335
|
|
View Code Duplication |
else { |
|
|
|
|
336
|
|
|
// If nothing from db - then revert to the defaults |
337
|
|
|
$result[$year][$month][$day][$hour][$minute] = (int) $keyed_units[$unit]->getDefaultValue(); |
338
|
|
|
} |
339
|
|
|
} |
340
|
|
|
} |
341
|
|
|
} |
342
|
|
|
} |
343
|
|
|
} |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
// Now fill in minute data coming from the database which the mock event did *not* cater for |
347
|
|
|
if (isset($db_events[$unit][Event::BAT_MINUTE])) { |
348
|
|
|
foreach ($db_events[$unit][Event::BAT_MINUTE] as $year => $months) { |
349
|
|
View Code Duplication |
foreach ($months as $month => $days) { |
|
|
|
|
350
|
|
|
foreach ($days as $day => $hours) { |
351
|
|
|
foreach ($hours as $hour => $minutes) { |
352
|
|
|
foreach ($minutes as $minute => $value) { |
353
|
|
|
$result[$year][$month][$day][$hour][$minute] = ((int) $value == 0 ? $keyed_units[$unit]->getDefaultValue() : (int) $value); |
354
|
|
|
} |
355
|
|
|
ksort($result[$year][$month][$day][$hour], SORT_NATURAL); |
356
|
|
|
} |
357
|
|
|
} |
358
|
|
|
} |
359
|
|
|
} |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
return $result; |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
/** |
366
|
|
|
* Given an itemized set of event data it will return an array of Events |
367
|
|
|
* |
368
|
|
|
* @param \DateTime $start_date |
369
|
|
|
* @param \DateTime $end_date |
370
|
|
|
* @param $events |
371
|
|
|
* |
372
|
|
|
* @return array |
373
|
|
|
*/ |
374
|
|
|
public function getEventsNormalized(\DateTime $start_date, \DateTime $end_date, $events) { |
375
|
|
|
// Daylight Saving Time |
376
|
|
|
$timezone = new \DateTimeZone(date_default_timezone_get()); |
377
|
|
|
$transitions = $timezone->getTransitions($start_date->getTimestamp(), $end_date->getTimestamp()); |
378
|
|
|
|
379
|
|
|
$dst_transitions = array(); |
380
|
|
|
unset($transitions[0]); |
381
|
|
|
foreach ($transitions as $transition) { |
382
|
|
|
if ($transition['isdst']) { |
383
|
|
|
$dst_transitions[] = $transition['ts'] - 60; |
384
|
|
|
} |
385
|
|
|
} |
386
|
|
|
$is_daylight_saving_time = (empty($dst_transitions)) ? FALSE : TRUE; |
387
|
|
|
|
388
|
|
|
$normalized_events = array(); |
389
|
|
|
|
390
|
|
|
$events_copy = $events; |
391
|
|
|
|
392
|
|
|
foreach ($events_copy as $unit_id => $data) { |
393
|
|
|
|
394
|
|
|
// Make sure years are sorted |
395
|
|
|
ksort($data[Event::BAT_DAY]); |
396
|
|
|
if (isset($data[Event::BAT_HOUR])) ksort($data[Event::BAT_HOUR]); |
397
|
|
|
if (isset($data[Event::BAT_MINUTE])) ksort($data[Event::BAT_MINUTE]); |
398
|
|
|
|
399
|
|
|
// Set up variables to keep track of stuff |
400
|
|
|
$current_value = NULL; |
401
|
|
|
$start_event = new \DateTime(); |
402
|
|
|
$end_event = new \DateTime(); |
403
|
|
|
|
404
|
|
|
foreach ($data[Event::BAT_DAY] as $year => $months) { |
405
|
|
|
// Make sure months are in right order |
406
|
|
|
ksort($months); |
407
|
|
|
foreach ($months as $month => $days) { |
408
|
|
|
foreach ($days as $day => $value) { |
409
|
|
|
if ($value == -1) { |
410
|
|
|
// Retrieve hour data |
411
|
|
|
$hour_data = $events[$unit_id][Event::BAT_HOUR][$year][$month][$day]; |
412
|
|
|
ksort($hour_data, SORT_NATURAL); |
413
|
|
|
foreach ($hour_data as $hour => $hour_value) { |
414
|
|
|
if ($hour_value == -1) { |
415
|
|
|
// We are going to need minute values |
416
|
|
|
$minute_data = $events[$unit_id][Event::BAT_MINUTE][$year][$month][$day][$hour]; |
417
|
|
|
ksort($minute_data, SORT_NATURAL); |
418
|
|
|
foreach ($minute_data as $minute => $minute_value) { |
419
|
|
|
if ($current_value === $minute_value) { |
420
|
|
|
// We are still in minutes and going through so add a minute |
421
|
|
|
$end_event->add(new \DateInterval('PT1M')); |
422
|
|
|
} |
423
|
|
|
elseif (($current_value != $minute_value) && ($current_value !== NULL)) { |
424
|
|
|
// Value just switched - let us wrap up with current event and start a new one |
425
|
|
|
$normalized_events[$unit_id][] = new Event($start_event, $end_event, $this->getUnit($unit_id), $current_value); |
426
|
|
|
$start_event = clone($end_event->add(new \DateInterval('PT1M'))); |
427
|
|
|
$end_event = new \DateTime($year . '-' . $month . '-' . substr($day, 1) . ' ' . substr($hour, 1) . ':' . substr($minute,1)); |
428
|
|
|
$current_value = $minute_value; |
429
|
|
|
} |
430
|
|
|
if ($current_value === NULL) { |
431
|
|
|
// We are down to minutes and haven't created and event yet - do one now |
432
|
|
|
$start_event = new \DateTime($year . '-' . $month . '-' . substr($day, 1) . ' ' . substr($hour, 1) . ':' . substr($minute,1)); |
433
|
|
|
$end_event = clone($start_event); |
434
|
|
|
} |
435
|
|
|
$current_value = $minute_value; |
436
|
|
|
} |
437
|
|
|
} |
438
|
|
|
elseif ($current_value === $hour_value) { |
439
|
|
|
// We are in hours and can add something |
440
|
|
|
$end_event->add(new \DateInterval('PT1H')); |
441
|
|
|
} |
442
|
|
|
elseif (($current_value != $hour_value) && ($current_value !== NULL)) { |
443
|
|
|
$skip_finalize_event = FALSE; |
444
|
|
|
|
445
|
|
|
if ($is_daylight_saving_time) { |
446
|
|
|
if (in_array($end_event->getTimestamp(), $dst_transitions)) { |
447
|
|
|
$skip_finalize_event = TRUE; |
448
|
|
|
} |
449
|
|
|
} |
450
|
|
|
|
451
|
|
|
if ($skip_finalize_event === FALSE) { |
452
|
|
|
// Value just switched - let us wrap up with current event and start a new one |
453
|
|
|
$normalized_events[$unit_id][] = new Event($start_event, $end_event, $this->getUnit($unit_id), $current_value); |
454
|
|
|
// Start event becomes the end event with a minute added |
455
|
|
|
$start_event = clone($end_event->add(new \DateInterval('PT1M'))); |
456
|
|
|
// End event comes the current point in time |
457
|
|
|
$end_event = new \DateTime($year . '-' . $month . '-' . substr($day, 1) . ' ' . substr($hour, 1) . ':59'); |
458
|
|
|
$current_value = $hour_value; |
459
|
|
|
} |
460
|
|
|
} |
461
|
|
|
if ($current_value === NULL) { |
462
|
|
|
// Got into hours and still haven't created an event so |
463
|
|
|
$start_event = new \DateTime($year . '-' . $month . '-' . substr($day, 1) . ' ' . substr($hour, 1) . ':00'); |
464
|
|
|
// We will be occupying at least this hour so might as well mark it |
465
|
|
|
$end_event = new \DateTime($year . '-' . $month . '-' . substr($day, 1) . ' ' . substr($hour, 1) . ':59'); |
466
|
|
|
$current_value = $hour_value; |
467
|
|
|
} |
468
|
|
|
} |
469
|
|
|
} |
470
|
|
|
elseif ($current_value === $value) { |
471
|
|
|
// We are adding a whole day so the end event gets moved to the end of the day we are adding |
472
|
|
|
$end_event = new \DateTime($year . '-' . $month . '-' . substr($day, 1) . ' ' . '23:59'); |
473
|
|
|
} |
474
|
|
|
elseif (($current_value !== $value) && ($current_value !== NULL)) { |
475
|
|
|
// Value just switched - let us wrap up with current event and start a new one |
476
|
|
|
$normalized_events[$unit_id][] = new Event($start_event, $end_event, $this->getUnit($unit_id), $current_value); |
477
|
|
|
// Start event becomes the end event with a minute added |
478
|
|
|
$start_event = clone($end_event->add(new \DateInterval('PT1M'))); |
479
|
|
|
// End event becomes the current day which we have not account for yet |
480
|
|
|
$end_event = new \DateTime($year . '-' . $month . '-' . substr($day, 1) . ' ' . '23:59'); |
481
|
|
|
$current_value = $value; |
482
|
|
|
} |
483
|
|
|
if ($current_value === NULL) { |
484
|
|
|
// We have not created an event yet so let's do it now |
485
|
|
|
$start_event = new \DateTime($year . '-' . $month . '-' . substr($day, 1) . ' ' . '00:00'); |
486
|
|
|
$end_event = new \DateTime($year . '-' . $month . '-' . substr($day, 1) . ' ' . '23:59'); |
487
|
|
|
$current_value = $value; |
488
|
|
|
} |
489
|
|
|
} |
490
|
|
|
} |
491
|
|
|
} |
492
|
|
|
|
493
|
|
|
// Add the last event in for which there is nothing in the loop to catch it |
494
|
|
|
$normalized_events[$unit_id][] = new Event($start_event, $end_event, $this->getUnit($unit_id), $current_value); |
495
|
|
|
} |
496
|
|
|
|
497
|
|
|
// Given the database structure we may get events that are not with the date ranges we were looking for |
498
|
|
|
// We get rid of them here so that the user has a clean result. |
499
|
|
|
foreach ($normalized_events as $unit_id => $events) { |
500
|
|
|
foreach ($events as $key => $event) { |
501
|
|
|
if ($event->overlaps($start_date, $end_date)) { |
502
|
|
|
// Adjust start or end dates of events so everything is within range |
503
|
|
|
if ($event->startsEarlier($start_date)) { |
504
|
|
|
$event->setStartDate($start_date); |
505
|
|
|
} |
506
|
|
|
if ($event->endsLater($end_date)) { |
507
|
|
|
$event->setEndDate($end_date); |
508
|
|
|
} |
509
|
|
|
} |
510
|
|
|
else { |
511
|
|
|
// Event completely not in range so unset it |
512
|
|
|
unset($normalized_events[$unit_id][$key]); |
513
|
|
|
} |
514
|
|
|
} |
515
|
|
|
} |
516
|
|
|
|
517
|
|
|
return $normalized_events; |
518
|
|
|
} |
519
|
|
|
|
520
|
|
|
/** |
521
|
|
|
* A simple utility function that given an array of datum=>value will group results based on |
522
|
|
|
* those that have the same value. Useful for grouping events based on state. |
523
|
|
|
* |
524
|
|
|
* @param $data |
525
|
|
|
* @param $length |
526
|
|
|
*/ |
527
|
|
|
public function groupData($data, $length) { |
528
|
|
|
$flipped = array(); |
529
|
|
|
$e = 0; |
530
|
|
|
$j = 0; |
531
|
|
|
$old_value = NULL; |
532
|
|
|
|
533
|
|
|
foreach ($data as $datum => $value) { |
534
|
|
|
$j++; |
535
|
|
|
if ($j <= $length) { |
536
|
|
|
// If the value has changed and we are not just starting |
537
|
|
|
if (($value != $old_value)) { |
538
|
|
|
$e++; |
539
|
|
|
$flipped[$e][$value][$datum] = $datum; |
540
|
|
|
$old_value = $value; |
541
|
|
|
} |
542
|
|
|
else { |
543
|
|
|
$flipped[$e][$value][$datum] = $datum; |
544
|
|
|
} |
545
|
|
|
} |
546
|
|
|
} |
547
|
|
|
} |
548
|
|
|
|
549
|
|
|
/** |
550
|
|
|
* Return an array of unit ids from the set of units |
551
|
|
|
* supplied to the Calendar. |
552
|
|
|
* |
553
|
|
|
* @return array |
554
|
|
|
*/ |
555
|
|
|
protected function getUnitIds() { |
556
|
|
|
$unit_ids = array(); |
557
|
|
|
foreach ($this->units as $unit) { |
558
|
|
|
$unit_ids[] = $unit->getUnitId(); |
559
|
|
|
} |
560
|
|
|
|
561
|
|
|
return $unit_ids; |
562
|
|
|
} |
563
|
|
|
|
564
|
|
|
/** |
565
|
|
|
* Return an array of units keyed by unit id |
566
|
|
|
* |
567
|
|
|
* @return array |
568
|
|
|
*/ |
569
|
|
|
protected function keyUnitsById() { |
570
|
|
|
$keyed_units = array(); |
571
|
|
|
foreach ($this->units as $unit) { |
572
|
|
|
$keyed_units[$unit->getUnitId()] = $unit; |
573
|
|
|
} |
574
|
|
|
|
575
|
|
|
return $keyed_units; |
576
|
|
|
} |
577
|
|
|
|
578
|
|
|
/** |
579
|
|
|
* Returns the unit object. |
580
|
|
|
* |
581
|
|
|
* @param $unit_id |
582
|
|
|
* @return Unit |
583
|
|
|
*/ |
584
|
|
|
protected function getUnit($unit_id) { |
585
|
|
|
$keyed = $this->keyUnitsById(); |
586
|
|
|
return $keyed[$unit_id]; |
587
|
|
|
} |
588
|
|
|
|
589
|
|
|
} |
590
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.