PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.22
Booktics – Appointment Booking Calendar for Service Businesses v1.0.22
1.0.29 1.0.28 1.0.27 1.0.26 1.0.25 1.0.24 1.0.23 1.0.22 1.0.21 1.0.20 1.0.19 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.2 1.0.3 All 31 releases
booktics / core / dashboard / controllers / dashboard-controller.php

dashboard-controller.php in Booktics – Appointment Booking Calendar for Service Businesses 1.0.22, at core/dashboard/controllers/dashboard-controller.php

507 lines 16.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Booktics\Dashboard\Controllers;
4
5 use Booktics\Abstracts\Base_Rest_Controller;
6 use Booktics\Abstracts\Booktics_Database;
7 use Booktics\Models\Appointment_Model;
8 use Booktics\Models\Guest_Model;
9 use Booktics\Models\Order_Model;
10 use WP_HTTP_Response;
11 use WP_REST_Request;
12 use WP_REST_Server;
13
14 /**
15 * Dashboard REST Controller
16 */
17 class Dashboard_Controller extends Base_Rest_Controller {
18
19 /**
20 * Database instance
21 *
22 * @var Booktics_Database
23 */
24 protected $db;
25
26 /**
27 * Supported report types
28 */
29 public const REPORT_TYPES = array(
30 'total_bookings',
31 'total_revenue',
32 'total_customers',
33 'booking_reports',
34 );
35
36 /**
37 * Constructor for DashboardController
38 *
39 * @param Booktics_Database $db
40 */
41 public function __construct() {
42 $this->db = new Booktics_Database();
43 }
44
45 /**
46 * Register all routes
47 *
48 * @return void
49 */
50 public function register_routes(): void {
51 register_rest_route(
52 'booktics/v1',
53 '/dashboard',
54 array(
55 'methods' => WP_REST_Server::READABLE,
56 'callback' => array( $this, 'get_dashboard_data' ),
57 'permission_callback' => array( $this, 'check_permission' ),
58 'args' => array(
59 'total_bookings' => array(
60 'type' => 'object',
61 'required' => false,
62 'properties' => array(
63 'start_date' => array(
64 'type' => 'string',
65 'format' => 'date',
66 ),
67 'end_date' => array(
68 'type' => 'string',
69 'format' => 'date',
70 ),
71 ),
72 ),
73 'total_revenue' => array(
74 'type' => 'object',
75 'required' => false,
76 'properties' => array(
77 'start_date' => array(
78 'type' => 'string',
79 'format' => 'date',
80 ),
81 'end_date' => array(
82 'type' => 'string',
83 'format' => 'date',
84 ),
85 ),
86 ),
87 'total_customers' => array(
88 'type' => 'object',
89 'required' => false,
90 'properties' => array(
91 'start_date' => array(
92 'type' => 'string',
93 'format' => 'date',
94 ),
95 'end_date' => array(
96 'type' => 'string',
97 'format' => 'date',
98 ),
99 ),
100 ),
101 'booking_reports' => array(
102 'type' => 'object',
103 'required' => false,
104 'properties' => array(
105 'start_date' => array(
106 'type' => 'string',
107 'format' => 'date',
108 ),
109 'end_date' => array(
110 'type' => 'string',
111 'format' => 'date',
112 ),
113 ),
114 ),
115 ),
116 )
117 );
118 }
119
120 /**
121 * Get dashboard data
122 *
123 * @param WP_REST_Request $request
124 *
125 * @return WP_HTTP_Response
126 */
127 public function get_dashboard_data( WP_REST_Request $request ) {
128 $data = array();
129
130 foreach ( self::REPORT_TYPES as $report_type ) {
131 $report_data = $request->get_param( $report_type );
132
133 if ( is_array( $report_data ) && isset( $report_data['start_date'], $report_data['end_date'] ) ) {
134 $date_range = sanitize_text_field( $report_data['start_date'] ) . ' to ' . sanitize_text_field( $report_data['end_date'] );
135 $data[ $report_type ] = $this->get_report_data( $report_type, $date_range );
136 }
137 }
138
139 if ( empty( $data ) ) {
140 $data = new \stdClass();
141 }
142
143 return $this->response( $data );
144 }
145
146 /**
147 * Get report data based on type
148 *
149 * @param string $report_type
150 * @param string $date_range
151 *
152 * @return mixed
153 */
154 private function get_report_data( $report_type, $date_range ) {
155 switch ( $report_type ) {
156 case 'total_bookings':
157 return $this->get_total_bookings( $date_range );
158 case 'total_revenue':
159 return $this->get_total_revenue( $date_range );
160 case 'total_customers':
161 return $this->get_total_customers( $date_range );
162 case 'booking_reports':
163 return $this->get_booking_reports( $date_range );
164 default:
165 return null;
166 }
167 }
168
169 /**
170 * Get total bookings count for a date range
171 *
172 * @param string $date_range
173 *
174 * @return array
175 */
176 private function get_total_bookings( $date_range ) {
177 $appointments = new Appointment_Model();
178
179 $dates = $this->parse_date_range( $date_range );
180 if ( ! $dates ) {
181 return array(
182 'total' => 0,
183 'percentage' => '0.00',
184 'trend' => 'neutral',
185 );
186 }
187
188 // Get current period bookings
189 $current_query = array(
190 'post_status' => array( 'completed', 'approved' ),
191 'date_query' => array(
192 'after' => $dates['start'],
193 'before' => $dates['end'],
194 'inclusive' => true,
195 ),
196 );
197 $current_query = apply_filters( 'booktics_appointment_args', $current_query );
198 $current_results = $appointments->all( $current_query );
199 $current_bookings = count( $current_results );
200
201 // Calculate previous period dates
202 $previous_dates = $this->get_previous_period_dates( $dates );
203
204 // Get previous period bookings
205 $previous_query = array(
206 'date_query' => array(
207 'after' => $previous_dates['start'],
208 'before' => $previous_dates['end'],
209 ),
210 'post_status' => array( 'completed', 'approved' ),
211 );
212 $previous_query = apply_filters( 'booktics_appointment_args', $previous_query );
213 $previous_results = $appointments->all( $previous_query );
214 $previous_bookings = count( $previous_results );
215
216 // Calculate percentage change
217 $percentage_change = 0;
218 $trend = 'neutral';
219 if ( 0 != $previous_bookings ) {
220 $percentage_change = ( ( $current_bookings - $previous_bookings ) / $previous_bookings ) * 100;
221 $trend = $this->get_trend( $percentage_change );
222 }
223
224 return array(
225 'total' => $current_bookings,
226 'percentage_changed' => number_format( $percentage_change, 2 ),
227 'trend' => $trend,
228 );
229 }
230
231 /**
232 * Get total revenue for a date range
233 *
234 * @param string $date_range
235 *
236 * @return array
237 */
238 private function get_total_revenue( $date_range ) {
239 $orders = new Order_Model( $this->db );
240
241 $dates = $this->parse_date_range( $date_range );
242 if ( ! $dates ) {
243 return array(
244 'total' => '0.00',
245 'percentage_changed' => '0.00',
246 'trend' => 'neutral',
247 );
248 }
249
250 // Get order IDs to filter by (if needed)
251 $order_ids = $this->get_order_ids();
252
253 // Get current period revenue using Order_Model method
254 $current_revenue = $orders->get_revenue_by_date_range(
255 $dates['start'],
256 $dates['end'],
257 $order_ids
258 );
259
260 // Calculate previous period dates
261 $previous_dates = $this->get_previous_period_dates( $dates );
262
263 // Get previous period revenue using Order_Model method
264 $previous_revenue = $orders->get_revenue_by_date_range(
265 $previous_dates['start'],
266 $previous_dates['end'],
267 $order_ids
268 );
269
270 // Calculate percentage change
271 $percentage_change = 0;
272 $trend = 'neutral';
273
274 // Ensure both values are valid numbers before calculation
275 if ( is_numeric( $previous_revenue ) && 0 != $previous_revenue && is_numeric( $current_revenue ) ) {
276 $percentage_change = ( ( $current_revenue - $previous_revenue ) / $previous_revenue ) * 100;
277
278 // Check if the result is a valid number
279 if ( !is_finite( $percentage_change ) ) {
280 $percentage_change = 0;
281 } else {
282 $trend = $this->get_trend( $percentage_change );
283 }
284 }
285
286 return array(
287 'total' => round( (float) $current_revenue, 2 ),
288 'percentage_changed' => round( (float) $percentage_change, 2 ),
289 'trend' => $trend,
290 );
291 }
292
293 /**
294 * Get order ids for a date range
295 *
296 * @param array $dates
297 *
298 * @return array
299 */
300 private function get_order_ids() {
301 $order_ids = array();
302 $args = array();
303 $args = apply_filters( 'booktics_appointment_args', $args );
304 $appointments = ( new Appointment_Model() )->all( $args );
305 foreach ( $appointments as $appointment ) {
306 $order_ids[] = $appointment->order_id;
307 }
308
309 return $order_ids;
310 }
311
312 /**
313 * Get total customers count for a date range
314 *
315 * @param string $date_range
316 *
317 * @return array
318 */
319 private function get_total_customers( $date_range ) {
320 $db = new Booktics_Database();
321 $guests = new Guest_Model( $db );
322
323 $dates = $this->parse_date_range( $date_range );
324 if ( ! $dates ) {
325 return array(
326 'total' => 0,
327 'percentage_changed' => '0.00',
328 'trend' => 'neutral',
329 );
330 }
331 if ( empty( $dates['start'] ) || empty( $dates['end'] ) ) {
332 return array(
333 'total' => 0,
334 'percentage_changed' => '0.00',
335 'trend' => 'neutral',
336 );
337 }
338 // Get current period customers
339 $current_results = $guests->count(
340 array(
341 'created_at' => array( '>=', $dates['start'] . ' 00:00:00' ),
342 'created_at' => array( '<=', $dates['end'] . ' 23:59:59' ),
343 )
344 );
345
346 // Calculate previous period dates
347 $previous_dates = $this->get_previous_period_dates( $dates );
348
349 // Get previous period customers
350 $previous_results = $guests->count(
351 array(
352 'created_at' => array( '>=', $previous_dates['start'] . ' 00:00:00' ),
353 'created_at' => array( '<=', $previous_dates['end'] . ' 23:59:59' ),
354 )
355 );
356
357 // Calculate percentage change
358 $percentage_change = 0;
359 $trend = 'neutral';
360 if ( 0 != $previous_results ) {
361 $percentage_change = ( ( $current_results - $previous_results ) / $previous_results ) * 100;
362 $trend = $this->get_trend( $percentage_change );
363 }
364
365 return array(
366 'total' => $current_results,
367 'percentage_changed' => number_format( $percentage_change, 2 ),
368 'trend' => $trend,
369 );
370 }
371
372 /**
373 * Get booking reports by date with completed and cancelled counts
374 *
375 * @param string $date_range
376 *
377 * @return array
378 */
379 private function get_booking_reports( $date_range ) {
380 $appointments = new Appointment_Model();
381
382 $dates = $this->parse_date_range( $date_range );
383 if ( ! $dates ) {
384 return array();
385 }
386
387 // Generate date range array
388 $start_date = new \DateTime( $dates['start'] );
389 $end_date = new \DateTime( $dates['end'] );
390 $interval = new \DateInterval( 'P1D' );
391 $period = new \DatePeriod( $start_date, $interval, $end_date );
392
393 $reports = array();
394 foreach ( $period as $date ) {
395 $date_str = $date->format( 'Y-m-d' );
396 $reports[ $date_str ] = array(
397 'completed' => 0,
398 'cancel' => 0,
399 );
400 }
401
402 // Query appointments using PostModel's methods
403 $query = array(
404 'post_type' => $appointments->get_post_type(),
405 'post_status' => array( 'completed', 'cancelled', 'canceled' ),
406 'date_query' => array(
407 array(
408 'after' => $dates['start'],
409 'before' => $dates['end'],
410 'inclusive' => true,
411 ),
412 ),
413 );
414 $query = apply_filters( 'booktics_appointment_args', $query );
415 $appointments = Appointment_Model::all( $query );
416
417 foreach ( $appointments as $appointment ) {
418 $date_str = $appointment->date;
419 if ( isset( $reports[ $date_str ] ) ) {
420 if ( $appointment->status === 'completed' ) {
421 ++$reports[ $date_str ]['completed'];
422 } elseif ( $appointment->status === 'cancelled' || $appointment->status === 'canceled' ) { //both spellings are used for backward compatibility
423 ++$reports[ $date_str ]['cancel'];
424 }
425 }
426 }
427
428 return $reports;
429 }
430
431 /**
432 * Parse date range string into start and end dates
433 *
434 * @param string $date_range
435 *
436 * @return array|null
437 */
438 private function parse_date_range( $date_range ) {
439 if ( empty( $date_range ) ) {
440 return null;
441 }
442
443 $dates = explode( 'to', $date_range );
444 if ( count( $dates ) !== 2 ) {
445 return null;
446 }
447
448 $start = trim( $dates[0] );
449 $end = trim( $dates[1] );
450
451 return array(
452 'start' => $start,
453 'end' => $end,
454 );
455 }
456
457 /**
458 * Get the trend based on the percentage change.
459 *
460 * @param float $percentage_change Percentage change from previous period
461 *
462 * @return string 'positive', 'negative', or 'neutral'
463 */
464 private function get_trend( $percentage_change ) {
465 if ( $percentage_change > 0 ) {
466 return 'positive';
467 } elseif ( $percentage_change < 0 ) {
468 return 'negative';
469 }
470
471 return 'neutral';
472 }
473
474 /**
475 * Calculate the previous period start and end dates given the current period start and end dates.
476 *
477 * @param array $dates Array of start and end dates
478 *
479 * @return array Array of previous period start and end dates
480 */
481 private function get_previous_period_dates( $dates ) {
482 $start_time_in_seconds = strtotime( $dates['start'] );
483 $end_time_in_seconds = strtotime( $dates['end'] );
484
485 // Calculate the gap in days (inclusive)
486 $gap_days = round( ( $end_time_in_seconds - $start_time_in_seconds ) / DAY_IN_SECONDS );
487
488 // Subtract the gap from both start and end
489 $previous_start = strtotime( "-$gap_days days", $start_time_in_seconds );
490 $previous_end = strtotime( "-$gap_days days", $end_time_in_seconds );
491
492 return array(
493 'start' => gmdate( 'Y-m-d', $previous_start ),
494 'end' => gmdate( 'Y-m-d', $previous_end ),
495 );
496 }
497
498 /**
499 * Check if user has permission to access dashboard data
500 *
501 * @return bool
502 */
503 public function check_permission() {
504 return current_user_can( 'manage_options' );
505 }
506 }
507