'string', 'sanitize_callback' => 'sanitize_text_field', 'show_in_rest' => false, ); register_setting( 'convrail', 'convrail_bridge_token', $text ); register_setting( 'convrail', 'convrail_ingest_token', $text ); register_setting( 'convrail', 'convrail_pixel_id', $text ); register_setting( 'convrail', Convrail_Upgrade::VERSION_OPTION, $text ); register_setting( 'convrail', 'convrail_api_url', array( 'type' => 'string', 'sanitize_callback' => 'esc_url_raw', 'show_in_rest' => false, ) ); register_setting( 'convrail', 'convrail_test_mode', array( 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', 'show_in_rest' => false, ) ); register_setting( 'convrail', 'convrail_last_full_sync', array( 'type' => 'integer', 'sanitize_callback' => 'absint', 'show_in_rest' => false, ) ); $error_record = array( 'type' => 'object', 'sanitize_callback' => array( __CLASS__, 'sanitize_error_record' ), 'show_in_rest' => false, ); register_setting( 'convrail', 'convrail_sync_error', $error_record ); register_setting( 'convrail', 'convrail_order_error', $error_record ); } /** * Trace d'un envoi définitivement échoué : uniquement des nombres (page ou commande, code HTTP, * horodatage). Tout autre champ est écarté. * * @param mixed $value Valeur a enregistrer. * @return array|null */ public static function sanitize_error_record( $value ) { if ( ! is_array( $value ) ) { return null; } $clean = array(); foreach ( array( 'page', 'order', 'status', 'at' ) as $key ) { if ( isset( $value[ $key ] ) ) { $clean[ $key ] = absint( $value[ $key ] ); } } return $clean; } /** * Slug de la page de connexion, préfixé comme tout nom global de WordPress. `ocl-settings` * jusqu'au 2026-10-19 : le plugin n'étant pas encore publié, aucun marchand n'en avait de favori. * Les redirections passent toutes par cette constante. */ const PAGE = 'convrail-settings'; public static function handle_connect() { if ( ! current_user_can( 'manage_woocommerce' ) || ! check_admin_referer( 'convrail_connect' ) ) { wp_die( esc_html__( 'You are not allowed to do this.', 'convrail' ) ); } // Premier appel reseau vers l'API: uniquement ici, au clic du marchand sur "Connect". // L'API rappelle /wp-json/convrail/v1/verify pendant cette requete pour prouver que ce // site controle bien le domaine (premiere connexion comme reconnexion). $payload = array( 'domain' => Convrail_Verify::site_domain(), 'currency' => get_woocommerce_currency(), 'challenge' => Convrail_Verify::create_challenge(), ); $country = WC()->countries->get_base_country(); if ( is_string( $country ) && '' !== $country ) { $payload['country'] = $country; } $timezone = wp_timezone_string(); if ( is_string( $timezone ) && '' !== $timezone ) { $payload['timezone'] = $timezone; } $result = Convrail_Client::post( '/bridge/wc/register', $payload, false, 45 ); // L'API rappelle le site pendant cette requete: jusqu'a 20 s d'attente de son cote. Convrail_Verify::clear_challenge(); if ( ! is_wp_error( $result ) && ! empty( $result['bridgeToken'] ) ) { // A la reconnexion, l'API fait tourner le bridge token: l'ancien ne marche plus. update_option( 'convrail_bridge_token', sanitize_text_field( $result['bridgeToken'] ) ); update_option( 'convrail_ingest_token', sanitize_text_field( $result['ingestToken'] ) ); if ( array_key_exists( 'testMode', $result ) ) { update_option( 'convrail_test_mode', ! empty( $result['testMode'] ) ); } Convrail_Products::schedule_full_sync(); /* * Ventes encaissees avant la connexion, ou pendant une coupure de jeton: elles n'ont * jamais ete envoyees parce que la boutique n'etait pas reliee. On ne les fait pas * attendre la tache de la nuit. */ Convrail_Orders::catch_up(); $notice = ! empty( $result['reconnected'] ) ? 'reconnected' : 'connected'; } else { $status = Convrail_Client::error_status( $result ); if ( 403 === $status ) { $notice = 'connect_verification_failed'; } elseif ( 409 === $status ) { $notice = 'connect_conflict'; } elseif ( 429 === $status ) { $notice = 'connect_rate_limited'; } else { $notice = 'connect_failed'; } } wp_safe_redirect( admin_url( 'admin.php?page=' . self::PAGE . '&convrail_notice=' . $notice ) ); exit; } /** * Messages affiches apres une redirection. Chaque code choisit un texte deja ecrit ici: un * code inconnu n'affiche rien, jamais l'identifiant brut. * * @return array code => [classe de notice, texte] */ private static function notices() { return array( 'connected' => array( 'success', __( 'Your store is connected. Your products are on their way to Convrail.', 'convrail' ) ), 'reconnected' => array( 'success', __( 'Your store is connected again, with a new connection key.', 'convrail' ) ), 'connect_verification_failed' => array( 'error', __( 'Convrail could not confirm that this site controls its domain. Make sure the WordPress REST API (/wp-json/) is reachable from the internet over HTTPS, then try again.', 'convrail' ) ), 'connect_conflict' => array( 'error', __( 'This domain is already connected to Convrail from another platform.', 'convrail' ) ), 'connect_rate_limited' => array( 'warning', __( 'Too many connection attempts. Wait a minute, then try again.', 'convrail' ) ), 'connect_failed' => array( 'error', __( 'Convrail could not be reached. Check that your site can reach the internet, then try again.', 'convrail' ) ), ); } public static function render() { if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( esc_html__( 'You are not allowed to see this page.', 'convrail' ) ); } $connected = Convrail_Client::is_connected(); $app_url = admin_url( 'admin.php?page=' . Convrail_Admin::PAGE ); /* * Message a afficher apres une redirection. Rien n'est traite ici: la valeur ne fait que * choisir un texte deja ecrit dans cette page, et l'action qui change l'etat verifie, elle, * son nonce. Un nonce sur un simple affichage rendrait le lien de retour invalide au bout * de quelques heures. */ // phpcs:ignore WordPress.Security.NonceVerification.Recommended $code = isset( $_GET['convrail_notice'] ) ? sanitize_text_field( wp_unslash( $_GET['convrail_notice'] ) ) : ''; $notices = self::notices(); ?>