$args Extra query args. */ public static function url( array $args = array() ): string { return add_query_arg( array_merge( array( 'page' => self::SLUG ), $args ), admin_url( 'admin.php' ) ); } public function handle_connect(): void { self::guard( 'cortexcommerce_connect' ); $code = isset( $_POST['cortex_code'] ) ? sanitize_text_field( wp_unslash( $_POST['cortex_code'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified in guard(). $outcome = ( new ConnectFlow() )->connect( $code, get_current_user_id() ); wp_safe_redirect( self::url( array( 'cortexcommerce_result' => $outcome ) ) ); exit; } public function handle_disconnect(): void { self::guard( 'cortexcommerce_disconnect' ); Disconnector::disconnect( 'disconnected' ); wp_safe_redirect( self::url( array( 'cortexcommerce_result' => 'disconnected' ) ) ); exit; } public function render(): void { if ( ! current_user_can( 'manage_woocommerce' ) ) { return; } echo '
'; self::render_header(); $this->render_notices(); if ( Options::is_connected() ) { $this->render_connected(); } else { $this->render_connect_form(); } echo '
'; } /** * The brand band, shared with the Enriched products page. WordPress moves admin notices below .wp-header-end, so * none of them land inside it. */ public static function render_header(): void { $connected = Options::is_connected(); echo '
'; printf( '', esc_url( plugin_dir_url( CORTEXCOMMERCE_FILE ) . 'assets/admin/logo.svg' ) ); echo '

' . esc_html__( 'CortexCommerce', 'cortexcommerce' ) . '

'; if ( $connected ) { echo esc_html( sprintf( /* translators: 1: the store's host name, 2: the date it connected. */ __( 'Connected: %1$s, since %2$s', 'cortexcommerce' ), Config::store_host(), (string) wp_date( (string) get_option( 'date_format' ), Options::connected_at() ) ) ); } else { /* translators: %s: the store's host name. */ echo esc_html( sprintf( __( 'Better product pages for %s', 'cortexcommerce' ), Config::store_host() ) ); } echo '

'; if ( $connected ) { self::external_button( StatsPanel::store_url( Config::dashboard_url() ), __( 'Open dashboard', 'cortexcommerce' ), 'button-primary' ); } echo '

'; } private function render_notices(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only. $result = isset( $_GET['cortexcommerce_result'] ) ? sanitize_key( wp_unslash( $_GET['cortexcommerce_result'] ) ) : ''; if ( 'connected' === $result && Options::is_connected() ) { self::notice( 'success', __( 'Your store is connected to CortexCommerce.', 'cortexcommerce' ) ); } elseif ( 'disconnected' === $result && ! Options::is_connected() ) { self::notice( 'success', __( 'Your store is disconnected. The API key was removed and product sync stopped.', 'cortexcommerce' ) ); } elseif ( 'busy' === $result ) { self::notice( 'warning', Messages::for_reason( 'busy' ) ); } // Only on the redirect right after the attempt that stored it: a stale error must never sit above a new code. $error = Options::last_error(); if ( null !== $error && $error['code'] === $result && ! Options::is_connected() ) { self::notice( 'error', $error['message'] ); } } private function render_connected(): void { // Sync problems stay on screen until a later sync succeeds (ProductSyncSender clears them). $error = Options::last_error(); if ( null !== $error && 0 === strpos( $error['code'], 'sync_' ) ) { self::notice( 'warning', $error['message'] ); } StatsPanel::render(); SyncSection::render(); echo '
' . esc_html__( 'Disconnect this store', 'cortexcommerce' ) . ''; echo '

' . esc_html__( 'Revokes the CortexCommerce API key and stops the product sync. To connect again you need a new code from app.cortexcommerce.ai.', 'cortexcommerce' ) . '

'; self::action_form( 'cortexcommerce_disconnect', __( 'Disconnect', 'cortexcommerce' ), 'button-secondary' ); echo '
'; } private function render_connect_form(): void { $blocker = Config::connect_blocker(); if ( null !== $blocker ) { self::notice( 'error', Messages::for_reason( $blocker ) ); } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- the code only pre-fills the form; nothing happens without the merchant's click. $code = isset( $_GET['cortex_code'] ) ? sanitize_text_field( wp_unslash( $_GET['cortex_code'] ) ) : ''; // Arriving with a code means the merchant is about to connect: no other way forward, and the grant is open to read. $has_code = '' !== $code; if ( ! $has_code ) { $this->render_paths(); } echo '
'; echo '

' . ( $has_code ? esc_html__( 'Connect this store', 'cortexcommerce' ) : esc_html__( 'Have your code?', 'cortexcommerce' ) ) . '

'; echo '
'; echo ''; wp_nonce_field( 'cortexcommerce_connect' ); echo '


'; printf( '

', esc_attr( $code ) ); echo '
' . esc_html__( 'What connecting shares with CortexCommerce', 'cortexcommerce' ) . ''; echo '
    '; echo '
  • ' . esc_html__( 'A WooCommerce REST API key named “CortexCommerce”, owned by your user account. This plugin limits it to reading and updating your products (with their variations, categories and tags), so CortexCommerce can read your catalog and write back the product changes you approve. It can’t reach orders, customers or settings.', 'cortexcommerce' ) . '
  • '; echo '
  • ' . esc_html__( 'Your products, sent from your store to CortexCommerce: the whole catalog once, then the products that changed or were deleted, every 5 minutes.', 'cortexcommerce' ) . '
  • '; echo '
'; echo '

' . esc_html__( 'You can disconnect at any time on this page. Disconnecting revokes the key and stops the product sync.', 'cortexcommerce' ) . '

'; echo '
'; echo '

' . esc_html__( 'Only continue if you started this connection yourself from your own account at app.cortexcommerce.ai. Connecting gives that account the access described above.', 'cortexcommerce' ) . '

'; printf( '

', $has_code ? 'button-primary' : 'button-secondary', null !== $blocker ? ' disabled' : '', esc_html__( 'Connect to CortexCommerce', 'cortexcommerce' ) ); echo '
'; } /** * The two ways to a connect code. Both end in the portal, which hands the code back here. */ private function render_paths(): void { echo '
'; echo '

' . esc_html__( 'New to CortexCommerce?', 'cortexcommerce' ) . '

'; echo '

' . esc_html__( 'Create a free account and add this store. You’ll get a code to bring back here.', 'cortexcommerce' ) . '

'; self::external_button( Config::signup_url(), __( 'Create free account', 'cortexcommerce' ), 'button-primary' ); echo '
'; echo '

' . esc_html__( 'Already have an account?', 'cortexcommerce' ) . '

'; echo '

' . esc_html__( 'Sign in and choose Add store, then WooCommerce, to get your code.', 'cortexcommerce' ) . '

'; self::external_button( Config::connect_url(), __( 'Get my code', 'cortexcommerce' ), 'button-secondary' ); echo '
'; } /** * A button-styled link into the portal, which opens in its own tab so the merchant keeps this screen. * * @param string $url The destination. * @param string $label The button text. * @param string $button_class button-primary or button-secondary. */ public static function external_button( string $url, string $label, string $button_class ): void { self::external_link( $url, $label, 'button ' . $button_class ); } /** * A link that opens in its own tab, marked with an arrow and, for screen readers, "(opens in a new tab)". * * @param string $url The destination. * @param string $label The link text. * @param string $css_class CSS classes, or '' for a plain link. */ public static function external_link( string $url, string $label, string $css_class = '' ): void { printf( '%3$s %4$s', esc_url( $url ), '' === $css_class ? '' : ' class="' . esc_attr( $css_class ) . '"', esc_html( $label ), esc_html__( '(opens in a new tab)', 'cortexcommerce' ) ); } private static function guard( string $action ): void { if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( esc_html__( 'Sorry, you are not allowed to do that.', 'cortexcommerce' ), 403 ); } check_admin_referer( $action ); } private static function notice( string $type, string $message ): void { printf( '

%2$s

', esc_attr( $type ), esc_html( $message ) ); } private static function action_form( string $action, string $label, string $button_class ): void { echo '
'; echo ''; wp_nonce_field( $action ); printf( '

', esc_attr( $button_class ), esc_html( $label ) ); echo '
'; } }