steps[ $step['id'] ] = $step; } } /** * Set links in the traverser * * @param array $db_links * * @return void */ public function set_links( $db_links ) { if ( ! is_array( $db_links ) || empty( $db_links ) ) { return; } foreach ( $db_links as $link ) { if ( empty( $link ) || ! isset( $link['source'] ) || ! isset( $link['target'] ) ) { continue; } $this->links[ $link['source'] ] = $link['target']; } } /** * Get current step id * * @return false|mixed */ public function get_current_step_id() { if ( empty( $this->steps ) || empty( $this->current_node_id ) || ! isset( $this->steps[ $this->current_node_id ] ) || ! isset( $this->steps[ $this->current_node_id ]['stepId'] ) ) { return false; } return $this->steps[ $this->current_node_id ]['stepId']; } /** * Check if the current step is an end step * * @return bool */ public function is_end() { return $this->current_node_id === 'end'; } /** * @param array $step_ids Goal step IDs * @param $goal_ins BWFAN_Goal_Controller * @param array $automation_contact * * @return array|false */ public function try_traverse_to_goal( $step_ids, $goal_ins, $automation_contact ) { if ( empty( $this->current_node_id ) || empty( $step_ids ) ) { return false; } $goal_ins->goal_steps = $this->get_step_nodes( $step_ids ); $run = 0; $goal_found_nodes = []; while ( $run < count( $this->steps ) ) { $run ++; $current_step = $this->steps[ $this->current_node_id ]; $this->log( 'current step', 'goal-check' ); $this->log( [ 'id' => $current_step['id'], 'type' => $current_step['type'] ], 'goal-check' ); switch ( $current_step['type'] ) { case 'action': case 'wait': $this->traverse_to_next_step(); $this->log( 'traversed to next action/ wait step', 'goal-check' ); break; case 'conditional': $this->process_goal_conditional( $goal_ins, $automation_contact ); $this->traverse_to_next_step(); $this->log( 'traversed to next conditional step', 'goal-check' ); break; case 'jump': $this->process_jump( $goal_ins, $automation_contact ); /** No need to traverse to next step as traversing already from process_jump function */ $this->log( 'traversed to next jump step', 'goal-check' ); break; case 'end': $this->log( 'end step', 'goal-check' ); $run = count( $this->steps ); break; case 'benchmark': if ( in_array( $current_step['id'], $goal_ins->goal_steps ) ) { $goal_found_nodes[] = $current_step['id']; $this->log( 'goal step found during traverse. goal step id: ' . $current_step['id'], 'goal-check' ); } $this->traverse_to_next_step(); $this->log( 'traversed to next step from goal step', 'goal-check' ); break; case 'split': if ( in_array( $current_step['id'], $goal_ins->goal_steps ) ) { $goal_found_nodes[] = $current_step['id']; $this->log( 'split step found during traverse. split step id: ' . $current_step['id'], 'goal-check' ); } $this->process_split_path( $automation_contact ); $this->traverse_to_next_step(); /** No need to traverse to next step as traversing already from process_jump function */ $this->log( 'traversed to next split step', 'goal-check' ); break; default: break; } } $goal_found_nodes = array_unique( $goal_found_nodes ); sort( $goal_found_nodes ); return $goal_found_nodes; } /** * Return current step data by step id * * @param int $step_id * * @return false|mixed */ public function get_step_by_step_id( $step_id ) { if ( empty( $step_id ) ) { return false; } foreach ( $this->steps as $step ) { if ( isset( $step['stepId'] ) && ( absint( $step['stepId'] ) === absint( $step_id ) ) ) { return $step; } } return false; } /** * Get node ids from step ids * * @param $step_ids * * @return array|false */ public function get_step_nodes( $step_ids ) { if ( empty( $step_ids ) ) { return false; } $nodes = []; foreach ( $this->steps as $step ) { if ( 'benchmark' !== $step['type'] ) { continue; } if ( isset( $step['stepId'] ) && in_array( $step['stepId'], $step_ids ) ) { $nodes[ $step['stepId'] ] = $step['id']; } } return empty( $nodes ) ? false : $nodes; } /** * Traverse to next step node * * @return bool */ public function traverse_to_next_step() { $current_step = $this->get_current_step(); $this->log( $this->trail_id . ' - current step id: ' . $current_step['stepId'] . ' and node id: ' . $current_step['id'] . ' and type: ' . $current_step['type'] ); if ( empty( $current_step ) || empty( $this->links ) ) { return false; } if ( 'conditional' === $current_step['type'] ) { /** Traverse conditional step */ $next_step = $this->traverse_conditional_step(); if ( empty( $next_step ) ) { return false; } $this->current_node_id = $next_step; $this->log( $this->trail_id . ' - traverse to next step from conditional step. New node id: ' . $this->current_node_id ); return true; } /** If current step is split */ if ( 'split' === $current_step['type'] ) { /** Traverse split step */ $next_step = $this->traverse_split_step(); if ( empty( $next_step ) ) { return false; } $this->current_node_id = $next_step; $this->log( $this->trail_id . ' - traverse to next path from split step. New node id: ' . $this->current_node_id ); return true; } if ( ! isset( $this->links[ $this->current_node_id ] ) ) { return false; } $this->current_node_id = $this->links[ $this->current_node_id ]; $this->log( $this->trail_id . ' - traverse to next step. New node id: ' . $this->current_node_id ); return true; } /** * Get current step data * * @return false|mixed */ public function get_current_step() { return ! empty( $this->steps ) && ! empty( $this->current_node_id ) && isset( $this->steps[ $this->current_node_id ] ) ? $this->steps[ $this->current_node_id ] : false; } /** * Return step id after processing conditional step result * * @return false|mixed */ public function traverse_conditional_step() { $conditional_node_id = $this->conditional_result_node_id; if ( isset( $this->links[ $conditional_node_id ] ) ) { return $this->links[ $conditional_node_id ]; } /** Maybe check if set in the database */ if ( $this->automation_contact_ins instanceof BWFAN_Automation_Controller ) { $data = $this->automation_contact_ins->automation_contact_data; if ( isset( $data['node_id'] ) && isset( $this->links[ $data['node_id'] ] ) ) { /** Removing saved node id from the DB */ $this->automation_contact_ins->update_conditional_step_result(); return $this->links[ $data['node_id'] ]; } } /** If somehow conditional step is deleted from table but its step node id exists in links then return target step of NO node */ if ( isset( $this->links[ $this->links[ $this->current_node_id ] ] ) ) { return $this->links[ $this->links[ $this->current_node_id ] ]; } BWFAN_Common::log_test_data( 'not able to traverse to condition next step ' . $this->current_node_id, 'traverse_conditional_failed' ); BWFAN_Common::log_test_data( 'conditional_node_id: ' . $conditional_node_id, 'traverse_conditional_failed' ); BWFAN_Common::log_test_data( $this->links, 'traverse_conditional_failed' ); return false; } /** * Return step id after processing split next path result * * @return false|mixed */ public function traverse_split_step() { $path_node_id = $this->split_result_node_id; if ( isset( $this->links[ $path_node_id ] ) ) { return $this->links[ $path_node_id ]; } BWFAN_Common::log_test_data( 'not able to traverse to split next path ' . $this->current_node_id, 'traverse_split_failed' ); BWFAN_Common::log_test_data( 'split_path_node_id: ' . $path_node_id, 'traverse_split_failed' ); BWFAN_Common::log_test_data( $this->links, 'traverse_split_failed' ); return false; } /** * Process conditional step data for goal only * * @param $goal_ins BWFAN_Goal_Controller * @param array $automation_contact automation contact row * * @return void */ public function process_goal_conditional( $goal_ins, $automation_contact ) { $current_step = $this->get_current_step(); $ins = new BWFAN_Conditional_Controller(); $ins->contact_id = $goal_ins->contact_id; $ins->automation_id = $goal_ins->automation_id; $ins->step_id = absint( $current_step['stepId'] ); $ins->populate_step_data( $current_step ); $ins->populate_automation_contact_data( $automation_contact ); $result = $ins->is_match(); $this->conditional_result_node_id = $current_step['id'] . ( ( true === $result ) ? 'yes' : 'no' ); } /** * Process jump step * * @param $goal_ins * @param $automation_contact * * @return void */ public function process_jump( $goal_ins, $automation_contact ) { $current_step = $this->get_current_step(); $ins = new BWFAN_Jump_Controller(); $ins->step_id = absint( $current_step['stepId'] ); $ins->populate_step_data( $current_step ); $jump_step_id = $ins->get_jump_step_id(); if ( empty( $jump_step_id ) ) { return; } /** Set current node to jump step */ $this->set_node_id_from_step_id( $jump_step_id ); } /** * Maybe traverse back to valid step * * @return void */ public function maybe_traverse_back() { global $wpdb; /** @var BWFAN_Automation_Controller $ins */ $ins = $this->automation_contact_ins; $step_id = $ins->step_id; $query = $wpdb->prepare( "SELECT `ID` FROM `{$wpdb->prefix}bwfan_automation_contact_trail` WHERE `tid` = %s AND `sid` = %d LIMIT 0,1;", $ins->automation_contact['trail'], $step_id ); $result = $wpdb->get_var( $query ); //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $valid_step_id = 0; if ( ! empty( $result ) ) { $query = $wpdb->prepare( "SELECT t.`sid` FROM `{$wpdb->prefix}bwfan_automation_contact_trail` as t INNER JOIN `{$wpdb->prefix}bwfan_automation_step` as s ON s.`ID` = t.`sid` WHERE t.`tid` = %s AND t.`ID` < %d AND s.`status` IN (%d, %d) ORDER BY t.ID DESC LIMIT 0,1;", $ins->automation_contact['trail'], $result, 0, 1 ); $result = $wpdb->get_var( $query ); //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching if ( ! empty( $result ) ) { $valid_step_id = strval( $result ); } } if ( empty( $valid_step_id ) ) { /** Step not found, start from the first node */ $ins->step_id = $ins->automation['start']; $this->set_node_id_from_step_id( $ins->step_id ); $ins->automation_contact['last'] = 0; return; } $this->set_node_id_from_step_id( $valid_step_id ); } /** * Set step node ID from the step ID * * @param int $step_id * * @return bool */ public function set_node_id_from_step_id( $step_id ) { $step = $this->get_step_by_step_id( $step_id ); if ( empty( $step ) || ! isset( $step['id'] ) ) { return false; } $this->current_node_id = $step['id']; return true; } /** * Process split step for goal only * * @param $automation_contact * * @return void */ public function process_split_path( $automation_contact ) { $current_step = $this->get_current_step(); $ins = new BWFAN_Split_Test_Controller(); $ins->current_node_id = $this->current_node_id; $ins->automation_id = $this->automation_id; $ins->step_id = absint( $current_step['stepId'] ); $ins->populate_step_data( $current_step ); $ins->populate_automation_contact_data( $automation_contact ); $path = $ins->get_next_path(); $this->split_result_node_id = $current_step['id'] . '-path-' . $path; } /** * Special log function for step execution * * @param $log * @param $name * * @return void */ public function log( $log, $name = 'fka-automation-step-id' ) { if ( empty( $log ) ) { return; } if ( false === $this->step_log ) { return; } if ( is_null( $this->step_log ) && false === apply_filters( 'bwfan_allow_automation_step_logging', BWFAN_Common::is_log_enabled( 'bwfan_step_logging' ) ) ) { $this->step_log = false; return; } $this->step_log = true; BWFAN_Common::log_test_data( $log, $name, true ); } }