store = $store; $this->api = $api; } /** * Register admin hooks. * * @return void */ public function register() { add_action( 'admin_menu', array( $this, 'add_menu_page' ) ); add_action( 'admin_init', array( $this, 'maybe_redirect_after_activation' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); add_action( 'admin_post_akintu_begin_connection', array( $this, 'begin_connection' ) ); add_action( 'admin_post_akintu_disconnect', array( $this, 'disconnect' ) ); add_action( 'wp_ajax_akintu_connection_status', array( $this, 'ajax_connection_status' ) ); } /** * Add the Akintu top-level page. * * @return void */ public function add_menu_page() { add_menu_page( __( 'Akintu', 'akintu' ), __( 'Akintu', 'akintu' ), 'manage_options', 'akintu', array( $this, 'render_page' ), AKINTU_PLUGIN_URL . 'assets/akintu-symbol.svg', 58 ); add_submenu_page( 'akintu', __( 'Dashboard', 'akintu' ), __( 'Dashboard', 'akintu' ), 'manage_options', 'akintu', array( $this, 'render_page' ) ); add_submenu_page( 'akintu', __( 'Conversazioni', 'akintu' ), __( 'Conversazioni', 'akintu' ), 'manage_options', 'akintu-conversations', array( $this, 'render_conversations_page' ) ); add_submenu_page( 'akintu', __( 'Fonti', 'akintu' ), __( 'Fonti', 'akintu' ), 'manage_options', 'akintu-sources', array( $this, 'render_sources_page' ) ); add_submenu_page( 'akintu', __( 'Istruzioni', 'akintu' ), __( 'Istruzioni', 'akintu' ), 'manage_options', 'akintu-settings', array( $this, 'render_settings_page' ) ); } /** * Redirect a normal activation to onboarding. * * @return void */ public function maybe_redirect_after_activation() { if ( '1' !== get_option( 'akintu_activation_redirect' ) || ! current_user_can( 'manage_options' ) ) { return; } delete_option( 'akintu_activation_redirect' ); if ( wp_doing_ajax() || is_network_admin() ) { return; } wp_safe_redirect( admin_url( 'admin.php?page=akintu' ) ); exit; } /** * Load polling only on the Akintu admin page. * * @param string $hook_suffix Current admin hook. * @return void */ public function enqueue_assets( $hook_suffix ) { $hooks = array( 'toplevel_page_akintu', 'akintu_page_akintu-conversations', 'akintu_page_akintu-sources', 'akintu_page_akintu-settings', ); if ( ! in_array( $hook_suffix, $hooks, true ) ) { return; } wp_enqueue_style( 'akintu-admin', AKINTU_PLUGIN_URL . 'assets/admin.css', array(), AKINTU_VERSION ); wp_enqueue_script( 'akintu-admin', AKINTU_PLUGIN_URL . 'assets/admin.js', array(), AKINTU_VERSION, true ); wp_localize_script( 'akintu-admin', 'akintuAdmin', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'akintu_connection_status' ), 'restUrl' => rest_url( 'akintu/v1/manage' ), 'restNonce' => wp_create_nonce( 'wp_rest' ), 'locale' => determine_locale(), 'accountUrl' => 'https://akintu.io/app/settings', 'billingUrl' => 'https://akintu.io/app/billing', 'docsUrl' => 'https://akintu.io/app/documentation/installazione', 'homeUrl' => home_url( '/' ), 'logoUrl' => AKINTU_PLUGIN_URL . 'assets/akintu-logo.svg', 'symbolUrl' => AKINTU_PLUGIN_URL . 'assets/akintu-symbol.svg', ) ); } /** * Generate installation credentials and open dashboard approval. * * @return void */ public function begin_connection() { $this->require_admin_action( 'akintu_begin_connection' ); $this->disconnect_existing_connection(); try { $client_key = 'pk_live_' . bin2hex( random_bytes( 24 ) ); $verification = 'akv_' . bin2hex( random_bytes( 24 ) ); $connection_secret = 'akc_' . bin2hex( random_bytes( 24 ) ); $management_token = 'akm_' . bin2hex( random_bytes( 24 ) ); } catch ( Exception $error ) { $this->redirect_with_error( 'secure_random_unavailable' ); } $response = $this->api->create_connection( array( 'siteUrl' => home_url( '/' ), 'returnUrl' => admin_url( 'admin.php?page=akintu&akintu_connect=1' ), 'pluginVersion' => AKINTU_VERSION, 'clientKeyPrefix' => substr( $client_key, 0, 12 ), 'clientKeyHash' => hash( 'sha256', $client_key ), 'verificationTokenHash' => hash( 'sha256', $verification ), 'connectionSecretHash' => hash( 'sha256', $connection_secret ), 'managementTokenHash' => hash( 'sha256', $management_token ), ) ); if ( is_wp_error( $response ) ) { $this->redirect_with_error( $response->get_error_code() ); } if ( ! $this->is_valid_connection_response( $response ) ) { $this->redirect_with_error( 'invalid_connection_response' ); } $this->store->save( array( 'connection_id' => sanitize_text_field( $response['connectionId'] ), 'connect_url' => esc_url_raw( $response['connectUrl'] ), 'expires_at' => sanitize_text_field( $response['expiresAt'] ), 'client_key' => $client_key, 'verification_token' => $verification, 'connection_secret' => $connection_secret, 'management_token' => $management_token, 'status' => 'pending', 'error_code' => '', ) ); wp_redirect( esc_url_raw( $response['connectUrl'] ) ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect exit; } /** * Revoke this installation and remove local credentials. * * @return void */ public function disconnect() { $this->require_admin_action( 'akintu_disconnect' ); $connection = $this->store->get(); if ( ! empty( $connection['connection_id'] ) && ! empty( $connection['management_token'] ) ) { $result = $this->api->disconnect( $connection['connection_id'], $connection['management_token'] ); if ( is_wp_error( $result ) ) { $this->redirect_with_error( $result->get_error_code() ); } } $this->store->delete(); wp_safe_redirect( admin_url( 'admin.php?page=akintu&disconnected=1' ) ); exit; } /** * Poll and advance approval to site verification. * * @return void */ public function ajax_connection_status() { check_ajax_referer( 'akintu_connection_status' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'code' => 'forbidden' ), 403 ); } $connection = $this->store->get(); if ( empty( $connection['connection_id'] ) || empty( $connection['connection_secret'] ) ) { wp_send_json_error( array( 'code' => 'connection_missing' ), 404 ); } $status = $this->api->get_connection( $connection['connection_id'], $connection['connection_secret'] ); if ( is_wp_error( $status ) ) { wp_send_json_error( array( 'code' => $status->get_error_code() ), 502 ); } $connection = $this->store->update_status( $status ); if ( 'approved' === $connection['status'] ) { $status = $this->api->request_verification( $connection['connection_id'], $connection['connection_secret'] ); if ( is_wp_error( $status ) ) { wp_send_json_error( array( 'code' => $status->get_error_code() ), 502 ); } $connection = $this->store->update_status( $status ); } wp_send_json_success( array( 'status' => $connection['status'], 'errorCode' => $connection['error_code'], ) ); } /** * Render onboarding and connected-state guidance. * * @return void */ public function render_page() { if ( ! current_user_can( 'manage_options' ) ) { return; } $connection = $this->store->get(); $status = isset( $connection['status'] ) ? $connection['status'] : 'disconnected'; ?>
<?php esc_attr_e( 'Akintu', 'akintu' ); ?>

render_feedback(); ?>

get_status_label( $status ) ); ?>

