$root, 'host' => $host, 'breeze' => (defined('BREEZE_VERSION') || class_exists('Breeze_Admin', false)) ? '1' : '0', 'redis' => (defined('WP_REDIS_HOST') || (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && class_exists('Redis', false))) ? '1' : '0', 'varnish' => isset($_SERVER['HTTP_X_VARNISH']) ? '1' : '0', 'wc' => class_exists('WooCommerce') ? '1' : '0', ); if (defined('ROOSTER_ONE_PARTNER_CODE')) { $evidence['partner'] = (string) ROOSTER_ONE_PARTNER_CODE; } $evidence = apply_filters('aieo_rooster_install_source', $evidence); $clean = array(); foreach ((array) $evidence as $k => $v) { $k = strtolower(preg_replace('/[^a-z0-9_]/i', '', (string) $k)); $v = strtolower(preg_replace('/[^a-z0-9_.\-]/i', '', is_scalar($v) ? (string) $v : '')); if ('' !== $k && '' !== $v) { $clean[substr($k, 0, 20)] = substr($v, 0, 40); } } return $clean; } public static function source_string() { $pairs = array(); foreach (self::source() as $k => $v) { $pairs[] = $k . '=' . $v; } return implode(';', $pairs); } public static function declared_partner_code() { $ev = self::source(); return isset($ev['partner']) ? strtoupper($ev['partner']) : ''; } public static function runtime_flag() { return (class_exists('AIEO_Runtime_Presence') && AIEO_Runtime_Presence::active()) ? 1 : 0; } private static function portal_post($path, array $body) { return wp_remote_post(self::portal_url() . '/wp-json/om-portal/v1/' . ltrim($path, '/'), array( 'headers' => array('Content-Type' => 'application/json', 'Accept' => 'application/json'), 'body' => wp_json_encode($body), 'timeout' => 15, 'sslverify' => (bool) apply_filters('aieo_rooster_signup_sslverify', true), )); } public static function handle_signup() { if (!current_user_can(AIEO_CAPABILITY)) { wp_die(esc_html__('Unauthorized', 'ai-eshop-optimizer')); } check_admin_referer('aieo_rooster_signup_action', 'aieo_rooster_signup_nonce'); $email = sanitize_email(wp_unslash($_POST['aieo_signup_email'] ?? '')); if (!is_email($email)) { update_option('aieo_account_message', __('Please enter a valid email address to create your Rooster account.', 'ai-eshop-optimizer')); self::back(); } $first = trim(sanitize_text_field(wp_unslash($_POST['aieo_signup_first'] ?? ''))); $last = trim(sanitize_text_field(wp_unslash($_POST['aieo_signup_last'] ?? ''))); $company = trim(sanitize_text_field(wp_unslash($_POST['aieo_signup_company'] ?? ''))); if ('' === $first || '' === $last || '' === $company) { update_option('aieo_account_message', __('Please enter your first name, surname and company to create your Rooster account.', 'ai-eshop-optimizer')); self::back(); } $aieo_declared = self::declared_partner_code(); $aieo_code = '' !== $aieo_declared ? $aieo_declared : strtoupper(sanitize_text_field(wp_unslash($_POST['aieo_signup_code'] ?? ''))); $resp = self::portal_post('content-install/signup', array( 'email' => $email, 'first_name' => $first, 'surname' => $last, 'company' => $company, 'fp' => self::fp(), 'plugin_version' => defined('AIEO_VERSION') ? AIEO_VERSION : '', 'site_url' => home_url(), 'src' => self::source_string(), 'partner_code' => $aieo_code, )); if (is_wp_error($resp)) { update_option('aieo_account_message', sprintf( __('Could not reach rooster.systems: %s — please try again.', 'ai-eshop-optimizer'), $resp->get_error_message() )); self::back(); } $code = (int) wp_remote_retrieve_response_code($resp); $body = json_decode(wp_remote_retrieve_body($resp), true); if (429 === $code) { update_option('aieo_account_message', __('Too many signup attempts — please try again in an hour.', 'ai-eshop-optimizer')); self::back(); } if (400 === $code && is_array($body) && 'missing_fields' === (string) ($body['error_code'] ?? '')) { update_option('aieo_account_message', __('Please enter your first name, surname and company to create your Rooster account.', 'ai-eshop-optimizer')); self::back(); } if ($code < 200 || $code >= 300 || empty($body['claim'])) { update_option('aieo_account_message', __('Signup could not be started. Please try again, or paste an account key instead.', 'ai-eshop-optimizer')); self::back(); } update_option(self::OPT_CLAIM, array( 'claim' => (string) $body['claim'], 'email' => $email, 'expires_at' => (string) ($body['expires_at'] ?? ''), 'started_at' => time(), 'trial_days' => max(0, (int) ($body['trial_days'] ?? 0)), 'code_unknown' => isset($body['code_unknown']) ? strtoupper(sanitize_text_field((string) $body['code_unknown'])) : '', ), false); update_option('aieo_account_message', sprintf( __('Check your inbox — we sent a confirmation link to %s. Your account is created only after you confirm.', 'ai-eshop-optimizer'), $email )); self::back(); } public static function handle_cancel() { if (!current_user_can(AIEO_CAPABILITY)) { wp_die(esc_html__('Unauthorized', 'ai-eshop-optimizer')); } check_admin_referer('aieo_rooster_signup_action', 'aieo_rooster_signup_nonce'); delete_option(self::OPT_CLAIM); delete_transient(self::POLL_THROTTLE); update_option('aieo_account_message', __('Signup cancelled. You can start again with a different email any time.', 'ai-eshop-optimizer')); self::back(); } private static function back() { wp_safe_redirect(wp_get_referer() ?: admin_url('admin.php?page=ai-eshop-optimizer')); exit; } public static function handle_disconnect() { if (!current_user_can(AIEO_CAPABILITY)) { wp_die(esc_html__('Unauthorized', 'ai-eshop-optimizer')); } check_admin_referer('aieo_rooster_disconnect_action', 'aieo_rooster_disconnect_nonce'); self::clear_connection(); update_option('aieo_account_message', __('Disconnected. This site no longer holds a Rooster key — your account, sites and subscriptions at rooster.systems are untouched. You can reconnect with a key, or create a new account below.', 'ai-eshop-optimizer')); self::back(); } private static function clear_connection() { foreach (array( 'aieo_rooster_enabled', 'aieo_rooster_api_key', 'aieo_rooster_status', 'aieo_rooster_status_message', 'aieo_rooster_sites', 'aieo_rooster_client_id', 'aieo_rooster_client_label', 'aieo_rooster_client_email', self::OPT_ENTITLEMENT, self::OPT_CLAIM, ) as $opt) { delete_option($opt); } delete_transient(self::POLL_THROTTLE); if (class_exists('AIEO_Credit_Notifier') && defined('AIEO_Credit_Notifier::STATUS_TRANSIENT')) { delete_transient(AIEO_Credit_Notifier::STATUS_TRANSIENT); } } public static function handle_detach_copy() { if (!current_user_can(AIEO_CAPABILITY)) { wp_die(esc_html__('Unauthorized', 'ai-eshop-optimizer')); } check_admin_referer('aieo_rooster_detach_copy_action', 'aieo_rooster_detach_copy_nonce'); self::clear_connection(); delete_option(self::OPT_FP); delete_option(self::OPT_FP_HOME); update_option('aieo_account_message', __('Detached. This copy no longer holds a Rooster key or the licence identity it inherited, and it has its own identity from now on. Your live site, your account and your subscriptions at rooster.systems were not touched — nothing was ever reported for this copy. Connect a key here only if you want this site to hold a seat of its own.', 'ai-eshop-optimizer')); self::back(); } public static function ajax_poll() { if (!current_user_can(AIEO_CAPABILITY)) { wp_send_json(array('status' => 'forbidden'), 403); } check_ajax_referer('aieo_rooster_signup_poll'); wp_send_json(self::poll_once()); } public static function maybe_poll_on_load() { $claim = get_option(self::OPT_CLAIM); if (empty($claim['claim'])) { return null; } if (get_transient(self::POLL_THROTTLE)) { return array('status' => 'pending'); } set_transient(self::POLL_THROTTLE, 1, 15); return self::poll_once(); } public static function poll_once() { $claim = get_option(self::OPT_CLAIM); if (empty($claim['claim'])) { return array('status' => 'none'); } $resp = self::portal_post('content-install/signup/poll', array( 'claim' => (string) $claim['claim'], 'fp' => self::fp(), )); if (is_wp_error($resp)) { return array('status' => 'pending'); } $code = (int) wp_remote_retrieve_response_code($resp); $body = json_decode(wp_remote_retrieve_body($resp), true); $status = (string) ($body['status'] ?? ($body['error_code'] ?? '')); if (404 === $code || 'not_found' === $status || 'expired' === $status) { delete_option(self::OPT_CLAIM); return array('status' => 'expired'); } if (409 === $code || 'fingerprint_mismatch' === $status) { delete_option(self::OPT_CLAIM); return array( 'status' => 'error', 'message' => __('This signup was started from a different site. Please start again from here.', 'ai-eshop-optimizer'), ); } if ('pending_approval' === $status) { $partner = sanitize_text_field((string) ($body['partner_name'] ?? '')); if (empty($claim['approval']) || ($claim['approval']['partner'] ?? '') !== $partner) { $claim['approval'] = array('partner' => $partner, 'since' => time()); update_option(self::OPT_CLAIM, $claim, false); } return array('status' => 'pending_approval', 'partner' => $partner); } if ('rejected' === $status) { delete_option(self::OPT_CLAIM); delete_transient(self::POLL_THROTTLE); $reason = (string) ($body['reason'] ?? ''); update_option('aieo_account_message', 'domain_owned_elsewhere' === substr($reason, 0, 22) ? __('This shop already belongs to a Rooster Systems account, and your email already belongs to a different one. Ask the manager of the shop\'s account to add you, or start the trial from a shop of your own.', 'ai-eshop-optimizer') : __('The manager of the account this shop belongs to did not approve the request. You can ask them directly, or paste a key they give you below.', 'ai-eshop-optimizer')); return array('status' => 'rejected'); } if ('ready' === $status && !empty($body['api_key'])) { $api_key = sanitize_text_field((string) $body['api_key']); $hub_url = self::portal_url(); update_option('aieo_rooster_enabled', 1); update_option('aieo_rooster_api_key', $api_key); update_option('aieo_rooster_hub_url', $hub_url); $connected = self::authenticate($api_key, $hub_url); if ($connected) { delete_option(self::OPT_CLAIM); delete_transient(self::POLL_THROTTLE); if (!empty($claim['approval'])) { update_option('aieo_account_message', sprintf( __('Connected. The manager approved you: this site now belongs to the account %s and uses its plan.', 'ai-eshop-optimizer'), (string) ($claim['approval']['partner'] ?? '') )); return array('status' => 'connected'); } $days = (int) ($claim['trial_days'] ?? 0); update_option('aieo_account_message', sprintf( __('Connected. Your %s trial has started: the Pro features built into this plugin are unlocked, including AI chat and managed AI credits, and the Subscription page now offers the Rooster One Pro plugin and the front-end runtime for download.', 'ai-eshop-optimizer'), $days > 0 ? sprintf(__('%d-day', 'ai-eshop-optimizer'), $days) : __('Pro', 'ai-eshop-optimizer') )); return array('status' => 'connected'); } return array( 'status' => 'pair_failed', 'message' => (string) get_option('aieo_rooster_status_message', ''), ); } return array('status' => 'pending'); } public static function authenticate($api_key, $hub_url) { $hub_url = rtrim((string) $hub_url, '/'); $response = wp_remote_get($hub_url . '/wp-json/om-hub/v1/content-connector/sites', array( 'headers' => array( 'X-Content-Connector-Key' => $api_key, 'Accept' => 'application/json', ), 'timeout' => 15, 'sslverify' => (bool) apply_filters('aieo_portal_sslverify', true), )); if (is_wp_error($response)) { update_option('aieo_rooster_status', 'error'); update_option('aieo_rooster_status_message', $response->get_error_message()); update_option('aieo_rooster_sites', ''); return false; } $code = wp_remote_retrieve_response_code($response); $body = json_decode(wp_remote_retrieve_body($response), true); if ($code >= 200 && $code < 300 && !empty($body['sites'])) { $auto_linked = null; $site_domain = wp_parse_url(home_url(), PHP_URL_HOST); foreach ($body['sites'] as $site) { $s_domain = wp_parse_url($site['site_url'] ?? '', PHP_URL_HOST); if ($s_domain && $site_domain && stripos($s_domain, $site_domain) !== false && !empty($site['has_token'])) { $auto_linked = $site; break; } } if ($auto_linked) { update_option('aieo_rooster_status', 'connected'); update_option('aieo_rooster_client_id', $auto_linked['id']); update_option('aieo_rooster_client_label', $auto_linked['label'] ?? ''); update_option('aieo_rooster_status_message', ''); update_option('aieo_rooster_sites', ''); return true; } if (count($body['sites']) === 1) { $only = $body['sites'][0]; if (!self::may_register_site()) { update_option('aieo_rooster_status', 'connected'); update_option('aieo_rooster_client_id', (int) $only['id']); update_option('aieo_rooster_client_label', $only['label'] ?? ''); update_option('aieo_rooster_status_message', ''); update_option('aieo_rooster_sites', ''); return true; } $reg_response = wp_remote_post($hub_url . '/wp-json/om-hub/v1/content-connector/register-site', array( 'headers' => array( 'X-Content-Connector-Key' => $api_key, 'Content-Type' => 'application/json', 'Accept' => 'application/json', ), 'body' => wp_json_encode(array( 'client_id' => (int) $only['id'], 'site_url' => home_url(), 'fp' => self::fp(), 'label' => get_bloginfo('name'), 'plugin_version' => defined('AIEO_VERSION') ? AIEO_VERSION : '', 'src' => self::source_string(), 'runtime' => self::runtime_flag(), )), 'timeout' => 15, 'sslverify' => (bool) apply_filters('aieo_portal_sslverify', true), )); if (!is_wp_error($reg_response)) { $reg_code = wp_remote_retrieve_response_code($reg_response); $reg_body = json_decode(wp_remote_retrieve_body($reg_response), true); if ($reg_code >= 200 && $reg_code < 300 && !empty($reg_body['success'])) { update_option('aieo_rooster_status', 'connected'); update_option('aieo_rooster_client_id', (int) $only['id']); update_option('aieo_rooster_client_label', $reg_body['label'] ?? ($only['label'] ?? '')); $partner_email = ''; foreach (array('email', 'partner_email', 'client_email', 'contact_email') as $ek) { if (!empty($reg_body[$ek]) && is_email($reg_body[$ek])) { $partner_email = $reg_body[$ek]; break; } } if ($partner_email !== '') { update_option('aieo_rooster_client_email', $partner_email); } if (!empty($reg_body['site']) && is_array($reg_body['site'])) { $ent = get_option('aieo_rooster_entitlement'); $ent = is_array($ent) ? $ent : array(); $ent['site'] = array( 'status' => sanitize_key((string) ($reg_body['site']['status'] ?? '')), 'trial_expires_at' => sanitize_text_field((string) ($reg_body['site']['trial_expires_at'] ?? '')), 'seats_used' => (int) ($reg_body['site']['seats_used'] ?? 0), 'site_limit' => (int) ($reg_body['site']['site_limit'] ?? 0), ); if (class_exists('AIEO_Entitlement_Binding')) { $ent = AIEO_Entitlement_Binding::stamp($ent); } update_option('aieo_rooster_entitlement', $ent, false); } update_option('aieo_rooster_status_message', ''); update_option('aieo_rooster_sites', ''); if (class_exists('AIEO_Credit_Notifier') && method_exists('AIEO_Credit_Notifier', 'fetch_status')) { AIEO_Credit_Notifier::fetch_status(true); } return true; } update_option('aieo_rooster_status', 'error'); update_option('aieo_rooster_status_message', $reg_body['message'] ?? "Registration failed (HTTP {$reg_code})"); update_option('aieo_rooster_sites', ''); return false; } update_option('aieo_rooster_status', 'error'); update_option('aieo_rooster_status_message', $reg_response->get_error_message()); update_option('aieo_rooster_sites', ''); return false; } update_option('aieo_rooster_status', 'select_site'); update_option('aieo_rooster_status_message', __('API key valid. Select your site below:', 'ai-eshop-optimizer')); update_option('aieo_rooster_sites', wp_json_encode($body['sites'])); return false; } if ($code >= 200 && $code < 300) { update_option('aieo_rooster_status', 'error'); update_option('aieo_rooster_status_message', __('API key valid, but no client sites found for this partner.', 'ai-eshop-optimizer')); update_option('aieo_rooster_sites', ''); return false; } update_option('aieo_rooster_status', 'error'); update_option('aieo_rooster_status_message', $body['message'] ?? "Authentication failed (HTTP {$code})"); update_option('aieo_rooster_sites', ''); return false; } public static function entitlement() { $e = get_option(self::OPT_ENTITLEMENT); return (is_array($e) && isset($e['tier'])) ? $e : null; } public static function days_remaining() { $e = self::entitlement(); if (!$e || empty($e['expires_at'])) { return null; } $ts = strtotime($e['expires_at'] . ' UTC'); if (!$ts) { return null; } return max(0, (int) ceil(($ts - time()) / DAY_IN_SECONDS)); } public static function maybe_render_notice() { if (!current_user_can(AIEO_CAPABILITY)) { return; } $screen = function_exists('get_current_screen') ? get_current_screen() : null; if (!$screen || !in_array($screen->id, array('dashboard', 'plugins'), true)) { return; } if (get_user_meta(get_current_user_id(), self::META_NOTICE_DISMISSED, true)) { return; } if ('' !== (string) get_option('aieo_rooster_api_key', '') || '1' === (string) get_option('aieo_activate_account', '') || !empty(get_option(self::OPT_CLAIM))) { return; } $url = class_exists('AIEO_Install_Status') ? AIEO_Install_Status::panel_url('get-pro') : admin_url('admin.php?page=ai-eshop-optimizer'); $nonce = wp_create_nonce('aieo_rooster_notice_dismiss'); ?>