>
*/
private array $tucked = array();
private string $html = '';
private string $important = '';
private int $count = 0;
private bool $shown = false;
private bool $placed = false;
private static ?self $current = null;
/**
* Classes for a notice that stays in view because money or renewals are at risk now.
*/
public static function important( string $type, bool $dismissible = false ): string {
return 'notice notice-' . $type . ( $dismissible ? ' is-dismissible' : '' ) . ' ' . self::IMPORTANT;
}
/**
* Classes for the result of what the merchant just did: in view, and only on the page it was done on.
*/
public static function feedback( string $type, bool $dismissible = false ): string {
return self::important( $type, $dismissible ) . ' ' . self::FEEDBACK;
}
/**
* Submex's own screens and WooCommerce → Settings, plus the orders screens when asked: where a notice is about the page in front of the merchant.
*/
public static function in_context( bool $with_orders = false ): bool {
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
if ( ! $screen ) {
return false;
}
if ( str_contains( (string) $screen->id, Menu::SLUG ) || 'woocommerce_page_wc-settings' === $screen->id ) {
return true;
}
// The orders list and an order's screen, with and without HPOS.
return $with_orders && in_array( $screen->id, array( 'woocommerce_page_wc-orders', 'edit-shop_order', 'shop_order' ), true );
}
public function register(): void {
self::$current = $this;
add_action( 'in_admin_header', array( $this, 'collect' ), 1 );
// Last resort: a Submex screen that draws no shell would otherwise swallow them.
add_action( 'admin_footer', array( $this, 'render_leftovers' ), 99 );
}
public function collect(): void {
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
if ( ! $screen || ! str_contains( (string) $screen->id, Menu::SLUG ) ) {
return;
}
foreach ( self::HOOKS as $hook ) {
$this->tucked[ $hook ] = $this->take( $hook );
add_action( $hook, array( $this, 'run' ) );
}
}
/**
* Runs the callbacks that were on the notice hook now firing, sorting what each one printed.
*/
public function run(): void {
$hook = current_action();
foreach ( $this->tucked[ $hook ] ?? array() as $callback ) {
ob_start();
call_user_func( $callback );
$output = (string) ob_get_clean();
if ( '' === trim( $output ) ) {
continue;
}
if ( self::is_important( $output ) ) {
$this->important .= $output;
continue;
}
$this->html .= $output;
$this->count += self::count( $output );
}
unset( $this->tucked[ $hook ] );
}
/**
* The bell that opens them, for the header bar. A details element, so it opens without JavaScript.
*/
public static function render_toggle(): void {
$notices = self::$current;
if ( ! $notices || '' === $notices->html ) {
return;
}
$notices->shown = true;
$label = sprintf(
/* translators: %d: number of notifications */
_n( '%d notification', '%d notifications', $notices->count, 'submex' ),
$notices->count
);
printf(
' %s%s
'
. '