get_default_integrations(); $saved = get_option( self::OPTION_KEY, [] ); foreach ( $defaults as $id => $default ) { $is_active = isset( $default['type'] ) && 'theme' === $default['type'] ? $this->is_themewinter_theme_active() : PluginManager::is_activated( $default['plugin_slug'] ); if ( $is_active ) { $existing = isset( $saved[ $id ] ) ? $saved[ $id ] : []; $existing['status'] = Integration_Status::CONNECTED; $saved[ $id ] = $existing; } } update_option( self::OPTION_KEY, $saved ); update_option( self::AUTOCONNECT_FLAG, true ); } /** * Get all Themewinter theme folder slugs. * * @return string[] */ private function get_themewinter_theme_slugs(): array { return [ 'exhibz', 'digiqole', 'instive', 'bizxpo', 'cafesio', 'gloreya', 'eventplace', 'autrics', 'deliciko', 'qoxag', ]; } /** * Check whether any Themewinter theme is currently active. * * @return bool */ private function is_themewinter_theme_active(): bool { $slugs = $this->get_themewinter_theme_slugs(); return in_array( get_stylesheet(), $slugs, true ) || in_array( get_template(), $slugs, true ); } /** * Default integration definitions * * @return array */ private function get_default_integrations() { return [ 'eventin' => [ 'id' => 'eventin', 'name' => 'Eventin', 'description' => __( 'AI-powered all-in-one event management solution for organizing events, selling tickets, managing schedules, and tracking attendees.', 'aisentic' ), 'category' => __( 'Event Management & Ticketing', 'aisentic' ), 'status' => 'not_connected', 'icon' => ' ', // Text domain used to detect an installed/active copy on this site. 'plugin_slug' => 'eventin', // wordpress.org repository slug used to build the download URL. Eventin // ships under the repo slug 'wp-event-solution' (its text domain is // 'eventin'), so the download slug and detection slug differ. 'download_slug' => 'wp-event-solution', ], 'wpcafe' => [ 'id' => 'wpcafe', 'name' => 'WP Cafe', 'description' => __( 'Complete restaurant management solution for food ordering, delivery, pickup, and table reservations for cafes and cloud kitchens.', 'aisentic' ), 'category' => __( 'Restaurant & Food Ordering', 'aisentic' ), 'status' => 'not_connected', 'icon' => ' ', 'plugin_slug' => 'wp-cafe', ], 'timetics' => [ 'id' => 'timetics', 'name' => 'Timetics', 'description' => __( 'AI-powered appointment booking and meeting scheduler with visual seat planning for businesses and professionals.', 'aisentic' ), 'category' => __( 'AI Appointment & Scheduling', 'aisentic' ), 'status' => 'not_connected', 'icon' => ' ', 'plugin_slug' => 'timetics', ], 'woocommerce' => [ 'id' => 'woocommerce', 'name' => 'WooCommerce', 'description' => __( 'AI sales associate for your store — semantic product search, tailored recommendations, and propose-and-confirm add-to-cart inside the chat widget.', 'aisentic' ), 'category' => __( 'eCommerce & Online Store', 'aisentic' ), 'status' => 'not_connected', 'icon' => ' ', 'plugin_slug' => 'woocommerce', ], 'booktics' => [ 'id' => 'booktics', 'name' => 'Booktics', 'description' => __( 'Comprehensive service booking and appointment calendar for salons, clinics, and service-based businesses.', 'aisentic' ), 'category' => __( 'Service Booking & Calendar', 'aisentic' ), 'status' => 'not_connected', 'icon' => ' ', 'plugin_slug' => 'booktics', ], 'pointics' => [ 'id' => 'pointics', 'name' => 'Pointics', 'description' => __( 'Loyalty and rewards program for tracking customer point balances, running earning rules, and redeeming points for discounts and perks.', 'aisentic' ), 'category' => __( 'Loyalty & Rewards', 'aisentic' ), 'status' => 'not_connected', 'icon' => ' ', 'plugin_slug' => 'pointics', ], 'themewinter-theme' => [ 'id' => 'themewinter-theme', 'name' => 'Themewinter Theme', 'description' => __( 'AI-powered intelligence tailored exclusively for Themewinter themes, with contextual help available directly inside the WordPress Theme Customizer.', 'aisentic' ), 'category' => __( 'Theme', 'aisentic' ), 'status' => 'not_connected', 'type' => 'theme', 'icon' => ' ', ], ]; } /** * Register routes * * @return void */ public function register_routes() { register_rest_route( $this->namespace, '/integrations', [ 'methods' => 'GET', 'callback' => [ $this, 'get_integrations' ], 'permission_callback' => [ $this, 'check_admin_permissions' ], ] ); register_rest_route( $this->namespace, '/integrations/(?P[a-zA-Z0-9_-]+)', [ 'methods' => 'PUT', 'callback' => [ $this, 'update_integration' ], 'permission_callback' => [ $this, 'check_admin_permissions' ], ] ); } /** * Check admin permissions * * @return bool|WP_Error */ public function check_admin_permissions() { if ( ! is_user_logged_in() || ! current_user_can( 'manage_options' ) ) { return new WP_Error( 'aisentic_forbidden', __( 'You do not have permission to manage integrations.', 'aisentic' ), [ 'status' => 403 ] ); } return true; } /** * Get all integrations * * Returns all defined integrations with their resolved status. * * @param WP_REST_Request $request Request object. * @return \WP_HTTP_Response */ public function get_integrations( WP_REST_Request $request ) { $integrations = $this->get_resolved_integrations(); return $this->response( [ 'integrations' => array_values( $integrations ) ], __( 'Integrations retrieved successfully', 'aisentic' ), 200 ); } /** * Update an integration * * Handles install, activate, connected and not_connected actions. * * @param WP_REST_Request $request Request object. * @return \WP_HTTP_Response */ public function update_integration( WP_REST_Request $request ) { $integration_id = sanitize_text_field( $request->get_param( 'id' ) ); $defaults = $this->get_default_integrations(); if ( ! isset( $defaults[ $integration_id ] ) ) { return $this->error( __( 'Integration not found.', 'aisentic' ), 404 ); } $integration = $defaults[ $integration_id ]; $is_theme = isset( $integration['type'] ) && 'theme' === $integration['type']; $plugin_slug = $is_theme ? null : $integration['plugin_slug']; $download_slug = $is_theme ? null : ( $integration['download_slug'] ?? $integration['plugin_slug'] ); $status = sanitize_text_field( $request->get_param( 'status' ) ); $valid_statuses = [ Integration_Status::ACTION_INSTALL, Integration_Status::ACTION_ACTIVATE, Integration_Status::CONNECTED, Integration_Status::NOT_CONNECTED, ]; if ( ! in_array( $status, $valid_statuses, true ) ) { return $this->error( __( 'Invalid status value.', 'aisentic' ), 400 ); } switch ( $status ) { case Integration_Status::ACTION_INSTALL: if ( $is_theme ) { return $this->error( __( 'Theme installation must be done from Appearance > Themes.', 'aisentic' ), 400 ); } // Skip the download when the plugin is already present. The download // slug can differ from the detection slug (Eventin ships as // 'wp-event-solution' but its text domain is 'eventin'), so // install_plugin()'s own "already installed" check keys off the wrong // name and would re-download every time — the upgrader then fails on // the existing folder and that WP_Error is silently truthy. if ( ! PluginManager::is_installed( $plugin_slug ) ) { // Download by the wordpress.org repository slug, then confirm // success by the real installed state (text domain). That check is // authoritative regardless of the upgrader's return value, which // can be a truthy WP_Error on filesystem/credential failures. PluginManager::install_plugin( $download_slug ); if ( ! PluginManager::is_installed( $plugin_slug ) ) { return $this->error( __( 'Failed to install plugin. Please try installing it manually from Plugins → Add New.', 'aisentic' ), 500 ); } } break; case Integration_Status::ACTION_ACTIVATE: if ( $is_theme ) { return $this->error( __( 'Theme activation must be done from Appearance > Themes.', 'aisentic' ), 400 ); } $result = PluginManager::activate_plugin( $plugin_slug ); if ( is_wp_error( $result ) ) { return $this->error( $result->get_error_message(), 500 ); } if ( ! $result ) { return $this->error( __( 'Failed to activate plugin.', 'aisentic' ), 500 ); } break; case Integration_Status::CONNECTED: case Integration_Status::NOT_CONNECTED: if ( ! $is_theme && ! PluginManager::is_activated( $plugin_slug ) ) { return $this->error( __( 'Plugin must be active to change connection status.', 'aisentic' ), 400 ); } $saved = get_option( self::OPTION_KEY, [] ); $existing = isset( $saved[ $integration_id ] ) ? $saved[ $integration_id ] : []; $existing['status'] = $status; $saved[ $integration_id ] = $existing; update_option( self::OPTION_KEY, $saved ); break; } $integrations = $this->get_resolved_integrations(); $updated = isset( $integrations[ $integration_id ] ) ? $integrations[ $integration_id ] : null; return $this->response( [ 'integration' => $updated ], __( 'Integration updated successfully', 'aisentic' ), 200 ); } /** * Build the resolved integrations list * * Merges defaults with saved settings and resolves each integration's * status from the real plugin state via PluginManager. * * @return array */ private function get_resolved_integrations() { $defaults = $this->get_default_integrations(); $saved = get_option( self::OPTION_KEY, [] ); $integrations = []; foreach ( $defaults as $id => $default ) { $is_theme = isset( $default['type'] ) && 'theme' === $default['type']; if ( $is_theme ) { // Theme integrations only ever have two user-facing states. $saved_status = isset( $saved[ $id ]['status'] ) ? $saved[ $id ]['status'] : Integration_Status::NOT_CONNECTED; $status = Integration_Status::CONNECTED === $saved_status ? Integration_Status::CONNECTED : Integration_Status::NOT_CONNECTED; } else { $plugin_slug = $default['plugin_slug']; $is_installed = PluginManager::is_installed( $plugin_slug ); $is_activated = PluginManager::is_activated( $plugin_slug ); if ( $is_activated ) { $saved_status = isset( $saved[ $id ]['status'] ) ? $saved[ $id ]['status'] : Integration_Status::NOT_CONNECTED; $status = in_array( $saved_status, [ Integration_Status::CONNECTED, Integration_Status::NOT_CONNECTED ], true ) ? $saved_status : Integration_Status::NOT_CONNECTED; } elseif ( $is_installed ) { $status = Integration_Status::DEACTIVE; } else { $status = Integration_Status::NOT_INSTALLED; } } // Merge any extra saved fields (e.g. icon overrides) then enforce resolved status. if ( isset( $saved[ $id ] ) ) { $default = array_merge( $default, $saved[ $id ] ); } $default['status'] = $status; // Strip internal-only field from API response. unset( $default['plugin_slug'] ); $integrations[ $id ] = $default; } return $integrations; } }