|null */ private static $items = null; /** * Hook admin_bar_menu (41, after "Edit Site"). * * @param \WP_Admin_Bar $bar The toolbar. */ public static function add( $bar ): void { if ( ! $bar instanceof \WP_Admin_Bar ) { return; } $items = self::items(); if ( count( $items ) < self::MIN ) { return; } $bar->add_node( array( 'id' => self::ID, 'title' => '' . esc_html_x( 'Rooster', 'the toolbar node of the ecosystem', 'ai-eshop-optimizer' ) . '', 'href' => reset( $items )[1], 'meta' => array( 'title' => __( 'The Rooster products on this site', 'ai-eshop-optimizer' ) ), ) ); foreach ( $items as $slug => [ $title, $href ] ) { $bar->add_node( array( 'id' => self::ID . '-' . $slug, 'parent' => self::ID, 'title' => $title, 'href' => $href, ) ); } } /** * Hook admin_enqueue_scripts: the node's icon on the toolbar's line. A dashicon in `.ab-icon` sits 2 px above * WordPress's own toolbar icons, which core moves down by 2 px each (`#wp-admin-bar-customize > .ab-item:before` and * the rest); the same here, only where the node shows. */ public static function style(): void { if ( ! function_exists( 'is_admin_bar_showing' ) || ! is_admin_bar_showing() || count( self::items() ) < self::MIN ) { return; } wp_add_inline_style( 'admin-bar', '#wpadminbar #wp-admin-bar-' . self::ID . ' .ab-icon:before{top:2px}' ); } /** * The products the person may open, decided once per login session (see the file comment). * * @return array slug => [ name, Home URL ], in the registry's order. */ public static function items(): array { if ( null !== self::$items ) { return self::$items; } $user = get_current_user_id(); $token = function_exists( 'wp_get_session_token' ) ? (string) wp_get_session_token() : ''; if ( ! $user || '' === $token ) { return self::$items = self::read(); // no login session to keep it in (an application password, a test) } $key = hash( 'sha256', $token . '|' . Library::VERSION ); $saved = get_user_meta( $user, self::META, true ); if ( is_array( $saved ) && hash_equals( $key, (string) ( $saved['session'] ?? '' ) ) && is_array( $saved['items'] ?? null ) ) { return self::$items = $saved['items']; } self::$items = self::read(); update_user_meta( $user, self::META, array( 'session' => $key, 'items' => self::$items ) ); return self::$items; } /** * Hooks activated_plugin, deactivated_plugin and switch_theme: the products may have changed, so every person's next * admin page decides again (one query, on those events only). */ public static function forget(): void { self::$items = null; delete_metadata( 'user', 0, self::META, '', true ); } /** * The registry's products whose Home the person may open. * * @return array */ private static function read(): array { $registry = Library::registry(); if ( null === $registry ) { return array(); } $items = array(); foreach ( $registry->products() as $slug => $product ) { if ( ! $product['home']['enabled'] || ! current_user_can( $product['home']['capability'] ) ) { // phpcs:ignore WordPress.WP.Capabilities.Undetermined -- the product's own capability (the catalogue is not read here). continue; } $items[ (string) $slug ] = array( (string) $product['name'], (string) $registry->home_url( $slug ) ); } return $items; } /** * Tests only: forget this request's items. */ public static function reset(): void { self::$items = null; } }