'GET', 'permission_callback' => '__return_true', 'callback' => function () { return [ 'ok' => true, 'plugin' => 'agentinstore', 'version' => AGENTI_PLUGIN_VERSION, 'connected' => (int) get_option('agenti_store_connected', 0) === 1, 'woocommerce' => class_exists('WooCommerce'), ]; }, ]); // Remote disconnect from AgentInStore dashboard (auth via shared webhook secret). register_rest_route('agentinstore/v1', '/disconnect', [ 'methods' => 'POST', 'permission_callback' => [__CLASS__, 'rest_verify_webhook_secret'], 'callback' => function () { AGENTI_API::disconnect(); return ['ok' => true, 'connected' => false]; }, ]); } /** * REST permission: require matching webhook secret (not open to the world). * * @param WP_REST_Request $request Request. * @return bool|WP_Error */ public static function rest_verify_webhook_secret($request) { $secret = ''; if (is_object($request) && method_exists($request, 'get_param')) { $secret = (string) $request->get_param('webhook_secret'); if ($secret === '' && method_exists($request, 'get_header')) { $secret = (string) $request->get_header('x-ca-webhook-secret'); } } $local = (string) get_option('agenti_webhook_secret', ''); if (!$local || !$secret || !hash_equals($local, $secret)) { return new WP_Error('agenti_forbidden', __('Invalid webhook secret', 'agentinstore'), ['status' => 403]); } return true; } public static function enqueue_assets($hook) { // Always constrain the admin-menu icon (large PNGs otherwise spill across the sidebar). wp_register_style('agenti-admin-menu', false, [], AGENTI_PLUGIN_VERSION); wp_enqueue_style('agenti-admin-menu'); wp_add_inline_style( 'agenti-admin-menu', '#adminmenu #toplevel_page_agentinstore .wp-menu-image img{' . 'width:20px!important;height:20px!important;max-width:20px!important;max-height:20px!important;' . 'padding:7px 0 0!important;object-fit:contain;opacity:.85}' . '#adminmenu #toplevel_page_agentinstore:hover .wp-menu-image img,' . '#adminmenu #toplevel_page_agentinstore.wp-has-current-submenu .wp-menu-image img,' . '#adminmenu #toplevel_page_agentinstore.current .wp-menu-image img{opacity:1}' ); if ($hook !== 'toplevel_page_agentinstore') { return; } // Keep our settings page clean , hide other plugins' promotional admin notices. remove_all_actions('admin_notices'); remove_all_actions('all_admin_notices'); remove_all_actions('network_admin_notices'); remove_all_actions('user_admin_notices'); add_action('admin_notices', [__CLASS__, 'maybe_notice_missing_wc']); wp_register_style('agenti-admin', false, [], AGENTI_PLUGIN_VERSION); wp_enqueue_style('agenti-admin'); wp_add_inline_style('agenti-admin', self::admin_css()); } private static function admin_css() { return ' .agenti-wrap{max-width:920px;margin:20px 20px 40px 0} .agenti-wrap h1.agenti-title{margin:0 0 1rem;font-size:1.5rem;font-weight:650;color:#0f172a} .agenti-hero{display:flex;gap:1rem;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;margin:0 0 1.25rem} .agenti-hero-brand{display:flex;gap:1rem;align-items:center} .agenti-hero-brand img.agenti-logo{height:72px;width:auto;max-width:240px;object-fit:contain;background:transparent} .agenti-hero p{margin:0;color:#64748b;max-width:46ch;line-height:1.5} .agenti-pill{display:inline-flex;align-items:center;gap:.35rem;padding:.28rem .7rem;border-radius:999px;font-size:12px;font-weight:700} .agenti-pill.on{background:#d1fae5;color:#065f46} .agenti-pill.off{background:#ffedd5;color:#9a3412} .agenti-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.85rem;margin:0 0 1.15rem} .agenti-stat{background:#fff;border:1px solid #e2e8f0;border-radius:14px;padding:1rem 1.1rem;box-shadow:0 1px 2px rgba(15,23,42,.04)} .agenti-stat .lbl{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:#64748b;font-weight:700} .agenti-stat .val{margin-top:.35rem;font-size:1.05rem;font-weight:750;color:#0f172a;word-break:break-word} .agenti-card{background:#fff;border:1px solid #e2e8f0;border-radius:16px;padding:1.2rem 1.25rem;margin:0 0 1rem;box-shadow:0 1px 2px rgba(15,23,42,.04)} .agenti-card h2{margin:0 0 .35rem;font-size:1.05rem} .agenti-card .sub{margin:0 0 1rem;color:#64748b;font-size:13px;line-height:1.45} .agenti-row{display:grid;grid-template-columns:160px minmax(0,1fr);gap:.75rem 1rem;align-items:start;margin:0 0 .95rem} .agenti-row label{font-weight:650;padding-top:.45rem;color:#334155} .agenti-row input[type=url],.agenti-row input[type=text],.agenti-row input[type=color],.agenti-row textarea{ width:100%;max-width:420px;border:1px solid #cbd5e1;border-radius:10px;padding:.55rem .7rem } .agenti-row input[type=color]{width:64px;height:40px;padding:3px;cursor:pointer} .agenti-hint{margin:.35rem 0 0;color:#64748b;font-size:12px;line-height:1.4} .agenti-actions{display:flex;flex-wrap:wrap;gap:.55rem;align-items:center;margin-top:.35rem} .agenti-actions .button-primary{background:#0d9488!important;border-color:#0f766e!important;text-shadow:none;box-shadow:none} .agenti-actions .button-primary:hover{background:#0f766e!important;border-color:#115e59!important} .agenti-actions .button-danger{color:#b91c1c;border-color:#fecaca;background:#fff} .agenti-actions .button-danger:hover{background:#fef2f2;border-color:#fca5a5;color:#991b1b} .agenti-confirm{background:#f0fdfa;border:1px solid #99f6e4;border-radius:14px;padding:1rem 1.1rem;margin:0 0 1rem} .agenti-sku{display:flex;flex-wrap:wrap;gap:.55rem;align-items:end} .agenti-sku input{flex:1 1 260px;min-width:0;border:1px solid #cbd5e1;border-radius:10px;padding:.55rem .7rem} .agenti-foot{margin-top:.5rem;color:#94a3b8;font-size:12px;line-height:1.5} .agenti-foot a{color:#0f766e} @media (max-width:720px){ .agenti-row{grid-template-columns:1fr} .agenti-row label{padding-top:0} }'; } /** * Prefill from AgentInStore one-click link. * Credentials stay in a user transient until confirmed via POST. */ public static function maybe_prefills_from_query() { if (!current_user_can('manage_options')) { return; } if (!self::is_settings_screen()) { return; } $api = isset($_GET['agenti_api_base']) ? esc_url_raw(wp_unslash($_GET['agenti_api_base'])) : ''; $key = isset($_GET['agenti_public_key']) ? sanitize_text_field(wp_unslash($_GET['agenti_public_key'])) : ''; if (!$api && !$key) { return; } $uid = get_current_user_id(); $pending = [ 'api_base' => $api, 'public_key' => $key, ]; set_transient('agenti_pending_connect_' . $uid, $pending, 10 * MINUTE_IN_SECONDS); $auto = isset($_GET['agenti_auto']) ? sanitize_text_field(wp_unslash($_GET['agenti_auto'])) : ''; $done = isset($_GET['agenti_done']) ? sanitize_text_field(wp_unslash($_GET['agenti_done'])) : ''; $confirm = isset($_GET['agenti_confirm']) ? sanitize_text_field(wp_unslash($_GET['agenti_confirm'])) : ''; if ($auto !== '' && $api && $key && $done === '' && $confirm === '') { set_transient('agenti_pending_autoconnect_' . $uid, 1, 10 * MINUTE_IN_SECONDS); wp_safe_redirect(admin_url('admin.php?page=agentinstore&agenti_confirm=1')); exit; } } /** * @return array{api_base:string,public_key:string} */ private static function pending_connect_creds() { $pending = get_transient('agenti_pending_connect_' . get_current_user_id()); if (!is_array($pending)) { return ['api_base' => '', 'public_key' => '']; } return [ 'api_base' => isset($pending['api_base']) ? (string) $pending['api_base'] : '', 'public_key' => isset($pending['public_key']) ? (string) $pending['public_key'] : '', ]; } public static function menu() { $icon = plugins_url('assets/menu-icon.png', AGENTI_PLUGIN_FILE); add_menu_page( __('AgentInStore', 'agentinstore'), __('AgentInStore', 'agentinstore'), 'manage_options', 'agentinstore', [__CLASS__, 'render_page'], $icon, 58 ); } public static function register_settings() { register_setting('agenti_settings', 'agenti_api_base', ['sanitize_callback' => 'esc_url_raw']); register_setting('agenti_settings', 'agenti_public_key', ['sanitize_callback' => 'sanitize_text_field']); register_setting('agenti_settings', 'agenti_bot_color', ['sanitize_callback' => 'sanitize_hex_color']); register_setting('agenti_settings', 'agenti_widget_enabled', [ 'type' => 'boolean', 'sanitize_callback' => function ($v) { return $v ? 1 : 0; }, 'default' => 1, ]); } public static function maybe_notice_missing_wc() { if (!current_user_can('manage_options') || !self::is_settings_screen()) { return; } if (!class_exists('WooCommerce')) { echo '
'; echo wp_kses( __('AgentInStore works best with WooCommerce for product sync. The chat widget still works without it.', 'agentinstore'), ['strong' => []] ); echo '
' . esc_html__('Connected to AgentInStore. Product catalog sync is running in the background , you will see products appear shortly. New changes sync automatically.', 'agentinstore') . '
' . esc_html(sprintf( /* translators: %d: product count */ __('Connected to AgentInStore. Catalog sync status: %d products. New changes sync automatically.', 'agentinstore'), intval($n) )) . '
' . esc_html__('Settings saved', 'agentinstore') . esc_html($extra) . '.
' . esc_html__('Disconnected from AgentInStore.', 'agentinstore') . '
' . esc_html__('Full catalog refresh queued in the background. AgentInStore will show updated counts and product images shortly.', 'agentinstore') . '
' . esc_html(sprintf( /* translators: 1: ok count, 2: failed, 3: missing */ __('SKU sync: %1$d pushed, %2$d failed, %3$d SKU(s) not found.', 'agentinstore'), $n, $fail, $miss )) . '
' . esc_html(sprintf( /* translators: %d: product count */ __('Synced %d product(s) by SKU.', 'agentinstore'), $n )) . '
' . esc_html($err) . '
' . esc_html__('API base URL must be a valid http or https URL.', 'agentinstore') . '
' . esc_html($detail) . '
' . esc_html__('Disconnect cancelled. Check the box and type DISCONNECT to confirm.', 'agentinstore') . '
not reconnect or re-sync products. When connected, color also updates your AgentInStore dashboard Settings.', 'agentinstore'), ['strong' => []]); ?>
that product only in realtime.', 'agentinstore'), ['strong' => []]); ?>
, agentinstore.com.', 'agentinstore'), 'https://agentinstore.com/' ), [ 'a' => ['href' => [], 'target' => [], 'rel' => []], ] ); ?>