database = $database ?: new AIGH_Database(); } /** * Execute a pipeline * * @param array $steps Array of AIGH_Pipeline_Step objects * @param AIGH_Pipeline_Context $context * @param int|null $pipeline_id Optional pipeline ID for logging * @return AIGH_Pipeline_Context|WP_Error Final context or error */ public function execute($steps, $context, $pipeline_id = null) { // Validate inputs if (empty($steps)) { return new WP_Error('empty_pipeline', 'Pipeline has no steps to execute'); } if (!($context instanceof AIGH_Pipeline_Context)) { return new WP_Error('invalid_context', 'Context must be an instance of AIGH_Pipeline_Context'); } // Create execution record $execution_id = $this->create_execution_record($pipeline_id, $context); $context->set_metadata('execution_id', $execution_id); $context->set_metadata('total_steps', count($steps)); $context->set_metadata('started_at', current_time('mysql')); $this->trigger_callback('execution_started', array( 'execution_id' => $execution_id, 'step_count' => count($steps), 'context' => $context )); // Execute each step in sequence foreach ($steps as $index => $step) { if (!($step instanceof AIGH_Pipeline_Step)) { $error = new WP_Error('invalid_step', 'All steps must be instances of AIGH_Pipeline_Step'); $this->log_execution_error($execution_id, $error); return $error; } $step_number = $index + 1; $this->trigger_callback('step_started', array( 'execution_id' => $execution_id, 'step_number' => $step_number, 'total_steps' => count($steps), 'step_name' => $step->get_name(), 'step_type' => $step->get_type() )); // Execute the step $result = $this->execute_step($step, $context, $execution_id, $step_number); if (is_wp_error($result)) { // Step execution failed $context->add_error($step->get_name(), $result->get_error_message(), array( 'step_number' => $step_number, 'step_type' => $step->get_type(), 'error_code' => $result->get_error_code() )); $this->trigger_callback('step_failed', array( 'execution_id' => $execution_id, 'step_number' => $step_number, 'step_name' => $step->get_name(), 'error' => $result )); if ($this->stop_on_error) { $this->complete_execution_record($execution_id, $context, 'failed'); $this->trigger_callback('execution_failed', array( 'execution_id' => $execution_id, 'failed_at_step' => $step_number, 'error' => $result )); return $result; } // Continue to next step even with error continue; } // Step succeeded - update context $context = $result; $this->trigger_callback('step_completed', array( 'execution_id' => $execution_id, 'step_number' => $step_number, 'step_name' => $step->get_name(), 'duration' => $context->get_metadata('last_step_duration', 0) )); } // All steps completed $context->set_metadata('completed_at', current_time('mysql')); $this->complete_execution_record($execution_id, $context, 'completed'); $this->trigger_callback('execution_completed', array( 'execution_id' => $execution_id, 'step_count' => count($steps), 'has_errors' => $context->has_errors(), 'context' => $context )); return $context; } /** * Execute a single step * * @param AIGH_Pipeline_Step $step * @param AIGH_Pipeline_Context $context * @param int $execution_id * @param int $step_number * @return AIGH_Pipeline_Context|WP_Error */ private function execute_step($step, $context, $execution_id, $step_number) { $start_time = microtime(true); // Create step execution record $step_execution_id = $this->create_step_execution_record( $execution_id, $step_number, $step ); // Validate step can execute $validation = $step->can_execute($context); if (is_wp_error($validation)) { $this->complete_step_execution_record( $step_execution_id, null, 'failed', $validation->get_error_message() ); return $validation; } // Execute the step try { $result = $step->execute($context); } catch (Exception $e) { $error = new WP_Error( 'step_exception', sprintf('Step "%s" threw exception: %s', $step->get_name(), $e->getMessage()) ); $this->complete_step_execution_record( $step_execution_id, null, 'failed', $e->getMessage() ); return $error; } $duration = microtime(true) - $start_time; // Check result if (is_wp_error($result)) { $this->complete_step_execution_record( $step_execution_id, null, 'failed', $result->get_error_message() ); return $result; } if (!($result instanceof AIGH_Pipeline_Context)) { $error = new WP_Error( 'invalid_result', sprintf('Step "%s" must return AIGH_Pipeline_Context or WP_Error', $step->get_name()) ); $this->complete_step_execution_record( $step_execution_id, null, 'failed', $error->get_error_message() ); return $error; } // Step succeeded $this->complete_step_execution_record( $step_execution_id, $result, 'completed', null, $duration ); return $result; } /** * Create execution record in database * * @param int|null $pipeline_id * @param AIGH_Pipeline_Context $context * @return int Execution ID */ private function create_execution_record($pipeline_id, $context) { global $wpdb; $table = $wpdb->prefix . 'aigh_executions'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom plugin table $wpdb->insert( $table, array( 'pipeline_id' => $pipeline_id, 'status' => 'running', 'context_data' => $context->to_json(), 'started_at' => current_time('mysql') ), array('%d', '%s', '%s', '%s') ); return $wpdb->insert_id; } /** * Complete execution record * * @param int $execution_id * @param AIGH_Pipeline_Context $context * @param string $status */ private function complete_execution_record($execution_id, $context, $status) { global $wpdb; $table = $wpdb->prefix . 'aigh_executions'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table $wpdb->update( $table, array( 'status' => $status, 'context_data' => $context->to_json(), 'completed_at' => current_time('mysql'), 'error_count' => count($context->get_errors()) ), array('id' => $execution_id), array('%s', '%s', '%s', '%d'), array('%d') ); } /** * Log execution error * * @param int $execution_id * @param WP_Error $error */ private function log_execution_error($execution_id, $error) { global $wpdb; $table = $wpdb->prefix . 'aigh_executions'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table $wpdb->update( $table, array( 'status' => 'failed', 'error_message' => $error->get_error_message(), 'completed_at' => current_time('mysql') ), array('id' => $execution_id), array('%s', '%s', '%s'), array('%d') ); } /** * Create step execution record * * @param int $execution_id * @param int $step_number * @param AIGH_Pipeline_Step $step * @return int Step execution ID */ private function create_step_execution_record($execution_id, $step_number, $step) { global $wpdb; $table = $wpdb->prefix . 'aigh_execution_steps'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom plugin table $wpdb->insert( $table, array( 'execution_id' => $execution_id, 'step_number' => $step_number, 'step_type' => $step->get_type(), 'step_name' => $step->get_name(), 'step_config' => json_encode($step->get_config()), 'status' => 'running', 'started_at' => current_time('mysql') ), array('%d', '%d', '%s', '%s', '%s', '%s', '%s') ); return $wpdb->insert_id; } /** * Complete step execution record * * @param int $step_execution_id * @param AIGH_Pipeline_Context|null $context * @param string $status * @param string|null $error_message * @param float|null $duration */ private function complete_step_execution_record($step_execution_id, $context, $status, $error_message = null, $duration = null) { global $wpdb; $table = $wpdb->prefix . 'aigh_execution_steps'; $data = array( 'status' => $status, 'completed_at' => current_time('mysql') ); $formats = array('%s', '%s'); if ($error_message) { $data['error_message'] = $error_message; $formats[] = '%s'; } if ($duration !== null) { $data['duration'] = $duration; $formats[] = '%f'; } if ($context) { $data['output_data'] = $context->to_json(); $formats[] = '%s'; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table $wpdb->update( $table, $data, array('id' => $step_execution_id), $formats, array('%d') ); } /** * Register a callback for pipeline events * * @param string $event Event name (execution_started, step_started, step_completed, etc.) * @param callable $callback */ public function on($event, $callback) { if (!isset($this->callbacks[$event])) { $this->callbacks[$event] = array(); } $this->callbacks[$event][] = $callback; } /** * Trigger a callback * * @param string $event * @param array $data */ private function trigger_callback($event, $data = array()) { if (isset($this->callbacks[$event])) { foreach ($this->callbacks[$event] as $callback) { call_user_func($callback, $data); } } } /** * Set whether to stop on first error * * @param bool $stop */ public function set_stop_on_error($stop) { $this->stop_on_error = (bool) $stop; } /** * Validate a pipeline before execution * * @param array $steps * @return bool|WP_Error True if valid, WP_Error if invalid */ public function validate_pipeline($steps) { if (empty($steps)) { return new WP_Error('empty_pipeline', 'Pipeline has no steps'); } foreach ($steps as $index => $step) { if (!($step instanceof AIGH_Pipeline_Step)) { return new WP_Error( 'invalid_step', sprintf('Step at index %d is not a valid Pipeline Step', $index) ); } } // Could add more validation here: // - Check for circular dependencies // - Validate input/output connections // - Check for required steps return true; } /** * Get estimated duration for a pipeline * * @param array $steps * @return int Estimated seconds */ public function get_estimated_duration($steps) { $total = 0; foreach ($steps as $step) { if ($step instanceof AIGH_Pipeline_Step) { $total += $step->get_estimated_duration(); } } return $total; } }