PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / trunk
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution vtrunk
2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 1.7.2 All 33 releases
← All changes | app/Http/Controllers/ReportController.php +102 -75 1.7.0trunk View file →
@@ -3,8 +3,9 @@
3 3 namespace FluentBooking\App\Http\Controllers;
4 4
5 5 use FluentBooking\App\Models\Booking;
6 6 use FluentBooking\App\Models\BookingActivity;
7 +use FluentBooking\App\Services\BookingReportService;
7 8 use FluentBooking\App\Services\ReportingHelperTrait;
8 9 use FluentBooking\Framework\Http\Request\Request;
9 10 use FluentBooking\Framework\Support\Arr;
10 11 use FluentBooking\App\Services\DateTimeHelper;
@@ -116,22 +117,15 @@
116 117
117 118 list($groupBy, $orderBy) = $this->getGroupAndOrder($frequency);
118 119
119 120 // Define a function to fetch booking data based on status
120 - $fetchBookingsByStatus = function ($status) use ($period, $groupBy, $orderBy, $frequency, $from, $to) {
121 -
122 - if (!PermissionManager::userCanSeeAllBookings()) {
123 -
124 - return Booking::select($this->prepareSelect($frequency))
125 - ->where('status', $status)
126 - ->whereBetween('created_at', [$from->format('Y-m-d'), $to->format('Y-m-d')])
127 - ->where('host_user_id', get_current_user_id())
128 - ->groupBy($groupBy)
129 - ->orderBy($orderBy, 'ASC')
130 - ->get();
131 - }
132 -
133 - return Booking::select($this->prepareSelect($frequency))
121 + // Scoped through BookingReportService so this graph counts the same
122 + // bookings as the widgets above it and the schedules list beside it.
123 + // It previously scoped on the host_user_id column alone, which hid
124 + // bookings a limited host was a secondary host on.
125 + $fetchBookingsByStatus = function ($status) use ($groupBy, $orderBy, $frequency, $from, $to) {
126 + return BookingReportService::scoped()
127 + ->select($this->prepareSelect($frequency))
134 128 ->where('status', $status)
135 129 ->whereBetween('created_at', [$from->format('Y-m-d'), $to->format('Y-m-d')])
136 130 ->groupBy($groupBy)
137 131 ->orderBy($orderBy, 'ASC')
@@ -160,43 +154,46 @@
160 154 $lastMonthStartTime = gmdate('Y-m-d H:i:s', strtotime("$startTime - $differenceInDays days")); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
161 155
162 156 $bookingStats = $this->getBookingStats($startTime, $endTime, $lastMonthStartTime, $startTime);
163 157
164 - $bookingStats['bookedComparison'] = $this->getComparisonMessage($bookingStats['bookedStat']);
165 - $bookingStats['completedComparison'] = $this->getComparisonMessage($bookingStats['completedStat']);
166 - $bookingStats['cancelledComparison'] = $this->getComparisonMessage($bookingStats['cancelledStat']);
167 - $bookingStats['guestComparison'] = $this->getComparisonMessage($bookingStats['guestStat']);
158 + $comparison = $this->getComparisonLabel($differenceInDays);
168 159
160 + $bookingStats['bookedComparison'] = $comparison;
161 + $bookingStats['completedComparison'] = $comparison;
162 + $bookingStats['cancelledComparison'] = $comparison;
163 + $bookingStats['guestComparison'] = $comparison;
164 +
169 165 return $bookingStats;
170 166 }
171 167
172 168 private function getBookingStats($currentMonthStart, $currentMonthEnd, $lastMonthStart, $lastMonthEnd)
173 169 {
174 - // Get booking query by created_at and end_time
175 - $currentMonthBookings = Booking::whereBetween('created_at', [$currentMonthStart, $currentMonthEnd])->get();
176 - $lastMonthBookings = Booking::whereBetween('created_at', [$lastMonthStart, $lastMonthEnd])->get();
170 + $createdBetween = function ($start, $end) {
171 + return BookingReportService::scoped()->whereBetween('created_at', [$start, $end]);
172 + };
177 173
178 - $currentMonthStartBookings = Booking::whereBetween('end_time', [$currentMonthStart, $currentMonthEnd])->get();
179 - $lastMonthStartBookings = Booking::whereBetween('end_time', [$lastMonthStart, $lastMonthEnd])->get();
174 + $endTimeBetween = function ($start, $end) {
175 + return BookingReportService::scoped()->whereBetween('end_time', [$start, $end]);
176 + };
180 177
181 - // Calculate bookings and guests based on 'created_at'
182 - $totalBookedCurrentMonth = $currentMonthBookings->count();
183 - $totalBookedLastMonth = $lastMonthBookings->count();
178 + // Bookings and guests based on 'created_at'
179 + $totalBookedCurrentMonth = $createdBetween($currentMonthStart, $currentMonthEnd)->count();
180 + $totalBookedLastMonth = $createdBetween($lastMonthStart, $lastMonthEnd)->count();
184 181
185 - $totalGuestsCurrentMonth = $currentMonthBookings->pluck('email')->unique()->count();
186 - $totalGuestsLastMonth = $lastMonthBookings->pluck('email')->unique()->count();
182 + $totalGuestsCurrentMonth = $createdBetween($currentMonthStart, $currentMonthEnd)->distinct()->count('email');
183 + $totalGuestsLastMonth = $createdBetween($lastMonthStart, $lastMonthEnd)->distinct()->count('email');
187 184
188 - // Calculate completed and cancelled based on 'end_time'
189 - $bookingCompletedCurrentMonth = $currentMonthStartBookings->where('status', 'completed')->count();
190 - $bookingCompletedLastMonth = $lastMonthStartBookings->where('status', 'completed')->count();
185 + // Completed and cancelled based on 'end_time'
186 + $bookingCompletedCurrentMonth = $endTimeBetween($currentMonthStart, $currentMonthEnd)->where('status', 'completed')->count();
187 + $bookingCompletedLastMonth = $endTimeBetween($lastMonthStart, $lastMonthEnd)->where('status', 'completed')->count();
191 188
192 - $bookingCancelledCurrentMonth = $lastMonthStartBookings->where('status', 'cancelled')->count();
193 - $bookingCancelledLastMonth = $currentMonthStartBookings->where('status', 'cancelled')->count();
189 + $bookingCancelledCurrentMonth = $endTimeBetween($currentMonthStart, $currentMonthEnd)->where('status', 'cancelled')->count();
190 + $bookingCancelledLastMonth = $endTimeBetween($lastMonthStart, $lastMonthEnd)->where('status', 'cancelled')->count();
194 191
195 - $bookingStats['bookedStat'] = $this->getPercentage($totalBookedCurrentMonth, $totalBookedLastMonth);
192 + $bookingStats['bookedStat'] = $this->getPercentage($totalBookedCurrentMonth, $totalBookedLastMonth);
196 193 $bookingStats['completedStat'] = $this->getPercentage($bookingCompletedCurrentMonth, $bookingCompletedLastMonth);
197 194 $bookingStats['cancelledStat'] = $this->getPercentage($bookingCancelledCurrentMonth, $bookingCancelledLastMonth);
198 - $bookingStats['guestStat'] = $this->getPercentage($totalGuestsCurrentMonth, $totalGuestsLastMonth);
195 + $bookingStats['guestStat'] = $this->getPercentage($totalGuestsCurrentMonth, $totalGuestsLastMonth);
199 196
200 197 return $bookingStats;
201 198 }
202 199
@@ -203,25 +200,35 @@
203 200 private function getPercentage($currentMonthTotal, $lastMonthTotal)
204 201 {
205 202 if ($lastMonthTotal > 0) {
206 203 return round((($currentMonthTotal - $lastMonthTotal) / $lastMonthTotal) * 100, 2);
207 - } else if (!$lastMonthTotal) {
208 - return 100;
209 204 }
210 - return 0;
205 +
206 + // Nothing before and nothing now is no change, not a rise from nothing - which is what
207 + // every tile on a fresh install used to claim.
208 + if (!$currentMonthTotal) {
209 + return 0;
210 + }
211 +
212 + return 100;
211 213 }
212 214
213 215 private function getBookingWidgetNumbers($startTime, $endTime)
214 216 {
215 - $statusQuery = Booking::whereBetween('end_time', [$startTime, $endTime])->get();
216 - $bookedQuery = Booking::whereBetween('created_at', [$startTime, $endTime])->get();
217 + $createdBetween = function () use ($startTime, $endTime) {
218 + return BookingReportService::scoped()->whereBetween('created_at', [$startTime, $endTime]);
219 + };
217 220
218 - $totalBooked = $bookedQuery->count();
219 - $totalGuests = $bookedQuery->pluck('email')->unique()->count();
221 + $endTimeBetween = function () use ($startTime, $endTime) {
222 + return BookingReportService::scoped()->whereBetween('end_time', [$startTime, $endTime]);
223 + };
220 224
221 - $bookingCompleted = $statusQuery->where('status', 'completed')->count();
222 - $bookingCancelled = $statusQuery->where('status', 'cancelled')->count();
225 + $totalBooked = $createdBetween()->count();
226 + $totalGuests = $createdBetween()->distinct()->count('email');
223 227
228 + $bookingCompleted = $endTimeBetween()->where('status', 'completed')->count();
229 + $bookingCancelled = $endTimeBetween()->where('status', 'cancelled')->count();
230 +
224 231 return [
225 232 'totalBooked' => $totalBooked,
226 233 'totalGuests' => $totalGuests,
227 234 'bookingCompleted' => $bookingCompleted,
@@ -230,41 +237,62 @@
230 237 }
231 238
232 239 private function getAllBookingWidgetNumbers()
233 240 {
234 - $permissionAccess = PermissionManager::userCan(['read_all_bookings', 'manage_all_bookings', 'read_other_calendars', 'manage_other_calendars']);
241 + // Four undated counts, so four full reads of the bookings table on
242 + // every dashboard load. They are lifetime totals; five minutes stale
243 + // is invisible.
244 + $cacheKey = 'fcal_report_all_widgets_' . get_current_blog_id() . '_' . get_current_user_id();
235 245
236 - if ($permissionAccess) {
237 - $totalBooked = Booking::count();
238 - $bookingCompleted = Booking::where('status', 'completed')->count();
239 - $bookingCancelled = Booking::where('status', 'cancelled')->count();
240 - $totalGuests = Booking::distinct()->count('email');
241 - } else {
242 - $totalBooked = Booking::where('host_user_id', get_current_user_id())->count();
243 - $bookingCompleted = Booking::where('status', 'completed')->where('host_user_id', get_current_user_id())->count();
244 - $bookingCancelled = Booking::where('status', 'cancelled')->where('host_user_id', get_current_user_id())->count();
245 - $totalGuests = Booking::distinct()->where('host_user_id', get_current_user_id())->count('email');
246 + $cached = get_transient($cacheKey);
247 +
248 + if (is_array($cached)) {
249 + return $cached;
246 250 }
247 251
248 - return [
252 + $totalBooked = BookingReportService::scoped()->count();
253 +
254 + $bookingCompleted = BookingReportService::scoped()->where('status', 'completed')->count();
255 +
256 + $bookingCancelled = BookingReportService::scoped()->where('status', 'cancelled')->count();
257 +
258 + $totalGuests = BookingReportService::scoped()->distinct()->count('email');
259 +
260 + $numbers = [
249 261 'totalBooked' => $totalBooked,
250 262 'totalGuests' => $totalGuests,
251 263 'bookingCompleted' => $bookingCompleted,
252 264 'bookingCancelled' => $bookingCancelled
253 265 ];
266 +
267 + set_transient($cacheKey, $numbers, 300);
268 +
269 + return $numbers;
254 270 }
255 271
256 - private function getComparisonMessage($change)
272 + /**
273 + * The window the percentage beside each number is measured against.
274 + *
275 + * This used to describe the direction of the change - "More than last month" - which is
276 + * what the percentage next to it already says, so the tile stated one fact twice and left
277 + * the window it was comparing against unstated. It was also wrong whenever a custom range
278 + * was picked, since the comparison is always against a preceding window of the same
279 + * length, not against a calendar month.
280 + *
281 + * @param float $days Length of the reporting window, in days.
282 + *
283 + * @return string
284 + */
285 + private function getComparisonLabel($days)
257 286 {
258 - if ($change > 0) {
259 - return __('More than last month', 'fluent-booking');
287 + $days = max(1, (int) round($days));
288 +
289 + if ($days === 1) {
290 + return __('vs. previous day', 'fluent-booking');
260 291 }
261 - if ($change < 0) {
262 - return __('Less than last month', 'fluent-booking');
263 - }
264 292
265 - return __('Same as last month', 'fluent-booking');
266 -
293 + /* translators: %d - the number of days being compared against */
294 + return sprintf(__('vs. previous %d days', 'fluent-booking'), $days);
267 295 }
268 296
269 297 private function getPaymentWidgets($startTime, $endTime)
270 298 {
@@ -288,9 +316,9 @@
288 316 $lastMonthStartTime = gmdate('Y-m-d H:i:s', strtotime("$startTime - $differenceInDays days")); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
289 317
290 318 $current_user_email = null;
291 319
292 - $cantSeeTotal = PermissionManager::userCan(['read_all_bookings', 'manage_all_bookings', 'read_other_calendars', 'manage_other_calendars']);
320 + $cantSeeTotal = PermissionManager::userCan(['manage_all_data', 'read_all_bookings', 'manage_all_bookings', 'read_other_calendars', 'manage_other_calendars']);
293 321
294 322 if (!$cantSeeTotal) {
295 323 $current_user_email = wp_get_current_user()->user_email;
296 324 }
@@ -319,9 +347,9 @@
319 347 ->total;
320 348
321 349 $paymentPercentage = $this->getPercentage($currentMonthTotal, $lastMonthTotal);
322 350
323 - $paymentComparison = $this->getComparisonMessage($paymentPercentage);
351 + $paymentComparison = $this->getComparisonLabel($differenceInDays);
324 352
325 353 $paymentStats['totalPayment'] = intval($currentMonthTotal);
326 354 $paymentStats['paymentComparison'] = $paymentComparison;
327 355 $paymentStats['paymentStat'] = $paymentPercentage;
@@ -332,18 +360,19 @@
332 360 public function getNextMeetings()
333 361 {
334 362 $bookingQuery = Booking::with(['slot'])
335 363 ->where('status', 'scheduled')
336 - ->orderBy('start_time', 'ASC')
337 - ->upcoming();
364 + ->upcoming()
365 + ->orderBy('start_time', 'ASC');
338 366
339 367 if (!PermissionManager::userCanSeeAllBookings()) {
340 - $bookingQuery->whereHas('calendar', function ($q) {
341 - $q->where('user_id', get_current_user_id());
342 - });
368 + $bookingQuery->whereHostAccess(get_current_user_id());
343 369 }
344 370
345 - $nextMeetings = $bookingQuery->groupBy('group_id')->latest()->take(5)->get();
371 + $nextMeetings = $bookingQuery->limit(50)->get()
372 + ->unique('group_id')
373 + ->take(5)
374 + ->values();
346 375
347 376 foreach ($nextMeetings as $meeting) {
348 377 if (!$meeting->slot) {
349 378 $meeting->author = [
@@ -368,11 +397,9 @@
368 397 {
369 398 $bookingQuery = Booking::whereIn('status', ['pending', 'scheduled', 'completed']);
370 399
371 400 if (!PermissionManager::userCanSeeAllBookings()) {
372 - $bookingQuery->whereHas('calendar', function ($q) {
373 - $q->where('user_id', get_current_user_id());
374 - });
401 + $bookingQuery->whereHostAccess(get_current_user_id());
375 402 }
376 403
377 404 return $bookingQuery->latest()->take(5)->get();
378 405 }