PluginProbe ʕ •ᴥ•ʔ
LatePoint – Calendar Booking Plugin for Appointments and Events / 5.6.6
LatePoint – Calendar Booking Plugin for Appointments and Events v5.6.6
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 3 months ago agent_helper.php 3 months ago analytics_helper.php 1 week ago auth_helper.php 6 days ago blocks_helper.php 3 weeks ago booking_helper.php 4 days ago bricks_helper.php 3 months ago bundles_helper.php 4 days ago calendar_helper.php 3 days ago carts_helper.php 3 months ago connector_helper.php 3 months ago csv_helper.php 3 months ago customer_helper.php 1 month ago customer_import_helper.php 1 month ago database_helper.php 1 week ago debug_helper.php 3 months ago defaults_helper.php 3 months ago elementor_helper.php 3 months ago email_helper.php 3 months ago encrypt_helper.php 3 months ago events_helper.php 3 months ago form_helper.php 3 months ago icalendar_helper.php 3 months ago image_helper.php 3 months ago invoices_helper.php 3 weeks ago license_helper.php 3 months ago location_helper.php 3 months ago marketing_systems_helper.php 3 months ago meeting_systems_helper.php 3 months ago menu_helper.php 3 weeks ago meta_helper.php 3 months ago migrations_helper.php 3 months ago money_helper.php 3 months ago notifications_helper.php 3 months ago nps_survey_helper.php 3 months ago order_intent_helper.php 2 months ago orders_helper.php 3 months ago otp_helper.php 3 months ago pages_helper.php 3 months ago params_helper.php 3 months ago payments_helper.php 2 months ago plugin_version_update_helper.php 2 months ago price_breakdown_helper.php 3 months ago process_jobs_helper.php 3 months ago processes_helper.php 3 months ago razorpay_connect_helper.php 1 week ago replacer_helper.php 3 months ago resource_helper.php 4 days ago roles_helper.php 3 weeks ago router_helper.php 3 months ago service_helper.php 3 months ago sessions_helper.php 3 months ago settings_helper.php 1 week ago short_links_systems_helper.php 3 months ago shortcodes_helper.php 3 weeks ago sms_helper.php 3 months ago steps_helper.php 1 week ago stripe_connect_helper.php 1 week ago styles_helper.php 3 months ago support_topics_helper.php 3 months ago time_helper.php 2 months ago timeline_helper.php 2 months ago transaction_helper.php 1 month ago transaction_intent_helper.php 3 months ago util_helper.php 1 month ago version_specific_updates_helper.php 3 months ago whatsapp_helper.php 1 month ago work_periods_helper.php 3 months ago wp_datetime.php 3 months ago wp_user_helper.php 3 months ago
process_jobs_helper.php
427 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, if there is no delay(time offset) specified
160 // todo add ability toggling setting to allow delayed execution even on instant events (to speed up frontend experience for customers)
161 if ( ! $is_in_the_future ) {
162 $job->run();
163 }
164 }
165
166 /**
167 * @param string $event_type
168 * @param array $objects example format: ['model' => 'booking', 'id' => $booking->id, 'model_ready' => OsModel $booking]
169 * @return void
170 */
171 public static function create_jobs_for_event( string $event_type, array $objects ) {
172 $processes = new OsProcessModel();
173 // find all processes that match this event type
174 $processes = $processes->where( [ 'event_type' => $event_type ] )->should_be_active()->get_results_as_models();
175 if ( $processes ) {
176 foreach ( $processes as $process ) {
177 $process->build_from_json();
178 self::create_jobs_for_process( $process, $objects );
179 }
180 }
181 }
182
183 /**
184 *
185 * Searches existing records that match this process conditions and schedules a job, for example if you created a new
186 * process that sends a notification 15 minute before the booking start - this method will find those bookings and
187 * schedule jobs to send notification
188 *
189 * @param OsProcessModel $process
190 * @return bool|void
191 */
192 public static function recreate_jobs_for_existing_records( OsProcessModel $process ) {
193 // don't create jobs for booking_updated event, since we don't capture the exact information of what was updated in
194 // the booking and can't check if event conditions are satisfied
195 if ( ! in_array( $process->event_type, [ 'booking_start', 'booking_end', 'booking_created', 'customer_created', 'transaction_created' ] ) ) {
196 return false;
197 }
198
199 // calculate the cutoff date to search records that could be affected by this event
200 $cutoff_datetime_utc = OsTimeHelper::now_datetime_utc();
201 $modify_by = self::should_modify_event_time( $process, true );
202 if ( $modify_by ) {
203 $cutoff_datetime_utc->modify( $modify_by );
204 } else {
205 // 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
206 // create any jobs, since other events have already past
207 if ( ! in_array( $process->event_type, [ 'booking_start', 'booking_end' ] ) ) {
208 return true;
209 }
210 }
211
212 $formatted_cutoff_utc = $cutoff_datetime_utc->format( LATEPOINT_DATETIME_DB_FORMAT );
213
214 $args = [];
215 switch ( $process->event_type ) {
216 case 'booking_start':
217 $args['start_datetime_utc >='] = $formatted_cutoff_utc;
218 break;
219 case 'booking_end':
220 $args['end_datetime_utc >='] = $formatted_cutoff_utc;
221 break;
222 case 'booking_created':
223 $args['created_at >='] = $formatted_cutoff_utc;
224 break;
225 case 'booking_updated':
226 $args['updated_at >='] = $formatted_cutoff_utc;
227 break;
228 case 'transaction_created':
229 $args['created_at >='] = $formatted_cutoff_utc;
230 break;
231 case 'customer_created':
232 $args['created_at >='] = $formatted_cutoff_utc;
233 break;
234 }
235 if ( $process->event_type == 'customer_created' ) {
236 $models = new OsCustomerModel();
237 $model_name = 'customer';
238 } else {
239 $models = new OsBookingModel();
240 $model_name = 'booking';
241 }
242 if ( $args ) {
243 $models->where( $args );
244 }
245 $models = $models->get_results_as_models();
246
247 foreach ( $models as $model ) {
248 $objects = [];
249 $objects[] = [
250 'model' => $model_name,
251 'id' => $model->id,
252 'model_ready' => $model,
253 ];
254 self::create_jobs_for_process( $process, $objects );
255 }
256 }
257
258 public static function process_scheduled_jobs() {
259 $jobs = new OsProcessJobModel();
260 // 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
261 $jobs = $jobs->where(
262 [
263 'status' => LATEPOINT_JOB_STATUS_SCHEDULED,
264 'to_run_after_utc <=' => OsTimeHelper::now_datetime_utc_in_db_format(),
265 'to_run_after_utc >=' => OsTimeHelper::custom_datetime_utc_in_db_format( '-24 hours' ),
266 ]
267 )->get_results_as_models();
268 foreach ( $jobs as $job ) {
269 $job->run();
270 $result = json_decode( $job->run_result, true );
271 echo '<div>' . esc_html( $job->id ) . ':' . esc_html( $result['status'] ) . ', ' . esc_html( $result['message'] ) . '</div>';
272 }
273 }
274
275
276 public static function init_hooks() {
277 add_action( 'latepoint_customer_created', 'OsProcessJobsHelper::handle_customer_created', 12 );
278 add_action( 'latepoint_transaction_created', 'OsProcessJobsHelper::handle_transaction_created', 12 );
279 add_action( 'latepoint_booking_created', 'OsProcessJobsHelper::handle_booking_created', 12 );
280 add_action( 'latepoint_booking_updated', 'OsProcessJobsHelper::handle_booking_updated', 12, 2 );
281 add_action( 'latepoint_order_created', 'OsProcessJobsHelper::handle_order_created', 12 );
282 add_action( 'latepoint_order_updated', 'OsProcessJobsHelper::handle_order_updated', 12, 2 );
283 add_action( 'latepoint_payment_request_created', 'OsProcessJobsHelper::handle_payment_request_created', 12 );
284 }
285
286 public static function handle_customer_created( OsCustomerModel $customer ) {
287 $objects = [];
288 $objects[] = [
289 'model' => 'customer',
290 'id' => $customer->id,
291 'model_ready' => $customer,
292 ];
293 self::create_jobs_for_event( 'customer_created', $objects );
294 }
295
296 public static function handle_transaction_created( OsTransactionModel $transaction ) {
297 $objects = [];
298 $objects[] = [
299 'model' => 'transaction',
300 'id' => $transaction->id,
301 'model_ready' => $transaction,
302 ];
303 self::create_jobs_for_event( 'transaction_created', $objects );
304 }
305
306 public static function get_nice_job_status_name( $status ) {
307 $names = [
308 LATEPOINT_JOB_STATUS_COMPLETED => __( 'Completed', 'latepoint' ),
309 LATEPOINT_JOB_STATUS_SCHEDULED => __( 'Scheduled', 'latepoint' ),
310 LATEPOINT_JOB_STATUS_CANCELLED => __( 'Cancelled', 'latepoint' ),
311 LATEPOINT_JOB_STATUS_ERROR => __( 'Error', 'latepoint' ),
312 ];
313 return $names[ $status ] ?? __( 'n/a', 'latepoint' );
314 }
315
316 public static function handle_booking_created( OsBookingModel $booking ) {
317 $objects = [];
318 $objects[] = [
319 'model' => 'booking',
320 'id' => $booking->id,
321 'model_ready' => $booking,
322 ];
323 self::create_jobs_for_event( 'booking_created', $objects );
324 self::create_jobs_for_event( 'booking_start', $objects );
325 self::create_jobs_for_event( 'booking_end', $objects );
326 }
327
328
329 public static function handle_booking_updated( OsBookingModel $new_booking, OsBookingModel $old_booking ) {
330 // remove previously scheduled jobs for this booking because it's changed and might not need them anymore
331 // remove only those that are in "scheduled" status, those that were already sent or errored should stay
332 $jobs = new OsProcessJobModel();
333 $jobs->delete_where(
334 [
335 'status' => LATEPOINT_JOB_STATUS_SCHEDULED,
336 'object_id' => $new_booking->id,
337 'object_model_type' => 'booking',
338 ]
339 );
340
341 $objects = [];
342 $objects[] = [
343 'model' => 'booking',
344 'id' => $new_booking->id,
345 'model_ready' => $new_booking,
346 ];
347 $objects[] = [
348 'model' => 'old_booking',
349 'id' => $old_booking->id,
350 'model_ready' => $old_booking,
351 ];
352 self::create_jobs_for_event( 'booking_updated', $objects );
353 // some changes might have triggered other webhooks (e.g. service changed, so now it could be required to be reminded of booking start/end)
354
355 self::create_jobs_for_event( 'booking_start', $objects );
356 self::create_jobs_for_event( 'booking_end', $objects );
357 }
358
359
360
361 public static function handle_order_created( OsOrderModel $order ) {
362 $objects = [];
363 $objects[] = [
364 'model' => 'order',
365 'id' => $order->id,
366 'model_ready' => $order,
367 ];
368 self::create_jobs_for_event( 'order_created', $objects );
369 }
370
371
372 public static function handle_payment_request_created( OsPaymentRequestModel $payment_request ) {
373 $objects = [];
374 $objects[] = [
375 'model' => 'payment_request',
376 'id' => $payment_request->id,
377 'model_ready' => $payment_request,
378 ];
379 self::create_jobs_for_event( 'payment_request_created', $objects );
380 }
381
382
383 public static function handle_order_updated( OsOrderModel $new_order, OsOrderModel $old_order ) {
384 // remove previously scheduled jobs for this order because it's changed and might not need them anymore
385 // remove only those that are in "scheduled" status, those that were already sent or errored should stay
386 $jobs = new OsProcessJobModel();
387 $jobs->delete_where(
388 [
389 'status' => LATEPOINT_JOB_STATUS_SCHEDULED,
390 'object_id' => $new_order->id,
391 'object_model_type' => 'order',
392 ]
393 );
394
395 $objects = [];
396 $objects[] = [
397 'model' => 'order',
398 'id' => $new_order->id,
399 'model_ready' => $new_order,
400 ];
401 $objects[] = [
402 'model' => 'old_order',
403 'id' => $old_order->id,
404 'model_ready' => $old_order,
405 ];
406 self::create_jobs_for_event( 'order_updated', $objects );
407 }
408
409 /**
410 * @param OsProcessModel $process
411 * @param $opposite determines if apply time offset in opposite direction (to search events eligible for cutoff)
412 * @return string
413 */
414 public static function should_modify_event_time( OsProcessModel $process, $opposite = false ): string {
415 if ( empty( $process->time_offset ) ) {
416 // no time offset
417 return '';
418 } else {
419 $time_offset_settings = $process->time_offset;
420 // offset, calculate how much to modify by
421 $sign = ( $opposite ) ? ( ( $time_offset_settings['before_after'] == 'after' ) ? '-' : '+' ) : ( ( $time_offset_settings['before_after'] == 'after' ) ? '+' : '-' );
422 $modify_by = $sign . $time_offset_settings['value'] . ' ' . $time_offset_settings['unit'];
423 return $modify_by;
424 }
425 }
426 }
427