*/ private array $pages = array(); /** * Hook suffixes returned by WordPress, keyed by page slug. * * @var array */ private array $hook_suffixes = array(); /** * Constructor. * * @param Container $container Container resolving the page controllers. */ public function __construct( private Container $container ) {} /** * Registers the menu with WordPress. * * Hooked to `admin_menu`. */ public function register(): void { $parent = $this->pages()[0] ?? null; if ( ! $parent instanceof AbstractPage ) { return; } $hook = add_menu_page( $parent->page_title(), __( 'FormPilot', 'devplugins-custom-form-builder' ), $parent->capability(), self::PARENT_SLUG, array( $parent, 'render' ), self::ICON, self::POSITION ); $this->remember( $parent, $hook ); foreach ( $this->pages() as $page ) { $hook = add_submenu_page( self::PARENT_SLUG, $page->page_title(), $page->menu_title(), $page->capability(), $page->slug(), array( $page, 'render' ) ); $this->remember( $page, $hook ); } } /** * Returns the hook suffix of every registered plugin screen. * * @return array Hook suffixes. */ public function hook_suffixes(): array { return array_values( $this->hook_suffixes ); } /** * Returns the hook suffix of a single screen. * * @param string $slug Page slug. * * @return string The hook suffix, or an empty string when the screen is not registered. */ public function hook_suffix( string $slug ): string { return $this->hook_suffixes[ $slug ] ?? ''; } /** * Determines whether a hook suffix belongs to a plugin screen. * * @param string $hook_suffix Hook suffix reported by WordPress. */ public function owns_screen( string $hook_suffix ): bool { return in_array( $hook_suffix, $this->hook_suffixes(), true ); } /** * Returns the registered controllers. * * @return array Page controllers in menu order. */ public function pages(): array { if ( array() === $this->pages ) { $this->pages = $this->build_pages(); } return $this->pages; } /** * Stores the hook suffix and wires the controller load handler. * * @param AbstractPage $page Page controller. * @param string|false $hook Hook suffix, or false when the user lacks the capability. */ private function remember( AbstractPage $page, string|false $hook ): void { if ( ! is_string( $hook ) || '' === $hook ) { return; } $this->hook_suffixes[ $page->slug() ] = $hook; add_action( 'load-' . $hook, array( $page, 'load' ) ); } /** * Instantiates the page controllers. * * The first entry becomes the top level menu item. * * @return array Page controllers in menu order. */ private function build_pages(): array { $classes = array( FormsPage::class, FormEditPage::class, EntriesPage::class, SettingsPage::class, ToolsPage::class, ); $pages = array(); foreach ( $classes as $class_name ) { $pages[] = $this->container->get( $class_name ); } /** * Filters the admin screens registered by the plugin. * * Every entry must extend {@see AbstractPage}; the first one backs the * top level menu item. Resolve add-on screens from the container so they * receive their dependencies the same way the built in ones do. * * @since 1.0.0 * * @param array $pages Page controllers in menu order. * @param Container $container Container resolving the controllers. */ $pages = (array) apply_filters( 'formpilot_admin_pages', $pages, $this->container ); return array_values( array_filter( $pages, static fn( $page ): bool => $page instanceof AbstractPage ) ); } }