PluginProbe ʕ •ᴥ•ʔ
Appointment Booking Plugin – LatePoint | Calendar & Scheduling for WordPress / 5.6.10
Appointment Booking Plugin – LatePoint | Calendar & Scheduling for WordPress v5.6.10
5.6.10 5.6.9 5.6.8 5.6.7 5.6.6 5.6.5 5.6.4 5.6.3 5.6.2 5.6.1 5.6.0 5.5.2 5.5.1 5.5.0 5.4.2 trunk 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.1.8 5.1.9 5.1.91 5.1.92 5.1.93 5.1.94 5.2.0 5.2.1 5.2.10 5.2.11 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.3.0 5.3.1 5.3.2 5.4.0 5.4.1
latepoint / lib / helpers / process_jobs_helper.php
latepoint / lib / helpers Last commit date
activities_helper.php 5 months ago admin_nudges_helper.php 3 weeks ago agent_helper.php 5 months ago analytics_helper.php 3 weeks ago auth_helper.php 1 month ago blocks_helper.php 1 month ago booking_helper.php 1 month ago bricks_helper.php 5 months ago bundles_helper.php 1 month ago calendar_helper.php 3 weeks ago carts_helper.php 5 months ago connector_helper.php 5 months ago csv_helper.php 5 months ago customer_helper.php 2 months ago customer_import_helper.php 2 months ago database_helper.php 1 month ago debug_helper.php 5 months ago defaults_helper.php 5 months ago elementor_helper.php 5 months ago email_helper.php 5 months ago encrypt_helper.php 5 months ago events_helper.php 4 months ago form_helper.php 5 months ago icalendar_helper.php 5 months ago image_helper.php 5 months ago invoices_helper.php 1 month ago license_helper.php 5 months ago location_helper.php 5 months ago marketing_systems_helper.php 5 months ago meeting_systems_helper.php 5 months ago menu_helper.php 1 month ago meta_helper.php 5 months ago migrations_helper.php 5 months ago money_helper.php 4 months ago notifications_helper.php 5 months ago nps_survey_helper.php 5 months ago order_intent_helper.php 3 months ago orders_helper.php 1 month ago otp_helper.php 4 months ago pages_helper.php 5 months ago params_helper.php 5 months ago payments_helper.php 1 month ago paypal_connect_helper.php 3 weeks ago plugin_version_update_helper.php 3 months ago price_breakdown_helper.php 5 months ago process_jobs_helper.php 3 weeks ago processes_helper.php 5 months ago razorpay_connect_helper.php 3 weeks ago replacer_helper.php 5 months ago resource_helper.php 1 month ago roles_helper.php 3 weeks ago router_helper.php 5 months ago service_helper.php 5 months ago sessions_helper.php 5 months ago settings_helper.php 1 month ago short_links_systems_helper.php 5 months ago shortcodes_helper.php 1 month ago sms_helper.php 5 months ago steps_helper.php 1 week ago stripe_connect_helper.php 1 month ago styles_helper.php 5 months ago support_topics_helper.php 5 months ago time_helper.php 4 months ago timeline_helper.php 3 months ago transaction_helper.php 2 months ago transaction_intent_helper.php 5 months ago util_helper.php 3 weeks ago version_specific_updates_helper.php 1 month ago whatsapp_helper.php 2 months ago work_periods_helper.php 4 months ago wp_datetime.php 5 months ago wp_user_helper.php 5 months ago
process_jobs_helper.php
429 lines
1 <?php
2 /*
3 * Copyright (c) 2022 LatePoint LLC. All rights reserved.
4 */
5
6 class OsProcessJobsHelper {
7
8 public static function create_jobs_for_process( OsProcessModel $process, array $objects ) {
9 if ( ! $process->check_if_objects_satisfy_trigger_conditions( $objects ) ) {
10 return;
11 }
12 $job = new OsProcessJobModel();
13 $job->process_id = $process->id;
14 $job->object_id = $objects[0]['model_ready']->id;
15
16 // check if job exists already
17 $existing_job = new OsProcessJobModel();
18 $exists = $existing_job->select( 'id' )->where(
19 [
20 'process_id' => $job->process_id,
21 'object_id' => $job->object_id,
22 'status' => LATEPOINT_JOB_STATUS_SCHEDULED,
23 ]
24 )->get_results();
25 if ( $exists ) {
26 return;
27 }
28
29 $job_settings = [];
30 foreach ( $process->actions as $action ) {
31 if ( $action->status != LATEPOINT_STATUS_ACTIVE ) {
32 continue;
33 }
34 $action->selected_data_objects = $objects;
35 $action->prepare_data_for_run();
36 $job_settings['action_info'][ $action->id ] = [ 'type' => $action->type ];
37 $job_settings['action_data'][ $action->id ] = $action->prepared_data_for_run;
38 }
39
40 $event_time_utc = null;
41 $object_model_type = null;
42 switch ( $process->event_type ) {
43 case 'booking_updated':
44 case 'booking_created':
45 case 'booking_start':
46 case 'booking_end':
47 $object_model_type = 'booking';
48 break;
49 case 'customer_created':
50 case 'customer_updated':
51 $object_model_type = 'customer';
52 break;
53 case 'order_created':
54 case 'order_updated':
55 $object_model_type = 'order';
56 break;
57 case 'service_created':
58 case 'service_updated':
59 $object_model_type = 'service';
60 break;
61 case 'agent_created':
62 case 'agent_updated':
63 $object_model_type = 'agent';
64 break;
65 case 'transaction_created':
66 case 'transaction_updated':
67 $object_model_type = 'transaction';
68 break;
69 case 'payment_request_created':
70 $object_model_type = 'payment_request';
71 break;
72 }
73 /**
74 * Determine a type of a model based on a process
75 *
76 * @since 5.1.0
77 * @hook latepoint_get_object_model_type_for_process
78 *
79 * @param {string} $object_model_type Type of model
80 * @param {OsProcessModel} $process Process object
81 * @param {array} $objects Array of objects used for a process
82 *
83 * @returns {string} Filtered type of model
84 */
85 $object_model_type = apply_filters( 'latepoint_get_object_model_type_for_process', $object_model_type, $process, $objects );
86 try {
87 switch ( $process->event_type ) {
88 case 'booking_updated':
89 case 'order_updated':
90 $event_time_utc = new OsWpDateTime( $objects[0]['model_ready']->updated_at, new DateTimeZone( 'UTC' ) );
91 break;
92 case 'order_created':
93 case 'booking_created':
94 case 'transaction_created':
95 case 'customer_created':
96 case 'payment_request_created':
97 $event_time_utc = new OsWpDateTime( $objects[0]['model_ready']->created_at, new DateTimeZone( 'UTC' ) );
98 break;
99 case 'booking_start':
100 $event_time_utc = new OsWpDateTime( $objects[0]['model_ready']->start_datetime_utc, new DateTimeZone( 'UTC' ) );
101 break;
102 case 'booking_end':
103 $event_time_utc = new OsWpDateTime( $objects[0]['model_ready']->end_datetime_utc, new DateTimeZone( 'UTC' ) );
104 break;
105 }
106
107 /**
108 * Determine UTC event time based on a process
109 *
110 * @since 5.1.0
111 * @hook latepoint_get_event_time_utc_for_process
112 *
113 * @param {string} $event_time_utc Event time in UTC
114 * @param {OsProcessModel} $process Process object
115 * @param {array} $objects Array of objects used for a process
116 *
117 * @returns {string} Filtered event time in UTC
118 */
119 $event_time_utc = apply_filters( 'latepoint_get_event_time_utc_for_process', $event_time_utc, $process, $objects );
120 } catch ( Exception $e ) {
121 OsDebugHelper::log( 'Error creating jobs for workflow', 'process_jobs_error', print_r( $process->id, true ) . ' ' . print_r( $objects, true ) . ' ' . $e->getMessage() );
122 return;
123 }
124 if ( empty( $event_time_utc ) ) {
125 return;
126 }
127
128 $job->settings = wp_json_encode( $job_settings );
129 $job->process_info = wp_json_encode( $process->get_info() );
130
131 // apply time offset if exists in process
132 $modify_by = self::should_modify_event_time( $process );
133 if ( ! empty( $modify_by ) ) {
134 $event_time_utc->modify( $modify_by );
135 }
136 $now_utc = new OsWpDateTime( 'now', new DateTimeZone( 'UTC' ) );
137
138 // we need to make sure we are not creating jobs that are already past their relevance (e.g. booking updated but
139 // 2 day before booking_start notification was already sent before, so scheduling a new job for that doesn't
140 // make sense). Problem is that is we have booking_udpated notification - then the time of "booking_updated" event
141 // is technically in the past, since the DB update happened couple of milliseconds ago. So solution is to create a
142 // buffer time to allow for these discrepancies in which we can resend the notification if it's within that buffer
143 $buffer = '+5 minutes';
144 $event_time_utc_buffered = clone $event_time_utc;
145 $event_time_utc_buffered->modify( $buffer );
146
147 // event time with buffer is already long passed (cron already ran it probably, or after changing booking times, this job is not relevant anymore)
148 if ( $event_time_utc_buffered < $now_utc ) {
149 return;
150 }
151
152
153 $is_in_the_future = $event_time_utc > $now_utc;
154 $job->object_model_type = $object_model_type ?? 'n/a';
155 $job->to_run_after_utc = $event_time_utc->format( LATEPOINT_DATETIME_DB_FORMAT );
156
157 $job->status = LATEPOINT_JOB_STATUS_SCHEDULED;
158 $job->save();
159 // execute immediately only when there is no configured time offset AND the event is not in the future;
160 // if an offset is set, always defer to the cron runner so the delay is honoured even on edge cases
161 // where the computed time lands at or just before now (e.g. "1 minute after" on a slow server).
162 $has_time_offset = ! empty( $process->time_offset );
163 if ( ! $is_in_the_future && ! $has_time_offset ) {
164 $job->run();
165 }
166 }
167
168 /**
169 * @param string $event_type
170 * @param array $objects example format: ['model' => 'booking', 'id' => $booking->id, 'model_ready' => OsModel $booking]
171 * @return void
172 */
173 public static function create_jobs_for_event( string $event_type, array $objects ) {
174 $processes = new OsProcessModel();
175 // find all processes that match this event type
176 $processes = $processes->where( [ 'event_type' => $event_type ] )->should_be_active()->get_results_as_models();
177 if ( $processes ) {
178 foreach ( $processes as $process ) {
179 $process->build_from_json();
180 self::create_jobs_for_process( $process, $objects );
181 }
182 }
183 }
184
185 /**
186 *
187 * Searches existing records that match this process conditions and schedules a job, for example if you created a new
188 * process that sends a notification 15 minute before the booking start - this method will find those bookings and
189 * schedule jobs to send notification
190 *
191 * @param OsProcessModel $process
192 * @return bool|void
193 */
194 public static function recreate_jobs_for_existing_records( OsProcessModel $process ) {
195 // don't create jobs for booking_updated event, since we don't capture the exact information of what was updated in
196 // the booking and can't check if event conditions are satisfied
197 if ( ! in_array( $process->event_type, [ 'booking_start', 'booking_end', 'booking_created', 'customer_created', 'transaction_created' ] ) ) {
198 return false;
199 }
200
201 // calculate the cutoff date to search records that could be affected by this event
202 $cutoff_datetime_utc = OsTimeHelper::now_datetime_utc();
203 $modify_by = self::should_modify_event_time( $process, true );
204 if ( $modify_by ) {
205 $cutoff_datetime_utc->modify( $modify_by );
206 } else {
207 // if there is no time offset for this process and event type is not "start" or "end" of the booking - we don't need to
208 // create any jobs, since other events have already past
209 if ( ! in_array( $process->event_type, [ 'booking_start', 'booking_end' ] ) ) {
210 return true;
211 }
212 }
213
214 $formatted_cutoff_utc = $cutoff_datetime_utc->format( LATEPOINT_DATETIME_DB_FORMAT );
215
216 $args = [];
217 switch ( $process->event_type ) {
218 case 'booking_start':
219 $args['start_datetime_utc >='] = $formatted_cutoff_utc;
220 break;
221 case 'booking_end':
222 $args['end_datetime_utc >='] = $formatted_cutoff_utc;
223 break;
224 case 'booking_created':
225 $args['created_at >='] = $formatted_cutoff_utc;
226 break;
227 case 'booking_updated':
228 $args['updated_at >='] = $formatted_cutoff_utc;
229 break;
230 case 'transaction_created':
231 $args['created_at >='] = $formatted_cutoff_utc;
232 break;
233 case 'customer_created':
234 $args['created_at >='] = $formatted_cutoff_utc;
235 break;
236 }
237 if ( $process->event_type == 'customer_created' ) {
238 $models = new OsCustomerModel();
239 $model_name = 'customer';
240 } else {
241 $models = new OsBookingModel();
242 $model_name = 'booking';
243 }
244 if ( $args ) {
245 $models->where( $args );
246 }
247 $models = $models->get_results_as_models();
248
249 foreach ( $models as $model ) {
250 $objects = [];
251 $objects[] = [
252 'model' => $model_name,
253 'id' => $model->id,
254 'model_ready' => $model,
255 ];
256 self::create_jobs_for_process( $process, $objects );
257 }
258 }
259
260 public static function process_scheduled_jobs() {
261 $jobs = new OsProcessJobModel();
262 // find jobs that are scheduled to run in a period from [24 hour ago to NOW] - so that we don't run old irrelevant jobs
263 $jobs = $jobs->where(
264 [
265 'status' => LATEPOINT_JOB_STATUS_SCHEDULED,
266 'to_run_after_utc <=' => OsTimeHelper::now_datetime_utc_in_db_format(),
267 'to_run_after_utc >=' => OsTimeHelper::custom_datetime_utc_in_db_format( '-24 hours' ),
268 ]
269 )->get_results_as_models();
270 foreach ( $jobs as $job ) {
271 $job->run();
272 $result = json_decode( $job->run_result, true );
273 echo '<div>' . esc_html( $job->id ) . ':' . esc_html( $result['status'] ) . ', ' . esc_html( $result['message'] ) . '</div>';
274 }
275 }
276
277
278 public static function init_hooks() {
279 add_action( 'latepoint_customer_created', 'OsProcessJobsHelper::handle_customer_created', 12 );
280 add_action( 'latepoint_transaction_created', 'OsProcessJobsHelper::handle_transaction_created', 12 );
281 add_action( 'latepoint_booking_created', 'OsProcessJobsHelper::handle_booking_created', 12 );
282 add_action( 'latepoint_booking_updated', 'OsProcessJobsHelper::handle_booking_updated', 12, 2 );
283 add_action( 'latepoint_order_created', 'OsProcessJobsHelper::handle_order_created', 12 );
284 add_action( 'latepoint_order_updated', 'OsProcessJobsHelper::handle_order_updated', 12, 2 );
285 add_action( 'latepoint_payment_request_created', 'OsProcessJobsHelper::handle_payment_request_created', 12 );
286 }
287
288 public static function handle_customer_created( OsCustomerModel $customer ) {
289 $objects = [];
290 $objects[] = [
291 'model' => 'customer',
292 'id' => $customer->id,
293 'model_ready' => $customer,
294 ];
295 self::create_jobs_for_event( 'customer_created', $objects );
296 }
297
298 public static function handle_transaction_created( OsTransactionModel $transaction ) {
299 $objects = [];
300 $objects[] = [
301 'model' => 'transaction',
302 'id' => $transaction->id,
303 'model_ready' => $transaction,
304 ];
305 self::create_jobs_for_event( 'transaction_created', $objects );
306 }
307
308 public static function get_nice_job_status_name( $status ) {
309 $names = [
310 LATEPOINT_JOB_STATUS_COMPLETED => __( 'Completed', 'latepoint' ),
311 LATEPOINT_JOB_STATUS_SCHEDULED => __( 'Scheduled', 'latepoint' ),
312 LATEPOINT_JOB_STATUS_CANCELLED => __( 'Cancelled', 'latepoint' ),
313 LATEPOINT_JOB_STATUS_ERROR => __( 'Error', 'latepoint' ),
314 ];
315 return $names[ $status ] ?? __( 'n/a', 'latepoint' );
316 }
317
318 public static function handle_booking_created( OsBookingModel $booking ) {
319 $objects = [];
320 $objects[] = [
321 'model' => 'booking',
322 'id' => $booking->id,
323 'model_ready' => $booking,
324 ];
325 self::create_jobs_for_event( 'booking_created', $objects );
326 self::create_jobs_for_event( 'booking_start', $objects );
327 self::create_jobs_for_event( 'booking_end', $objects );
328 }
329
330
331 public static function handle_booking_updated( OsBookingModel $new_booking, OsBookingModel $old_booking ) {
332 // remove previously scheduled jobs for this booking because it's changed and might not need them anymore
333 // remove only those that are in "scheduled" status, those that were already sent or errored should stay
334 $jobs = new OsProcessJobModel();
335 $jobs->delete_where(
336 [
337 'status' => LATEPOINT_JOB_STATUS_SCHEDULED,
338 'object_id' => $new_booking->id,
339 'object_model_type' => 'booking',
340 ]
341 );
342
343 $objects = [];
344 $objects[] = [
345 'model' => 'booking',
346 'id' => $new_booking->id,
347 'model_ready' => $new_booking,
348 ];
349 $objects[] = [
350 'model' => 'old_booking',
351 'id' => $old_booking->id,
352 'model_ready' => $old_booking,
353 ];
354 self::create_jobs_for_event( 'booking_updated', $objects );
355 // some changes might have triggered other webhooks (e.g. service changed, so now it could be required to be reminded of booking start/end)
356
357 self::create_jobs_for_event( 'booking_start', $objects );
358 self::create_jobs_for_event( 'booking_end', $objects );
359 }
360
361
362
363 public static function handle_order_created( OsOrderModel $order ) {
364 $objects = [];
365 $objects[] = [
366 'model' => 'order',
367 'id' => $order->id,
368 'model_ready' => $order,
369 ];
370 self::create_jobs_for_event( 'order_created', $objects );
371 }
372
373
374 public static function handle_payment_request_created( OsPaymentRequestModel $payment_request ) {
375 $objects = [];
376 $objects[] = [
377 'model' => 'payment_request',
378 'id' => $payment_request->id,
379 'model_ready' => $payment_request,
380 ];
381 self::create_jobs_for_event( 'payment_request_created', $objects );
382 }
383
384
385 public static function handle_order_updated( OsOrderModel $new_order, OsOrderModel $old_order ) {
386 // remove previously scheduled jobs for this order because it's changed and might not need them anymore
387 // remove only those that are in "scheduled" status, those that were already sent or errored should stay
388 $jobs = new OsProcessJobModel();
389 $jobs->delete_where(
390 [
391 'status' => LATEPOINT_JOB_STATUS_SCHEDULED,
392 'object_id' => $new_order->id,
393 'object_model_type' => 'order',
394 ]
395 );
396
397 $objects = [];
398 $objects[] = [
399 'model' => 'order',
400 'id' => $new_order->id,
401 'model_ready' => $new_order,
402 ];
403 $objects[] = [
404 'model' => 'old_order',
405 'id' => $old_order->id,
406 'model_ready' => $old_order,
407 ];
408 self::create_jobs_for_event( 'order_updated', $objects );
409 }
410
411 /**
412 * @param OsProcessModel $process
413 * @param $opposite determines if apply time offset in opposite direction (to search events eligible for cutoff)
414 * @return string
415 */
416 public static function should_modify_event_time( OsProcessModel $process, $opposite = false ): string {
417 if ( empty( $process->time_offset ) || empty( $process->time_offset['value'] ) || empty( $process->time_offset['unit'] ) ) {
418 // no time offset, or incomplete offset data
419 return '';
420 }
421 $time_offset_settings = $process->time_offset;
422 $before_after = $time_offset_settings['before_after'] ?? 'after';
423 // offset, calculate how much to modify by
424 $sign = ( $opposite ) ? ( ( $before_after == 'after' ) ? '-' : '+' ) : ( ( $before_after == 'after' ) ? '+' : '-' );
425 $modify_by = $sign . absint( $time_offset_settings['value'] ) . ' ' . $time_offset_settings['unit'];
426 return $modify_by;
427 }
428 }
429