(int) ( $raw['dismissed_at'] ?? 0 ), 'dismiss_count' => (int) ( $raw['dismiss_count'] ?? 0 ), 'success_hidden' => ! empty( $raw['success_hidden'] ), ); } /** * Whether the card is offered now (administrators, tested key, no account yet, not dismissed — or dismissed once * more than REOFFER_DAYS ago). * * @param int|null $now Unix time. */ public static function eligible( ?int $now = null ): bool { if ( ! OwnKeySection::can_manage() || ! KeyStore::active() || Connection::connected() ) { return false; } $state = self::state(); if ( 0 === $state['dismiss_count'] ) { return true; } return 1 === $state['dismiss_count'] && ( $now ?? time() ) - $state['dismissed_at'] >= self::REOFFER_DAYS * DAY_IN_SECONDS; } /** * Sends the opt-in to AddSynapse — the ONLY place that does (from the card's form, after an administrator clicked * «Create account»). * * @param string $email Email. * @param bool $marketing News-and-offers box ticked. * @return array{status:string,message:string,link:string} status created | sign_in | error. */ public static function create( string $email, bool $marketing ): array { $fail = static function ( string $message, string $status = 'error', string $link = '' ): array { return array( 'status' => $status, 'message' => $message, 'link' => $link, ); }; if ( '' === $email || ! is_email( $email ) ) { return $fail( __( 'Enter a valid email address.', 'addsynapse-store-assistant' ) ); } $answer = Api::post( '/api/v1/assistant/optin', array( 'email' => $email, 'marketing' => $marketing, 'consent_version' => self::CONSENT_VERSION, 'home_url' => home_url( '/' ), ) ); if ( is_wp_error( $answer ) ) { return $fail( $answer->get_error_message() ); } $status = (string) ( $answer['body']['status'] ?? '' ); if ( 'created' === $status && Connection::save( $answer['body'] ) ) { update_option( self::OPTION, array( 'success_hidden' => false ) + self::state(), false ); return $fail( '', 'created' ); } if ( 'sign_in' === $status ) { $portal = (string) ( $answer['body']['portal_url'] ?? '' ); return $fail( __( 'This email or this site already has an AddSynapse account. Sign in to it on addsynapse.com to add this site; nothing was created.', 'addsynapse-store-assistant' ), 'sign_in', Api::link_allowed( $portal ) ? esc_url_raw( $portal ) : '' ); } $message = (string) ( $answer['body']['message'] ?? '' ); return $fail( '' !== $message ? sanitize_text_field( mb_substr( $message, 0, 300 ) ) : __( 'The account could not be created. Try again later.', 'addsynapse-store-assistant' ) ); } /** * «Create account» from the card (admin-post). */ public function handle_create(): void { if ( ! OwnKeySection::can_manage() ) { wp_die( esc_html__( 'You are not allowed to access this page.', 'addsynapse-store-assistant' ), 403 ); } check_admin_referer( self::ACTION_CREATE ); $email = isset( $_POST['addsynapse_assistant_optin_email'] ) ? sanitize_email( wp_unslash( $_POST['addsynapse_assistant_optin_email'] ) ) : ''; $marketing = ! empty( $_POST['addsynapse_assistant_optin_marketing'] ); $back = self::back_url( isset( $_POST['addsynapse_assistant_tab'] ) ? sanitize_key( wp_unslash( $_POST['addsynapse_assistant_tab'] ) ) : '' ); $result = self::create( $email, $marketing ); if ( 'created' === $result['status'] ) { self::redirect( $back, 'account_created' ); } set_transient( self::ERROR_TRANSIENT . get_current_user_id(), array( 'message' => $result['message'], 'link' => $result['link'], ), 5 * MINUTE_IN_SECONDS ); self::redirect( $back, 'account_failed' ); } /** * «Skip»: hides the card (it comes back once, REOFFER_DAYS later). */ public function handle_dismiss(): void { if ( ! OwnKeySection::can_manage() ) { wp_die( esc_html__( 'You are not allowed to access this page.', 'addsynapse-store-assistant' ), 403 ); } check_admin_referer( self::ACTION_DISMISS ); $back = self::back_url( isset( $_POST['addsynapse_assistant_tab'] ) ? sanitize_key( wp_unslash( $_POST['addsynapse_assistant_tab'] ) ) : '' ); $state = self::state(); if ( Connection::connected() ) { $state['success_hidden'] = true; } else { $state['dismissed_at'] = time(); $state['dismiss_count'] = min( 2, $state['dismiss_count'] + 1 ); } update_option( self::OPTION, $state, false ); self::redirect( $back, '' ); } /** * «Disconnect»: forgets the account on this site (announcements stop; the account on addsynapse.com stays). */ public function handle_forget(): void { if ( ! OwnKeySection::can_manage() ) { wp_die( esc_html__( 'You are not allowed to access this page.', 'addsynapse-store-assistant' ), 403 ); } check_admin_referer( self::ACTION_FORGET ); Connection::forget(); update_option( self::OPTION, array( 'success_hidden' => true ) + self::state(), false ); wp_safe_redirect( SettingsPage::url() . '#addsynapse-assistant-account' ); exit; } /** * Where a form of the card returns: the tab it was posted from (our screens only). * * @param string $tab Posted tab. */ private static function back_url( string $tab ): string { return AdminPage::back_url( $tab ); } /** * Redirect back with a notice code. * * @param string $url Back URL. * @param string $notice Notice. */ private static function redirect( string $url, string $notice ): void { wp_safe_redirect( ( '' !== $notice ? add_query_arg( 'addsynapse_assistant_optin', $notice, $url ) : $url ) ); exit; } /** * Renders the card (or the «account ready» panel) on one of the plugin's screens. * * @param string $tab chat | settings. */ public static function render( string $tab ): void { $notice = sanitize_key( (string) filter_input( INPUT_GET, 'addsynapse_assistant_optin', FILTER_UNSAFE_RAW ) ); if ( 'account_failed' === $notice && OwnKeySection::can_manage() ) { $error = get_transient( self::ERROR_TRANSIENT . get_current_user_id() ); if ( is_array( $error ) ) { echo '

' . esc_html( (string) $error['message'] ); if ( '' !== (string) $error['link'] ) { echo ' ' . esc_html__( 'Sign in', 'addsynapse-store-assistant' ) . ''; } echo '

'; } } $connection = Connection::get(); if ( null !== $connection ) { if ( OwnKeySection::can_manage() && ( 'account_created' === $notice || ! self::state()['success_hidden'] ) ) { self::render_ready( $connection, $tab ); } return; } if ( ! self::eligible() ) { return; } $email = (string) get_option( 'admin_email', '' ); echo '
'; echo '

' . esc_html__( 'Optional: create a free AddSynapse account to manage your sites in one place on addsynapse.com. The assistant works the same without it.', 'addsynapse-store-assistant' ) . '

'; echo '
'; wp_nonce_field( self::ACTION_CREATE ); echo ''; echo ''; echo '

'; echo ' '; echo '

'; echo '
' . esc_html__( 'What do we receive?', 'addsynapse-store-assistant' ) . ''; echo '

' . esc_html__( 'Your email, the address of your shop and your choice above, only when you click «Create account». Never your AI key.', 'addsynapse-store-assistant' ) . ' '; echo '' . esc_html__( 'Terms', 'addsynapse-store-assistant' ) . ' · ' . esc_html__( 'Privacy', 'addsynapse-store-assistant' ) . '

'; echo '

'; submit_button( __( 'Create account', 'addsynapse-store-assistant' ), 'primary', 'submit', false ); // «Skip» posts its own form (HTML form attribute), shown next to «Create account». submit_button( __( 'Skip', 'addsynapse-store-assistant' ), 'secondary', 'submit', false, array( 'form' => 'addsynapse-assistant-optin-dismiss' ) ); echo '

'; echo '
'; wp_nonce_field( self::ACTION_DISMISS, '_wpnonce', true, true ); echo ''; echo ''; echo '
'; echo '
'; } /** * «Your account is ready»: what happens next, set a password, hide. * * @param array $connection Connection. * @param string $tab Tab. */ private static function render_ready( array $connection, string $tab ): void { echo '
'; echo '

' . esc_html__( 'Your AddSynapse account is ready', 'addsynapse-store-assistant' ) . '

'; /* translators: %s: email address. */ echo '

' . esc_html( sprintf( __( 'Account: %s. Set a password to sign in to addsynapse.com.', 'addsynapse-store-assistant' ), (string) $connection['email'] ) ) . '

'; echo '

' . esc_html__( 'Sign in to addsynapse.com to see your sites. Store Assistant Pro is there too, if you ever need it.', 'addsynapse-store-assistant' ) . '

'; echo '

'; if ( '' !== $connection['portal_url'] ) { echo '' . esc_html__( 'Set a password', 'addsynapse-store-assistant' ) . ' '; } submit_button( __( 'Hide', 'addsynapse-store-assistant' ), 'secondary', 'submit', false, array( 'form' => 'addsynapse-assistant-optin-dismiss' ) ); echo '

'; echo '
'; wp_nonce_field( self::ACTION_DISMISS ); echo ''; echo ''; echo '
'; } /** * Settings → «AddSynapse account»: the state, and «Disconnect» (administrators). */ public static function render_settings(): void { $connection = Connection::get(); if ( null === $connection || ! OwnKeySection::can_manage() ) { return; } echo '

' . esc_html__( 'AddSynapse account', 'addsynapse-store-assistant' ) . '

'; /* translators: %s: email address. */ echo '

' . esc_html( sprintf( __( 'This site created an AddSynapse account (%s). Once a day, while you open these screens, the plugin asks AddSynapse for short announcements (it sends this site\'s key, the plugin version and the site language).', 'addsynapse-store-assistant' ), (string) $connection['email'] ) ) . '

'; echo '
'; wp_nonce_field( self::ACTION_FORGET ); echo ''; submit_button( __( 'Disconnect this site', 'addsynapse-store-assistant' ), 'secondary', 'submit', false ); echo '

' . esc_html__( 'Stops the announcements and forgets the account on this site. Your account on addsynapse.com stays until you delete it there.', 'addsynapse-store-assistant' ) . '

'; echo '
'; } }