*/ public array $config = array(); /** * Derived class names keyed by component. * * @var array> */ public array $class_names = array(); /** * Localized strings. * * @var array */ public array $strings = array(); /** * Style configuration. * * @var array */ public array $style = array(); /** * Base class prefix shared by all banners. * * @var string */ public string $base_prefix = 'wz-admin-banner'; /** * Unique class prefix derived from the provided prefix. * * @var string */ public string $unique_prefix = 'admin-banner'; /** * Constructor. * * @param array $config Configuration arguments for the banner. */ public function __construct( array $config ) { $defaults = array( 'capability' => 'manage_options', 'allow_network' => false, 'prefix' => '', 'screen_ids' => array(), 'page_slugs' => array(), 'sections' => array(), 'exclude_screen_bases' => array( 'post', 'post-new' ), 'strings' => array(), 'link_target' => '_self', 'style' => array(), ); $this->config = wp_parse_args( $config, $defaults ); $this->strings = $this->prepare_strings( $this->config['strings'] ?? array() ); $this->config['sections'] = $this->sanitize_sections( $this->config['sections'] ); $this->unique_prefix = $this->resolve_wrapper_prefix( (string) $this->config['prefix'] ); $this->class_names = $this->derive_class_names(); $this->style = $this->prepare_style_config( $this->config['style'] ?? array() ); if ( empty( $this->config['screen_ids'] ) ) { $this->config['screen_ids'] = $this->collect_targets_from_sections( 'screen_ids' ); } if ( empty( $this->config['page_slugs'] ) ) { $this->config['page_slugs'] = $this->collect_targets_from_sections( 'page_slugs' ); } $this->hooks(); } /** * Register hooks. */ public function hooks(): void { Hook_Registry::add_action( 'admin_enqueue_scripts', array( $this, 'maybe_enqueue_styles' ) ); Hook_Registry::add_action( 'in_admin_header', array( $this, 'render' ) ); } /** * Enqueue banner styles if required on the current screen or page slug. */ public function maybe_enqueue_styles(): void { if ( empty( $this->style['url'] ) ) { return; } $screen = ! is_network_admin() ? get_current_screen() : null; $page_slug = $this->get_request_page_slug(); if ( $screen instanceof \WP_Screen && $this->should_render_on_screen( $screen, $page_slug ) ) { $this->enqueue_style(); return; } if ( '' !== $page_slug && in_array( $page_slug, $this->config['page_slugs'], true ) ) { $this->enqueue_style(); } } /** * Render the admin banner markup when conditions are met. */ public function render(): void { if ( is_network_admin() && ! $this->config['allow_network'] ) { return; } $screen = get_current_screen(); if ( ! ( $screen instanceof \WP_Screen ) || ! current_user_can( $this->config['capability'] ) ) { return; } $page_slug = $this->get_request_page_slug(); if ( ! $this->should_render_on_screen( $screen, $page_slug ) ) { return; } $current_section = $this->resolve_current_section( $screen, $page_slug ); ?>
strings['eyebrow'] ) ) : ?> strings['eyebrow'] ); ?> strings['title'] ) ) : ?>

strings['title'] ); ?>

strings['text'] ) ) : ?>

strings['text'] ); ?>

