|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;
}
}