PluginProbe
Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) / trunk
Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) vtrunk
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 1.3.1 All 56 releases
darkify / src / Admin / Assets.php

Assets.php in Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) trunk, at src/Admin/Assets.php

183 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Centralized admin asset loader for the React SPA.
5 *
6 * Single owner of the React admin bundle (CSS + JS), the WordPress media library
7 * and the `window.darkifyAdmin` runtime config the SPA reads. The whole admin —
8 * every settings section, License and Help — is one SPA mounted on
9 * `#darkify_react`, so this loads only on Darkify's own admin page.
10 *
11 * @package darkify
12 * @subpackage darkify/src/Admin
13 * @author ThemeAtelier<themeatelierbd@gmail.com>
14 */
15
16 namespace ThemeAtelier\Darkify\Admin;
17
18 use ThemeAtelier\Darkify\Admin\Rest\AbstractRestController;
19
20 if (! defined('ABSPATH')) {
21 die;
22 }
23
24 class Assets
25 {
26 /**
27 * The React admin bundle handle.
28 */
29 const HANDLE = 'darkify-admin';
30
31 /**
32 * The admin page slugs that host the SPA — the settings screen and the Help
33 * screen, which is its own WordPress menu entry (see Admin\Menu). Both mount
34 * the same bundle; only the route the app opens on differs.
35 *
36 * `darkify` is unchanged from the old options screen, so existing links and
37 * bookmarks to `?page=darkify` still land here.
38 */
39 const SPA_SLUGS = [
40 'darkify',
41 'darkify-help',
42 ];
43
44 public function __construct()
45 {
46 \add_action('admin_enqueue_scripts', [$this, 'enqueue'], 100);
47 \add_action('admin_head', [$this, 'hide_admin_notices']);
48 }
49
50 /**
51 * Suppress every other plugin/theme/core admin notice on Darkify's own SPA
52 * pages (Settings + Help), so the app reads as a clean, distraction-free
53 * screen. Scoped strictly to `is_darkify_admin_page()` — every other wp-admin
54 * screen keeps notices exactly as before.
55 *
56 * Hooked on `admin_head`, which fires after every plugin/theme has had its
57 * chance to `add_action( 'admin_notices', … )` (typically done on `init` /
58 * `admin_init` / their own `admin_menu` handlers, all of which run earlier)
59 * but strictly before WordPress calls those hooks — `admin_notices` /
60 * `all_admin_notices` fire later, while rendering `#wpbody-content`. Removing
61 * *all* callbacks on those hooks (rather than filtering) is the standard
62 * approach for a distraction-free admin screen; it only touches notices, not
63 * the pages themselves.
64 */
65 public function hide_admin_notices(): void
66 {
67 if (! $this->is_darkify_admin_page()) {
68 return;
69 }
70 \remove_all_actions('admin_notices');
71 \remove_all_actions('all_admin_notices');
72 \remove_all_actions('network_admin_notices');
73 }
74
75 /**
76 * Whether the current admin screen is one of the Darkify SPA pages.
77 */
78 public function is_darkify_admin_page(): bool
79 {
80 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only screen check.
81 $page = isset($_GET['page']) ? \sanitize_key(\wp_unslash($_GET['page'])) : '';
82 return \in_array($page, self::SPA_SLUGS, true);
83 }
84
85 /**
86 * Enqueue the React bundle (CSS + JS) and print the SPA runtime config.
87 */
88 public function enqueue(): void
89 {
90 if (! $this->is_darkify_admin_page()) {
91 return;
92 }
93
94 // Printed independently of the bundle handle so it is available both to
95 // the built bundle and to the Vite dev server during development.
96 \add_action('admin_print_scripts', function () {
97 echo '<script>window.darkifyAdmin = ' . \wp_json_encode($this->config()) . ';</script>';
98 });
99
100 // Needed by the React upload/media fields.
101 \wp_enqueue_media();
102
103 // Needed by the Get Help page's Recommended tab: the "More Details"
104 // links open plugin-install.php in a TB_iframe (WordPress thickbox).
105 \add_thickbox();
106
107 // No icon webfont is enqueued: every admin icon is now an inline lucide SVG
108 // rendered by React (see darkify-react/src/lib/navIcons.js), matching Chat
109 // Help Pro. The schema's `icon` values are still icofont class names — the
110 // config files are shared with the free plugin and left untouched — but the
111 // React side resolves icons from the node id and ignores them.
112
113 $base_url = DARKIFY_DIR_URL . 'src/Admin/assets/js/';
114 $base_dir = DARKIFY_PATH . 'src/Admin/assets/js/';
115
116 // Styles in <head> so they apply before first paint. The bundle itself
117 // loads in the footer; if the CSS were injected by that JS, the whole
118 // admin (WP menu included) would repaint and visibly flicker.
119 \wp_enqueue_style(
120 self::HANDLE,
121 $base_url . self::HANDLE . '.css',
122 [],
123 $this->asset_version($base_dir . self::HANDLE . '.css')
124 );
125
126 \wp_enqueue_script(
127 self::HANDLE,
128 $base_url . self::HANDLE . '.js',
129 ['wp-i18n'],
130 $this->asset_version($base_dir . self::HANDLE . '.js'),
131 true
132 );
133
134 \wp_set_script_translations(self::HANDLE, 'darkify', DARKIFY_PATH . 'languages');
135 }
136
137 /**
138 * Cache-busting version for a built asset.
139 *
140 * The plugin version alone is not enough during development: rebuilding the
141 * bundle does not bump it, so the browser happily serves the previous
142 * darkify-admin.js from cache and the new admin never appears. The file's
143 * mtime changes on every build, so it always does.
144 *
145 * Released builds still carry the plugin version as well, so the URL changes
146 * on update even if a filesystem copy preserves mtimes.
147 *
148 * @param string $path Absolute path to the built asset.
149 */
150 private function asset_version(string $path): string
151 {
152 $mtime = \is_readable($path) ? \filemtime($path) : false;
153 return $mtime ? DARKIFY_VERSION . '.' . $mtime : DARKIFY_VERSION;
154 }
155
156 /**
157 * The SPA runtime config global (window.darkifyAdmin).
158 *
159 * @return array<string,mixed>
160 */
161 private function config(): array
162 {
163 return [
164 'restUrl' => \esc_url_raw(\rest_url(AbstractRestController::NS)),
165 'nonce' => \wp_create_nonce('wp_rest'),
166 'ajaxUrl' => \admin_url('admin-ajax.php'),
167 'dirUrl' => DARKIFY_DIR_URL,
168 'version' => DARKIFY_VERSION,
169 'adminUrl' => \admin_url('admin.php?page=' . self::SPA_SLUGS[0]),
170 'docsUrl' => \defined('DARKIFY_DOCS_URL') ? DARKIFY_DOCS_URL : '',
171 'demoUrl' => \defined('DARKIFY_DEMO_URL') ? DARKIFY_DEMO_URL : '',
172 'supportUrl' => 'https://wordpress.org/support/plugin/darkify/',
173 // The free build surfaces upgrade CTAs (Pro-locked fields, License
174 // page); one canonical pricing URL keeps the campaign tagging uniform.
175 'upgradeUrl' => 'https://darkifywp.com/pricing/?utm_source=darkify_plugin&utm_medium=react_admin&utm_campaign=pro_lock',
176 // Live preview: the site homepage, plus a nonce the frontend verifies
177 // before swapping in the admin's unsaved settings (see PreviewRest).
178 'homeUrl' => \esc_url_raw(\home_url('/')),
179 'previewNonce' => \wp_create_nonce(\ThemeAtelier\Darkify\Admin\Rest\PreviewRest::PREVIEW_NONCE_ACTION),
180 ];
181 }
182 }
183