Skip to content

Commit 32bc144

Browse files
Merge pull request #2122 from WordPress/add/new-feature-admin-pointer
Add admin pointers to promote new Performance Lab features Co-authored-by: westonruter <[email protected]> Co-authored-by: felixarntz <[email protected]>
2 parents 75130a1 + 511d71e commit 32bc144

File tree

3 files changed

+179
-51
lines changed

3 files changed

+179
-51
lines changed

plugins/performance-lab/includes/admin/load.php

Lines changed: 120 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,46 @@ function perflab_render_settings_page(): void {
6767
<?php
6868
}
6969

70+
/**
71+
* Gets dismissed admin pointer IDs.
72+
*
73+
* @since n.e.x.t
74+
*
75+
* @return non-empty-string[] Dismissed admin pointer IDs.
76+
*/
77+
function perflab_get_dismissed_admin_pointer_ids(): array {
78+
return array_filter(
79+
explode(
80+
',',
81+
(string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true )
82+
)
83+
);
84+
}
85+
86+
/**
87+
* Gets the admin pointers.
88+
*
89+
* @since n.e.x.t
90+
*
91+
* @return array<non-empty-string, string> Admin pointer messages with the admin pointer IDs as the keys.
92+
*/
93+
function perflab_get_admin_pointers(): array {
94+
$pointers = array(
95+
'perflab-admin-pointer' => __( 'You can now test upcoming WordPress performance features.', 'performance-lab' ),
96+
'perflab-feature-view-transitions' => __( 'New <strong>View Transitions</strong> feature now available.', 'performance-lab' ),
97+
);
98+
99+
if (
100+
defined( 'SPECULATION_RULES_VERSION' )
101+
&&
102+
version_compare( SPECULATION_RULES_VERSION, '1.6.0', '>=' )
103+
) {
104+
$pointers['perflab-feature-speculation-rules-auth'] = __( '<strong>Speculative Loading</strong> now includes an opt-in setting for logged-in users.', 'performance-lab' );
105+
}
106+
107+
return $pointers;
108+
}
109+
70110
/**
71111
* Initializes admin pointer.
72112
*
@@ -83,19 +123,29 @@ function perflab_admin_pointer( ?string $hook_suffix = '' ): void {
83123
if ( is_network_admin() || is_user_admin() ) {
84124
return;
85125
}
86-
$current_user = get_current_user_id();
87-
$dismissed = array_filter( explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ) );
88126

89-
if ( in_array( 'perflab-admin-pointer', $dismissed, true ) ) {
127+
$admin_pointers = perflab_get_admin_pointers();
128+
$admin_pointer_ids = array_keys( $admin_pointers );
129+
$dismissed_pointer_ids = perflab_get_dismissed_admin_pointer_ids();
130+
131+
// All pointers have been dismissed already.
132+
if ( count( array_diff( $admin_pointer_ids, $dismissed_pointer_ids ) ) === 0 ) {
90133
return;
91134
}
92135

136+
// Do not show the admin pointer when not on the dashboard or plugins list table.
93137
if ( ! in_array( $hook_suffix, array( 'index.php', 'plugins.php' ), true ) ) {
94138

95-
// Do not show on the settings page and dismiss the pointer.
96-
if ( isset( $_GET['page'] ) && PERFLAB_SCREEN === $_GET['page'] && ( ! in_array( 'perflab-admin-pointer', $dismissed, true ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
97-
$dismissed[] = 'perflab-admin-pointer';
98-
update_user_meta( $current_user, 'dismissed_wp_pointers', implode( ',', $dismissed ) );
139+
// And if we're on the Performance screen, automatically dismiss the pointers.
140+
if ( isset( $_GET['page'] ) && PERFLAB_SCREEN === $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
141+
update_user_meta(
142+
get_current_user_id(),
143+
'dismissed_wp_pointers',
144+
implode(
145+
',',
146+
array_unique( array_merge( $dismissed_pointer_ids, $admin_pointer_ids ) )
147+
)
148+
);
99149
}
100150

101151
return;
@@ -104,69 +154,89 @@ function perflab_admin_pointer( ?string $hook_suffix = '' ): void {
104154
// Enqueue pointer CSS and JS.
105155
wp_enqueue_style( 'wp-pointer' );
106156
wp_enqueue_script( 'wp-pointer' );
107-
add_action( 'admin_print_footer_scripts', 'perflab_render_pointer', 10, 0 );
108-
}
109-
add_action( 'admin_enqueue_scripts', 'perflab_admin_pointer' );
110157

111-
/**
112-
* Renders the Admin Pointer.
113-
*
114-
* Handles the rendering of the admin pointer.
115-
*
116-
* @since 1.0.0
117-
* @since 2.4.0 Optional arguments were added to make the function reusable for different pointers.
118-
*
119-
* @param string $pointer_id Optional. ID of the pointer. Default 'perflab-admin-pointer'.
120-
* @param array{heading?: string, content?: string} $args Optional. Pointer arguments. Supports 'heading' and 'content' entries.
121-
* Defaults are the heading and content for the 'perflab-admin-pointer'.
122-
*/
123-
function perflab_render_pointer( string $pointer_id = 'perflab-admin-pointer', array $args = array() ): void {
124-
if ( ! isset( $args['heading'] ) ) {
125-
$args['heading'] = __( 'Performance Lab', 'performance-lab' );
126-
}
127-
if ( ! isset( $args['content'] ) ) {
128-
$args['content'] = sprintf(
129-
/* translators: %s: settings page link */
130-
esc_html__( 'You can now test upcoming WordPress performance features. Open %s to individually toggle the performance features.', 'performance-lab' ),
131-
'<a href="' . esc_url( add_query_arg( 'page', PERFLAB_SCREEN, admin_url( 'options-general.php' ) ) ) . '">' . esc_html__( 'Settings > Performance', 'performance-lab' ) . '</a>'
132-
);
158+
$new_install_pointer_id = 'perflab-admin-pointer';
159+
if ( ! in_array( $new_install_pointer_id, $dismissed_pointer_ids, true ) ) {
160+
$needed_pointer_ids = array( $new_install_pointer_id );
161+
} else {
162+
$needed_pointer_ids = array_diff( $admin_pointer_ids, $dismissed_pointer_ids );
133163
}
134164

165+
$args = array(
166+
'heading' => __( 'Performance Lab', 'performance-lab' ),
167+
);
168+
169+
$args['content'] = implode(
170+
'',
171+
array_map(
172+
static function ( string $needed_pointer ) use ( $admin_pointers ): string {
173+
return '<p>' . $admin_pointers[ $needed_pointer ] . '</p>';
174+
},
175+
$needed_pointer_ids
176+
)
177+
);
178+
179+
$args['content'] .= '<p>' . sprintf(
180+
/* translators: %s: settings page link */
181+
esc_html__( 'Open %s to individually toggle the performance features and access any relevant settings.', 'performance-lab' ),
182+
'<a href="' . esc_url( add_query_arg( 'page', PERFLAB_SCREEN, admin_url( 'options-general.php' ) ) ) . '">' . esc_html__( 'Settings > Performance', 'performance-lab' ) . '</a>'
183+
) . '</p>';
184+
135185
$wp_kses_options = array(
136-
'a' => array(
186+
'a' => array(
137187
'href' => array(),
138188
),
189+
'p' => array(),
190+
'strong' => array(),
139191
);
140192

193+
$pointer_ids_to_dismiss = array_values( array_diff( $admin_pointer_ids, $dismissed_pointer_ids ) );
194+
195+
ob_start();
141196
?>
142-
<script id="<?php echo esc_attr( $pointer_id ); ?>" type="text/javascript">
197+
<script>
143198
jQuery( function() {
199+
const pointerIdsToDismiss = <?php echo wp_json_encode( $pointer_ids_to_dismiss, JSON_OBJECT_AS_ARRAY ); ?>;
200+
const nonce = <?php echo wp_json_encode( wp_create_nonce( 'dismiss_pointer' ) ); ?>;
201+
202+
function dismissNextPointer() {
203+
const pointerId = pointerIdsToDismiss.shift();
204+
if ( ! pointerId ) {
205+
return;
206+
}
207+
208+
jQuery.post(
209+
window.ajaxurl,
210+
{
211+
pointer: pointerId,
212+
action: 'dismiss-wp-pointer',
213+
_wpnonce: nonce,
214+
}
215+
).then( dismissNextPointer );
216+
}
217+
144218
// Pointer Options.
145219
const options = {
146-
content: <?php echo wp_json_encode( '<h3>' . esc_html( $args['heading'] ) . '</h3><p>' . wp_kses( $args['content'], $wp_kses_options ) . '</p>' ); ?>,
220+
content: <?php echo wp_json_encode( '<h3>' . esc_html( $args['heading'] ) . '</h3>' . wp_kses( $args['content'], $wp_kses_options ) ); ?>,
147221
position: {
148222
edge: 'left',
149223
align: 'right',
150224
},
151225
pointerClass: 'wp-pointer arrow-top',
152226
pointerWidth: 420,
153-
close: function() {
154-
jQuery.post(
155-
window.ajaxurl,
156-
{
157-
pointer: <?php echo wp_json_encode( $pointer_id ); ?>,
158-
action: 'dismiss-wp-pointer',
159-
_wpnonce: <?php echo wp_json_encode( wp_create_nonce( 'dismiss_pointer' ) ); ?>,
160-
}
161-
);
162-
}
227+
close: dismissNextPointer
163228
};
164229

165230
jQuery( '#menu-settings' ).pointer( options ).pointer( 'open' );
166231
} );
167232
</script>
168233
<?php
234+
$processor = new WP_HTML_Tag_Processor( (string) ob_get_clean() );
235+
if ( $processor->next_tag( array( 'tag_name' => 'SCRIPT' ) ) ) {
236+
wp_add_inline_script( 'wp-pointer', $processor->get_modifiable_text() );
237+
}
169238
}
239+
add_action( 'admin_enqueue_scripts', 'perflab_admin_pointer' );
170240

171241
/**
172242
* Adds a link to the features page to the plugin's entry in the plugins list table.
@@ -207,7 +277,11 @@ function perflab_plugin_action_links_add_settings( $links ) {
207277
* @since 2.3.0
208278
*/
209279
function perflab_dismiss_wp_pointer_wrapper(): void {
210-
if ( isset( $_POST['pointer'] ) && 'perflab-admin-pointer' !== $_POST['pointer'] ) {
280+
if (
281+
isset( $_POST['pointer'] )
282+
&&
283+
! in_array( $_POST['pointer'], array_keys( perflab_get_admin_pointers() ), true )
284+
) {
211285
// Another plugin's pointer, do nothing.
212286
return;
213287
}

plugins/performance-lab/readme.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,10 @@ Contributions are always welcome! Learn more about how to get involved in the [C
610610

611611
== Upgrade Notice ==
612612

613+
= n.e.x.t =
614+
615+
This release introduces a new feature plugin called View Transitions which adds smooth transitions between navigations on your site.
616+
613617
= 3.2.0 =
614618

615619
This release introduces a new feature plugin called Image Prioritizer which optimizes the loading of images to improve LCP.

plugins/performance-lab/tests/includes/admin/test-load.php

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,37 @@ public function test_perflab_render_settings_page(): void {
8686
$this->assertStringNotContainsString( "<input type='hidden' name='option_page' value='" . PERFLAB_SCREEN . "' />", $output );
8787
}
8888

89+
/**
90+
* @covers ::perflab_get_dismissed_admin_pointer_ids
91+
*/
92+
public function test_perflab_get_dismissed_admin_pointer_ids(): void {
93+
$user_id = self::factory()->user->create( array( 'role' => 'administrator' ) );
94+
wp_set_current_user( $user_id );
95+
96+
// No dismissed pointers.
97+
$this->assertSame( array(), perflab_get_dismissed_admin_pointer_ids() );
98+
99+
// Dismiss a single pointer.
100+
update_user_meta( $user_id, 'dismissed_wp_pointers', 'perflab-admin-pointer' );
101+
$this->assertSame( array( 'perflab-admin-pointer' ), perflab_get_dismissed_admin_pointer_ids() );
102+
103+
// Dismiss multiple pointers.
104+
update_user_meta( $user_id, 'dismissed_wp_pointers', 'perflab-admin-pointer,another-pointer' );
105+
$this->assertSame( array( 'perflab-admin-pointer', 'another-pointer' ), perflab_get_dismissed_admin_pointer_ids() );
106+
107+
// Dismiss all pointers.
108+
update_user_meta( $user_id, 'dismissed_wp_pointers', implode( ',', array_keys( perflab_get_admin_pointers() ) ) );
109+
$this->assertSame( array_keys( perflab_get_admin_pointers() ), perflab_get_dismissed_admin_pointer_ids() );
110+
}
111+
112+
/**
113+
* @covers ::perflab_get_admin_pointers
114+
*/
115+
public function test_perflab_get_admin_pointers(): void {
116+
$pointers = perflab_get_admin_pointers();
117+
$this->assertArrayHasKey( 'perflab-admin-pointer', $pointers );
118+
}
119+
89120
/**
90121
* @return array<string, array{ hook_suffix: string|null, expected: bool }>
91122
*/
@@ -119,23 +150,32 @@ public function data_provider_test_perflab_admin_pointer(): array {
119150
'assert' => null,
120151
'dismissed_wp_pointers' => '',
121152
),
122-
'dashboard_yes_dismissed' => array(
153+
'dashboard_new_dismissed' => array(
123154
'set_up' => static function (): void {
124155
update_user_meta( wp_get_current_user()->ID, 'dismissed_wp_pointers', 'perflab-admin-pointer' );
125156
},
126157
'hook_suffix' => 'index.php',
127-
'expected' => false,
158+
'expected' => true,
128159
'assert' => null,
129160
'dismissed_wp_pointers' => 'perflab-admin-pointer',
130161
),
162+
'dashboard_all_dismissed' => array(
163+
'set_up' => static function (): void {
164+
update_user_meta( wp_get_current_user()->ID, 'dismissed_wp_pointers', implode( ',', array_keys( perflab_get_admin_pointers() ) ) );
165+
},
166+
'hook_suffix' => 'index.php',
167+
'expected' => false,
168+
'assert' => null,
169+
'dismissed_wp_pointers' => implode( ',', array_keys( perflab_get_admin_pointers() ) ),
170+
),
131171
'perflab_screen_first_time' => array(
132172
'set_up' => static function (): void {
133173
$_GET['page'] = PERFLAB_SCREEN;
134174
},
135175
'hook_suffix' => 'options-general.php',
136176
'expected' => false,
137177
'assert' => null,
138-
'dismissed_wp_pointers' => 'perflab-admin-pointer',
178+
'dismissed_wp_pointers' => implode( ',', array_keys( perflab_get_admin_pointers() ) ),
139179
),
140180
'perflab_screen_second_time' => array(
141181
'set_up' => static function (): void {
@@ -145,7 +185,7 @@ public function data_provider_test_perflab_admin_pointer(): array {
145185
'hook_suffix' => 'options-general.php',
146186
'expected' => false,
147187
'assert' => null,
148-
'dismissed_wp_pointers' => 'perflab-admin-pointer',
188+
'dismissed_wp_pointers' => implode( ',', array_keys( perflab_get_admin_pointers() ) ),
149189
),
150190
);
151191
}
@@ -168,7 +208,17 @@ public function test_perflab_admin_pointer( ?Closure $set_up, ?string $hook_suff
168208
}
169209
$this->assertFalse( is_network_admin() || is_user_admin() );
170210
perflab_admin_pointer( $hook_suffix );
171-
$this->assertSame( $expected ? 10 : false, has_action( 'admin_print_footer_scripts', 'perflab_render_pointer' ) );
211+
212+
$after_script = '';
213+
$script_dependency = wp_scripts()->query( 'wp-pointer' );
214+
if ( $script_dependency instanceof _WP_Dependency ) {
215+
$after_script = implode( "\n", array_filter( $script_dependency->extra['after'] ?? array() ) );
216+
}
217+
if ( $expected ) {
218+
$this->assertStringContainsString( 'pointerIdsToDismiss', $after_script );
219+
} else {
220+
$this->assertStringNotContainsString( 'pointerIdsToDismiss', $after_script );
221+
}
172222
$this->assertSame( $expected, wp_script_is( 'wp-pointer', 'enqueued' ) );
173223
$this->assertSame( $expected, wp_style_is( 'wp-pointer', 'enqueued' ) );
174224
$this->assertSame( $dismissed_wp_pointers, get_user_meta( $user_id, 'dismissed_wp_pointers', true ) );

0 commit comments

Comments
 (0)