`. There is no score and no count in section headings. * Styling lives in `assets/css/health.css`. The page is fully expanded without * JavaScript; `inline_script()` only collapses the passing rows on load and * drives the "Show all passing checks" toggle and the "Jump to check" links. */ final class Health_Renderer { /** Style handle registered by whoever enqueues `assets/css/health.min.css`. */ const STYLE_HANDLE = 'adminoptim-health'; /** Handle of the (file-less) script that carries `inline_script()`. */ const SCRIPT_HANDLE = 'adminoptim-health'; /** Id of the wrapper around the check sections; the toggle's `aria-controls`. */ const LAYOUT_ID = 'adminoptim-health-layout'; /** Prefix of every row's DOM id, the target of "Jump to check". */ const ROW_ID_PREFIX = 'adminoptim-health-row-'; /** Most rows "Needs attention" lists: every Critical, then Recommended up to this total. */ const ATTENTION_LIMIT = 5; /** Statuses whose rows show the current / recommended / reason lines. */ const DETAIL_STATUSES = [ 'critical', 'warning', 'unverified' ]; /** * Dashicon per status. Decorative; the badge text carries the meaning. * * @var array */ const STATUS_ICONS = [ 'ok' => 'yes-alt', 'warning' => 'warning', 'critical' => 'dismiss', 'info' => 'info-outline', 'off' => 'minus', 'unverified' => 'editor-help', ]; /** * Dismiss / Restore form settings, or null when rows are read-only (the * dashboard widget). Keys: `url`, `dismiss_action`, `restore_action`, * `dismiss_nonce`, `restore_nonce`. * * @var array|null */ private static $controls = null; /** * Turn on the Dismiss / Restore controls for the rows rendered afterwards. * * @param array|null $controls Form settings, or null for none. * * @return void */ public static function set_dismiss_controls( ?array $controls ): void { self::$controls = $controls; } /** * Header strip: title, one-line description, "Last checked" and Refresh. * * @param array{title:string,description:string,generated_at:int,refresh_url:string} $args Header data. * * @return void */ public static function render_header( array $args ): void { ?>

' . esc_html( $number . ' ' . $label ) . ''; $spoken[] = $number . ' ' . strtolower( $label ); } echo '

'; echo '' . esc_html( implode( ', ', $spoken ) ) . ''; echo ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- every part is escaped above. if ( $toggle ) { // Hidden until the script runs: without JavaScript every row is already shown. printf( '', esc_attr( self::LAYOUT_ID ), esc_attr__( 'Show all passing checks', 'admin-optimizer' ), esc_attr__( 'Hide all passing checks', 'admin-optimizer' ) ); } echo '

'; } /** * Whether a row is a passing one: Good or Info. Those sit behind each * section's strip; Critical, Recommended, Couldn't verify and Off rows (and * the locked Pro row) always stay visible. * * @param array $row Normalized row. * * @return bool */ public static function is_passing_row( array $row ): bool { return empty( $row['locked'] ) && in_array( $row['status'] ?? '', [ 'ok', 'info' ], true ); } /** * Whether any section has rows that collapse (so the toggle is worth showing). * * @param array $sections Output of `Health_Engine::group_rows()`. * * @return bool */ public static function has_passing_rows( array $sections ): bool { foreach ( $sections as $section ) { if ( 'table' === $section['layout'] ) { continue; } foreach ( $section['rows'] as $row ) { if ( self::is_passing_row( $row ) ) { return true; } } } return false; } /** * Render all sections. Sections that name a `column` (1 or 2) go into a * responsive two-column grid in DOM order (column 1, then column 2, which is * also the single-column order); other row sections follow full width, then * table sections (Site information). * * @param array $sections Output of `Health_Engine::group_rows()`. * * @return void */ public static function render_sections( array $sections ): void { $columns = [ 1 => [], 2 => [], ]; $wide = []; $tables = []; foreach ( $sections as $section ) { $column = (int) ( $section['column'] ?? 0 ); if ( 'table' === $section['layout'] ) { $tables[] = $section; } elseif ( isset( $columns[ $column ] ) ) { $columns[ $column ][] = $section; } else { $wide[] = $section; } } echo '
'; if ( $columns[1] || $columns[2] ) { echo '
'; foreach ( $columns as $column_sections ) { if ( $column_sections ) { echo '
'; foreach ( $column_sections as $section ) { self::render_section( $section ); } echo '
'; } } echo '
'; } foreach ( array_merge( $wide, $tables ) as $section ) { self::render_section( $section ); } echo '
'; } /** * "Needs attention": every Critical item, then Recommended items in section * order, up to {@see self::ATTENTION_LIMIT} rows in total (Critical rows are * never cut). Omitted when there is nothing Critical or Recommended. Rows * repeat the status pill, title and one-line description, with a "Jump to * check" link instead of the row's own link and without the detail lines. * * @param array $sections Output of `Health_Engine::group_rows()`. * * @return void */ public static function render_attention( array $sections ): void { $critical = []; $recommended = []; $dismissed = []; foreach ( self::layout_order( $sections ) as $section ) { if ( 'table' === $section['layout'] ) { continue; } foreach ( $section['rows'] as $row ) { if ( ! empty( $row['locked'] ) ) { continue; } if ( 'critical' === $row['status'] ) { $critical[] = $row; } elseif ( ! empty( $row['dismissed'] ) ) { $dismissed[] = $row; } elseif ( 'warning' === $row['status'] ) { $recommended[] = $row; } } } // Dismissed items stay reachable, so the card shows for them alone too. if ( ! $critical && ! $recommended && ! $dismissed ) { return; } $room = max( 0, self::ATTENTION_LIMIT - count( $critical ) ); $shown = array_merge( $critical, array_slice( $recommended, 0, $room ) ); $hidden = count( $recommended ) - $room; $heading = 'adminoptim-health-attention'; ?>

: works without JavaScript and the summary is a real control. echo '
' . esc_html( sprintf( /* translators: %d: number of dismissed items. */ _n( '%d dismissed', '%d dismissed', count( $dismissed ), 'admin-optimizer' ), count( $dismissed ) ) ) . ''; self::render_rows( $dismissed, 'adminoptim-health__rows', true ); echo '
'; } if ( $shown ) { self::render_rows( $shown, 'adminoptim-health__rows', true ); } else { echo '

' . esc_html__( 'Nothing needs attention.', 'admin-optimizer' ) . '

'; } if ( $hidden > 0 ) { echo '

' . esc_html( sprintf( /* translators: %d: number of Recommended items not listed. */ _n( '+ %d more recommended item in the sections below', '+ %d more recommended items in the sections below', $hidden, 'admin-optimizer' ), $hidden ) ) . '

'; } ?>
$sections Sections. * * @return array */ private static function layout_order( array $sections ): array { $buckets = [ 1 => [], 2 => [], 3 => [], 4 => [], ]; foreach ( $sections as $section ) { $column = (int) ( $section['column'] ?? 0 ); if ( 'table' === $section['layout'] ) { $buckets[4][] = $section; } else { $buckets[ in_array( $column, [ 1, 2 ], true ) ? $column : 3 ][] = $section; } } return array_merge( ...array_values( $buckets ) ); } /** * The vanilla JavaScript for the Site Health tab (no library, no asset file). * Collapses the passing rows on load, drives the master toggle, and moves * "Jump to check" links to the full row: scroll, focus, brief highlight. After * Dismiss or Restore the page reloads at the row's anchor, so focus goes there. * * @return string */ public static function inline_script(): string { return '(function(){' . 'var layout=document.getElementById("' . self::LAYOUT_ID . '"),btn=document.querySelector(".adminoptim-health__toggle");' . 'if(layout){layout.classList.add("is-passing-collapsed");}' . 'if(layout&&btn){btn.hidden=false;btn.addEventListener("click",function(){' . 'var open=layout.classList.toggle("is-passing-collapsed")===false;' . 'btn.setAttribute("aria-expanded",open?"true":"false");btn.textContent=open?btn.dataset.hide:btn.dataset.show;});}' . 'document.addEventListener("click",function(e){' . 'var a=e.target.closest&&e.target.closest(".adminoptim-health__jump");if(!a){return;}' . 'var t=document.getElementById(a.getAttribute("href").slice(1));if(!t){return;}' . 'e.preventDefault();' . 'var calm=window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches;' . 't.scrollIntoView({behavior:calm?"auto":"smooth",block:"center"});' . 't.focus({preventScroll:true});t.classList.add("is-highlighted");' . 'setTimeout(function(){t.classList.remove("is-highlighted");},2000);});' . '})();'; } /** * Render one section card. * * @param array $section Section data. * * @return void */ private static function render_section( array $section ): void { $heading_id = 'adminoptim-health-' . $section['id']; $is_table = 'table' === $section['layout']; $collapsed = ! empty( $section['collapsed'] ); $tag = $collapsed ? 'details' : 'section'; ?> < class="adminoptim-health__section" aria-labelledby="">

> ' . esc_html( $section['description'] ) . '

'; } } /** * A section's checks: the rows that always show, then (when there are * passing rows) the strip and the Good / Info rows. The strip is a label * only; the CSS class the script adds hides the passing list. * * @param array $rows Section rows, already sorted by status. * * @return void */ private static function render_checks( array $rows ): void { $visible = []; $good = []; $info = []; foreach ( $rows as $row ) { if ( ! self::is_passing_row( $row ) ) { $visible[] = $row; } elseif ( 'ok' === $row['status'] ) { $good[] = $row; } else { $info[] = $row; } } // Problem rows first, then dismissed rows, then Off rows, then the locked Pro row (sort is stable). $rank = static function ( array $row ): int { if ( ! empty( $row['locked'] ) ) { return 3; } return 'off' === $row['status'] ? 2 : ( ! empty( $row['dismissed'] ) ? 1 : 0 ); }; usort( $visible, static function ( array $a, array $b ) use ( $rank ): int { return $rank( $a ) <=> $rank( $b ); } ); if ( $visible ) { self::render_rows( $visible ); } if ( $good || $info ) { self::render_passing_strip( $good, $info ); self::render_rows( array_merge( $good, $info ), 'adminoptim-health__rows adminoptim-health__rows--passing' ); } } /** * The strip: "✓ 5 Good HTTPS, file editing, …" wrapping over as many lines * as needed, with ", plus N Info" when Info rows exist. Text only. * * @param array $good Good rows. * @param array $info Info rows. * * @return void */ private static function render_passing_strip( array $good, array $info ): void { $lead = $good ? $good : $info; $status = $good ? 'ok' : 'info'; $names = implode( ', ', wp_list_pluck( $lead, 'label' ) ); $count = count( $lead ); ?>
$rows Rows. * @param string $list_class List class. * @param bool $attention Compact "Needs attention" form of each row. * * @return void */ public static function render_rows( array $rows, string $list_class = 'adminoptim-health__rows', bool $attention = false ): void { echo '
    '; foreach ( $rows as $row ) { self::render_row( $row, $attention ); } echo '
'; } /** * One row. * * @param array $row Normalized row. * @param bool $attention True for the compact "Needs attention" form: no id, no * detail lines, and a "Jump to check" link in place of the row's own. * * @return void */ private static function render_row( array $row, bool $attention = false ): void { $status = $row['status']; $locked = ! empty( $row['locked'] ); $dismissed = $row['dismissed'] ?? null; $icon = self::STATUS_ICONS[ $status ] ?? self::STATUS_ICONS['info']; // A dismissed Recommended row is muted and labelled "Dismissed" instead of "Recommended". $state = $locked ? 'locked' : ( $dismissed ? 'dismissed' : $status ); if ( $dismissed ) { $icon = 'hidden'; } $value = self::value_with_activity( $row ); // A message must add information: never print an empty one, or one that repeats the label. $message = self::message_to_show( $row ); $note = (string) ( $row['note'] ?? '' ); $row_dom_id = self::ROW_ID_PREFIX . $row['id']; $show_problem_detail = in_array( $status, self::DETAIL_STATUSES, true ) && ( '' !== $row['current'] || '' !== $row['recommended'] || '' !== $row['reason'] ); $show_problem_detail = $show_problem_detail && ! $dismissed; $show_detail = ! $attention && ( $show_problem_detail || '' !== $note ); $can_dismiss = null !== self::$controls && ! $dismissed && Health_Engine::can_dismiss( $row ); $can_restore = null !== self::$controls && $dismissed; $input_id = ( $attention ? 'adminoptim-health-note-a-' : 'adminoptim-health-note-' ) . $row['id']; if ( $attention && '' === $message ) { $message = $value; $value = ''; } ?>
  • >

    0 ? wp_date( get_option( 'date_format' ), $dismissed['time'] ) : ''; /* translators: %s: date the item was dismissed. */ echo esc_html( '' !== $when ? sprintf( __( 'Dismissed on %s', 'admin-optimizer' ), $when ) : __( 'Dismissed', 'admin-optimizer' ) ); echo '' !== $dismissed['note'] ? esc_html( ': ' . $dismissed['note'] ) : ''; ?>

    : the summary is the "Dismiss" link and the form opens inline, with or without JavaScript. ?>
  • $rows Rows. * * @return void */ private static function render_table( array $rows ): void { ?>
    ` where it appears. Only the escaped code * substring is ever turned into markup, so nothing else can inject any. * * @param string $message Plain-text message. * @param string $code Part of the message to show in monospace, or ''. * * @return string */ public static function message_html( string $message, string $code ): string { $html = esc_html( $message ); if ( '' === $code ) { return $html; } $escaped_code = esc_html( $code ); $position = strpos( $html, $escaped_code ); return false === $position ? $html : substr_replace( $html, '' . $escaped_code . '', $position, strlen( $escaped_code ) ); } /** * The message to print for a row: empty when it is empty or merely repeats * the label (compared ignoring case, spacing and trailing punctuation). * * @param array $row Normalized row. * * @return string */ public static function message_to_show( array $row ): string { $normalize = static function ( string $text ): string { return strtolower( trim( preg_replace( '/\s+/', ' ', $text ), " \t\n\r\0\x0B.!:" ) ); }; $message = (string) ( $row['message'] ?? '' ); if ( '' === trim( $message ) || $normalize( $message ) === $normalize( (string) ( $row['label'] ?? '' ) ) ) { return ''; } return $message; } /** * Row value, with "· 2 hours ago" appended when the row reports a * last-activity time. Formatted here so the wording is the same for every * module and the cached rows never carry stale relative text. * * @param array $row Normalized row. * * @return string */ public static function value_with_activity( array $row ): string { $value = $row['value']; if ( empty( $row['last_activity'] ) ) { return $value; } $ago = sprintf( /* translators: %s: relative time, e.g. "2 hours". */ __( '%s ago', 'admin-optimizer' ), human_time_diff( (int) $row['last_activity'], time() ) ); return '' === $value ? $ago : $value . ' · ' . $ago; } }