hook_suffix ) || ! function_exists( 'get_current_screen' ) ) {
return false;
}
$screen = get_current_screen();
return $screen && $screen->id === $this->hook_suffix;
}
/**
* Open a hidden wrapper before admin notices render
*
* WordPress core relocates stray `.notice` elements to just after the first
* `.wp-header-end` node. Opening the wrapper — and printing the catcher —
* before any notice fires collects them all inside a hidden container, so
* the React header can sit at the very top of the page.
*
* @since 2.0.2
*
* @return void
*/
public function inject_before_notices() {
if ( ! $this->is_texty_admin_page() ) {
return;
}
echo '
';
echo '';
}
/**
* Close the hidden notice wrapper opened in inject_before_notices()
*
* @since 2.0.2
*
* @return void
*/
public function inject_after_notices() {
if ( ! $this->is_texty_admin_page() ) {
return;
}
echo '
';
}
/**
* Add Texty admin menu
*
* @return void
*/
public function register_menu() {
global $submenu;
$capability = apply_filters( 'texty_admin_menu_capability', 'manage_options' );
if ( ! current_user_can( $capability ) ) {
return;
}
$menu_position = apply_filters( 'texty_menu_position', 58 );
$slug = 'texty';
$menu_icon = 'data:image/svg+xml;base64,' . base64_encode( '' ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
$dashboard = add_menu_page(
__( 'Texty', 'texty' ),
__( 'Texty', 'texty' ),
$capability,
$slug,
[ $this, 'render_page' ],
$menu_icon,
$menu_position
);
$this->hook_suffix = $dashboard;
$submenus = apply_filters(
'texty_admin_menu',
[
[
'path' => 'dashboard',
'title' => __( 'Dashboard', 'texty' ),
],
[
'path' => 'gateway',
'title' => __( 'Gateway', 'texty' ),
],
[
'path' => 'notifications',
'title' => __( 'Notifications', 'texty' ),
],
[
'path' => 'logs',
'title' => __( 'Logs', 'texty' ),
],
],
$capability,
$slug
);
foreach ( $submenus as $item ) {
$path = 'admin.php?page=' . $slug . '#/' . $item['path'];
if ( filter_var( $item['path'], FILTER_VALIDATE_URL ) ) {
$path = $item['path'];
}
$submenu[ $slug ][] = [ // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$item['title'],
$capability,
$path,
];
}
do_action( 'texty_admin_menu', $capability, $menu_position );
add_action( 'admin_print_scripts-' . $dashboard, [ $this, 'enqueue_scripts' ] );
}
/**
* Render the page
*
* The header mounts in its own root so it paints at the very top of the
* screen, above the captured admin notices. The notice slot deliberately
* stays outside the `.texty-app` Tailwind scope — that scope's
* importantized preflight would strip core notice styling. Captured
* notices are moved into it on the JS side.
*
* @return void
*/
public function render_page() {
echo '';
echo '';
echo '';
}
/**
* Enqueue JS and CSS
*
* @return void
*/
public function enqueue_scripts() {
$asset_path = TEXTY_DIR . '/dist/index.asset.php';
if ( ! file_exists( $asset_path ) ) {
error_log( 'Texty: missing build artifact ' . $asset_path . ' — run `npm run build`. The admin app was not enqueued.' );
return;
}
$asset_file = include $asset_path;
$deps = isset( $asset_file['dependencies'] ) && is_array( $asset_file['dependencies'] ) ? $asset_file['dependencies'] : [];
$version = isset( $asset_file['version'] ) ? $asset_file['version'] : TEXTY_VERSION;
// The shared bundles (`texty-plugin-ui` → window.texty.pluginUi,
// `texty-components` → window.texty.components) are registered globally
// by Texty\Assets so add-ons can depend on them on both admin and
// storefront. `texty-plugin-ui` is already listed in $deps here, since
// the SPA imports @wedevs/plugin-ui.
//
// WP silently drops a handle whose dependency is unregistered, so an
// unbuilt shared entry would blank the admin page with no error. Say so
// in the log instead of failing invisibly.
foreach ( $deps as $dep ) {
if ( strpos( $dep, 'texty-' ) === 0 && ! wp_script_is( $dep, 'registered' ) ) {
error_log( 'Texty: shared script handle "' . $dep . '" is not registered — the admin app will not load. Run `npm run build`.' );
}
}
wp_register_script(
'texty-admin',
TEXTY_URL . '/dist/index.js',
$deps,
$version,
true
);
// Merged instead of wp_localize_script's `var texty = {…}`: the shared
// bundles assign onto the same `window.texty` namespace and print
// *before* this script (they are dependencies of it), so a plain
// assignment would wipe out window.texty.pluginUi and crash the SPA.
$localized = wp_json_encode( $this->localize_script() );
wp_add_inline_script(
'texty-admin',
'window.texty = Object.assign( window.texty || {}, ' . ( $localized ? $localized : '{}' ) . ' );',
'before'
);
$vendor_style_deps = [ 'wp-components' ];
if ( wp_style_is( 'texty-plugin-ui', 'registered' ) ) {
$vendor_style_deps[] = 'texty-plugin-ui';
}
wp_register_style(
'texty-vendor-css',
TEXTY_URL . '/dist/style-index.css',
$vendor_style_deps,
$version
);
wp_register_style(
'texty-css',
TEXTY_URL . '/dist/index.css',
[ 'texty-vendor-css' ],
$version
);
wp_enqueue_script( 'texty-admin' );
wp_enqueue_style( 'texty-css' );
}
/**
* Get the localize script
*
* @return array
*/
public function localize_script() {
$i18n = [
'asset_url' => trailingslashit( TEXTY_URL ) . 'assets/',
'site_name' => get_bloginfo( 'name' ),
'rest_url' => esc_url_raw( rest_url() ),
'ajax_url' => esc_url_raw( admin_url( 'admin-ajax.php' ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'version' => [
'lite' => TEXTY_VERSION,
],
];
return apply_filters( 'texty_localize_script', $i18n );
}
}