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;
}
}