PluginProbe
Performance Lab / 2.6.1
Performance Lab v2.6.1
trunk 1.0.0 1.0.0-beta.1 1.0.0-beta.2 1.0.0-beta.3 1.0.0-rc.1 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 1.7.0 1.8.0 1.9.0 2.0.0 2.1.0 2.2.0 2.3.0 2.4.0 2.5.0 2.6.0 2.6.1 2.7.0 2.8.0 All 44 releases
performance-lab / admin / load.php

load.php in Performance Lab 2.6.1, at admin/load.php

506 lines 16.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Admin integration file
4 *
5 * @package performance-lab
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit; // Exit if accessed directly.
10 }
11
12 /**
13 * Adds the modules page to the Settings menu.
14 *
15 * @since 1.0.0
16 */
17 function perflab_add_modules_page() {
18 // Don't add a page if active modules are controlled programmatically.
19 if ( has_filter( 'perflab_active_modules' ) ) {
20 return false;
21 }
22
23 $hook_suffix = add_options_page(
24 __( 'Performance Modules', 'performance-lab' ),
25 __( 'Performance', 'performance-lab' ),
26 'manage_options',
27 PERFLAB_MODULES_SCREEN,
28 'perflab_render_modules_page'
29 );
30
31 // Add the following hooks only if the screen was successfully added.
32 if ( false !== $hook_suffix ) {
33 add_action( "load-{$hook_suffix}", 'perflab_load_modules_page', 10, 0 );
34 add_filter( 'plugin_action_links_' . plugin_basename( PERFLAB_MAIN_FILE ), 'perflab_plugin_action_links_add_settings' );
35 }
36
37 return $hook_suffix;
38 }
39 add_action( 'admin_menu', 'perflab_add_modules_page' );
40
41 /**
42 * Initializes settings sections and fields for the modules page.
43 *
44 * @global array $wp_settings_sections Registered WordPress settings sections.
45 *
46 * @since 1.0.0
47 *
48 * @param array|null $modules Associative array of available module data, keyed by module slug. By default, this
49 * will rely on {@see perflab_get_modules()}.
50 * @param array|null $focus_areas Associative array of focus area data, keyed by focus area slug. By default, this will
51 * rely on {@see perflab_get_focus_areas()}.
52 */
53 function perflab_load_modules_page( $modules = null, $focus_areas = null ) {
54 global $wp_settings_sections;
55
56 // Register sections for all focus areas, plus 'Other'.
57 if ( ! is_array( $focus_areas ) ) {
58 $focus_areas = perflab_get_focus_areas();
59 }
60 $sections = $focus_areas;
61 $sections['other'] = array( 'name' => __( 'Other', 'performance-lab' ) );
62 foreach ( $sections as $section_slug => $section_data ) {
63 add_settings_section(
64 $section_slug,
65 $section_data['name'],
66 null,
67 PERFLAB_MODULES_SCREEN
68 );
69 }
70
71 // Register fields for all modules.
72 if ( ! is_array( $modules ) ) {
73 $modules = perflab_get_modules();
74 }
75 $settings = perflab_get_module_settings();
76 foreach ( $modules as $module_slug => $module_data ) {
77 $module_settings = isset( $settings[ $module_slug ] ) ? $settings[ $module_slug ] : array();
78 $module_section = isset( $sections[ $module_data['focus'] ] ) ? $module_data['focus'] : 'other';
79
80 // Mark this module's section as added.
81 $sections[ $module_section ]['added'] = true;
82
83 add_settings_field(
84 $module_slug,
85 $module_data['name'],
86 static function () use ( $module_slug, $module_data, $module_settings ) {
87 perflab_render_modules_page_field( $module_slug, $module_data, $module_settings );
88 },
89 PERFLAB_MODULES_SCREEN,
90 $module_section
91 );
92 }
93
94 // Remove all sections for which there are no modules.
95 foreach ( $sections as $section_slug => $section_data ) {
96 if ( empty( $section_data['added'] ) ) {
97 unset( $wp_settings_sections[ PERFLAB_MODULES_SCREEN ][ $section_slug ] );
98 }
99 }
100 }
101
102 /**
103 * Renders the modules page.
104 *
105 * @since 1.0.0
106 */
107 function perflab_render_modules_page() {
108 ?>
109 <div class="wrap">
110 <h1>
111 <?php esc_html_e( 'Performance Modules', 'performance-lab' ); ?>
112 </h1>
113
114 <form action="options.php" method="post">
115 <?php settings_fields( PERFLAB_MODULES_SCREEN ); ?>
116 <?php do_settings_sections( PERFLAB_MODULES_SCREEN ); ?>
117 <?php submit_button(); ?>
118 </form>
119 </div>
120 <?php
121 }
122
123 /**
124 * Renders fields for a given module on the modules page.
125 *
126 * @since 1.0.0
127 *
128 * @param string $module_slug Slug of the module.
129 * @param array $module_data Associative array of the module's parsed data.
130 * @param array $module_settings Associative array of the module's current settings.
131 */
132 function perflab_render_modules_page_field( $module_slug, $module_data, $module_settings ) {
133 $base_id = sprintf( 'module_%s', $module_slug );
134 $base_name = sprintf( '%1$s[%2$s]', PERFLAB_MODULES_SETTING, $module_slug );
135 $enabled = isset( $module_settings['enabled'] ) && $module_settings['enabled'];
136 $can_load_module = perflab_can_load_module( $module_slug );
137 $is_standalone_plugin_loaded = perflab_is_standalone_plugin_loaded( $module_slug );
138 ?>
139 <fieldset>
140 <legend class="screen-reader-text">
141 <?php echo esc_html( $module_data['name'] ); ?>
142 </legend>
143 <label for="<?php echo esc_attr( "{$base_id}_enabled" ); ?>">
144 <?php if ( $can_load_module && ! $is_standalone_plugin_loaded ) { ?>
145 <input type="checkbox" id="<?php echo esc_attr( "{$base_id}_enabled" ); ?>" name="<?php echo esc_attr( "{$base_name}[enabled]" ); ?>" aria-describedby="<?php echo esc_attr( "{$base_id}_description" ); ?>" value="1"<?php checked( $enabled ); ?>>
146 <?php
147 if ( $module_data['experimental'] ) {
148 printf(
149 wp_kses(
150 /* translators: %s: module name */
151 __( 'Enable %s <strong>(experimental)</strong>', 'performance-lab' ),
152 array( 'strong' => array() )
153 ),
154 esc_html( $module_data['name'] )
155 );
156 } else {
157 printf(
158 /* translators: %s: module name */
159 esc_html__( 'Enable %s', 'performance-lab' ),
160 esc_html( $module_data['name'] )
161 );
162 }
163 ?>
164 <?php } else { ?>
165 <input type="checkbox" id="<?php echo esc_attr( "{$base_id}_enabled" ); ?>" aria-describedby="<?php echo esc_attr( "{$base_id}_description" ); ?>" disabled>
166 <input type="hidden" name="<?php echo esc_attr( "{$base_name}[enabled]" ); ?>" value="<?php echo $enabled ? '1' : '0'; ?>">
167 <?php
168 if ( $is_standalone_plugin_loaded ) {
169 esc_html_e( 'The module cannot be managed with Performance Lab since it is already active as a standalone plugin.', 'performance-lab' );
170 } else {
171 printf(
172 /* translators: %s: module name */
173 esc_html__( '%s is already part of your WordPress version and therefore cannot be loaded as part of the plugin.', 'performance-lab' ),
174 esc_html( $module_data['name'] )
175 );
176 }
177 ?>
178 <?php } ?>
179 </label>
180 <p id="<?php echo esc_attr( "{$base_id}_description" ); ?>" class="description">
181 <?php echo esc_html( $module_data['description'] ); ?>
182 </p>
183 </fieldset>
184 <?php
185 }
186
187 /**
188 * Gets all available focus areas.
189 *
190 * @since 1.0.0
191 *
192 * @return array Associative array of focus area data, keyed by focus area slug. Fields for every focus area include
193 * 'name'.
194 */
195 function perflab_get_focus_areas() {
196 return array(
197 'images' => array(
198 'name' => __( 'Images', 'performance-lab' ),
199 ),
200 'js-and-css' => array(
201 'name' => __( 'JS & CSS', 'performance-lab' ),
202 ),
203 'database' => array(
204 'name' => __( 'Database', 'performance-lab' ),
205 ),
206 'measurement' => array(
207 'name' => __( 'Measurement', 'performance-lab' ),
208 ),
209 'object-cache' => array(
210 'name' => __( 'Object Cache', 'performance-lab' ),
211 ),
212 );
213 }
214
215 /**
216 * Gets all available modules.
217 *
218 * This function iterates through the modules directory and therefore should only be called on the modules page.
219 * It searches all modules, similar to how plugins are searched in the WordPress core function `get_plugins()`.
220 *
221 * @since 1.0.0
222 *
223 * @param string $modules_root Modules root directory to look for modules in. Default is the `/modules` directory
224 * in the plugin's root.
225 * @return array Associative array of parsed module data, keyed by module slug. Fields for every module include
226 * 'name', 'description', 'focus', and 'experimental'.
227 */
228 function perflab_get_modules( $modules_root = null ) {
229 if ( null === $modules_root ) {
230 $modules_root = dirname( __DIR__ ) . '/modules';
231 }
232
233 $modules = array();
234 $module_files = array();
235 // PHPCS ignore reason: A modules directory is always present.
236 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
237 $modules_dir = @opendir( $modules_root );
238
239 // Modules are organized as {focus}/{module-slug} in the modules folder.
240 if ( $modules_dir ) {
241 // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
242 while ( ( $focus = readdir( $modules_dir ) ) !== false ) {
243 if ( '.' === substr( $focus, 0, 1 ) ) {
244 continue;
245 }
246
247 // Each focus area must be a directory.
248 if ( ! is_dir( $modules_root . '/' . $focus ) ) {
249 continue;
250 }
251
252 // PHPCS ignore reason: Only the focus area directory is allowed.
253 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
254 $focus_dir = @opendir( $modules_root . '/' . $focus );
255 if ( $focus_dir ) {
256 // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
257 while ( ( $file = readdir( $focus_dir ) ) !== false ) {
258 // Unlike plugins, modules must be in a directory.
259 if ( ! is_dir( $modules_root . '/' . $focus . '/' . $file ) ) {
260 continue;
261 }
262
263 // PHPCS ignore reason: Only the module directory is allowed.
264 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
265 $module_dir = @opendir( $modules_root . '/' . $focus . '/' . $file );
266 if ( $module_dir ) {
267 // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
268 while ( ( $subfile = readdir( $module_dir ) ) !== false ) {
269 if ( '.' === substr( $subfile, 0, 1 ) ) {
270 continue;
271 }
272
273 // Unlike plugins, module main files must be called `load.php`.
274 if ( 'load.php' !== $subfile ) {
275 continue;
276 }
277
278 $module_files[] = "$focus/$file/$subfile";
279 }
280
281 closedir( $module_dir );
282 }
283 }
284
285 closedir( $focus_dir );
286 }
287 }
288
289 closedir( $modules_dir );
290 }
291
292 foreach ( $module_files as $module_file ) {
293 if ( ! is_readable( "$modules_root/$module_file" ) ) {
294 continue;
295 }
296 $module_dir = dirname( $module_file );
297 $module_data = perflab_get_module_data( "$modules_root/$module_file" );
298 if ( ! $module_data ) {
299 continue;
300 }
301
302 $modules[ $module_dir ] = $module_data;
303 }
304
305 uasort(
306 $modules,
307 static function ( $a, $b ) {
308 return strnatcasecmp( $a['name'], $b['name'] );
309 }
310 );
311
312 return $modules;
313 }
314
315 /**
316 * Parses the module main file to get the module's metadata.
317 *
318 * This is similar to how plugin data is parsed in the WordPress core function `get_plugin_data()`.
319 * The user-facing strings will be translated.
320 *
321 * @since 1.0.0
322 *
323 * @param string $module_file Absolute path to the main module file.
324 * @return array|bool Associative array of parsed module data, or false on failure. Fields for every module include
325 * 'name', 'description', 'focus', and 'experimental'.
326 */
327 function perflab_get_module_data( $module_file ) {
328 // Extract the module dir in the form {focus}/{module-slug}.
329 preg_match( '/.*\/(.*\/.*)\/load\.php$/i', $module_file, $matches );
330 $module_dir = $matches[1];
331
332 $default_headers = array(
333 'name' => 'Module Name',
334 'description' => 'Description',
335 'experimental' => 'Experimental',
336 );
337
338 $module_data = get_file_data( $module_file, $default_headers, 'perflab_module' );
339
340 // Module name and description are the minimum requirements.
341 if ( ! $module_data['name'] || ! $module_data['description'] ) {
342 return false;
343 }
344
345 // Experimental should be a boolean.
346 if ( 'yes' === strtolower( trim( $module_data['experimental'] ) ) ) {
347 $module_data['experimental'] = true;
348 } else {
349 $module_data['experimental'] = false;
350 }
351
352 // Extract the module focus from the module directory.
353 if ( strpos( $module_dir, '/' ) ) {
354 list( $focus, $slug ) = explode( '/', $module_dir );
355 $module_data['focus'] = $focus;
356 $module_data['slug'] = $slug;
357 }
358
359 // Translate fields using low-level function since they come from PHP comments, including the necessary context for
360 // `_x()`. This must match how these are translated in the generated `/module-i18n.php` file.
361 $translatable_fields = array(
362 'name' => 'module name',
363 'description' => 'module description',
364 );
365 foreach ( $translatable_fields as $field => $context ) {
366 // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralContext,WordPress.WP.I18n.NonSingularStringLiteralText
367 $module_data[ $field ] = translate_with_gettext_context( $module_data[ $field ], $context, 'performance-lab' );
368 }
369
370 return $module_data;
371 }
372
373 /**
374 * Initializes admin pointer.
375 *
376 * Handles the bootstrapping of the admin pointer.
377 * Mainly jQuery code that is self-initialising.
378 *
379 * @param string $hook_suffix The current admin page.
380 * @since 1.0.0
381 */
382 function perflab_admin_pointer( $hook_suffix ) {
383 if ( ! in_array( $hook_suffix, array( 'index.php', 'plugins.php' ), true ) ) {
384 return;
385 }
386
387 // Do not show admin pointer in multisite Network admin or User admin UI.
388 if ( is_network_admin() || is_user_admin() ) {
389 return;
390 }
391
392 $current_user = get_current_user_id();
393 $dismissed = explode( ',', (string) get_user_meta( $current_user, 'dismissed_wp_pointers', true ) );
394
395 if ( in_array( 'perflab-admin-pointer', $dismissed, true ) ) {
396 return;
397 }
398
399 // Enqueue pointer CSS and JS.
400 wp_enqueue_style( 'wp-pointer' );
401 wp_enqueue_script( 'wp-pointer' );
402 add_action( 'admin_print_footer_scripts', 'perflab_render_pointer', 10, 0 );
403 }
404 add_action( 'admin_enqueue_scripts', 'perflab_admin_pointer' );
405
406 /**
407 * Renders the Admin Pointer.
408 *
409 * Handles the rendering of the admin pointer.
410 *
411 * @since 1.0.0
412 * @since 2.4.0 Optional arguments were added to make the function reusable for different pointers.
413 *
414 * @param string $pointer_id Optional. ID of the pointer. Default 'perflab-admin-pointer'.
415 * @param array $args Optional. Pointer arguments. Supports 'heading' and 'content' entries.
416 * Defaults are the heading and content for the 'perflab-admin-pointer'.
417 */
418 function perflab_render_pointer( $pointer_id = 'perflab-admin-pointer', $args = array() ) {
419 if ( ! isset( $args['heading'] ) ) {
420 $args['heading'] = __( 'Performance Lab', 'performance-lab' );
421 }
422 if ( ! isset( $args['content'] ) ) {
423 $args['content'] = sprintf(
424 /* translators: %s: settings page link */
425 __( 'You can now test upcoming WordPress performance features. Open %s to individually toggle the performance features included in the plugin.', 'performance-lab' ),
426 '<a href="' . esc_url( add_query_arg( 'page', PERFLAB_MODULES_SCREEN, admin_url( 'options-general.php' ) ) ) . '">' . __( 'Settings > Performance', 'performance-lab' ) . '</a>'
427 );
428 }
429
430 $wp_kses_options = array(
431 'a' => array(
432 'href' => array(),
433 ),
434 );
435
436 ?>
437 <script id="<?php echo esc_attr( $pointer_id ); ?>" type="text/javascript">
438 jQuery( function() {
439 // Pointer Options.
440 var options = {
441 content: '<h3><?php echo esc_js( $args['heading'] ); ?></h3><p><?php echo wp_kses( $args['content'], $wp_kses_options ); ?></p>',
442 position: {
443 edge: 'left',
444 align: 'right',
445 },
446 pointerClass: 'wp-pointer arrow-top',
447 pointerWidth: 420,
448 close: function() {
449 jQuery.post(
450 window.ajaxurl,
451 {
452 pointer: '<?php echo esc_js( $pointer_id ); ?>',
453 action: 'dismiss-wp-pointer',
454 _wpnonce: <?php echo wp_json_encode( wp_create_nonce( 'dismiss_pointer' ) ); ?>,
455 }
456 );
457 }
458 };
459
460 jQuery( '#menu-settings' ).pointer( options ).pointer( 'open' );
461 } );
462 </script>
463 <?php
464 }
465
466 /**
467 * Adds a link to the modules page to the plugin's entry in the plugins list table.
468 *
469 * This function is only used if the modules page exists and is accessible.
470 *
471 * @since 1.0.0
472 * @see perflab_add_modules_page()
473 *
474 * @param array $links List of plugin action links HTML.
475 * @return array Modified list of plugin action links HTML.
476 */
477 function perflab_plugin_action_links_add_settings( $links ) {
478 // Add link as the first plugin action link.
479 $settings_link = sprintf(
480 '<a href="%s">%s</a>',
481 esc_url( add_query_arg( 'page', PERFLAB_MODULES_SCREEN, admin_url( 'options-general.php' ) ) ),
482 esc_html__( 'Settings', 'performance-lab' )
483 );
484 array_unshift( $links, $settings_link );
485
486 return $links;
487 }
488
489 /**
490 * Dismisses notification pointer after verifying nonce.
491 *
492 * This function adds a nonce check before dismissing perflab-admin-pointer
493 * It runs before the dismiss-wp-pointer AJAX action is performed.
494 *
495 * @since 2.3.0
496 * @see perflab_render_modules_pointer()
497 */
498 function perflab_dismiss_wp_pointer_wrapper() {
499 if ( isset( $_POST['pointer'] ) && 'perflab-admin-pointer' !== $_POST['pointer'] ) {
500 // Another plugin's pointer, do nothing.
501 return;
502 }
503 check_ajax_referer( 'dismiss_pointer' );
504 }
505 add_action( 'wp_ajax_dismiss-wp-pointer', 'perflab_dismiss_wp_pointer_wrapper', 0 );
506