runtime_loader = $runtime_loader; } /** * Register submenu/asset hooks, run the concrete setup(), and boot every * ordinary module this Section declares through section_nav_modules(). * * @return void */ public function init() { add_action( 'adminoptimizer_add_submenu_page', [ $this, 'add_submenu' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); $this->setup(); foreach ( array_keys( $this->get_nav_modules() ) as $module_id ) { $this->runtime_loader->boot_module( $module_id ); } } /** * Concrete Section-level runtime setup. Overridden by subclasses to wire * their own hooks and features; the base implementation is a no-op. * * @return void */ public function setup() {} /** * Register this Section's submenu page and, when successful, its admin * screen hook suffix with Module_Registry. Fired on * `adminoptimizer_add_submenu_page`. * * @return void */ public function add_submenu() { $this->page_hook = add_submenu_page( ADMINOPTIMIZER_MODULES_MENU_SLUG, $this->screen_title(), $this->screen_title(), 'manage_options', static::PAGE_SLUG, [ $this, 'render' ] ); if ( is_string( $this->page_hook ) && '' !== static::SECTION_ID ) { Module_Registry::register_admin_screen( static::SECTION_ID, $this->page_hook ); } } /** * The Section's screen/menu title. * * @return string */ abstract protected function screen_title(): string; /** * Enqueue shared Section assets on this Section's own screen, then * delegate to the renderer for the currently selected module's assets. * * @param string $hook_suffix Current admin page hook suffix. * * @return void */ public function enqueue_assets( string $hook_suffix ) { if ( ! $this->is_own_screen( $hook_suffix ) ) { return; } wp_enqueue_style( 'adminoptim-sections', ADMINOPTIMIZER_ADMIN_URI . 'assets/css/sections.min.css', [], ADMINOPTIMIZER_VERSION ); wp_enqueue_script( 'adminoptim-module-activation', ADMINOPTIMIZER_ADMIN_URI . 'assets/js/module-activation.min.js', [], ADMINOPTIMIZER_VERSION, true ); wp_enqueue_script( 'adminoptim-section-screen', ADMINOPTIMIZER_ADMIN_URI . 'assets/js/section-screen.min.js', [ 'adminoptim-module-activation' ], ADMINOPTIMIZER_VERSION, true ); wp_localize_script( 'adminoptim-module-activation', 'AdminOptimizerModuleActivation', [ 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'action' => Module_Activation::ACTION, 'nonce' => wp_create_nonce( Module_Activation::NONCE_ACTION ), ] ); wp_enqueue_script( 'adminoptim-pin-activation', ADMINOPTIMIZER_ADMIN_URI . 'assets/js/pin-activation.min.js', [], ADMINOPTIMIZER_VERSION, true ); wp_localize_script( 'adminoptim-pin-activation', 'AdminOptimizerPinActivation', [ 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'action' => Module_Activation::PIN_ACTION, 'nonce' => wp_create_nonce( Module_Activation::PIN_NONCE_ACTION ), ] ); wp_enqueue_script( 'adminoptim-module-jump', ADMINOPTIMIZER_ADMIN_URI . 'assets/js/module-jump.min.js', [], ADMINOPTIMIZER_VERSION, true ); $this->renderer()->enqueue_assets( $hook_suffix ); } /** * Whether the current admin screen is this Section's own screen. * * @param string $hook_suffix Current admin page hook suffix. * * @return bool */ private function is_own_screen( string $hook_suffix ): bool { return is_string( $this->page_hook ) && $hook_suffix === $this->page_hook; } /** * The shared renderer for this Section, constructed lazily since it is * only needed when the screen actually renders/enqueues. * * @return Section_Renderer */ protected function renderer(): Section_Renderer { if ( null === $this->renderer ) { $this->renderer = new Section_Renderer( static::SECTION_ID, static::PAGE_SLUG, $this->get_nav_modules(), $this->runtime_loader, $this->section_feature_group() ); } return $this->renderer; } /** * Nav modules for this Section, enriched via the `adminoptim_section_nav_module` * filter. Final so every Section's nav goes through the same filter seam — * subclasses provide their own list via `section_nav_modules()` instead of * overriding this directly. * * @return array */ final protected function get_nav_modules() { $nav_modules = apply_filters( 'adminoptim_section_nav_module', $this->section_nav_modules(), static::SECTION_ID ); return is_array( $nav_modules ) ? $nav_modules : []; } /** * @return array */ protected function section_nav_modules(): array { return []; } /** * Optional Section-owned presentation group of explicit SECTION_FEATURE * members. * * @return array{label?:string,description?:string,members?:array}|null */ protected function section_feature_group(): ?array { return null; } /** * Render the Section screen: header, Pro upsell notice/button when Pro * is inactive, and the composed Section content via Section_Renderer. * * @return void */ public function render() { if ( '' !== static::SECTION_ID && ! Module_Registry::is_pro_active() ) { Notification::request_toast( static::SECTION_ID, 'pro-upgrade', [ 'title' => __( 'Unlock more with Admin Optimizer Pro', 'admin-optimizer' ), 'message' => __( 'Get advanced features, automation, AI tools, and additional controls across the Admin Optimizer toolkit.', 'admin-optimizer' ), 'actions' => [ [ 'type' => 'link', 'label' => __( 'View Pro', 'admin-optimizer' ), 'url' => ao_get_upgrade_url( static::SECTION_ID . '-toast' ), 'style' => 'primary', ], ], ] ); } ?>

screen_title() ); ?>


renderer()->render_section(); ?>