'; /** * Connection. * * @var Connection */ private $connection; /** * Stand service. * * @var Stand */ private $stand; /** * Screen by admin page hook suffix. * * @var array */ private $screens = array(); /** * Constructor. * * @param Connection $connection Connection. * @param Stand $stand Stand service. */ public function __construct( Connection $connection, Stand $stand ) { $this->connection = $connection; $this->stand = $stand; } /** * The capability for the plugin's screens. Filterable. */ public static function capability(): string { /** * Filters the capability needed to use Stand Chat's admin screens. * * @param string $capability Default manage_options. */ $capability = apply_filters( 'stand_chat_capability', 'manage_options' ); return is_string( $capability ) && '' !== $capability ? $capability : 'manage_options'; } /** * Hooks. */ public function register(): void { add_action( 'admin_menu', array( $this, 'menu' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) ); add_action( 'admin_init', array( $this, 'redirect_after_activation' ), 5 ); add_action( 'admin_notices', array( $this, 'admin_wide_notices' ) ); add_filter( 'plugin_action_links_' . plugin_basename( STAND_CHAT_FILE ), array( $this, 'action_links' ) ); add_filter( 'plugin_row_meta', array( $this, 'row_meta' ), 10, 2 ); add_filter( 'admin_body_class', array( $this, 'body_class' ) ); } /** * Adds the menu. Before connecting it offers Get started; after, the five * screens. */ public function menu(): void { $capability = self::capability(); $connected = $this->connection->is_connected(); $title = __( 'Stand Chat', 'stand-chat' ); $top = add_menu_page( $title, $title, $capability, 'stand-chat', array( $this, 'render' ), 'data:image/svg+xml;base64,' . base64_encode( self::MENU_ICON ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Menu icon data URI. 26 ); $this->screens[ (string) $top ] = $connected ? 'overview' : 'welcome'; $pages = $connected ? array( 'stand-chat' => array( __( 'Overview', 'stand-chat' ), 'overview' ), 'stand-chat-standins' => array( __( 'Stand-ins', 'stand-chat' ), 'standins' ), 'stand-chat-knowledge' => array( __( 'Knowledge', 'stand-chat' ), 'knowledge' ), 'stand-chat-widget' => array( __( 'Chat widget', 'stand-chat' ), 'widget' ), 'stand-chat-settings' => array( __( 'Settings', 'stand-chat' ), 'settings' ), ) : array( 'stand-chat' => array( __( 'Get started', 'stand-chat' ), 'welcome' ), 'stand-chat-widget' => array( __( 'Chat widget', 'stand-chat' ), 'widget' ), 'stand-chat-settings' => array( __( 'Settings', 'stand-chat' ), 'settings' ), ); foreach ( $pages as $slug => $page ) { $hook = add_submenu_page( 'stand-chat', $page[0] . ' ‹ ' . $title, $page[0], $capability, $slug, array( $this, 'render' ) ); if ( $hook ) { $this->screens[ (string) $hook ] = $page[1]; } } } /** * Renders a screen's mount point. */ public function render(): void { $screen = $this->current_screen(); ?>

screens[ (string) $hook ] ) ) { return; } $asset = Frontend::asset( 'admin' ); wp_enqueue_script( self::SCRIPT_HANDLE, STAND_CHAT_URL . 'build/admin.js', $asset['dependencies'], $asset['version'], true ); wp_enqueue_style( self::SCRIPT_HANDLE, STAND_CHAT_URL . 'build/admin.css', array( 'wp-components' ), $asset['version'] ); wp_set_script_translations( self::SCRIPT_HANDLE, 'stand-chat', STAND_CHAT_DIR . 'languages' ); wp_enqueue_media(); $rest = Plugin::get( Rest::class ); wp_add_inline_script( self::SCRIPT_HANDLE, 'window.standChatAdmin = ' . wp_json_encode( $this->boot_data( $this->screens[ (string) $hook ], $rest instanceof Rest ? $rest->state() : array() ) ) . ';', 'before' ); } /** * Data the admin app starts with. * * @param string $screen Screen. * @param array $state Initial state. * @return array */ private function boot_data( string $screen, array $state ): array { $user = wp_get_current_user(); $roles = array(); foreach ( wp_roles()->get_names() as $key => $name ) { $roles[] = array( 'key' => (string) $key, 'name' => translate_user_role( $name ), ); } return array( 'screen' => $screen, 'version' => STAND_CHAT_VERSION, 'restPath' => '/' . Rest::NAMESPACE, 'connection' => Connection::fingerprint( $this->connection->identity_seen() ), 'adminUrl' => admin_url( 'admin.php' ), 'homeUrl' => home_url( '/' ), 'urls' => array( 'app' => Service_Urls::app(), 'chats' => Service_Urls::app( '/' ), 'standins' => Service_Urls::app( '/agents' ), 'history' => Service_Urls::app( '/history' ), 'sites' => Service_Urls::app( '/sites' ), 'team' => Service_Urls::app( '/team' ), 'subscription' => Service_Urls::app( '/team#subscription' ), 'profile' => Service_Urls::app( '/profile' ), 'digest' => Service_Urls::app( '/digest' ), 'signup' => Service_Urls::website( '/#register' ), 'pricing' => Service_Urls::website( '/pricing' ), 'terms' => Service_Urls::website( '/terms' ), 'privacy' => Service_Urls::website( '/privacy' ), 'dpa' => Service_Urls::website( '/dpa' ), 'guide' => Service_Urls::website( '/guide/wordpress' ), 'widget' => Service_Urls::get( 'widget' ), 'policyGuide' => admin_url( 'options-privacy.php?tab=policyguide' ), 'permalinks' => admin_url( 'options-permalink.php' ), 'general' => admin_url( 'options-general.php' ), 'consentApi' => admin_url( 'plugin-install.php?s=wp-consent-api&tab=search&type=term' ), 'siteHealth' => admin_url( 'site-health.php' ), 'menus' => current_theme_supports( 'menus' ) && ! wp_is_block_theme() ? admin_url( 'nav-menus.php' ) : admin_url( 'site-editor.php?p=%2Fnavigation' ), ), 'wp' => array_merge( Prompt_Prefill::site(), array( 'locale' => get_locale(), 'language' => strtolower( (string) strtok( get_locale(), '_' ) ), 'siteIconId' => (int) get_option( 'site_icon' ), 'blockTheme' => wp_is_block_theme(), 'roles' => $roles, 'wooCommerce' => class_exists( 'WooCommerce' ), 'user' => array( 'email' => (string) $user->user_email, 'firstName' => (string) get_user_meta( $user->ID, 'first_name', true ), 'lastName' => (string) get_user_meta( $user->ID, 'last_name', true ), 'displayName' => (string) $user->display_name, ), ) ), 'state' => $state, ); } /** * The screen for the current admin page. */ private function current_screen(): string { $screen = get_current_screen(); $id = $screen ? (string) $screen->id : ''; return $this->screens[ $id ] ?? ( $this->connection->is_connected() ? 'overview' : 'welcome' ); } /** * After a single activation, opens Welcome once. */ public function redirect_after_activation(): void { $user = get_transient( 'stand_chat_activation_redirect' ); if ( false === $user ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only check of core's bulk-activation flag. if ( wp_doing_ajax() || is_network_admin() || isset( $_GET['activate-multi'] ) || get_current_user_id() !== (int) $user || ! current_user_can( self::capability() ) ) { return; } delete_transient( 'stand_chat_activation_redirect' ); wp_safe_redirect( admin_url( 'admin.php?page=stand-chat' ) ); exit; } /** * Notices shown once outside the plugin's pages. */ public function admin_wide_notices(): void { if ( ! current_user_can( self::capability() ) ) { return; } $screen = get_current_screen(); if ( $screen && ( isset( $this->screens[ (string) $screen->id ] ) || 'dashboard' === $screen->id ) ) { return; } $active = Notices::admin_wide( $this->connection ); $ids = array(); foreach ( $active as $notice ) { $ids[] = $notice['id']; printf( '

%2$s %4$s

', esc_attr( $notice['status'] ), esc_html( $notice['message'] ), esc_url( $notice['link'] ), esc_html( $notice['label'] ) ); // Shown once; the plugin's pages and Dashboard widget keep showing it. State::dismiss( $notice['id'] ); } // A problem that went away may come back later; show it again then. $dismissed = State::get( 'dismissed', array() ); $gone = array(); foreach ( array( 'admin_reconnect', 'admin_address', 'admin_duplicate' ) as $id ) { if ( is_array( $dismissed ) && isset( $dismissed[ $id ] ) && ! in_array( $id, $ids, true ) && ! $this->admin_condition( $id ) ) { $gone[] = $id; } } if ( ! empty( $gone ) ) { State::change( 'dismissed', static function ( $stored ) use ( $gone ): array { $stored = is_array( $stored ) ? $stored : array(); foreach ( $gone as $id ) { unset( $stored[ $id ] ); } return $stored; } ); } } /** * Whether an admin-wide notice's problem still exists. * * @param string $id Notice ID. */ private function admin_condition( string $id ): bool { switch ( $id ) { case 'admin_reconnect': return $this->connection->needs_reconnect(); case 'admin_address': return $this->connection->is_connected() && Environment::is_production() && Environment::domain_mismatch(); case 'admin_duplicate': $check = Installation_Check::last(); return $check && 'duplicate' === ( $check['status'] ?? '' ); } return false; } /** * "Get started" or "Settings" on the Plugins screen. * * @param array $links Action links. * @return array */ public function action_links( $links ) { $links = is_array( $links ) ? $links : array(); $set = $this->connection->is_connected() || '' !== Settings::get( 'site_id' ); array_unshift( $links, sprintf( '%s', esc_url( admin_url( $set ? 'admin.php?page=stand-chat-settings' : 'admin.php?page=stand-chat' ) ), $set ? esc_html__( 'Settings', 'stand-chat' ) : esc_html__( 'Get started', 'stand-chat' ) ) ); return $links; } /** * Docs link on the Plugins screen. * * @param array $meta Row meta. * @param string $file Plugin file. * @return array */ public function row_meta( $meta, $file ) { $meta = is_array( $meta ) ? $meta : array(); if ( plugin_basename( STAND_CHAT_FILE ) === $file ) { $meta[] = sprintf( '%s', esc_url( Service_Urls::website( '/guide/wordpress' ) ), esc_html__( 'Docs', 'stand-chat' ) ); } return $meta; } /** * Body class for the plugin's screens. * * @param string $classes Classes. */ public function body_class( $classes ): string { $screen = get_current_screen(); if ( $screen && isset( $this->screens[ (string) $screen->id ] ) ) { $classes .= ' stand-chat-screen'; } return (string) $classes; } }