PluginProbe
Texty – SMS Notification for WordPress, WooCommerce, Dokan and more / 1.1
Texty – SMS Notification for WordPress, WooCommerce, Dokan and more v1.1
2.0.2 trunk 0.2 1.0 1.1 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 2.0.0 2.0.1
← All changes | includes/Admin/Menu.php +33 -206 trunk1.1 View file →
@@ -7,167 +7,42 @@
7 7 */
8 8 class Menu {
9 9
10 10 /**
11 - * Hook suffix of the Texty admin page
12 - *
13 - * @since 2.0.2
14 - *
15 - * @var string
11 + * Initialize
16 12 */
17 - private $hook_suffix = '';
18 -
19 - /**
20 - * Class constructor
21 - *
22 - * @return void
23 - */
24 13 public function __construct() {
25 14 add_action( 'admin_menu', [ $this, 'register_menu' ] );
26 -
27 - // Capture admin notices so the Texty header renders above them.
28 - add_action( 'admin_notices', [ $this, 'inject_before_notices' ], -9999 );
29 - add_action( 'admin_notices', [ $this, 'inject_after_notices' ], PHP_INT_MAX );
30 15 }
31 16
32 17 /**
33 - * Whether the current admin screen is the Texty page
18 + * Register admin menu
34 19 *
35 - * @since 2.0.2
36 - *
37 - * @return bool
38 - */
39 - private function is_texty_admin_page() {
40 - if ( empty( $this->hook_suffix ) || ! function_exists( 'get_current_screen' ) ) {
41 - return false;
42 - }
43 -
44 - $screen = get_current_screen();
45 -
46 - return $screen && $screen->id === $this->hook_suffix;
47 - }
48 -
49 - /**
50 - * Open a hidden wrapper before admin notices render
51 - *
52 - * WordPress core relocates stray `.notice` elements to just after the first
53 - * `.wp-header-end` node. Opening the wrapper — and printing the catcher —
54 - * before any notice fires collects them all inside a hidden container, so
55 - * the React header can sit at the very top of the page.
56 - *
57 - * @since 2.0.2
58 - *
59 20 * @return void
60 21 */
61 - public function inject_before_notices() {
62 - if ( ! $this->is_texty_admin_page() ) {
63 - return;
64 - }
65 -
66 - echo '<div class="texty-notice-list-hide" id="texty__notice-list">';
67 - echo '<div class="wp-header-end" id="texty__notice-catcher"></div>';
68 - }
69 -
70 - /**
71 - * Close the hidden notice wrapper opened in inject_before_notices()
72 - *
73 - * @since 2.0.2
74 - *
75 - * @return void
76 - */
77 - public function inject_after_notices() {
78 - if ( ! $this->is_texty_admin_page() ) {
79 - return;
80 - }
81 -
82 - echo '</div>';
83 - }
84 -
85 - /**
86 - * Add Texty admin menu
87 - *
88 - * @return void
89 - */
90 22 public function register_menu() {
91 - global $submenu;
23 + $position = apply_filters( 'texty_menu_position', 58 );
92 24
93 - $capability = apply_filters( 'texty_admin_menu_capability', 'manage_options' );
94 - if ( ! current_user_can( $capability ) ) {
95 - return;
96 - }
97 -
98 - $menu_position = apply_filters( 'texty_menu_position', 58 );
99 - $slug = 'texty';
100 - $menu_icon = 'data:image/svg+xml;base64,' . base64_encode( '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path fill="#a0a5aa" d="M10.52 1c.603 0 1.191.056 1.761.164a5.358 5.358 0 00-1.1 2.754 6.538 6.538 0 00-5.737 10.626l.629.773-.966 1.645h5.414a6.538 6.538 0 006.511-7.138 5.355 5.355 0 002.764-1.075 9.423 9.423 0 01-9.275 11.097H0l2.602-4.314-.013-.02A9.423 9.423 0 0110.521 1zm6.018 0a3.462 3.462 0 110 6.923 3.462 3.462 0 010-6.923z"/></svg>' ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
101 -
102 - $dashboard = add_menu_page(
25 + $menu = add_menu_page(
103 26 __( 'Texty', 'texty' ),
104 27 __( 'Texty', 'texty' ),
105 - $capability,
106 - $slug,
28 + 'manage_options',
29 + 'texty',
107 30 [ $this, 'render_page' ],
108 - $menu_icon,
109 - $menu_position
31 + 'data:image/svg+xml;base64,' . base64_encode( '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path fill="#a0a5aa" d="M10.52 1c.603 0 1.191.056 1.761.164a5.358 5.358 0 00-1.1 2.754 6.538 6.538 0 00-5.737 10.626l.629.773-.966 1.645h5.414a6.538 6.538 0 006.511-7.138 5.355 5.355 0 002.764-1.075 9.423 9.423 0 01-9.275 11.097H0l2.602-4.314-.013-.02A9.423 9.423 0 0110.521 1zm6.018 0a3.462 3.462 0 110 6.923 3.462 3.462 0 010-6.923z"/></svg>' ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
32 + $position
110 33 );
111 34
112 - $this->hook_suffix = $dashboard;
113 -
114 - $submenus = apply_filters(
115 - 'texty_admin_menu',
116 - [
117 - [
118 - 'path' => 'dashboard',
119 - 'title' => __( 'Dashboard', 'texty' ),
120 - ],
121 - [
122 - 'path' => 'gateway',
123 - 'title' => __( 'Gateway', 'texty' ),
124 - ],
125 - [
126 - 'path' => 'notifications',
127 - 'title' => __( 'Notifications', 'texty' ),
128 - ],
129 - [
130 - 'path' => 'logs',
131 - 'title' => __( 'Logs', 'texty' ),
132 - ],
133 - ],
134 - $capability,
135 - $slug
136 - );
137 -
138 - foreach ( $submenus as $item ) {
139 - $path = 'admin.php?page=' . $slug . '#/' . $item['path'];
140 - if ( filter_var( $item['path'], FILTER_VALIDATE_URL ) ) {
141 - $path = $item['path'];
142 - }
143 - $submenu[ $slug ][] = [ // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
144 - $item['title'],
145 - $capability,
146 - $path,
147 - ];
148 - }
149 -
150 - do_action( 'texty_admin_menu', $capability, $menu_position );
151 -
152 - add_action( 'admin_print_scripts-' . $dashboard, [ $this, 'enqueue_scripts' ] );
35 + add_action( 'admin_print_scripts-' . $menu, [ $this, 'enqueue_scripts' ] );
153 36 }
154 37
155 38 /**
156 39 * Render the page
157 40 *
158 - * The header mounts in its own root so it paints at the very top of the
159 - * screen, above the captured admin notices. The notice slot deliberately
160 - * stays outside the `.texty-app` Tailwind scope — that scope's
161 - * importantized preflight would strip core notice styling. Captured
162 - * notices are moved into it on the JS side.
163 - *
164 41 * @return void
165 42 */
166 43 public function render_page() {
167 - echo '<div id="texty-header" class="texty-app"></div>';
168 - echo '<div id="texty-notices"></div>';
169 - echo '<div id="texty-app" class="texty-app"></div>';
44 + echo '<div id="texty-app"></div>';
170 45 }
171 46
172 47 /**
173 48 * Enqueue JS and CSS
@@ -174,75 +49,36 @@
174 49 *
175 50 * @return void
176 51 */
177 52 public function enqueue_scripts() {
178 - $asset_path = TEXTY_DIR . '/dist/index.asset.php';
53 + $assets = [
54 + 'version' => TEXTY_VERSION,
55 + 'dependencies' => [
56 + 'wp-api-fetch',
57 + 'wp-i18n',
58 + ],
59 + ];
179 60
180 - if ( ! file_exists( $asset_path ) ) {
181 - error_log( 'Texty: missing build artifact ' . $asset_path . ' — run `npm run build`. The admin app was not enqueued.' );
61 + $url = TEXTY_URL . '/dist';
182 62
183 - return;
63 + // for local development
64 + // when webpack "hot module replacement" is enabled, this
65 + // constant needs to be turned "true" on "wp-config.php"
66 + if ( defined( 'WP_LOCAL_DEV' ) && WP_LOCAL_DEV ) {
67 + $url = 'http://localhost:8080';
184 68 }
185 69
186 - $asset_file = include $asset_path;
187 - $deps = isset( $asset_file['dependencies'] ) && is_array( $asset_file['dependencies'] ) ? $asset_file['dependencies'] : [];
188 - $version = isset( $asset_file['version'] ) ? $asset_file['version'] : TEXTY_VERSION;
70 + // register scripts
71 + wp_register_script( 'texty-runtime', $url . '/runtime.js', $assets['dependencies'], $assets['version'], true );
72 + wp_register_script( 'texty-vendors', $url . '/vendors.js', [ 'texty-runtime' ], $assets['version'], true );
73 + wp_register_script( 'texty-admin', $url . '/app.js', [ 'texty-vendors' ], $assets['version'], true );
74 + wp_localize_script( 'texty-admin', 'texty', $this->localize_script() );
189 75
190 - // The shared bundles (`texty-plugin-ui` → window.texty.pluginUi,
191 - // `texty-components` → window.texty.components) are registered globally
192 - // by Texty\Assets so add-ons can depend on them on both admin and
193 - // storefront. `texty-plugin-ui` is already listed in $deps here, since
194 - // the SPA imports @wedevs/plugin-ui.
195 - //
196 - // WP silently drops a handle whose dependency is unregistered, so an
197 - // unbuilt shared entry would blank the admin page with no error. Say so
198 - // in the log instead of failing invisibly.
199 - foreach ( $deps as $dep ) {
200 - if ( strpos( $dep, 'texty-' ) === 0 && ! wp_script_is( $dep, 'registered' ) ) {
201 - error_log( 'Texty: shared script handle "' . $dep . '" is not registered — the admin app will not load. Run `npm run build`.' );
202 - }
203 - }
76 + // register styles
77 + wp_register_style( 'texty-vendors-css', $url . '/vendors.css', [ 'wp-components' ], $assets['version'] );
78 + wp_register_style( 'texty-css', $url . '/app.css', [ 'texty-vendors-css' ], $assets['version'] );
204 79
205 - wp_register_script(
206 - 'texty-admin',
207 - TEXTY_URL . '/dist/index.js',
208 - $deps,
209 - $version,
210 - true
211 - );
212 -
213 - // Merged instead of wp_localize_script's `var texty = {…}`: the shared
214 - // bundles assign onto the same `window.texty` namespace and print
215 - // *before* this script (they are dependencies of it), so a plain
216 - // assignment would wipe out window.texty.pluginUi and crash the SPA.
217 - $localized = wp_json_encode( $this->localize_script() );
218 -
219 - wp_add_inline_script(
220 - 'texty-admin',
221 - 'window.texty = Object.assign( window.texty || {}, ' . ( $localized ? $localized : '{}' ) . ' );',
222 - 'before'
223 - );
224 -
225 - $vendor_style_deps = [ 'wp-components' ];
226 -
227 - if ( wp_style_is( 'texty-plugin-ui', 'registered' ) ) {
228 - $vendor_style_deps[] = 'texty-plugin-ui';
229 - }
230 -
231 - wp_register_style(
232 - 'texty-vendor-css',
233 - TEXTY_URL . '/dist/style-index.css',
234 - $vendor_style_deps,
235 - $version
236 - );
237 -
238 - wp_register_style(
239 - 'texty-css',
240 - TEXTY_URL . '/dist/index.css',
241 - [ 'texty-vendor-css' ],
242 - $version
243 - );
244 -
80 + // enqueue scripts and styles
245 81 wp_enqueue_script( 'texty-admin' );
246 82 wp_enqueue_style( 'texty-css' );
247 83 }
248 84
@@ -251,18 +87,9 @@
251 87 *
252 88 * @return array
253 89 */
254 90 public function localize_script() {
255 - $i18n = [
256 - 'asset_url' => trailingslashit( TEXTY_URL ) . 'assets/',
257 - 'site_name' => get_bloginfo( 'name' ),
258 - 'rest_url' => esc_url_raw( rest_url() ),
259 - 'ajax_url' => esc_url_raw( admin_url( 'admin-ajax.php' ) ),
260 - 'nonce' => wp_create_nonce( 'wp_rest' ),
261 - 'version' => [
262 - 'lite' => TEXTY_VERSION,
263 - ],
264 - ];
91 + $i18n = [];
265 92
266 93 return apply_filters( 'texty_localize_script', $i18n );
267 94 }
268 95 }