PluginProbe
ActivityPub / 9.2.1
ActivityPub v9.2.1
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
activitypub / includes / wp-admin / class-app.php

class-app.php in ActivityPub 9.2.1, at includes/wp-admin/class-app.php

456 lines 11.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * App admin page file.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub\WP_Admin;
9
10 /**
11 * ActivityPub App Admin Page Class.
12 */
13 class App {
14
15 const MOUNT_ID = 'activitypub-app-root';
16 const LOADER_MODULE = '@activitypub/app';
17 const FEED_CONTENT_MODULE = '@activitypub/app/routes/feed/content';
18 const FEED_ROUTE_MODULE = '@activitypub/app/routes/feed/route';
19
20 /**
21 * Whether the WordPress admin app boot stack is available.
22 *
23 * This is the WordPress 7.0+ gate for the Social Web app. Instead of a
24 * `version_compare()` against `7.0`, it detects the boot stack by capability:
25 * the Script Modules API plus core's `@wordpress/boot` module asset, which
26 * ships in 7.0. Capability detection is deliberate, because a `version_compare`
27 * would lock out pre-release builds — `7.0-alpha`/`7.0-RC1` fail
28 * `version_compare( …, '7.0', '>=' )` yet already ship the boot module — so
29 * early-adopter installs would wrongly fall back to no app.
30 *
31 * @return bool True when the Social Web app can be booted (WordPress 7.0+).
32 */
33 public static function is_supported() {
34 return \function_exists( 'wp_register_script_module' ) && \is_array( self::get_boot_asset() );
35 }
36
37 /**
38 * Initialize the App page.
39 *
40 * Must run early (on admin_init) before the admin bar is initialized.
41 */
42 public static function init() {
43 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
44 if ( isset( $_GET['page'] ) && 'activitypub-social-web' === $_GET['page'] ) {
45 \add_filter( 'wp_admin_bar_class', '__return_false' );
46 }
47 }
48
49 /**
50 * Remove admin notices from the App page.
51 */
52 public static function remove_admin_notices() {
53 \remove_all_actions( 'admin_notices' );
54 \remove_all_actions( 'all_admin_notices' );
55
56 // Add fullscreen mode body class.
57 \add_filter(
58 'admin_body_class',
59 static function ( $classes ) {
60 return "$classes is-fullscreen-mode";
61 }
62 );
63 }
64
65 /**
66 * Enqueue scripts and styles for the App page.
67 */
68 public static function enqueue_scripts() {
69 \wp_dequeue_style( 'colors' );
70 \wp_dequeue_script( 'common' );
71 \wp_dequeue_script( 'svg-painter' );
72
73 if ( ! self::is_supported() ) {
74 return;
75 }
76
77 $boot_asset = self::get_boot_asset();
78
79 if ( ! \is_array( $boot_asset ) ) {
80 return;
81 }
82
83 self::preload_rest_data();
84
85 $routes = self::get_routes();
86 self::add_loader_data( $routes );
87
88 \wp_register_script(
89 'activitypub-app-prerequisites',
90 false,
91 self::get_script_dependencies( $boot_asset ),
92 $boot_asset['version'],
93 true
94 );
95
96 $style_dependencies = \array_filter(
97 $boot_asset['dependencies'],
98 static function ( $handle ) {
99 return \wp_style_is( $handle, 'registered' );
100 }
101 );
102
103 \wp_register_style(
104 'activitypub-app-prerequisites',
105 false,
106 $style_dependencies,
107 $boot_asset['version']
108 );
109
110 self::register_app_modules( $routes );
111 self::enqueue_app_styles();
112
113 \wp_enqueue_script( 'activitypub-app-prerequisites' );
114 \wp_enqueue_script_module( self::LOADER_MODULE );
115 \wp_enqueue_style( 'activitypub-app-prerequisites' );
116 }
117
118 /**
119 * Get classic script dependencies required before app script modules run.
120 *
121 * @param array $boot_asset Core boot module asset metadata.
122 * @return array Script handles.
123 */
124 private static function get_script_dependencies( $boot_asset ) {
125 $dependencies = \array_merge(
126 $boot_asset['dependencies'] ?? array(),
127 array(
128 'lodash',
129 'moment',
130 'react',
131 'react-dom',
132 'react-jsx-runtime',
133 'wp-api-fetch',
134 'wp-commands',
135 'wp-components',
136 'wp-compose',
137 'wp-core-data',
138 'wp-data',
139 'wp-data-controls',
140 'wp-date',
141 'wp-dom',
142 'wp-element',
143 'wp-html-entities',
144 'wp-hooks',
145 'wp-i18n',
146 'wp-keyboard-shortcuts',
147 'wp-keycodes',
148 'wp-notices',
149 'wp-preferences',
150 'wp-primitives',
151 'wp-private-apis',
152 'wp-url',
153 'wp-viewport',
154 )
155 );
156
157 /**
158 * Filters the ActivityPub app prerequisite script dependencies.
159 *
160 * @param array $dependencies Script handles.
161 * @param array $boot_asset Core boot module asset metadata.
162 */
163 $dependencies = (array) \apply_filters(
164 'activitypub_app_prerequisite_dependencies',
165 \array_values( \array_unique( $dependencies ) ),
166 $boot_asset
167 );
168
169 return \array_values(
170 \array_filter(
171 \array_unique( $dependencies ),
172 static function ( $handle ) {
173 return \wp_script_is( $handle, 'registered' );
174 }
175 )
176 );
177 }
178
179 /**
180 * Add boot configuration for the app loader module.
181 *
182 * @param array $routes Route definitions.
183 */
184 private static function add_loader_data( $routes ) {
185 $mount_id = self::MOUNT_ID;
186
187 \add_filter(
188 'script_module_data_' . self::LOADER_MODULE,
189 static function ( $data ) use ( $mount_id, $routes ) {
190 $data['mountId'] = $mount_id;
191 $data['routes'] = $routes;
192
193 return $data;
194 }
195 );
196 }
197
198 /**
199 * Preload REST data used by the first app render.
200 */
201 private static function preload_rest_data() {
202 // Define paths to preload - must match exact fields from entities.js.
203 $preload_paths = array(
204 '/?_fields=description,gmt_offset,home,name,site_icon,site_icon_url,site_logo,timezone_string,url,page_for_posts,page_on_front,show_on_front',
205 array( '/wp/v2/settings', 'OPTIONS' ),
206 );
207
208 $preload_data = \array_reduce(
209 $preload_paths,
210 'rest_preload_api_request',
211 array()
212 );
213
214 \wp_add_inline_script(
215 'wp-api-fetch',
216 \sprintf( 'wp.apiFetch.use( wp.apiFetch.createPreloadingMiddleware( %s ) );', \wp_json_encode( $preload_data ) ),
217 'after'
218 );
219 }
220
221 /**
222 * Get the app route registry.
223 *
224 * @return array Route definitions.
225 */
226 private static function get_routes() {
227 $routes = array(
228 array(
229 'path' => '/',
230 'content_module' => self::FEED_CONTENT_MODULE,
231 'route_module' => self::FEED_ROUTE_MODULE,
232 ),
233 );
234
235 /**
236 * Filters the ActivityPub app route registry.
237 *
238 * @param array $routes Route definitions.
239 */
240 return \apply_filters( 'activitypub_app_routes', $routes );
241 }
242
243 /**
244 * Register app script modules.
245 *
246 * @param array $routes Route definitions.
247 */
248 private static function register_app_modules( $routes ) {
249 $module_assets = self::get_app_module_assets();
250 $module_ids = array();
251
252 foreach ( $routes as $route ) {
253 foreach ( array( 'content_module', 'route_module' ) as $module_key ) {
254 if ( ! empty( $route[ $module_key ] ) ) {
255 $module_ids[] = $route[ $module_key ];
256 }
257 }
258 }
259
260 foreach ( \array_unique( $module_ids ) as $module_id ) {
261 if ( ! isset( $module_assets[ $module_id ] ) ) {
262 continue;
263 }
264
265 self::register_script_module(
266 $module_id,
267 $module_assets[ $module_id ]['script'],
268 $module_assets[ $module_id ]['asset']
269 );
270 }
271
272 $loader_asset = self::get_asset( 'build/app/loader.asset.php' );
273
274 \wp_register_script_module(
275 self::LOADER_MODULE,
276 \plugins_url( 'build/app/loader.js', ACTIVITYPUB_PLUGIN_FILE ),
277 self::get_boot_dependencies( $routes ),
278 \is_array( $loader_asset ) && isset( $loader_asset['version'] ) ? $loader_asset['version'] : ACTIVITYPUB_PLUGIN_VERSION
279 );
280 }
281
282 /**
283 * Get built app module asset paths keyed by script module ID.
284 *
285 * @return array Module asset definitions.
286 */
287 private static function get_app_module_assets() {
288 return array(
289 self::FEED_CONTENT_MODULE => array(
290 'script' => 'build/app/routes/feed/content.js',
291 'asset' => 'build/app/routes/feed/content.asset.php',
292 ),
293 self::FEED_ROUTE_MODULE => array(
294 'script' => 'build/app/routes/feed/route.js',
295 'asset' => 'build/app/routes/feed/route.asset.php',
296 ),
297 );
298 }
299
300 /**
301 * Register a built script module.
302 *
303 * @param string $module_id Script module ID.
304 * @param string $script Script path relative to the plugin root.
305 * @param string $asset_file Asset metadata path relative to the plugin root.
306 */
307 private static function register_script_module( $module_id, $script, $asset_file ) {
308 $asset = self::get_asset( $asset_file );
309
310 if ( ! $asset ) {
311 return;
312 }
313
314 \wp_register_script_module(
315 $module_id,
316 \plugins_url( $script, ACTIVITYPUB_PLUGIN_FILE ),
317 isset( $asset['dependencies'] ) && \is_array( $asset['dependencies'] ) ? $asset['dependencies'] : array(),
318 isset( $asset['version'] ) ? $asset['version'] : ACTIVITYPUB_PLUGIN_VERSION
319 );
320 }
321
322 /**
323 * Build dependencies for the app loader module.
324 *
325 * @param array $routes Route definitions.
326 * @return array Script module dependencies.
327 */
328 private static function get_boot_dependencies( $routes ) {
329 $dependencies = array(
330 array(
331 'id' => '@wordpress/boot',
332 'import' => 'static',
333 ),
334 );
335
336 foreach ( $routes as $route ) {
337 if ( ! empty( $route['route_module'] ) ) {
338 $dependencies[] = array(
339 'id' => $route['route_module'],
340 'import' => 'static',
341 );
342 }
343
344 if ( ! empty( $route['content_module'] ) ) {
345 $dependencies[] = array(
346 'id' => $route['content_module'],
347 'import' => 'dynamic',
348 );
349 }
350 }
351
352 /**
353 * Filters the ActivityPub app loader module dependencies.
354 *
355 * @param array $dependencies Script module dependencies.
356 * @param array $routes Route definitions.
357 */
358 return \apply_filters( 'activitypub_app_boot_dependencies', $dependencies, $routes );
359 }
360
361 /**
362 * Enqueue styles emitted by the module build.
363 */
364 private static function enqueue_app_styles() {
365 $style_path = 'build/app/routes/feed/style-content.css';
366 $asset = self::get_asset( 'build/app/routes/feed/content.asset.php' );
367
368 if ( ! \file_exists( \plugin_dir_path( ACTIVITYPUB_PLUGIN_FILE ) . $style_path ) ) {
369 return;
370 }
371
372 $style_dependencies = \array_filter(
373 array( 'wp-components', 'wp-edit-site' ),
374 static function ( $handle ) {
375 return \wp_style_is( $handle, 'registered' );
376 }
377 );
378
379 \wp_enqueue_style(
380 'activitypub-app',
381 \plugins_url( $style_path, ACTIVITYPUB_PLUGIN_FILE ),
382 $style_dependencies,
383 \is_array( $asset ) && isset( $asset['version'] ) ? $asset['version'] : ACTIVITYPUB_PLUGIN_VERSION
384 );
385 }
386
387 /**
388 * Get the core boot asset file path.
389 *
390 * @return string Boot asset file path.
391 */
392 private static function get_boot_asset_file() {
393 return ABSPATH . WPINC . '/js/dist/script-modules/boot/index.min.asset.php';
394 }
395
396 /**
397 * Get the core boot asset metadata.
398 *
399 * @return array|null Boot asset metadata, or null when unavailable.
400 */
401 private static function get_boot_asset() {
402 static $cached = false;
403 static $asset = null;
404
405 if ( $cached ) {
406 return $asset;
407 }
408
409 $cached = true;
410
411 $boot_asset_file = self::get_boot_asset_file();
412
413 if ( ! \file_exists( $boot_asset_file ) ) {
414 return $asset;
415 }
416
417 $boot_asset = include $boot_asset_file;
418 $asset = \is_array( $boot_asset ) ? $boot_asset : null;
419
420 return $asset;
421 }
422
423 /**
424 * Get built asset metadata.
425 *
426 * @param string $asset_file Asset metadata path relative to the plugin root.
427 * @return array|null Asset metadata, or null when unavailable.
428 */
429 private static function get_asset( $asset_file ) {
430 $path = \plugin_dir_path( ACTIVITYPUB_PLUGIN_FILE ) . $asset_file;
431
432 if ( ! \file_exists( $path ) ) {
433 return null;
434 }
435
436 return include $path;
437 }
438
439 /**
440 * Render the App admin page.
441 */
442 public static function render_page() {
443 if ( ! self::is_supported() ) {
444 ?>
445 <div class="notice notice-error">
446 <p><?php \esc_html_e( 'The Social Web screen requires a newer WordPress admin app runtime. Please update WordPress to use this screen.', 'activitypub' ); ?></p>
447 </div>
448 <?php
449 return;
450 }
451 ?>
452 <div id="<?php echo \esc_attr( self::MOUNT_ID ); ?>" class="boot-layout-container activitypub-app-layout"></div>
453 <?php
454 }
455 }
456