';
/**
* 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(
'',
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;
}
}