' . esc_html__( 'Settings', 'helpo' ) . ''
);
return $links;
}
/**
* The single admin page. The React app takes over #kasp-admin-root.
*
* @return void
*/
public function render_page() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You do not have permission to view this page.', 'helpo' ) );
}
// Read-only status flags set by the connect redirects. They are escaped
// on output and carry no privileged meaning.
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$error_slug = isset( $_GET['kasp_error'] ) ? sanitize_key( wp_unslash( $_GET['kasp_error'] ) ) : '';
// phpcs:enable WordPress.Security.NonceVerification.Recommended
echo '
',
esc_html__( 'Helpo rejected this site’s connection. Reconnect to restore your AI support agent.', 'helpo' ),
esc_url( Kasp_Connect::connect_url() ),
esc_html__( 'Reconnect', 'helpo' )
);
}
if ( ! $this->has_build() ) {
printf(
'
%s
',
esc_html__( 'The helpo admin interface has not been built yet. Run "npm install && npm run build" in the plugin folder.', 'helpo' )
);
}
echo '';
printf(
'',
esc_html__( 'Helpo needs JavaScript enabled in wp-admin.', 'helpo' )
);
echo '
';
}
/**
* Enqueue the built React app on our page, and the tiny dashboard-widget
* script on the WordPress Dashboard.
*
* @param string $hook Current admin page hook.
* @return void
*/
public function enqueue_assets( $hook ) {
if ( 'index.php' === $hook ) {
$this->enqueue_dashboard_widget_script();
return;
}
if ( 'toplevel_page_' . self::PAGE_SLUG !== $hook || ! current_user_can( 'manage_options' ) ) {
return;
}
$asset = $this->asset_manifest();
wp_enqueue_script(
self::SCRIPT_HANDLE,
KASP_PLUGIN_URL . 'build/index.js',
$asset['dependencies'],
$asset['version'],
true
);
$style = KASP_PLUGIN_DIR . 'build/index.css';
if ( file_exists( $style ) ) {
wp_enqueue_style(
self::SCRIPT_HANDLE,
KASP_PLUGIN_URL . 'build/index.css',
array( 'wp-components' ),
$asset['version']
);
// @wordpress/scripts emits build/index-rtl.css alongside the LTR
// sheet; without this, right-to-left admins get the LTR layout.
if ( file_exists( KASP_PLUGIN_DIR . 'build/index-rtl.css' ) ) {
wp_style_add_data( self::SCRIPT_HANDLE, 'rtl', 'replace' );
}
}
if ( function_exists( 'wp_set_script_translations' ) ) {
wp_set_script_translations( self::SCRIPT_HANDLE, 'helpo', KASP_PLUGIN_DIR . 'languages' );
}
wp_localize_script( self::SCRIPT_HANDLE, 'kaspAdmin', $this->script_data() );
}
/**
* The data handed to the React app.
*
* Deliberately excludes the integration token — the app reaches helpo only
* through the kasp/v1 REST namespace.
*
* @return array
*/
private function script_data() {
$connection = Kasp_Settings::get_connection();
$connected = Kasp_Settings::is_connected();
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$just_connected = isset( $_GET['connected'] ) && '1' === $_GET['connected'];
$disconnected = isset( $_GET['disconnected'] ) && '1' === $_GET['disconnected'];
// phpcs:enable WordPress.Security.NonceVerification.Recommended
return array(
'restUrl' => esc_url_raw( rest_url( Kasp_Rest::NAMESPACE_V1 . '/' ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'connected' => $connected,
'connectionBroken' => Kasp_Settings::is_connection_broken(),
'justConnected' => $just_connected,
'justDisconnected' => $disconnected,
'connectUrl' => Kasp_Connect::connect_url(),
'disconnectUrl' => Kasp_Connect::disconnect_url(),
'appUrl' => kasp_app_url(),
'widgetUrl' => kasp_widget_url(),
'siteUrl' => home_url( '/' ),
'siteName' => wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ),
'adminUrl' => admin_url( 'admin.php?page=' . self::PAGE_SLUG ),
'pluginVersion' => KASP_VERSION,
'settings' => Kasp_Settings::get_settings(),
'woo' => Kasp_Woo::status(),
'projectId' => $connected ? $connection['project_id'] : '',
'projectName' => $connected ? $connection['project_name'] : '',
// Public key: it is printed in the front-end embed snippet anyway.
'siteKey' => $connected ? $connection['site_key'] : '',
);
}
/**
* Dependencies and cache-busting version for the built bundle.
*
* @return array
*/
private function asset_manifest() {
$manifest = KASP_PLUGIN_DIR . 'build/index.asset.php';
if ( file_exists( $manifest ) ) {
$asset = include $manifest;
if ( is_array( $asset ) ) {
return array(
'dependencies' => isset( $asset['dependencies'] ) ? (array) $asset['dependencies'] : array(),
'version' => isset( $asset['version'] ) ? (string) $asset['version'] : KASP_VERSION,
);
}
}
return array(
'dependencies' => array( 'wp-element', 'wp-i18n', 'wp-api-fetch', 'wp-components' ),
'version' => KASP_VERSION,
);
}
/**
* Has the admin app been built?
*
* @return bool
*/
private function has_build() {
return file_exists( KASP_PLUGIN_DIR . 'build/index.js' );
}
/* --------------------------------------------------------------------- */
/* WordPress Dashboard widget */
/* --------------------------------------------------------------------- */
/**
* Register the "at a glance" card.
*
* @return void
*/
public function register_dashboard_widget() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
wp_add_dashboard_widget(
'kasp_dashboard_widget',
__( 'helpo — AI Support Chat', 'helpo' ),
array( $this, 'render_dashboard_widget' )
);
}
/**
* Server-rendered card. When connected, the placeholders below are filled
* in by one small fetch to kasp/v1/bootstrap.
*
* @return void
*/
public function render_dashboard_widget() {
$page_url = admin_url( 'admin.php?page=' . self::PAGE_SLUG );
if ( ! Kasp_Settings::is_connected() ) {
printf(
'
',
esc_html__( 'Connect this site to helpo to add an AI support agent trained on your own content.', 'helpo' ),
esc_url( Kasp_Connect::connect_url() ),
esc_html__( 'Connect to helpo', 'helpo' )
);
return;
}
$project = Kasp_Settings::get_project_name();
echo '