$state, 'verifier' => $verifier), 15 * MINUTE_IN_SECONDS); $query = array( 'site' => home_url('/'), 'return' => admin_url('admin-post.php?action=rayachat_connect_return'), 'state' => $state, // RFC 7636 S256: base64url(sha256(verifier)), without padding. 'challenge' => rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '='), 'woo' => class_exists('WooCommerce') ? '1' : '0', // This version can show the chatbot's inbox in wp-admin, so the owner is asked about it. 'inbox' => '1', 'name' => wp_strip_all_tags(get_bloginfo('name')), ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- the approval page is on Rayachat, not on this site. wp_redirect(RAYACHAT_ORIGIN . '/connect/wordpress?' . http_build_query($query, '', '&', PHP_QUERY_RFC3986)); exit; } /** * Back from Rayachat: check that this admin started the flow, exchange the * code, and save what comes back. Every outcome ends on the settings page * with a notice saying what happened and what to do next. */ function rayachat_connect_return() { if (!current_user_can('manage_options')) { wp_die(esc_html__('You are not allowed to change Rayachat’s settings.', 'raya-ai'), 403); } $pending = get_transient(rayachat_connect_key()); delete_transient(rayachat_connect_key()); // phpcs:disable WordPress.Security.NonceVerification.Recommended -- the state parameter, checked below against what this admin stored, is this flow's nonce. $state = isset($_GET['state']) ? sanitize_text_field(wp_unslash($_GET['state'])) : ''; $error = isset($_GET['error']) ? sanitize_key(wp_unslash($_GET['error'])) : ''; $code = isset($_GET['code']) ? sanitize_text_field(wp_unslash($_GET['code'])) : ''; // phpcs:enable if (!is_array($pending) || $state === '' || !hash_equals((string) $pending['state'], $state)) { rayachat_connect_done('error', __('This connection was not started from this page, or it took longer than 15 minutes. Click “Connect to Rayachat” again.', 'raya-ai')); } if ($error !== '') { rayachat_connect_done('info', __('The connection was cancelled. Nothing was changed.', 'raya-ai')); } $response = wp_remote_post(RAYACHAT_ORIGIN . '/api/integrations/wordpress/exchange', array( 'timeout' => 20, 'headers' => array('Content-Type' => 'application/json'), 'body' => wp_json_encode(array( 'code' => $code, 'verifier' => $pending['verifier'], 'site' => home_url('/'), )), )); if (is_wp_error($response)) { rayachat_connect_done('error', sprintf( /* translators: %s: the network error. */ __('This site could not reach Rayachat, so nothing was connected: %s. If it keeps happening, ask your host whether outgoing requests to rayachat.net are blocked.', 'raya-ai'), $response->get_error_message() )); } $status = (int) wp_remote_retrieve_response_code($response); $body = json_decode(wp_remote_retrieve_body($response), true); $body = is_array($body) ? $body : array(); if ($status !== 200) { $reason = isset($body['error']) ? (string) $body['error'] : ''; $messages = array( 'invalid_grant' => __('The approval expired or was already used. Click “Connect to Rayachat” again.', 'raya-ai'), 'site_mismatch' => __('This site’s address doesn’t match the one approved on Rayachat. If you changed the site address, connect again from here.', 'raya-ai'), 'no_widget' => __('The chosen chatbot has no website widget. Open it in the Rayachat dashboard, or pick another chatbot.', 'raya-ai'), ); rayachat_connect_done('error', $status === 429 ? __('Too many attempts in a short time. Wait a minute, then connect again.', 'raya-ai') : ($messages[$reason] ?? __('Rayachat could not finish the connection. Please try again in a minute.', 'raya-ai'))); } $secret = rayachat_parse_secret(isset($body['widgetSecret']) ? (string) $body['widgetSecret'] : ''); if ($secret === '') { rayachat_connect_done('error', __('Rayachat sent an answer this plugin can’t read. Update the plugin, then connect again.', 'raya-ai')); } $agent = isset($body['agentName']) ? sanitize_text_field((string) $body['agentName']) : ''; update_option(RAYACHAT_OPTION, array_merge(rayachat_settings(), array( 'secret' => $secret, 'enabled' => true, 'agent' => $agent, 'dashboard' => isset($body['dashboardUrl']) ? esc_url_raw((string) $body['dashboardUrl']) : '', 'connected_at' => time(), ))); /* translators: %s: the chatbot's name. */ $done = sprintf(__('Connected to the chatbot “%s”. The widget is on your site now — it is in the corner of this page too.', 'raya-ai'), $agent); // Kept when the owner gave this site the inbox, and forgotten when they didn't. rayachat_inbox_adopt(isset($body['inboxToken']) ? (string) $body['inboxToken'] : ''); if (rayachat_inbox_active()) { $done .= ' ' . __('Its conversations are in Rayachat → Inbox, and a summary is on the Dashboard.', 'raya-ai'); } $token = isset($body['syncToken']) ? (string) $body['syncToken'] : ''; if ($token === '' || !function_exists('rayachat_sync_adopt')) { rayachat_connect_done('success', $done); } $synced = rayachat_sync_adopt($token); if (is_wp_error($synced)) { rayachat_connect_done('warning', $done . ' ' . sprintf( /* translators: %s: why the product sync could not start. */ __('The product sync could not start: %s', 'raya-ai'), $synced->get_error_message() )); } rayachat_sync_update($synced); rayachat_connect_done('success', $done . ' ' . __('Your products are being sent in the background; the count below fills in as they arrive.', 'raya-ai')); } /** * Disconnects this site: the sync key stops working on Rayachat too, so the * chatbot stops quoting this store's prices at once, and so does the inbox's * key. The products stay in Rayachat until the owner deletes them there. */ function rayachat_disconnect() { check_admin_referer('rayachat_disconnect'); if (!current_user_can('manage_options')) { wp_die(esc_html__('You are not allowed to change Rayachat’s settings.', 'raya-ai'), 403); } if (function_exists('rayachat_sync_forget')) { rayachat_sync_forget(true); } rayachat_inbox_adopt(''); update_option(RAYACHAT_OPTION, array_merge(rayachat_settings(), array( 'secret' => '', 'agent' => '', 'dashboard' => '', 'connected_at' => 0, ))); rayachat_connect_done('success', __('Disconnected. The widget is off your site and nothing is sent to Rayachat any more.', 'raya-ai')); } // The notice waits for this admin's next view of the settings page. function rayachat_connect_done($type, $message) { set_transient('rayachat_notice_' . get_current_user_id(), array('type' => $type, 'message' => $message), 5 * MINUTE_IN_SECONDS); wp_safe_redirect(rayachat_settings_url()); exit; } function rayachat_render_flash() { $key = 'rayachat_notice_' . get_current_user_id(); $notice = get_transient($key); if (!is_array($notice)) { return; } delete_transient($key); $type = in_array($notice['type'], array('success', 'info', 'warning', 'error'), true) ? $notice['type'] : 'info'; printf('

%s

', esc_attr($type), esc_html($notice['message'])); }