self::TAB_SLUG, 'label' => __( 'File Manager', 'acrossai-abilities-manager' ), 'priority' => 30, ); return $tabs; } /** * Register the placeholder settings section that hosts the React mount. * * The TabbedPageRenderer wraps every tab in a Settings API form and calls * `do_settings_sections()` on the tab-scoped page slug — so we need one * section registered against that slug to give React a container. The * section render callback emits the mount div; React takes over from there. * * @return void */ public function register_settings(): void { if ( ! class_exists( '\\AcrossAI_Main_Menu\\SettingsPage' ) ) { return; } $renderer = \AcrossAI_Main_Menu\SettingsPage::get_settings_renderer(); if ( ! $renderer ) { return; } $page_slug = $renderer->tab_page_slug( self::TAB_SLUG ); add_settings_section( 'acrossai_file_manager_root', '', // Empty section heading — the React panels supply their own. array( $this, 'render_root' ), $page_slug ); } /** * Section-body render callback. Emits the React mount + a small note. * * @return void */ public function render_root(): void { printf( '
', esc_attr( self::ROOT_ID ), esc_html__( 'The File Manager settings require JavaScript to be enabled.', 'acrossai-abilities-manager' ) ); } /** * Enqueue the React bundle when this tab is the active one on the shared * settings page. * * Hooked to `admin_enqueue_scripts`. * * @param string $hook_suffix Current admin page hook. * @return void */ public function enqueue_assets( string $hook_suffix ): void { if ( ! $this->is_active_tab_page( $hook_suffix ) ) { return; } $build_url = defined( 'ACROSSAI_ABILITIES_MANAGER_PLUGIN_URL' ) ? \ACROSSAI_ABILITIES_MANAGER_PLUGIN_URL . 'build/' : plugins_url( 'build/', dirname( __DIR__, 2 ) . '/acrossai-abilities-manager.php' ); $build_path = defined( 'ACROSSAI_ABILITIES_MANAGER_PLUGIN_PATH' ) ? \ACROSSAI_ABILITIES_MANAGER_PLUGIN_PATH . 'build/' : dirname( __DIR__, 2 ) . '/build/'; $asset_file = $build_path . 'js/file-manager-settings.asset.php'; if ( ! file_exists( $asset_file ) ) { // Bundle hasn't been built yet — nothing to enqueue. return; } $asset = require $asset_file; wp_enqueue_script( 'acrossai-file-manager-settings', $build_url . 'js/file-manager-settings.js', (array) ( $asset['dependencies'] ?? array() ), (string) ( $asset['version'] ?? '0.0.0' ), true ); wp_localize_script( 'acrossai-file-manager-settings', 'acrossaiFileManagerSettings', array( 'restNamespace' => 'acrossai/v1', 'restBase' => 'file-manager-settings', 'nonce' => wp_create_nonce( 'wp_rest' ), ) ); if ( file_exists( $build_path . 'css/file-manager-settings.css' ) ) { wp_enqueue_style( 'acrossai-file-manager-settings', $build_url . 'css/file-manager-settings.css', array(), (string) ( $asset['version'] ?? '0.0.0' ) ); } // Suppress the vendor tab's Save button — our React panels save // themselves via REST and the form Save is unused / confusing here. wp_add_inline_style( 'acrossai-file-manager-settings', '.wrap form:has(#' . esc_attr( self::ROOT_ID ) . ') .submit { display: none !important; }' ); } /** * Detect whether the current admin page is the shared settings page AND * this tab is the active one. * * @param string $hook_suffix Current admin page hook. * @return bool */ private function is_active_tab_page( string $hook_suffix ): bool { // The shared settings page hook is stable across setups — the page // slug is 'acrossai-settings' and lands as 'acrossai_page_acrossai-settings'. if ( false === strpos( $hook_suffix, 'acrossai-settings' ) ) { return false; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : ''; return self::TAB_SLUG === $tab; } }