File "api.php"

Full Path: /home/fresvfqn/waterdamagerestorationandrepairsmithtown.com/wp-content/plugins/astra-sites/inc/lib/one-onboarding/includes/api/api.php
File size: 16.16 KB
MIME-type: text/x-php
Charset: utf-8

<?php
/**
 * API Class.
 *
 * @package One_Onboarding
 * @since 1.0.0
 */

namespace One_Onboarding\Api;

use One_Onboarding\Core;

if ( ! class_exists( 'Api' ) ) {

	/**
	 * API Class
	 *
	 * @since 1.0.0
	 */
	class Api {
		/**
		 * Instance
		 *
		 * @access private
		 * @var self Class Instance.
		 * @since 1.0.0
		 */
		private static $instance;

		/**
		 * Constructor
		 *
		 * @since 1.0.0
		 */
		public function __construct() {
			$this->init_hooks();
		}

		/**
		 * Initiator
		 *
		 * @since 1.0.0
		 * @return self initialized object of class.
		 */
		public static function get_instance() {
			if ( null === self::$instance ) {
				self::$instance = new self();
			}
			return self::$instance;
		}

		/**
		 * Initialize hooks
		 *
		 * @since 1.0.0
		 *
		 * @return void
		 */
		private function init_hooks(): void {
			add_action( 'rest_api_init', array( $this, 'register_rest_routes' ) );
		}

		/**
		 * Register REST API routes
		 *
		 * @since 1.0.0
		 * @return void
		 */
		public function register_rest_routes(): void {
			// Plugin status endpoint.
			register_rest_route(
				'one-onboarding/v1',
				'/plugin-status/(?P<plugin>[a-zA-Z0-9_-]+)',
				array(
					'methods'             => 'GET',
					'callback'            => array( $this, 'get_plugin_status' ),
					'permission_callback' => array( $this, 'can_manage_options' ),
				)
			);

			// Install plugin endpoint.
			register_rest_route(
				'one-onboarding/v1',
				'/install-plugin',
				array(
					'methods'             => 'POST',
					'callback'            => array( $this, 'install_plugin' ),
					'permission_callback' => array( $this, 'can_install_plugins' ),
				)
			);

			// Activate plugin endpoint.
			register_rest_route(
				'one-onboarding/v1',
				'/activate-plugin',
				array(
					'methods'             => 'POST',
					'callback'            => array( $this, 'activate_plugin' ),
					'permission_callback' => array( $this, 'can_activate_plugins' ),
				)
			);

			// Save onboarding state endpoint.
			register_rest_route(
				'one-onboarding/v1',
				'/save-onboarding-state',
				array(
					'methods'             => 'POST',
					'callback'            => array( $this, 'save_onboarding_state' ),
					'permission_callback' => array( $this, 'can_manage_options' ),
				)
			);

			// Onboarding completion endpoint.
			register_rest_route(
				'one-onboarding/v1',
				'/onboarding-completion',
				array(
					'methods'             => 'POST',
					'callback'            => array( $this, 'handle_onboarding_completion' ),
					'permission_callback' => array( $this, 'can_manage_options' ),
				)
			);
		}

		/**
		 * Check if user can manage options
		 *
		 * @since 1.0.0
		 * @return bool
		 */
		public function can_manage_options(): bool {
			return current_user_can( 'manage_options' );
		}

		/**
		 * Check if user can install plugins
		 *
		 * @since 1.0.0
		 * @return bool
		 */
		public function can_install_plugins(): bool {
			return current_user_can( 'install_plugins' );
		}

		/**
		 * Check if user can activate plugins
		 *
		 * @since 1.0.0
		 * @return bool
		 */
		public function can_activate_plugins(): bool {
			return current_user_can( 'activate_plugins' );
		}

		/**
		 * Get plugin status
		 *
		 * @since 1.0.0
		 * @param \WP_REST_Request $request The REST request object.
		 * @return \WP_REST_Response|\WP_Error
		 * @phpstan-ignore-next-line
		 */
		public function get_plugin_status( \WP_REST_Request $request ) {
			$plugin_slug = is_string( $request->get_param( 'plugin' ) ) ? $request->get_param( 'plugin' ) : '';

			if ( empty( $plugin_slug ) ) {
				return new \WP_Error(
					'missing_plugin',
					__( 'Plugin slug is required', 'astra-sites' ),
					array( 'status' => 400 )
				);
			}

			$plugin_file = $this->get_plugin_file( $plugin_slug );
			$installed   = ! is_null( $plugin_file );
			$active      = $installed && is_plugin_active( $plugin_file );

			return rest_ensure_response(
				array(
					'installed'   => $installed,
					'active'      => $active,
					'plugin_file' => $plugin_file,
				)
			);
		}

		/**
		 * Install plugin
		 *
		 * @since 1.0.0
		 * @param \WP_REST_Request $request The REST request object.
		 * @return \WP_REST_Response|\WP_Error
		 * @phpstan-ignore-next-line
		 */
		public function install_plugin( \WP_REST_Request $request ) {
			$plugin_slug = sanitize_text_field( is_string( $request->get_param( 'plugin' ) ) ? $request->get_param( 'plugin' ) : '' );

			if ( empty( $plugin_slug ) ) {
				return new \WP_Error(
					'missing_plugin',
					__( 'Plugin slug is required', 'astra-sites' ),
					array( 'status' => 400 )
				);
			}

			// Check if plugin is already installed.
			$plugin_file = $this->get_plugin_file( $plugin_slug );
			if ( $plugin_file ) {
				return rest_ensure_response(
					array(
						'success'     => true,
						'message'     => __( 'Plugin is already installed', 'astra-sites' ),
						'plugin_file' => $plugin_file,
					)
				);
			}

			// Include necessary WordPress files.
			if ( ! function_exists( 'plugins_api' ) ) {
				require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
			}
			if ( ! class_exists( 'WP_Upgrader' ) ) {
				require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
			}
			if ( ! function_exists( 'request_filesystem_credentials' ) ) {
				require_once ABSPATH . 'wp-admin/includes/file.php';
			}

			// Get plugin information.
			$api = plugins_api(
				'plugin_information',
				array(
					'slug'   => $plugin_slug,
					'fields' => array( 'sections' => false ),
				)
			);

			if ( is_wp_error( $api ) ) {
				return new \WP_Error(
					'plugin_not_found',
					// translators: %s: plugin slug.
					sprintf( __( 'Plugin %s not found in repository', 'astra-sites' ), $plugin_slug ),
					array( 'status' => 404 )
				);
			}

			// Install the plugin.
			$upgrader = new \Plugin_Upgrader( new \Automatic_Upgrader_Skin() );
			$result   = $upgrader->install( $api->download_link ?? '' );

			if ( is_wp_error( $result ) ) {
				return new \WP_Error(
					'installation_failed',
					$result->get_error_message(),
					array( 'status' => 500 )
				);
			}

			// Get the newly installed plugin file.
			$plugin_file = $this->get_plugin_file( $plugin_slug );

			return rest_ensure_response(
				array(
					'success'     => true,
					'message'     => __( 'Plugin installed successfully', 'astra-sites' ),
					'plugin_file' => $plugin_file,
				)
			);
		}

		/**
		 * Activate plugin
		 *
		 * @since 1.0.0
		 * @param \WP_REST_Request $request The REST request object.
		 * @return \WP_REST_Response|\WP_Error
		 * @phpstan-ignore-next-line
		 */
		public function activate_plugin( \WP_REST_Request $request ) {
			$plugin_slug = sanitize_text_field( is_string( $request->get_param( 'plugin' ) ) ? $request->get_param( 'plugin' ) : '' );

			if ( empty( $plugin_slug ) ) {
				return new \WP_Error(
					'missing_plugin',
					__( 'Plugin slug is required', 'astra-sites' ),
					array( 'status' => 400 )
				);
			}

			$plugin_file = $this->get_plugin_file( $plugin_slug );

			if ( ! $plugin_file ) {
				return new \WP_Error(
					'plugin_not_installed',
					__( 'Plugin is not installed', 'astra-sites' ),
					array( 'status' => 404 )
				);
			}

			if ( is_plugin_active( $plugin_file ) ) {
				return rest_ensure_response(
					array(
						'success' => true,
						'message' => __( 'Plugin is already active', 'astra-sites' ),
					)
				);
			}

			$result = activate_plugin( $plugin_file );

			if ( is_wp_error( $result ) ) {
				return new \WP_Error(
					'activation_failed',
					$result->get_error_message(),
					array( 'status' => 500 )
				);
			}

			/**
			 * Plugin activated action
			 *
			 * @param string $plugin_slug Plugin slug.
			 * @param string $plugin_file Plugin file.
			 * @param \WP_REST_Request $request The REST request object.
			 * @since 1.0.0
			 */
			do_action( 'one_onboarding_plugin_activated', $plugin_slug, $plugin_file, $request );

			return rest_ensure_response(
				array(
					'success' => true,
					'message' => __( 'Plugin activated successfully', 'astra-sites' ),
				)
			);
		}

		/**
		 * Save onboarding state
		 *
		 * @since 1.0.0
		 * @param \WP_REST_Request $request The REST request object.
		 * @return \WP_REST_Response|\WP_Error
		 * @phpstan-ignore-next-line
		 */
		public function save_onboarding_state( \WP_REST_Request $request ) {
			$onboarding_state = $request->get_param( 'onboardingState' );

			if ( empty( $onboarding_state ) ) {
				return new \WP_Error(
					'missing_onboarding_state',
					__( 'Onboarding state is required', 'astra-sites' ),
					array( 'status' => 400 )
				);
			}

			// Process onboarding state with save-specific data.
			$state_data = $this->process_onboarding_state(
				$onboarding_state,
				array(
					'exited_early' => $onboarding_state['exitedEarly'] ?? false,
					'exit_screen'  => $onboarding_state['currentScreen'] ?? '',
				)
			);

			// Extract product information from onboarding state.
			$product_id = $onboarding_state['productId'] ?? '';

			/**
			 * Onboarding state saved action
			 *
			 * Allows other plugins to extend the onboarding state saving process.
			 *
			 * @param array $state_data Complete state data including onboarding state, user info, and product details.
			 * @param \WP_REST_Request $request The REST request object.
			 * @since 1.0.0
			 */
			do_action( 'one_onboarding_state_saved', $state_data, $request );

			// Fire product-specific state saved action.
			if ( ! empty( $product_id ) ) {
				/**
				 * Product-specific onboarding state saved action
				 *
				 * Allows product-specific handling of onboarding state saving.
				 *
				 * @param array $state_data Complete state data including onboarding state, user info, and product details.
				 * @param \WP_REST_Request $request The REST request object.
				 * @since 1.0.0
				 */
				do_action( "one_onboarding_state_saved_{$product_id}", $state_data, $request );
			}

			return rest_ensure_response(
				array(
					'success' => true,
					'message' => 'Onboarding state saved successfully',
					'data'    => $state_data,
				)
			);
		}

		/**
		 * Handle onboarding completion
		 *
		 * @since 1.0.0
		 * @param \WP_REST_Request $request The REST request object.
		 * @return \WP_REST_Response|\WP_Error
		 * @phpstan-ignore-next-line
		 */
		public function handle_onboarding_completion( \WP_REST_Request $request ) {
			$onboarding_state = $request->get_param( 'onboardingState' );

			if ( empty( $onboarding_state ) ) {
				return new \WP_Error(
					'missing_onboarding_state',
					__( 'Onboarding state is required', 'astra-sites' ),
					array( 'status' => 400 )
				);
			}

			// Process onboarding state with completion-specific data.
			$completion_data = $this->process_onboarding_state(
				$onboarding_state,
				array(
					'completion_screen'         => $onboarding_state['currentScreen'] ?? '', // Screen where onboarding was completed.
					'exited_early'              => false,
					'exit_screen'               => '',
					'starter_templates_builder' => $onboarding_state['starterTemplatesBuilder'] ?? '',
				)
			);

			// Extract product information from onboarding state.
			$product_id = $onboarding_state['productId'] ?? '';

			// Parse user info -- Source, Benefit and New User.
			$user_info = is_array( $completion_data['user_info'] ) ? $completion_data['user_info'] : array();

			$source = $user_info['source'] ?? array();
			$source = is_array( $source ) && isset( $source['id'] ) ? sanitize_text_field( $source['id'] ) : '';

			$benefit      = $user_info['benefit'] ?? array();
			$benefit_id   = is_array( $benefit ) && isset( $benefit['id'] ) ? sanitize_text_field( $benefit['id'] ) : '';
			$benefit_text = '';
			if ( is_array( $benefit ) ) {
				if ( isset( $benefit['id'] ) && 'other' === $benefit['id'] && isset( $benefit['other'] ) ) {
					$benefit_text = sanitize_text_field( $benefit['other'] );
				} elseif ( isset( $benefit['name'] ) ) {
					$benefit_text = sanitize_text_field( $benefit['name'] );
				}
			}

			$new_user = $user_info['newUser'] ?? array();
			$new_user = is_array( $new_user ) && isset( $new_user['id'] ) ? sanitize_text_field( $new_user['id'] ) : '';

			if ( ! is_array( $completion_data['user_info'] ) ) {
				$completion_data['user_info'] = array();
			}

			$completion_data['user_info']['source']      = $source;
			$completion_data['user_info']['benefitId']   = $benefit_id;
			$completion_data['user_info']['benefitText'] = $benefit_text;
			$completion_data['user_info']['newUser']     = $new_user;

			/**
			 * Onboarding completion action
			 *
			 * Allows other plugins to extend the onboarding completion process.
			 *
			 * @param array $completion_data Complete data including onboarding state, user info, and product details.
			 * @param \WP_REST_Request $request The REST request object.
			 * @since 1.0.0
			 */
			do_action( 'one_onboarding_completion', $completion_data, $request );

			// Fire product-specific completion action.
			if ( ! empty( $product_id ) ) {
				/**
				 * Product-specific onboarding completion action
				 *
				 * Allows product-specific handling of onboarding completion.
				 *
				 * @param array $completion_data Complete data including onboarding state, user info, and product details.
				 * @param \WP_REST_Request $request The REST request object.
				 * @since 1.0.0
				 */
				do_action( "one_onboarding_completion_{$product_id}", $completion_data, $request );
			}

			return rest_ensure_response(
				array(
					'success' => true,
					'message' => 'Onboarding completion processed successfully',
					'data'    => $completion_data,
				)
			);
		}

		/**
		 * Process and prepare onboarding state data
		 *
		 * @since 1.0.0
		 * @param array<string, mixed> $onboarding_state The onboarding state data.
		 * @param array<string, mixed> $additional_data  Additional data to merge with the processed state.
		 * @return array<string, mixed> Processed onboarding data.
		 */
		private function process_onboarding_state( array $onboarding_state, array $additional_data = array() ): array {
			// Extract product information from onboarding state.
			$product_id = $onboarding_state['productId'] ?? '';

			// Prepare base data structure.
			$processed_data = array(
				'product_id'       => $product_id, // Unique identifier for the product.
				'screens'          => $onboarding_state['screens'] ?? array(), // Screens shown during onboarding, contains skipped attr.
				'user_info'        => $onboarding_state['userInfo'] ?? array(), // User's personal information collected during onboarding.
				'selected_addons'  => $onboarding_state['selectedAddons'] ?? array(), // Add-ons selected by the user.
				'activated_addons' => $onboarding_state['activatedAddons'] ?? array(), // Add-ons that were successfully activated.
				'current_screen'   => $onboarding_state['currentScreen'] ?? '', // Current screen.
				'pro_features'     => $onboarding_state['proFeatures'] ?? array(), // Premium features selected by the user.
			);

			// Append starter templates builder data if exists.
			if ( isset( $onboarding_state['starterTemplatesBuilder'] ) ) {
				$processed_data['starter_templates_builder'] = $onboarding_state['starterTemplatesBuilder'];
			}

			// Merge additional data.
			$processed_data = array_merge( $processed_data, $additional_data );

			// Store onboarding data to option using product-specific option name.
			if ( ! empty( $product_id ) && is_string( $product_id ) ) {
				$option_name = Core\Register::get_option_name( $product_id );

				// Get existing onboarding data from option.
				$onboarding_data = get_option( $option_name );
				if ( ! empty( $onboarding_data ) && is_array( $onboarding_data ) ) {
					$processed_data = array_merge( $onboarding_data, $processed_data );
				}

				update_option( $option_name, $processed_data );
			}

			return $processed_data;
		}

		/**
		 * Get plugin file path from slug
		 *
		 * @since 1.0.0
		 * @param string $plugin_slug Plugin slug.
		 * @return string|null Plugin file path or null if not found.
		 */
		private function get_plugin_file( string $plugin_slug ): ?string {
			if ( ! function_exists( 'get_plugins' ) ) {
				require_once ABSPATH . 'wp-admin/includes/plugin.php';
			}

			$plugins = get_plugins();

			foreach ( $plugins as $plugin_file => $plugin_data ) {
				$current_slug = dirname( $plugin_file );
				if ( $current_slug === $plugin_slug ) {
					return $plugin_file;
				}
			}

			return null;
		}
	}
}