PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.24
Booktics – Appointment Booking Calendar for Service Businesses v1.0.24
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 / base / models / appointment-model.php

appointment-model.php in Booktics – Appointment Booking Calendar for Service Businesses 1.0.24, at base/models/appointment-model.php

347 lines 10.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Appoint Model
4 *
5 * @package Booktics/Models
6 */
7
8 namespace Booktics\Models;
9
10 use Booktics\Abstracts\Booktics_Database;
11 use Booktics\Abstracts\Post_Model;
12 use Booktics\Models\Service_Model;
13 use Exception;
14
15 /**
16 * AppointmentModel class
17 */
18 class Appointment_Model extends Post_Model {
19 /**
20 * Store appointment data
21 *
22 * @var array
23 */
24 protected $fillable = array(
25 'id' => '',
26 'title' => '',
27 'content' => '',
28 'start_time' => '',
29 'end_time' => '',
30 'date' => '',
31 'buffer_before' => '',
32 'buffer_after' => '',
33 'duration' => '',
34 'status' => '',
35 'payment_status' => '',
36 'customer_id' => '',
37 'customer_name' => '',
38 'team_member_id' => '',
39 'service_id' => '',
40 'additional_duration_id' => '',
41 'group_booking' => array(),
42 'order_id' => '',
43 'quantity' => 0,
44 'price' => 0,
45 'subtotal' => 0,
46 'total' => 0,
47 'note' => '',
48 'source' => '',
49 'location_id' => '',
50 'location' => array(),
51 'extra_services' => array(),
52 'security_token' => '',
53 'total_attendees' => 1,
54 'timezone' => '',
55 'cancel_note' => '',
56 );
57
58 /**
59 * Accepted post statuses for appointments
60 *
61 * @var array
62 */
63 public static $accepted_statuses = array(
64 'pending',
65 'active',
66 'cancelled',
67 'canceled',
68 'completed',
69 'approved',
70 'not_now',
71 );
72
73 /**
74 * Get post type
75 *
76 * @return string Post type name
77 */
78 public function get_post_type() {
79 return 'booktics-appointment';
80 }
81
82 /**
83 * Get accepted statuses
84 *
85 * @return array Accepted statuses
86 */
87 public static function get_accepted_statuses() {
88 return self::$accepted_statuses;
89 }
90
91 /**
92 * Specify data which should be serialized to JSON.
93 *
94 * @return array
95 */
96 public function jsonSerialize(): array {
97 $data = $this->to_array();
98
99 $service = Service_Model::find( $this->service_id );
100 $db = new Booktics_Database();
101 $customer = ( new Guest_Model( $db ) )->find( array( 'id' => $this->customer_id ) );
102 $team_member = ( new Team_Member_Model() )->find( $this->team_member_id );
103
104 $appointment_data = array(
105 'service' => $service,
106 'team_member' => $team_member,
107 'customer' => $customer,
108 );
109
110 return array_merge( $data, $appointment_data );
111 }
112
113 /**
114 * Get appointments for a specific date and service/team member
115 *
116 * @param string $date
117 * @param int $team_member_id
118 *
119 * @return array
120 * @throws Exception
121 */
122 public function get_appointments_for_slot( string $date, int $team_member_id ): array {
123 $args = array(
124 'post_type' => $this->get_post_type(),
125 'post_status' => booktics_get_option( 'appointment_statuses_for_blocked_slot', array( 'publish', 'active', 'pending' ) ),
126 'posts_per_page' => - 1,
127 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query is necessary for filtering appointments by meta fields
128 'meta_query' => array(
129 'relation' => 'AND',
130 array(
131 'key' => 'date',
132 'value' => $date,
133 'compare' => '=',
134 ),
135 array(
136 'key' => 'team_member_id',
137 'value' => $team_member_id,
138 'compare' => '=',
139 ),
140 ),
141 );
142
143 $query = new \WP_Query( $args );
144
145 return array_map(
146 function ( $post ) {
147 return new static( $post );
148 }, $query->posts
149 );
150 }
151
152 /**
153 * Override load_attributes to include payment status from orders
154 *
155 * @return void
156 */
157 protected function load_attributes() {
158 parent::load_attributes();
159
160 if ( $this->order_id ) {
161 $db = new Booktics_Database();
162 $order_model = ( new Order_Model( $db ) )->find( array( 'id' => $this->order_id ) );
163
164 $this->attributes['payment_status'] = $order_model->payment_status ?? 'pending';
165 } else {
166 $this->attributes['payment_status'] = 'pending';
167 }
168 }
169
170 /**
171 * Get appointments count by order ID
172 *
173 * @param int $order_id Order ID
174 *
175 * @return int Number of appointments
176 */
177 public function get_appointments_count_by_order_id( $order_id ) {
178 $args = array(
179 'post_type' => $this->get_post_type(),
180 'post_status' => array( 'publish', 'active', 'approved', 'completed', 'pending' ),
181 'posts_per_page' => - 1,
182 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query is necessary for filtering appointments by meta fields
183 'meta_query' => array(
184 array(
185 'key' => 'order_id',
186 'value' => $order_id,
187 'compare' => '=',
188 ),
189 ),
190 );
191
192 $query = new \WP_Query( $args );
193
194 return $query->found_posts;
195 }
196
197 /**
198 * Get all appointments attached to an order
199 *
200 * @param int $order_id Order ID
201 *
202 * @return array<static>
203 */
204 public function get_appointments_by_order_id( $order_id ) {
205 $args = array(
206 'post_type' => $this->get_post_type(),
207 'post_status' => static::$accepted_statuses,
208 'posts_per_page' => - 1,
209 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query is necessary for filtering appointments by order.
210 'meta_query' => array(
211 array(
212 'key' => 'order_id',
213 'value' => $order_id,
214 'compare' => '=',
215 ),
216 ),
217 );
218
219 $query = new \WP_Query( $args );
220
221 return array_map(
222 function ( $post ) {
223 return new static( $post );
224 }, $query->posts
225 );
226 }
227
228 /**
229 * Get appointments count by team member ID
230 *
231 * @param int $team_member_id Team member ID
232 *
233 * @return int Number of appointments
234 */
235 public function get_appointments_count_by_team_member_id( $team_member_id ) {
236 $args = array(
237 'post_type' => $this->get_post_type(),
238 'post_status' => array( 'publish', 'active', 'pending' ),
239 'posts_per_page' => - 1,
240 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query is necessary for filtering appointments by meta fields
241 'meta_query' => array(
242 array(
243 'key' => 'team_member_id',
244 'value' => $team_member_id,
245 'compare' => '=',
246 ),
247 ),
248 );
249
250 $query = new \WP_Query( $args );
251
252 return $query->found_posts;
253 }
254
255 /**
256 * Get reschedule link
257 *
258 * @return string Reschedule link
259 */
260 public function get_reschedule_link() {
261 if ( ! booktics_get_option( 'allow_customer_rescheduling', false ) ) {
262 return '';
263 }
264
265 $this->add_security_token_if_not_exists();
266
267 $service_permalink = $this->get_associated_service_permalink();
268
269 $reschedule_url = add_query_arg( [
270 'service_id' => $this->service_id,
271 'appointment_id' => $this->id,
272 'booking_action' => 'reschedule',
273 'appointment_token' => $this->security_token,
274 'order_id' => $this->order_id,
275 'team_member_id' => $this->team_member_id,
276 ], $service_permalink );
277
278 return esc_url( $reschedule_url );
279 }
280
281 /**
282 * Get cancel link
283 *
284 * @return string Cancel link
285 */
286 public function get_cancel_link() {
287 if ( ! booktics_get_option( 'allow_customer_cancellation', false ) ) {
288 return '';
289 }
290
291 $this->add_security_token_if_not_exists();
292
293 $service_permalink = $this->get_associated_service_permalink();
294
295 $cancel_url = add_query_arg( [
296 'service_id' => $this->service_id,
297 'appointment_id' => $this->id,
298 'booking_action' => 'cancel',
299 'appointment_token' => $this->security_token,
300 'order_id' => $this->order_id,
301 'team_member_id' => $this->team_member_id,
302 ], $service_permalink );
303
304 return esc_url( $cancel_url );
305 }
306
307 /**
308 * Get associated service permalink
309 *
310 * @return string Service permalink
311 */
312 private function get_associated_service_permalink() {
313 if ( ! $this->service_id ) {
314 return '';
315 }
316
317 $service = Service_Model::find( $this->service_id );
318
319 return $service->link ?? '';
320 }
321
322 /**
323 * Generate a security token for the appointment
324 * This generated token is used to verify access to the appointment rescheduling and cancellation
325 *
326 * @return string Security token
327 */
328 public function generate_security_token() {
329 $token = bin2hex( random_bytes(4) ); // 8 hex chars
330
331 return $token;
332 }
333
334 /**
335 * Check if the security token exists, if not generate and save it to the post meta.
336 * This token is used to verify access to the appointment rescheduling and cancellation.
337 *
338 * @return void
339 */
340 public function add_security_token_if_not_exists() {
341 if ( empty( $this->security_token ) ) {
342 $this->security_token = $this->generate_security_token();
343 $this->save();
344 }
345 }
346 }
347