1
|
|
|
<?php namespace VojtaSvoboda\Reservations\Facades; |
2
|
|
|
|
3
|
|
|
use Auth; |
4
|
|
|
use Carbon\Carbon; |
5
|
|
|
use Config; |
6
|
|
|
use Event; |
7
|
|
|
use Illuminate\Database\Eloquent\Collection; |
8
|
|
|
use Illuminate\Support\Facades\DB; |
9
|
|
|
use October\Rain\Exception\ApplicationException; |
10
|
|
|
use October\Rain\Exception\ValidationException; |
11
|
|
|
use VojtaSvoboda\Reservations\Classes\DatesResolver; |
12
|
|
|
use VojtaSvoboda\Reservations\Mailers\ReservationAdminMailer; |
13
|
|
|
use VojtaSvoboda\Reservations\Mailers\ReservationMailer; |
14
|
|
|
use VojtaSvoboda\Reservations\Models\Reservation; |
15
|
|
|
use VojtaSvoboda\Reservations\Models\Settings; |
16
|
|
|
use VojtaSvoboda\Reservations\Models\Status; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Public reservations facade. |
20
|
|
|
* |
21
|
|
|
* Usage: App::make(ReservationsFacade::class); |
22
|
|
|
* |
23
|
|
|
* @package VojtaSvoboda\Reservations\Facades |
24
|
|
|
*/ |
25
|
|
|
class ReservationsFacade |
26
|
|
|
{ |
27
|
|
|
/** @var Reservation $reservations */ |
28
|
|
|
private $reservations; |
29
|
|
|
|
30
|
|
|
/** @var Status $statuses */ |
31
|
|
|
private $statuses; |
32
|
|
|
|
33
|
|
|
/** @var DatesResolver $datesResolver */ |
34
|
|
|
private $datesResolver; |
35
|
|
|
|
36
|
|
|
/** @var array $returningUsersCache */ |
37
|
|
|
private $returningUsersCache; |
38
|
|
|
|
39
|
|
|
/** @var ReservationMailer $mailer */ |
40
|
|
|
private $mailer; |
41
|
|
|
|
42
|
|
|
/** @var ReservationAdminMailer $adminMailer */ |
43
|
|
|
private $adminMailer; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* ReservationsFacade constructor. |
47
|
|
|
* |
48
|
|
|
* @param Reservation $reservations |
49
|
|
|
* @param Status $statuses |
50
|
|
|
* @param DatesResolver $resolver |
51
|
|
|
* @param ReservationMailer $mailer |
52
|
|
|
* @param ReservationAdminMailer $adminMailer |
53
|
|
|
*/ |
54
|
9 |
|
public function __construct( |
55
|
|
|
Reservation $reservations, Status $statuses, DatesResolver $resolver, |
56
|
|
|
ReservationMailer $mailer, ReservationAdminMailer $adminMailer |
57
|
|
|
) { |
58
|
9 |
|
$this->reservations = $reservations; |
59
|
9 |
|
$this->statuses = $statuses; |
60
|
9 |
|
$this->datesResolver = $resolver; |
61
|
9 |
|
$this->mailer = $mailer; |
62
|
9 |
|
$this->adminMailer = $adminMailer; |
63
|
9 |
|
} |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Create and store reservation. |
67
|
|
|
* |
68
|
|
|
* @param array $data |
69
|
|
|
* |
70
|
|
|
* @return Reservation $reservation |
71
|
|
|
* |
72
|
|
|
* @throws ApplicationException |
73
|
|
|
* @throws ValidationException |
74
|
|
|
*/ |
75
|
8 |
|
public function storeReservation($data) |
76
|
|
|
{ |
77
|
|
|
// check number of sends |
78
|
8 |
|
$this->checkLimits($data); |
|
|
|
|
79
|
|
|
|
80
|
|
|
// transform date and time to Carbon |
81
|
6 |
|
$data['date'] = $this->transformDateTime($data); |
82
|
|
|
|
83
|
|
|
// create reservation |
84
|
6 |
|
$reservation = $this->reservations->create($data); |
85
|
|
|
|
86
|
|
|
// send mails to client and admin |
87
|
6 |
|
$this->sendMails($reservation); |
88
|
|
|
|
89
|
|
|
return $reservation; |
90
|
6 |
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
6 |
|
* Send mail to client and admin. |
94
|
|
|
* |
95
|
6 |
|
* @param Reservation $reservation |
96
|
|
|
*/ |
97
|
|
|
public function sendMails($reservation) |
98
|
|
|
{ |
99
|
|
|
// calculate reservations by same email |
100
|
|
|
$sameCount = $this->getReservationsCountByMail($reservation->email); |
101
|
|
|
|
102
|
|
|
// send reservation confirmation to customer |
103
|
|
|
$this->mailer->send($reservation, $sameCount); |
104
|
|
|
|
105
|
|
|
// send reservation confirmation to admin |
106
|
|
|
$this->adminMailer->send($reservation, $sameCount); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Get all reservations. |
111
|
|
|
* |
112
|
|
|
* @return Collection |
113
|
|
|
*/ |
114
|
|
|
public function getReservations() |
115
|
|
|
{ |
116
|
|
|
return $this->reservations->all(); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Get all active (not cancelled) reservations. |
121
|
|
|
* |
122
|
|
|
* @return Collection |
123
|
|
|
*/ |
124
|
|
|
public function getActiveReservations() |
125
|
|
|
{ |
126
|
|
|
return $this->reservations->notCancelled()->get(); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Get all reserved time slots. |
131
|
|
|
* |
132
|
|
|
* @return array |
133
|
|
|
*/ |
134
|
|
|
public function getReservedDates() |
135
|
|
|
{ |
136
|
|
|
$reservations = $this->getActiveReservations(); |
137
|
|
|
|
138
|
|
|
return $this->datesResolver->getDatesFromReservations($reservations); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* Get all reservations by given date interval. |
143
|
|
|
* |
144
|
|
|
* @param Carbon $since Date from. |
145
|
|
|
* @param Carbon $till Date to. |
146
|
|
|
* |
147
|
|
|
* @return mixed |
148
|
|
|
*/ |
149
|
|
|
public function getReservationsByInterval(Carbon $since, Carbon $till) |
150
|
6 |
|
{ |
151
|
|
|
return $this->reservations->whereBetween('date', [$since, $till])->get(); |
152
|
6 |
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Get reservations count by one email. |
156
|
|
|
* |
157
|
|
|
* @param $email |
158
|
|
|
* |
159
|
|
|
* @return int |
160
|
|
|
*/ |
161
|
|
|
public function getReservationsCountByMail($email) |
162
|
1 |
|
{ |
163
|
|
|
return $this->reservations->where('email', $email)->notCancelled()->count(); |
164
|
|
|
} |
165
|
1 |
|
|
166
|
1 |
|
/** |
167
|
|
|
* Is user returning or not? You have to set this parameter at Backend Reservations setting. |
168
|
|
|
* |
169
|
|
|
* @param $email |
170
|
|
|
* |
171
|
1 |
|
* @return bool |
172
|
|
|
*/ |
173
|
1 |
|
public function isUserReturning($email) |
174
|
1 |
|
{ |
175
|
1 |
|
// when disabled, user is never returning |
176
|
1 |
|
$threshold = Settings::get('returning_mark', 0); |
177
|
|
|
if ($threshold < 1) { |
178
|
1 |
|
return false; |
179
|
1 |
|
} |
180
|
|
|
|
181
|
|
|
// load emails count |
182
|
|
|
if ($this->returningUsersCache === null) { |
183
|
1 |
|
$items = $this |
184
|
1 |
|
->reservations |
185
|
|
|
->select(DB::raw('email, count(*) as count')) |
186
|
1 |
|
->groupBy('email') |
187
|
|
|
->get(); |
188
|
|
|
// refactor to mapWithKeys after upgrade to Laravel 5.3. |
189
|
|
|
foreach($items as $item) { |
190
|
|
|
$this->returningUsersCache[$item['email']] = $item['count']; |
191
|
|
|
} |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
$returning = $this->returningUsersCache; |
195
|
|
|
$actual = isset($returning[$email]) ? $returning[$email] : 0; |
196
|
|
|
|
197
|
|
|
return $threshold > 0 && $actual >= $threshold; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Bulk reservation state change. |
202
|
|
|
* |
203
|
|
|
* @param array $ids |
204
|
|
|
* @param string $ident |
205
|
|
|
*/ |
206
|
|
|
public function bulkStateChange($ids, $ident) |
207
|
|
|
{ |
208
|
|
|
// get state |
209
|
|
|
$status = $this->statuses->where('ident', $ident)->first(); |
210
|
|
|
if (!$status) { |
211
|
|
|
return; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
// go through reservations |
215
|
|
|
foreach ($ids as $id) |
216
|
|
|
{ |
217
|
|
|
$reservation = $this->reservations->find($id); |
218
|
|
|
if (!$reservation) { |
219
|
|
|
continue; |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
$reservation->status = $status; |
223
|
|
|
$reservation->save(); |
224
|
|
|
} |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Bulk reservations delete. |
229
|
|
|
* |
230
|
|
|
* @param array $ids |
231
|
|
|
*/ |
232
|
|
|
public function bulkDelete($ids) |
233
|
|
|
{ |
234
|
|
|
// go through reservations |
235
|
|
|
foreach ($ids as $id) |
236
|
|
|
{ |
237
|
|
|
$reservation = $this->reservations->find($id); |
238
|
|
|
if (!$reservation) { |
239
|
|
|
continue; |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
$reservation->delete(); |
243
|
|
|
} |
244
|
9 |
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
9 |
|
* Transform date and time to DateTime string. |
248
|
1 |
|
* |
249
|
|
|
* @param $data |
250
|
8 |
|
* |
251
|
1 |
|
* @return Carbon |
252
|
|
|
* |
253
|
|
|
* @throws ApplicationException |
254
|
7 |
|
*/ |
255
|
|
|
public function transformDateTime($data) |
256
|
7 |
|
{ |
257
|
|
|
// validate date |
258
|
|
|
if (empty($data['date'])) { |
259
|
|
|
throw new ApplicationException('You have to select pickup date!'); |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
// validate time |
263
|
|
|
if (empty($data['time'])) { |
264
|
|
|
throw new ApplicationException('You have to select pickup hour!'); |
265
|
|
|
} |
266
|
6 |
|
|
267
|
|
|
$format = Config::get('vojtasvoboda.reservations::config.formats.datetime', 'd/m/Y H:i'); |
268
|
|
|
|
269
|
6 |
|
return Carbon::createFromFormat($format, trim($data['date'] . ' ' . $data['time'])); |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
/** |
273
|
|
|
* Validate reservation date and time. |
274
|
6 |
|
* |
275
|
1 |
|
* @param Reservation $reservation |
276
|
|
|
* |
277
|
6 |
|
* @return bool |
278
|
|
|
*/ |
279
|
|
|
public function validateReservation(Reservation $reservation) |
280
|
|
|
{ |
281
|
|
|
$reservationId = isset($reservation->id) ? $reservation->id : null; |
282
|
|
|
|
283
|
|
|
return $this->isDateAvailable($reservation->date, $reservationId); |
284
|
|
|
} |
285
|
|
|
|
286
|
6 |
|
/** |
287
|
|
|
* Returns if given date is available. |
288
|
|
|
* |
289
|
6 |
|
* @param Carbon $date |
290
|
|
|
* @param int $exceptId Except reservation ID. |
291
|
|
|
* |
292
|
6 |
|
* @return bool |
293
|
6 |
|
*/ |
294
|
6 |
|
public function isDateAvailable($date, $exceptId = null) |
295
|
|
|
{ |
296
|
|
|
// get boundary dates for given reservation date |
297
|
6 |
|
$boundaries = $this->datesResolver->getBoundaryDates($date); |
298
|
6 |
|
|
299
|
6 |
|
// get all reservations in this date |
300
|
|
|
$query = $this->reservations->notCancelled()->whereBetween('date', $boundaries); |
301
|
|
|
|
302
|
6 |
|
// if updating reservation, we should skip existing reservation |
303
|
|
|
if ($exceptId) { |
|
|
|
|
304
|
6 |
|
$query->where('id', '!=', $exceptId); |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
return $query->count() === 0; |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
/** |
311
|
|
|
* Check reservations amount limit per time. |
312
|
6 |
|
* |
313
|
|
|
* @throws ApplicationException |
314
|
6 |
|
*/ |
315
|
|
|
private function checkLimits() |
316
|
|
|
{ |
317
|
|
|
if ($this->isCreatedWhileAgo()) { |
318
|
|
|
throw new ApplicationException('You can sent only one reservation per 30 seconds, please wait a second.'); |
319
|
|
|
} |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
/** |
323
|
|
|
* Try to find some reservation in less then given limit (default 30 seconds). |
324
|
|
|
* |
325
|
|
|
* @return boolean |
326
|
|
|
*/ |
327
|
|
|
public function isCreatedWhileAgo() |
328
|
|
|
{ |
329
|
|
|
// protection time |
330
|
|
|
$time = Config::get('vojtasvoboda.reservations::config.protection_time', '-30 seconds'); |
331
|
|
|
$timeLimit = Carbon::parse($time)->toDateTimeString(); |
332
|
|
|
|
333
|
|
|
// try to find some message |
334
|
|
|
$item = $this->reservations->machine()->where('created_at', '>', $timeLimit)->first(); |
335
|
|
|
|
336
|
|
|
return $item !== null; |
337
|
|
|
} |
338
|
|
|
} |
339
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.