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
5 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
7 months ago
router_helper.php
1 year ago
service_helper.php
1 year ago
sessions_helper.php
1 year ago
settings_helper.php
5 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
5 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
7 months ago
version_specific_updates_helper.php
9 months ago
whatsapp_helper.php
1 year ago
work_periods_helper.php
5 months 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 | } |