PluginProbe
Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) / 2.1.3
Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) v2.1.3
2.1.3 2.1.2 2.1.1 2.1.0 2.0.4 2.0.3 2.0.2 2.0.1 2.0.0 1.5.5 1.5.4 1.5.3 1.5.2 1.5.1 1.5.0 trunk 1.0.1 1.1.0 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 All 57 releases
← All changes | src/Admin/Menu.php +101 -0 2.0.22.1.3 View file →
@@ -48,8 +48,10 @@
48 48
49 49 public function __construct()
50 50 {
51 51 \add_action('admin_menu', [$this, 'register']);
52 + \add_action('admin_head', [$this, 'menu_icon_style']);
53 + \add_action('admin_footer', [$this, 'upgrade_link_new_tab']);
52 54 // Old bookmarks / links to the retired "Get Help" screen are forwarded to
53 55 // the SPA's own Help route (see redirect_legacy_help_page()).
54 56 \add_action('admin_page_access_denied', [$this, 'redirect_legacy_help_page']);
55 57 }
@@ -111,8 +113,45 @@
111 113 );
112 114 }
113 115
114 116 /**
117 + * Open the "Upgrade To Pro!" submenu link in a new tab.
118 + *
119 + * That submenu is registered with an external URL as its slug, so WordPress
120 + * renders it as an ordinary anchor into wp-admin's menu. Leaving wp-admin
121 + * entirely is not what a menu click should do — someone half-way through
122 + * configuring the plugin loses the screen they were on, and the back button
123 + * is their only way back.
124 + *
125 + * There is no filter for the attributes of that anchor: wp-admin builds the
126 + * markup itself in wp-admin/menu-header.php, and the only thing under our
127 + * control is the title string, which is escaped into the link text. So the
128 + * attributes are set client-side instead, keyed off the class the title
129 + * already carries.
130 + *
131 + * `rel` is not optional here. A `target="_blank"` link hands the opened page
132 + * a `window.opener` reference to this one; `noopener` severs it, and
133 + * `noreferrer` keeps the admin URL (which can carry screen and query
134 + * context) out of the referrer header sent to the marketing site.
135 + */
136 + public function upgrade_link_new_tab(): void
137 + {
138 + ?>
139 + <script>
140 + (function () {
141 + var label = document.querySelector('#adminmenu .darkify-get-pro-text');
142 + var link = label && label.closest('a');
143 + if (!link) {
144 + return;
145 + }
146 + link.target = '_blank';
147 + link.rel = 'noopener noreferrer';
148 + })();
149 + </script>
150 + <?php
151 + }
152 +
153 + /**
115 154 * Forward the retired `?page=darkify-help` screen to the SPA's Help route.
116 155 *
117 156 * "Get Help" used to be its own menu page; it is now a route inside the one
118 157 * SPA page. Without this, an existing bookmark (or any third-party link) to
@@ -135,8 +174,70 @@
135 174 }
136 175
137 176 \wp_safe_redirect(\admin_url('admin.php?page=' . self::PAGE_SLUG . '#/help'));
138 177 exit;
178 + }
179 +
180 + /**
181 + * Make the menu icon take its colour from the menu, like every other icon.
182 + *
183 + * WordPress renders a `data:` menu icon as a CSS `background-image` on
184 + * `div.wp-menu-image.svg`, and ours has `fill="white"` baked into the SVG. A
185 + * background-image cannot inherit `color`, so while WordPress's own icons are
186 + * dashicons — font glyphs that follow the menu's text colour and dim to 0.6
187 + * opacity — ours painted flat white and read as brighter than everything
188 + * around it. Dark mode made it obvious, but it was never matching; the dark
189 + * sidebar just hid it.
190 + *
191 + * Switching from background-image to a `mask` inverts the relationship: the
192 + * SVG becomes a stencil and `background-color: currentColor` supplies the
193 + * paint, so the icon follows the menu's colour exactly as a dashicon does —
194 + * under any admin colour scheme, under any Darkify palette, with no colour
195 + * hardcoded here.
196 + *
197 + * Printed from the same schema value `register()` passes to add_menu_page(),
198 + * so there is only ever one copy of the SVG to keep correct.
199 + */
200 + public function menu_icon_style(): void
201 + {
202 + $args = SchemaRegistry::options(self::OPTION_KEY);
203 + $icon = $args['menu_icon'] ?? '';
204 +
205 + /*
206 + * Dashicon slugs and image files are already coloured (or already correct)
207 + * by WordPress; only a base64 inline SVG needs this treatment.
208 + *
209 + * Matched against a strict pattern rather than escaped with esc_url():
210 + * `data:` is not in wp_allowed_protocols(), so esc_url() strips the whole
211 + * value and the icon disappears. The pattern is what makes it safe to
212 + * print raw — base64 cannot contain the `<` that would be needed to close
213 + * the surrounding <style> element, and nothing outside the alphabet gets
214 + * through.
215 + */
216 + if (! \is_string($icon) || ! \preg_match('#^data:image/svg\+xml;base64,[A-Za-z0-9+/]+={0,2}$#', $icon)) {
217 + return;
218 + }
219 +
220 + $selector = '#adminmenu .toplevel_page_' . self::PAGE_SLUG . ' div.wp-menu-image.svg';
221 + $url = "url('" . $icon . "')";
222 +
223 + $css = $selector . '{'
224 + . 'background-image:none!important;'
225 + . '-webkit-mask:' . $url . ' no-repeat center;'
226 + . 'mask:' . $url . ' no-repeat center;'
227 + . '-webkit-mask-size:20px auto;'
228 + . 'mask-size:20px auto;'
229 + . 'background-color:currentColor!important;'
230 + // Match the 0.6 dashicons rest at, and the full opacity they take on
231 + // hover and while current.
232 + . 'opacity:.6;'
233 + . '}'
234 + . '#adminmenu .toplevel_page_' . self::PAGE_SLUG . ':hover div.wp-menu-image.svg,'
235 + . '#adminmenu .toplevel_page_' . self::PAGE_SLUG . '.current div.wp-menu-image.svg,'
236 + . '#adminmenu .toplevel_page_' . self::PAGE_SLUG . '.wp-has-current-submenu div.wp-menu-image.svg'
237 + . '{opacity:1;}';
238 +
239 + echo '<style id="darkify-menu-icon">' . $css . '</style>';
139 240 }
140 241
141 242 /**
142 243 * The page body: just the node React mounts on. Every menu entry renders this