Passed
Pull Request — master (#13)
by Vojta
11:49 queued 05:10
created

ReservationsFacade   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 314
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 65.06%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 31
c 4
b 0
f 0
lcom 1
cbo 3
dl 0
loc 314
ccs 54
cts 83
cp 0.6506
rs 9.8

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A storeReservation() 0 16 1
A sendMails() 0 11 1
A getReservations() 0 4 1
A getActiveReservations() 0 4 1
A getReservedDates() 0 6 1
B isUserReturning() 0 26 6
A bulkStateChange() 0 20 4
A bulkDelete() 0 13 3
A getReservationsCountByMail() 0 4 1
A transformDateTime() 0 16 3
A validateReservation() 0 6 2
A isDateAvailable() 0 15 2
A checkLimits() 0 6 2
A isCreatedWhileAgo() 0 11 1
A getReservationsByInterval() 0 4 1
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);
0 ignored issues
show
Unused Code introduced by
The call to ReservationsFacade::checkLimits() has too many arguments starting with $data.

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.

Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $exceptId of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
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