PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
jetpack / jetpack_vendor / automattic / jetpack-menu-badges / src / class-menu-renderer.php

class-menu-renderer.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3, at jetpack_vendor/automattic/jetpack-menu-badges/src/class-menu-renderer.php

147 lines 4.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Renders notification-count badges into the wp-admin $menu/$submenu globals.
4 *
5 * @package automattic/jetpack-menu-badges
6 */
7
8 namespace Automattic\Jetpack\Menu_Badges;
9
10 /**
11 * Reads the Notification_Counts registry and writes the top-level and submenu
12 * badges. Sole writer of Jetpack menu badges in wp-admin. Idempotent.
13 */
14 class Menu_Renderer {
15
16 /**
17 * Build a single badge span.
18 *
19 * @param string $id Owning entry id (or 'total').
20 * @param int $count Count to show.
21 * @param bool $is_total Whether this is the top-level total badge.
22 * @return string
23 */
24 public static function badge_markup( $id, $count, $is_total = false ) {
25 $count = (int) $count;
26 // A zero-count badge still renders (so client live-updates have an element to
27 // target) but ships hidden. The inline style trails the data-jp-* attributes so
28 // strip()'s idempotency regex still matches; setBadgeCount() clears it to reveal.
29 $hidden = $count > 0 ? '' : ' style="display:none"';
30 // The top-level Jetpack total badge carries the core `awaiting-mod` and
31 // `update-plugins` classes so it picks up WordPress's standard menu-bubble styling;
32 // submenu badges use only our own `menu-counter` hook. `menu-counter` is always
33 // present for CSS/JS targeting.
34 $classes = $is_total ? 'awaiting-mod update-plugins menu-counter' : 'menu-counter';
35 $attrs = sprintf(
36 'class="%1$s count-%2$d" data-jp-menu-badge="%3$s" data-jp-menu-count="%2$d"%4$s%5$s',
37 $classes,
38 $count,
39 esc_attr( $id ),
40 $is_total ? ' data-jp-menu-badge-total="1"' : '',
41 $hidden
42 );
43 return sprintf(
44 ' <span %1$s><span class="count">%2$s</span></span>',
45 $attrs,
46 number_format_i18n( $count )
47 );
48 }
49
50 /**
51 * Strip any badge this renderer previously wrote from a menu title (idempotency).
52 *
53 * @param string $title Menu title.
54 * @return string
55 */
56 private static function strip( $title ) {
57 return trim( (string) preg_replace( '/\s*<span class="[^"]*menu-counter count-\d+" data-jp-menu-badge=.*$/s', '', (string) $title ) );
58 }
59
60 /**
61 * Render badges into the current $menu/$submenu globals.
62 *
63 * @return void
64 */
65 public static function render() {
66 global $menu, $submenu;
67
68 if ( ! is_array( $menu ) ) {
69 return;
70 }
71
72 $entries = Notification_Counts::all();
73
74 // Submenu badges: one per registered menu_slug. A registered slug always gets a
75 // badge span — even at count 0 — so a later client live-update (0 -> positive)
76 // has an element to reveal. badge_markup() ships the zero case hidden.
77 if ( isset( $submenu['jetpack'] ) && is_array( $submenu['jetpack'] ) ) {
78 foreach ( $submenu['jetpack'] as $i => $item ) {
79 if ( ! isset( $item[2] ) || ! isset( $item[0] ) ) {
80 continue;
81 }
82 $slug = $item[2];
83 $title = self::strip( $item[0] );
84 if ( self::has_registered_slug( $entries, $slug ) ) {
85 $title .= self::badge_markup( $slug, Notification_Counts::get_for_menu( $slug ) );
86 }
87 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
88 $submenu['jetpack'][ $i ][0] = $title;
89 }
90 }
91
92 // Nothing registered: leave the Jetpack parent untouched (no badge to write or strip).
93 if ( empty( $entries ) ) {
94 return;
95 }
96
97 // Top-level total on the Jetpack parent. Rendered even at 0 (hidden) so a live
98 // update can reveal it without a reload.
99 $parent_index = self::find_parent_index( $menu );
100 if ( null === $parent_index ) {
101 return;
102 }
103 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
104 $menu[ $parent_index ][0] = self::strip( $menu[ $parent_index ][0] ) . self::badge_markup( 'total', Notification_Counts::get_total(), true );
105 }
106
107 /**
108 * Whether any registered entry badges the given submenu slug.
109 *
110 * @param array<string,array> $entries Registry entries (from Notification_Counts::all()).
111 * @param string $slug Submenu slug.
112 * @return bool
113 */
114 private static function has_registered_slug( array $entries, $slug ) {
115 foreach ( $entries as $entry ) {
116 if ( isset( $entry['menu_slug'] ) && $entry['menu_slug'] === $slug ) {
117 return true;
118 }
119 }
120 return false;
121 }
122
123 /**
124 * Locate the Jetpack top-level menu row to carry the total badge.
125 *
126 * Prefers the row whose slug (item[2]) is 'jetpack' — portable across self-hosted,
127 * Atomic, and WP.com Simple, where jetpack-mu-wpcom builds the parent with a different
128 * capability. Falls back to the plugin's 'jetpack_admin_page' capability (item[1]).
129 *
130 * @param array $menu The wp-admin $menu global.
131 * @return int|string|null Matching key, or null when no Jetpack parent is present.
132 */
133 private static function find_parent_index( array $menu ) {
134 foreach ( $menu as $i => $item ) {
135 if ( isset( $item[0] ) && isset( $item[2] ) && 'jetpack' === $item[2] ) {
136 return $i;
137 }
138 }
139 foreach ( $menu as $i => $item ) {
140 if ( isset( $item[0] ) && isset( $item[1] ) && 'jetpack_admin_page' === $item[1] ) {
141 return $i;
142 }
143 }
144 return null;
145 }
146 }
147