render_application_page( 'conversations' ); } /** Render knowledge sources and site scan tools. */ public function render_sources_page() { $this->render_application_page( 'sources' ); } /** Render the three instruction tabs. */ public function render_settings_page() { $this->render_application_page( 'settings' ); } /** * Render a connected application shell or the onboarding entry point. * * @param string $screen Screen identifier. * @return void */ private function render_application_page( $screen ) { if ( ! current_user_can( 'manage_options' ) ) { return; } if ( ! $this->store->is_connected() ) { $this->render_page(); return; } ?>

render_disconnect_form(); ?>

store->get(); if ( empty( $connection['connection_id'] ) || empty( $connection['management_token'] ) ) { return; } $result = $this->api->disconnect( $connection['connection_id'], $connection['management_token'] ); if ( is_wp_error( $result ) ) { $this->redirect_with_error( $result->get_error_code() ); } $this->store->delete(); } /** * Check capability and nonce for an admin-post action. * * @param string $action Nonce action. * @return void */ private function require_admin_action( $action ) { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Non hai i permessi per gestire Akintu.', 'akintu' ), esc_html__( 'Accesso negato', 'akintu' ), array( 'response' => 403 ) ); } check_admin_referer( $action ); } /** * Validate the small response surface trusted for an external redirect. * * @param array $response Response payload. * @return bool */ private function is_valid_connection_response( array $response ) { if ( empty( $response['connectionId'] ) || empty( $response['connectUrl'] ) || empty( $response['expiresAt'] ) ) { return false; } $url = wp_parse_url( $response['connectUrl'] ); $is_allowed = is_array( $url ) && isset( $url['scheme'], $url['host'] ) && 'https' === $url['scheme'] && ( 'akintu.io' === $url['host'] || 0 === substr_compare( $url['host'], '.akintu.io', -10 ) ); return (bool) apply_filters( 'akintu_is_allowed_connect_url', $is_allowed, $url ); } /** * Redirect back to the plugin page with a non-sensitive error code. * * @param string $code Error code. * @return never */ private function redirect_with_error( $code ) { wp_safe_redirect( add_query_arg( 'akintu_error', sanitize_key( $code ), admin_url( 'admin.php?page=akintu' ) ) ); exit; } /** * Human-readable setup state. * * @param string $status Status code. * @return string */ private function get_status_label( $status ) { $labels = array( 'pending' => __( 'In attesa dell’approvazione nella dashboard Akintu…', 'akintu' ), 'approved' => __( 'Approvato. Avvio della verifica del dominio…', 'akintu' ), 'verifying' => __( 'Verifica del dominio in corso…', 'akintu' ), ); return isset( $labels[ $status ] ) ? $labels[ $status ] : __( 'Collegamento in corso…', 'akintu' ); } }