style['handle'], $this->style['url'], (array) $this->style['deps'], $this->style['version'] ); wp_enqueue_style( $this->style['handle'] ); } /** * Determine whether the banner should display on the current screen. * * @param \WP_Screen $screen Current admin screen. * @param string $page_slug Current request page slug. */ public function should_render_on_screen( \WP_Screen $screen, string $page_slug ): bool { $screen_base = (string) $screen->base; if ( '' !== $screen_base && in_array( $screen_base, (array) $this->config['exclude_screen_bases'], true ) ) { return false; } $screen_id = (string) $screen->id; if ( '' !== $screen_id && in_array( $screen_id, (array) $this->config['screen_ids'], true ) ) { return true; } if ( '' !== $page_slug && in_array( $page_slug, (array) $this->config['page_slugs'], true ) ) { return true; } return false; } /** * Resolve the banner section to highlight based on current screen or page slug. * * @param \WP_Screen $screen Current admin screen. * @param string $page_slug Current request page slug. */ public function resolve_current_section( \WP_Screen $screen, string $page_slug ): string { $screen_id = (string) $screen->id; foreach ( $this->config['sections'] as $section_key => $section ) { if ( ! empty( $section['screen_ids'] ) && in_array( $screen_id, (array) $section['screen_ids'], true ) ) { return $section_key; } } foreach ( $this->config['sections'] as $section_key => $section ) { if ( ! empty( $section['page_slugs'] ) && in_array( $page_slug, (array) $section['page_slugs'], true ) ) { return $section_key; } } return ''; } /** * Prepare localized strings. * * @param array $strings Raw strings array. */ public function prepare_strings( array $strings ): array { $defaults = array( 'region_label' => '', 'nav_label' => '', 'eyebrow' => '', 'title' => '', 'text' => '', ); return wp_parse_args( $strings, $defaults ); } /** * Resolve the wrapper prefix based on base prefix provided. * * @param string $prefix Base prefix. */ public function resolve_wrapper_prefix( string $prefix ): string { $prefix = sanitize_key( $prefix ); if ( '' === $prefix ) { return $this->base_prefix; } return false === strpos( $prefix, $this->base_prefix ) ? "{$prefix}-admin-banner" : $prefix; } /** * Prepare style configuration. * * @param array $style Style configuration. */ public function prepare_style_config( array $style ): array { $defaults = array( 'handle' => $this->sanitize_handle( "{$this->unique_prefix}-styles" ), 'deps' => array(), 'version' => self::DEFAULT_STYLE_VERSION, 'filename' => 'admin-banner', 'url' => '', ); $style_config = wp_parse_args( $style, $defaults ); if ( empty( $style_config['url'] ) ) { $assets_base = trailingslashit( plugin_dir_url( __FILE__ ) ) . 'css/'; $min_suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; $rtl_suffix = is_rtl() ? '-rtl' : ''; $style_config['url'] = $assets_base . $style_config['filename'] . $rtl_suffix . $min_suffix . '.css'; } return $style_config; } /** * Sanitize the sections configuration. * * @param array $sections Sections configuration. * * @return array */ public function sanitize_sections( array $sections ): array { $sanitized = array(); foreach ( $sections as $key => $section ) { if ( empty( $section['label'] ) || empty( $section['url'] ) ) { continue; } $section_key = sanitize_key( $key ); $sanitized[ $section_key ] = array( 'label' => $section['label'], 'url' => $section['url'], 'type' => isset( $section['type'] ) ? sanitize_key( $section['type'] ) : 'secondary', 'target' => isset( $section['target'] ) ? $section['target'] : '_self', 'rel' => isset( $section['rel'] ) ? $section['rel'] : '', 'screen_ids' => isset( $section['screen_ids'] ) ? (array) $section['screen_ids'] : array(), 'page_slugs' => isset( $section['page_slugs'] ) ? array_map( 'sanitize_key', (array) $section['page_slugs'] ) : array(), ); } return $sanitized; } /** * Derive class names following the provided prefix alongside the base prefix. * * @return array> */ public function derive_class_names(): array { $build = function ( string $suffix = '' ): array { $classes = array( $this->base_prefix . $suffix ); if ( $this->unique_prefix !== $this->base_prefix ) { $classes[] = $this->unique_prefix . $suffix; } return $classes; }; return array( 'wrapper' => $build(), 'intro' => $build( '__intro' ), 'eyebrow' => $build( '__eyebrow' ), 'title' => $build( '__title' ), 'text' => $build( '__text' ), 'links_wrapper' => $build( '__links' ), 'link' => $build( '__link' ), 'link_primary' => $build( '__link--primary' ), 'link_secondary' => $build( '__link--secondary' ), 'link_current' => $build( '__link--current' ), 'link_new' => $build( '__link--new' ), ); } /** * Collect screen IDs or page slugs from the sections configuration. * * @param string $target_key screen_ids|page_slugs key. * * @return array */ public function collect_targets_from_sections( string $target_key ): array { $values = array(); foreach ( $this->config['sections'] as $section ) { if ( empty( $section[ $target_key ] ) ) { continue; } foreach ( (array) $section[ $target_key ] as $value ) { $values[] = (string) $value; } } return array_values( array_unique( array_filter( $values ) ) ); } /** * Retrieve the CSS classes for a section link. * * @param array $section Section configuration. */ public function get_section_link_classes( array $section ): array { $classes = $this->class_names['link'] ?? array(); $type = isset( $section['type'] ) ? sanitize_key( $section['type'] ) : 'secondary'; $type = '' !== $type ? $type : 'secondary'; $type_key = "link_{$type}"; if ( isset( $this->class_names[ $type_key ] ) ) { $classes = array_merge( $classes, (array) $this->class_names[ $type_key ] ); } elseif ( isset( $this->class_names['link_secondary'] ) ) { $classes = array_merge( $classes, (array) $this->class_names['link_secondary'] ); } return array_values( array_unique( array_filter( $classes ) ) ); } /** * Implode a class array into a string. * * @param array $classes Class list. * @return string Class attribute string. */ public function implode_classes( array $classes ): string { return implode( ' ', array_unique( array_filter( $classes ) ) ); } /** * Retrieve a flattened class attribute by key. * * @param string $key Classes array key. * @return string Class attribute string. */ public function class_attr( string $key ): string { return $this->implode_classes( $this->class_names[ $key ] ?? array() ); } /** * Sanitize a style handle. * * @param string $handle Raw handle. */ public function sanitize_handle( string $handle ): string { return sanitize_title_with_dashes( $handle ); } /** * Get the current page slug from the request. */ public function get_request_page_slug(): string { if ( isset( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $page_raw = sanitize_text_field( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended } else { return ''; } $page_slug = strtolower( (string) strtok( $page_raw, '&' ) ); return sanitize_key( $page_slug ); } }