HEX
Server: LiteSpeed
System: Linux baran.dnsprotection.me 4.18.0-553.83.1.lve.el8.x86_64 #1 SMP Wed Nov 12 10:04:12 UTC 2025 x86_64
User: ddir12 (3618)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/ddir12/up/elementor-pro/modules/atomic-form/actions/action-runner.php
<?php
namespace ElementorPro\Modules\AtomicForm\Actions;

use ElementorPro\Modules\Forms\Submissions\Database\Query;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}

class Action_Runner {

	/**
	 * Registered actions.
	 *
	 * @var Action_Base[]
	 */
	private static $actions = [];

	/**
	 * Register an action.
	 *
	 * @param Action_Base $action Action instance to register.
	 * @return void
	 */
	public static function register_action( Action_Base $action ): void {
		self::$actions[ $action->get_type() ] = $action;
	}

	/**
	 * Create an action instance by type.
	 *
	 * @param string $type Action type.
	 * @return Action_Base|null Action instance or null if not found.
	 */
	public static function create_action( string $type ): ?Action_Base {
		if ( ! isset( self::$actions[ $type ] ) ) {
			return null;
		}

		return self::$actions[ $type ];
	}

	/**
	 * Get all registered actions.
	 *
	 * @return Action_Base[] Array of registered actions.
	 */
	public static function get_registered_actions(): array {
		return self::$actions;
	}

	/**
	 * Check if an action type is registered.
	 *
	 * @param string $type Action type.
	 * @return bool
	 */
	public static function has_action( string $type ): bool {
		return isset( self::$actions[ $type ] );
	}

	/**
	 * Execute multiple actions and gather results.
	 *
	 * @param string[] $actions Array of action type strings.
	 * @param array $form_data Sanitized form data.
	 * @param array $widget_settings Full widget settings for actions to extract what they need.
	 * @param array $context Form context (post_id, form_id, form_name).
	 * @return array Results containing actionResults, allActionsSucceeded, failedActions, and optional submissionId.
	 */
	public static function execute_actions( array $actions, array $form_data, array $widget_settings, array $context ): array {
		$actions = self::normalize_actions_order( $actions );

		$pending_actions_count = self::count_non_collect_actions( $actions );
		$context['pending_actions_count'] = $pending_actions_count;

		$action_results = [];
		$failed_actions = [];
		$actions_succeeded_count = 0;

		foreach ( $actions as $action_type ) {

			if ( ! self::has_action( $action_type ) ) {
				$error = sprintf(
					__( 'Invalid action type: %s', 'elementor-pro' ),
					$action_type
				);
				$action_results[] = [
					'type' => $action_type,
					'status' => 'failed',
					'error' => $error,
				];
				$failed_actions[] = $action_type;
				self::persist_action_log( $context, $action_type, false, $error, $actions_succeeded_count );
				continue;
			}

			try {
				$action = self::create_action( $action_type );

				if ( ! $action ) {
					throw new \Exception(
						sprintf(
							__( 'Could not create action: %s', 'elementor-pro' ),
							$action_type
						)
					);
				}

				$result = $action->execute( $form_data, $widget_settings, $context );

				$action_results[] = array_merge(
					[ 'type' => $action_type ],
					$result
				);

				if ( Action_Type::COLLECT_SUBMISSIONS === $action_type && 'success' === $result['status'] ) {
					$submission_id = $result['submissionId'] ?? null;
					if ( $submission_id ) {
						$context['submission_id'] = $submission_id;
					}
				}

				$success = 'success' === $result['status'];
				$error_message = $success ? null : $result['error'];
				self::persist_action_log( $context, $action_type, $success, $error_message, $actions_succeeded_count );
			} catch ( \Exception $e ) {
				$action_results[] = [
					'type' => $action_type,
					'status' => 'failed',
					'error' => $e->getMessage(),
				];
				$failed_actions[] = $action_type;
				self::persist_action_log( $context, $action_type, false, $e->getMessage(), $actions_succeeded_count );
			}
		}

		$all_actions_succeeded = empty( $failed_actions );

		return [
			'actionResults' => $action_results,
			'allActionsSucceeded' => $all_actions_succeeded,
			'failedActions' => $failed_actions,
		];
	}

	/**
	 * Run "Collect submissions" first when present so a submission ID exists before sibling actions (same idea as v3).
	 *
	 * @param string[] $actions
	 * @return string[]
	 */
	private static function normalize_actions_order( array $actions ): array {
		if ( ! in_array( Action_Type::COLLECT_SUBMISSIONS, $actions, true ) ) {
			return $actions;
		}

		$rest = array_values(
			array_filter(
				$actions,
				function ( $type ) {
					return Action_Type::COLLECT_SUBMISSIONS !== $type;
				}
			)
		);

		return array_merge( [ Action_Type::COLLECT_SUBMISSIONS ], $rest );
	}

	/**
	 * @param string[] $actions
	 */
	private static function count_non_collect_actions( array $actions ): int {
		$n = 0;

		foreach ( $actions as $type ) {
			if ( Action_Type::COLLECT_SUBMISSIONS !== $type ) {
				$n++;
			}
		}

		return $n;
	}

	/**
	 * Log outcomes for actions other than collect; updates `actions_succeeded_count` on success.
	 *
	 * @param array $context
	 * @param int   $actions_succeeded_count
	 */
	private static function persist_action_log( array $context, string $action_type, bool $success, ?string $error_message, int &$actions_succeeded_count ): void {
		if ( Action_Type::COLLECT_SUBMISSIONS === $action_type ) {
			return;
		}

		$submission_id = isset( $context['submission_id'] ) ? (int) $context['submission_id'] : 0;

		if ( $submission_id <= 0 ) {
			return;
		}

		$query = Query::get_instance();
		$presenter = new Action_Log_Presenter( $action_type, self::resolve_action_label( $action_type ) );

		if ( $success ) {
			$actions_succeeded_count++;
			$query->update_submission(
				$submission_id,
				[ 'actions_succeeded_count' => $actions_succeeded_count ]
			);
			$query->add_action_log( $submission_id, $presenter, Query::ACTIONS_LOG_STATUS_SUCCESS, null );
		} else {
			$query->add_action_log( $submission_id, $presenter, Query::ACTIONS_LOG_STATUS_FAILED, $error_message );
		}
	}

	private static function resolve_action_label( string $action_type ): string {
		$labels = [
			Action_Type::EMAIL => esc_html__( 'Email', 'elementor-pro' ),
			Action_Type::WEBHOOK => esc_html__( 'Webhook', 'elementor-pro' ),
		];

		if ( isset( $labels[ $action_type ] ) ) {
			return $labels[ $action_type ];
		}

		$default_label = ucwords( str_replace( '-', ' ', $action_type ) );

		/**
		 * Filters the admin label for an atomic form action in the submissions action log.
		 * First argument: default label string. Second: action type slug.
		 */
		return (string) apply_filters(
			'elementor_pro/atomic_forms/action_log_label',
			$default_label,
			$action_type
		);
	}

	/**
	 * Initialize default actions.
	 *
	 * @return void
	 */
	public static function init(): void {
		self::register_action( new Email_Action() );
		self::register_action( new Collect_Submissions_Action() );
		self::register_action( new Webhook_Action() );

		/**
		 * Allow registering custom actions.
		 *
		 * @param Action_Factory $factory The action factory instance.
		 */
		do_action( 'elementor_pro/atomic_forms/actions/register', __CLASS__ );
	}
}