PluginProbe ʕ •ᴥ•ʔ
Appointment Booking Plugin – LatePoint | Calendar & Scheduling for WordPress / 5.2.4
Appointment Booking Plugin – LatePoint | Calendar & Scheduling for WordPress v5.2.4
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 1 year ago agent_helper.php 1 year ago auth_helper.php 9 months ago blocks_helper.php 1 year ago booking_helper.php 1 year ago bricks_helper.php 1 year ago bundles_helper.php 1 year ago calendar_helper.php 9 months ago carts_helper.php 1 year ago connector_helper.php 9 months ago csv_helper.php 9 months ago customer_helper.php 9 months ago customer_import_helper.php 9 months ago database_helper.php 9 months ago debug_helper.php 1 year ago defaults_helper.php 1 year ago elementor_helper.php 1 year ago email_helper.php 9 months ago encrypt_helper.php 1 year ago events_helper.php 9 months ago form_helper.php 9 months ago icalendar_helper.php 1 year ago image_helper.php 1 year ago invoices_helper.php 9 months ago license_helper.php 1 year ago location_helper.php 1 year ago marketing_systems_helper.php 1 year ago meeting_systems_helper.php 1 year ago menu_helper.php 9 months ago meta_helper.php 1 year ago migrations_helper.php 1 year ago money_helper.php 1 year ago notifications_helper.php 9 months ago order_intent_helper.php 9 months ago orders_helper.php 1 year ago otp_helper.php 9 months ago pages_helper.php 1 year ago params_helper.php 1 year ago payments_helper.php 1 year ago price_breakdown_helper.php 1 year ago process_jobs_helper.php 1 year ago processes_helper.php 1 year ago replacer_helper.php 1 year ago resource_helper.php 1 year ago roles_helper.php 9 months ago router_helper.php 1 year ago service_helper.php 1 year ago sessions_helper.php 1 year ago settings_helper.php 9 months ago short_links_systems_helper.php 9 months ago shortcodes_helper.php 9 months ago sms_helper.php 1 year ago steps_helper.php 9 months ago stripe_connect_helper.php 9 months ago styles_helper.php 9 months ago support_topics_helper.php 1 year ago time_helper.php 9 months ago timeline_helper.php 9 months ago transaction_helper.php 1 year ago transaction_intent_helper.php 1 year ago util_helper.php 9 months ago version_specific_updates_helper.php 9 months ago whatsapp_helper.php 1 year ago work_periods_helper.php 1 year ago wp_datetime.php 1 year ago wp_user_helper.php 1 year ago
process_jobs_helper.php
345 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)) return;
10 $job = new OsProcessJobModel();
11 $job->process_id = $process->id;
12 $job->object_id = $objects[0]['model_ready']->id;
13
14 // check if job exists already
15 $existing_job = new OsProcessJobModel();
16 $exists = $existing_job->select('id')->where(['process_id' => $job->process_id, 'object_id' => $job->object_id, 'status' => LATEPOINT_JOB_STATUS_SCHEDULED])->get_results();
17 if($exists) return;
18
19 $job_settings = [];
20 foreach($process->actions as $action){
21 if($action->status != LATEPOINT_STATUS_ACTIVE) continue;
22 $action->selected_data_objects = $objects;
23 $action->prepare_data_for_run();
24 $job_settings['action_info'][$action->id] = ['type' => $action->type];
25 $job_settings['action_data'][$action->id] = $action->prepared_data_for_run;
26 }
27
28 $event_time_utc = null;
29 $object_model_type = null;
30 switch($process->event_type){
31 case 'booking_updated';
32 case 'booking_created';
33 case 'booking_start';
34 case 'booking_end';
35 $object_model_type = 'booking';
36 break;
37 case 'customer_created':
38 case 'customer_updated':
39 $object_model_type = 'customer';
40 break;
41 case 'order_created':
42 case 'order_updated':
43 $object_model_type = 'order';
44 break;
45 case 'service_created':
46 case 'service_updated':
47 $object_model_type = 'service';
48 break;
49 case 'agent_created':
50 case 'agent_updated':
51 $object_model_type = 'agent';
52 break;
53 case 'transaction_created':
54 case 'transaction_updated':
55 $object_model_type = 'transaction';
56 break;
57 case 'payment_request_created':
58 $object_model_type = 'payment_request';
59 break;
60 }
61 /**
62 * Determine a type of a model based on a process
63 *
64 * @since 5.1.0
65 * @hook latepoint_get_object_model_type_for_process
66 *
67 * @param {string} $object_model_type Type of model
68 * @param {OsProcessModel} $process Process object
69 * @param {array} $objects Array of objects used for a process
70 *
71 * @returns {string} Filtered type of model
72 */
73 $object_model_type = apply_filters('latepoint_get_object_model_type_for_process', $object_model_type, $process, $objects);
74 try{
75 switch($process->event_type){
76 case 'booking_updated':
77 case 'order_updated':
78 $event_time_utc = new OsWpDateTime($objects[0]['model_ready']->updated_at, new DateTimeZone('UTC'));
79 break;
80 case 'order_created':
81 case 'booking_created':
82 case 'transaction_created':
83 case 'customer_created':
84 case 'payment_request_created':
85 $event_time_utc = new OsWpDateTime($objects[0]['model_ready']->created_at, new DateTimeZone('UTC'));
86 break;
87 case 'booking_start':
88 $event_time_utc = new OsWpDateTime($objects[0]['model_ready']->start_datetime_utc, new DateTimeZone('UTC'));
89 break;
90 case 'booking_end':
91 $event_time_utc = new OsWpDateTime($objects[0]['model_ready']->end_datetime_utc, new DateTimeZone('UTC'));
92 break;
93 }
94
95 /**
96 * Determine UTC event time based on a process
97 *
98 * @since 5.1.0
99 * @hook latepoint_get_event_time_utc_for_process
100 *
101 * @param {string} $event_time_utc Event time in UTC
102 * @param {OsProcessModel} $process Process object
103 * @param {array} $objects Array of objects used for a process
104 *
105 * @returns {string} Filtered event time in UTC
106 */
107 $event_time_utc = apply_filters('latepoint_get_event_time_utc_for_process', $event_time_utc, $process, $objects);
108 }catch(Exception $e){
109 OsDebugHelper::log('Error creating jobs for workflow', 'process_jobs_error', print_r($process->id, true).' '.print_r($objects,true).' '.$e->getMessage());
110 return;
111 }
112 if(empty($event_time_utc)) return;
113
114 $job->settings = wp_json_encode($job_settings);
115 $job->process_info = wp_json_encode($process->get_info());
116
117 // apply time offset if exists in process
118 $modify_by = self::should_modify_event_time($process);
119 if(!empty($modify_by)) $event_time_utc->modify($modify_by);
120 $now_utc = new OsWpDateTime('now', new DateTimeZone('UTC'));
121
122 // we need to make sure we are not creating jobs that are already past their relevance (e.g. booking updated but
123 // 2 day before booking_start notification was already sent before, so scheduling a new job for that doesn't
124 // make sense). Problem is that is we have booking_udpated notification - then the time of "booking_updated" event
125 // is technically in the past, since the DB update happened couple of milliseconds ago. So solution is to create a
126 // buffer time to allow for these discrepancies in which we can resend the notification if it's within that buffer
127 $buffer = '+5 minutes';
128 $event_time_utc_buffered = clone $event_time_utc;
129 $event_time_utc_buffered->modify($buffer);
130
131 // event time with buffer is already long passed (cron already ran it probably, or after changing booking times, this job is not relevant anymore)
132 if($event_time_utc_buffered < $now_utc) return;
133
134
135 $is_in_the_future = $event_time_utc > $now_utc;
136 $job->object_model_type = $object_model_type ?? 'n/a';
137 $job->to_run_after_utc =$event_time_utc->format(LATEPOINT_DATETIME_DB_FORMAT);
138
139 $job->status = LATEPOINT_JOB_STATUS_SCHEDULED;
140 $job->save();
141 // execute immediately, if there is no delay(time offset) specified
142 // todo add ability toggling setting to allow delayed execution even on instant events (to speed up frontend experience for customers)
143 if(!$is_in_the_future){
144 $job->run();
145 }
146 }
147
148 /**
149 * @param string $event_type
150 * @param array $objects example format: ['model' => 'booking', 'id' => $booking->id, 'model_ready' => OsModel $booking]
151 * @return void
152 */
153 public static function create_jobs_for_event(string $event_type, array $objects){
154 $processes = new OsProcessModel();
155 // find all processes that match this event type
156 $processes = $processes->where(['event_type' => $event_type])->should_be_active()->get_results_as_models();
157 if($processes){
158 foreach($processes as $process){
159 $process->build_from_json();
160 self::create_jobs_for_process($process, $objects);
161 }
162 }
163 }
164
165 /**
166 *
167 * Searches existing records that match this process conditions and schedules a job, for example if you created a new
168 * process that sends a notification 15 minute before the booking start - this method will find those bookings and
169 * schedule jobs to send notification
170 *
171 * @param OsProcessModel $process
172 * @return bool|void
173 */
174 public static function recreate_jobs_for_existing_records(OsProcessModel $process){
175 // don't create jobs for booking_updated event, since we don't capture the exact information of what was updated in
176 // the booking and can't check if event conditions are satisfied
177 if(!in_array($process->event_type, ['booking_start', 'booking_end', 'booking_created', 'customer_created', 'transaction_created'])) return false;
178
179 // calculate the cutoff date to search records that could be affected by this event
180 $cutoff_datetime_utc = OsTimeHelper::now_datetime_utc();
181 $modify_by = self::should_modify_event_time($process, true);
182 if($modify_by){
183 $cutoff_datetime_utc->modify($modify_by);
184 }else{
185 // 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
186 // create any jobs, since other events have already past
187 if(!in_array($process->event_type, ['booking_start', 'booking_end'])) return true;
188 }
189
190 $formatted_cutoff_utc = $cutoff_datetime_utc->format(LATEPOINT_DATETIME_DB_FORMAT);
191
192 $args = [];
193 switch($process->event_type){
194 case 'booking_start':
195 $args['start_datetime_utc >='] = $formatted_cutoff_utc;
196 break;
197 case 'booking_end':
198 $args['end_datetime_utc >='] = $formatted_cutoff_utc;
199 break;
200 case 'booking_created':
201 $args['created_at >='] = $formatted_cutoff_utc;
202 break;
203 case 'booking_updated':
204 $args['updated_at >='] = $formatted_cutoff_utc;
205 break;
206 case 'transaction_created':
207 $args['created_at >='] = $formatted_cutoff_utc;
208 break;
209 case 'customer_created':
210 $args['created_at >='] = $formatted_cutoff_utc;
211 break;
212 }
213 if($process->event_type == 'customer_created'){
214 $models = new OsCustomerModel();
215 $model_name = 'customer';
216 }else{
217 $models = new OsBookingModel();
218 $model_name = 'booking';
219 }
220 if($args) $models->where($args);
221 $models = $models->get_results_as_models();
222
223 foreach($models as $model){
224 $objects = [];
225 $objects[] = ['model' => $model_name, 'id' => $model->id, 'model_ready' => $model];
226 self::create_jobs_for_process($process, $objects);
227 }
228 }
229
230 public static function process_scheduled_jobs(){
231 $jobs = new OsProcessJobModel();
232 // 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
233 $jobs = $jobs->where(['status' => LATEPOINT_JOB_STATUS_SCHEDULED, 'to_run_after_utc <=' => OsTimeHelper::now_datetime_utc_in_db_format(), 'to_run_after_utc >=' => OsTimeHelper::custom_datetime_utc_in_db_format('-24 hours')])->get_results_as_models();
234 foreach($jobs as $job){
235 $job->run();
236 $result = json_decode($job->run_result, true);
237 echo '<div>'.esc_html($job->id).':'.esc_html($result['status']).', '.esc_html($result['message']).'</div>';
238 }
239 }
240
241
242 public static function init_hooks(){
243 add_action('latepoint_customer_created', 'OsProcessJobsHelper::handle_customer_created', 12);
244 add_action('latepoint_transaction_created', 'OsProcessJobsHelper::handle_transaction_created', 12);
245 add_action('latepoint_booking_created', 'OsProcessJobsHelper::handle_booking_created', 12);
246 add_action('latepoint_booking_updated', 'OsProcessJobsHelper::handle_booking_updated', 12, 2);
247 add_action('latepoint_order_created', 'OsProcessJobsHelper::handle_order_created', 12);
248 add_action('latepoint_order_updated', 'OsProcessJobsHelper::handle_order_updated', 12, 2);
249 add_action('latepoint_payment_request_created', 'OsProcessJobsHelper::handle_payment_request_created', 12);
250 }
251
252 public static function handle_customer_created(OsCustomerModel $customer){
253 $objects = [];
254 $objects[] = ['model' => 'customer', 'id' => $customer->id, 'model_ready' => $customer];
255 self::create_jobs_for_event('customer_created', $objects);
256 }
257
258 public static function handle_transaction_created(OsTransactionModel $transaction){
259 $objects = [];
260 $objects[] = ['model' => 'transaction', 'id' => $transaction->id, 'model_ready' => $transaction];
261 self::create_jobs_for_event('transaction_created', $objects);
262 }
263
264 public static function get_nice_job_status_name($status){
265 $names = [
266 LATEPOINT_JOB_STATUS_COMPLETED => __('Completed', 'latepoint'),
267 LATEPOINT_JOB_STATUS_SCHEDULED => __('Scheduled', 'latepoint'),
268 LATEPOINT_JOB_STATUS_CANCELLED => __('Cancelled', 'latepoint'),
269 LATEPOINT_JOB_STATUS_ERROR => __('Error', 'latepoint'),
270 ];
271 return $names[$status] ?? __('n/a', 'latepoint');
272 }
273
274 public static function handle_booking_created(OsBookingModel $booking){
275 $objects = [];
276 $objects[] = ['model' => 'booking', 'id' => $booking->id, 'model_ready' => $booking];
277 self::create_jobs_for_event('booking_created', $objects);
278 self::create_jobs_for_event('booking_start', $objects);
279 self::create_jobs_for_event('booking_end', $objects);
280 }
281
282
283 public static function handle_booking_updated(OsBookingModel $new_booking, OsBookingModel $old_booking){
284 // remove previously scheduled jobs for this booking because it's changed and might not need them anymore
285 // remove only those that are in "scheduled" status, those that were already sent or errored should stay
286 $jobs = new OsProcessJobModel();
287 $jobs->delete_where(['status' => LATEPOINT_JOB_STATUS_SCHEDULED, 'object_id' => $new_booking->id, 'object_model_type' => 'booking']);
288
289 $objects = [];
290 $objects[] = ['model' => 'booking', 'id' => $new_booking->id, 'model_ready' => $new_booking];
291 $objects[] = ['model' => 'old_booking', 'id' => $old_booking->id, 'model_ready' => $old_booking];
292 self::create_jobs_for_event('booking_updated', $objects);
293 // some changes might have triggered other webhooks (e.g. service changed, so now it could be required to be reminded of booking start/end)
294
295 self::create_jobs_for_event('booking_start', $objects);
296 self::create_jobs_for_event('booking_end', $objects);
297 }
298
299
300
301 public static function handle_order_created(OsOrderModel $order){
302 $objects = [];
303 $objects[] = ['model' => 'order', 'id' => $order->id, 'model_ready' => $order];
304 self::create_jobs_for_event('order_created', $objects);
305 }
306
307
308 public static function handle_payment_request_created(OsPaymentRequestModel $payment_request){
309 $objects = [];
310 $objects[] = ['model' => 'payment_request', 'id' => $payment_request->id, 'model_ready' => $payment_request];
311 self::create_jobs_for_event('payment_request_created', $objects);
312 }
313
314
315 public static function handle_order_updated(OsOrderModel $new_order, OsOrderModel $old_order){
316 // remove previously scheduled jobs for this order because it's changed and might not need them anymore
317 // remove only those that are in "scheduled" status, those that were already sent or errored should stay
318 $jobs = new OsProcessJobModel();
319 $jobs->delete_where(['status' => LATEPOINT_JOB_STATUS_SCHEDULED, 'object_id' => $new_order->id, 'object_model_type' => 'order']);
320
321 $objects = [];
322 $objects[] = ['model' => 'order', 'id' => $new_order->id, 'model_ready' => $new_order];
323 $objects[] = ['model' => 'old_order', 'id' => $old_order->id, 'model_ready' => $old_order];
324 self::create_jobs_for_event('order_updated', $objects);
325 }
326
327 /**
328 * @param OsProcessModel $process
329 * @param $opposite determines if apply time offset in opposite direction (to search events eligible for cutoff)
330 * @return string
331 */
332 public static function should_modify_event_time(OsProcessModel $process, $opposite = false): string{
333 if(empty($process->time_offset)) {
334 // no time offset
335 return '';
336 }else{
337 $time_offset_settings = $process->time_offset;
338 // offset, calculate how much to modify by
339 $sign = ($opposite) ? (($time_offset_settings['before_after'] == 'after') ? '-' : '+') : (($time_offset_settings['before_after'] == 'after') ? '+' : '-');
340 $modify_by = $sign.$time_offset_settings['value'].' '.$time_offset_settings['unit'];
341 return $modify_by;
342 }
343 }
344
345 }