connection = $connection; $this->stand = $stand; } /** * Hooks. */ public function register(): void { add_action( 'wp_dashboard_setup', array( $this, 'add' ) ); } /** * Adds the widget for users who manage the plugin. */ public function add(): void { if ( current_user_can( Admin::capability() ) ) { wp_add_dashboard_widget( 'stand_chat_dashboard', __( 'Stand Chat', 'stand-chat' ), array( $this, 'render' ) ); } } /** * Renders the widget. */ public function render(): void { $overview = admin_url( 'admin.php?page=stand-chat' ); if ( ! $this->connection->is_connected() && '' === Settings::get( 'site_id' ) ) { printf( '

%s

%s

', esc_html__( 'Put an AI assistant on your site that answers visitors from your pages and emails you leads.', 'stand-chat' ), esc_url( $overview ), esc_html__( 'Create your free account', 'stand-chat' ) ); return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verified here. if ( isset( $_GET['stand-chat-refresh'] ) && isset( $_GET['_wpnonce'] ) && wp_verify_nonce( sanitize_key( wp_unslash( $_GET['_wpnonce'] ) ), self::NONCE ) ) { Cache::flush(); } $data = Cache::remember( 'dashboard', static function ( array $data ): int { // While Stand is down, only briefly: it shows again soon after. return empty( $data['status']['stale'] ) ? 300 : 60; }, function () { return $this->data(); } ); $status = $data['status']; echo '
'; printf( '

%2$s%3$s

', esc_attr( (string) $status['state'] ), esc_html( (string) $status['title'] ), '' !== $data['answering'] ? ' · ' . esc_html( $data['answering'] ) : '' ); if ( ! empty( $status['stale'] ) ) { echo '

' . esc_html__( 'Stand is temporarily unreachable. Showing the last known status.', 'stand-chat' ) . '

'; } if ( null !== $data['chats'] ) { /* translators: %d: number of chats */ printf( '

%s

', esc_html( sprintf( _n( '%d chat this week', '%d chats this week', (int) $data['chats'], 'stand-chat' ), (int) $data['chats'] ) ) ); } if ( '' !== $data['plan'] ) { printf( '

%s

', esc_html( $data['plan'] ) ); } if ( $data['changed'] > 0 ) { printf( '

%1$s %3$s

', esc_html( sprintf( /* translators: %d: number of pages */ _n( '%d page changed since your AI assistant read it.', '%d pages changed since your AI assistant read them.', (int) $data['changed'], 'stand-chat' ), (int) $data['changed'] ) ), esc_url( admin_url( 'admin.php?page=stand-chat-knowledge' ) ), esc_html__( 'Read again', 'stand-chat' ) ); } if ( in_array( (string) $status['state'], array( 'reconnect', 'unknown' ), true ) ) { // Reconnect starts on the Overview, which shows the sign-in code. $primary = sprintf( '%2$s', esc_url( $overview ), esc_html__( 'Reconnect', 'stand-chat' ) ); } else { $primary = sprintf( '%2$s', esc_url( Service_Urls::app( '/' ) ), esc_html__( 'Open Chats', 'stand-chat' ) ); } printf( '

%1$s %3$s %5$s

', $primary, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped above. esc_url( $overview ), esc_html__( 'Overview', 'stand-chat' ), esc_url( wp_nonce_url( add_query_arg( 'stand-chat-refresh', '1', admin_url( 'index.php' ) ), self::NONCE ) ), esc_html__( 'Refresh', 'stand-chat' ) ); echo '
'; } /** * What the widget shows. * * @return array */ private function data(): array { $out = array( 'status' => Status::compute( $this->connection, $this->stand ), 'answering' => '', 'chats' => null, 'plan' => '', 'changed' => 0, ); // Once Stand can't be reached, the rest isn't asked of it: each would // wait for its timeout. if ( ! $this->connection->is_usable() || ! empty( $out['status']['stale'] ) ) { return $out; } $matches = $this->stand->matches(); if ( ! is_wp_error( $matches ) && ! empty( $matches ) ) { $first = $matches[0]; $out['answering'] = 'rep' === ( $first['responderType'] ?? '' ) /* translators: %s: teammate name */ ? sprintf( __( '%s is answering', 'stand-chat' ), (string) ( $first['name'] ?? '' ) ) /* translators: %s: AI assistant name */ : sprintf( __( '%s (AI) is answering', 'stand-chat' ), (string) ( $first['name'] ?? '' ) ); } $week = $this->stand->week(); if ( ! is_wp_error( $week ) ) { $out['chats'] = (int) ( $week['chats'] ?? 0 ); } $plan = $this->stand->plan(); if ( ! is_wp_error( $plan ) && null !== ( $plan['usage']['chatQuota'] ?? null ) ) { $out['plan'] = sprintf( /* translators: 1: plan name, 2: chats used, 3: chat quota, 4: reset date */ __( '%1$s · %2$d of %3$d chats · resets %4$s', 'stand-chat' ), 'base' === ( $plan['plan']['planCode'] ?? '' ) ? __( 'Free plan', 'stand-chat' ) : (string) ( $plan['plan']['name'] ?? '' ), (int) $plan['usage']['chatCount'], (int) $plan['usage']['chatQuota'], Status::date( (string) $plan['usage']['periodEnd'] ) ); } $knowledge = new Knowledge( $this->stand ); $out['changed'] = count( $knowledge->changed() ); return $out; } }