diff --git a/includes/admin/load.php b/includes/admin/load.php index 15097f5f0..250c395d9 100644 --- a/includes/admin/load.php +++ b/includes/admin/load.php @@ -231,6 +231,22 @@ function perflab_enqueue_features_page_scripts(): void { wp_enqueue_script( 'wp-a11y' ); } +/** + * Sanitizes a plugin slug. + * + * @since 3.1.0 + * + * @param mixed $unsanitized_plugin_slug Unsanitized plugin slug. + * @return string|null Validated and sanitized slug or else null. + */ +function perflab_sanitize_plugin_slug( $unsanitized_plugin_slug ): ?string { + if ( in_array( $unsanitized_plugin_slug, perflab_get_standalone_plugins(), true ) ) { + return $unsanitized_plugin_slug; + } else { + return null; + } +} + /** * Callback for handling installation/activation of plugin. * @@ -248,8 +264,8 @@ function perflab_install_activate_plugin_callback(): void { wp_die( esc_html__( 'Missing required parameter.', 'performance-lab' ) ); } - $plugin_slug = sanitize_text_field( wp_unslash( $_GET['slug'] ) ); - if ( ! in_array( $plugin_slug, perflab_get_standalone_plugins(), true ) ) { + $plugin_slug = perflab_sanitize_plugin_slug( wp_unslash( $_GET['slug'] ) ); + if ( ! $plugin_slug ) { wp_die( esc_html__( 'Invalid plugin.', 'performance-lab' ) ); } @@ -262,7 +278,7 @@ function perflab_install_activate_plugin_callback(): void { $url = add_query_arg( array( 'page' => PERFLAB_SCREEN, - 'activate' => 'true', + 'activate' => $plugin_slug, ), admin_url( 'options-general.php' ) ); @@ -287,12 +303,31 @@ function perflab_print_features_page_style(): void { margin-left: 0; } .plugin-card-top { - min-height: auto; + /* This is required to ensure the Settings link does not extend below the bottom of a plugin card on a wide screen. */ + min-height: 90px; + } + @media screen and (max-width: 782px) { + .plugin-card-top { + /* Same reason as above, but now the button is taller to make it easier to tap on touch screens. */ + min-height: 110px; + } } .plugin-card .perflab-plugin-experimental { font-size: 80%; font-weight: normal; } + + @media screen and (max-width: 1100px) and (min-width: 782px), (max-width: 480px) { + .plugin-card .action-links { + margin-left: auto; + } + /* Make sure the settings link gets spaced out from the Learn more link. */ + .plugin-card .plugin-action-buttons > li:nth-child(3) { + margin-left: 2ex; + border-left: solid 1px; + padding-left: 2ex; + } + } settings.', 'performance-lab' ), esc_url( $plugin_settings_url ) ); + } + wp_admin_notice( - esc_html__( 'Feature activated.', 'performance-lab' ), + wp_kses( + $message, + array( + 'a' => array( + 'href' => array(), + ), + ) + ), array( 'type' => 'success', 'dismissible' => true, @@ -375,3 +430,44 @@ function addPluginProgressIndicator( message ) { array( 'type' => 'module' ) ); } + +/** + * Gets the URL to the plugin settings screen if one exists. + * + * @since n.e.x.t + * + * @param string $plugin_slug Plugin slug passed to generate the settings link. + * @return string|null Either the plugin settings URL or null if not available. + */ +function perflab_get_plugin_settings_url( string $plugin_slug ): ?string { + $plugin_file = null; + + foreach ( array_keys( get_plugins() ) as $file ) { + if ( strtok( $file, '/' ) === $plugin_slug ) { + $plugin_file = $file; + break; + } + } + + if ( null === $plugin_file ) { + return null; + } + + /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */ + $plugin_links = apply_filters( "plugin_action_links_{$plugin_file}", array() ); + + if ( ! is_array( $plugin_links ) || ! array_key_exists( 'settings', $plugin_links ) ) { + return null; + } + + $p = new WP_HTML_Tag_Processor( $plugin_links['settings'] ); + if ( ! $p->next_tag( array( 'tag_name' => 'A' ) ) ) { + return null; + } + $href = $p->get_attribute( 'href' ); + if ( $href && is_string( $href ) ) { + return $href; + } + + return null; +} diff --git a/includes/admin/plugins.php b/includes/admin/plugins.php index a27c778e9..162b1f6c2 100644 --- a/includes/admin/plugins.php +++ b/includes/admin/plugins.php @@ -318,8 +318,7 @@ function perflab_render_plugin_card( array $plugin_data ): void { /** This filter is documented in wp-admin/includes/class-wp-plugin-install-list-table.php */ $description = apply_filters( 'plugin_install_description', $description, $plugin_data ); - $availability = perflab_get_plugin_availability( $plugin_data ); - + $availability = perflab_get_plugin_availability( $plugin_data ); $compatible_php = $availability['compatible_php']; $compatible_wp = $availability['compatible_wp']; @@ -398,6 +397,14 @@ function perflab_render_plugin_card( array $plugin_data ): void { esc_html__( 'Visit plugin site', 'default' ) ); } + + if ( $availability['activated'] ) { + $settings_url = perflab_get_plugin_settings_url( $plugin_data['slug'] ); + if ( $settings_url ) { + /* translators: %s is the settings URL */ + $action_links[] = sprintf( '%s', esc_url( $settings_url ), esc_html__( 'Settings', 'performance-lab' ) ); + } + } ?>
assertStringNotContainsString( "", $output ); } + /** + * @covers ::perflab_plugin_action_links_add_settings + */ public function test_perflab_plugin_action_links_add_settings(): void { $original_links = array( 'deactivate' => 'Deactivate', @@ -73,4 +79,40 @@ public function test_perflab_plugin_action_links_add_settings(): void { $actual_links = perflab_plugin_action_links_add_settings( $original_links ); $this->assertSame( $expected_links, $actual_links ); } + + /** + * @return array + */ + public function data_provider_to_test_perflab_sanitize_plugin_slug(): array { + return array( + array( + 'webp-uploads', + 'webp-uploads', + ), + array( + 'akismet', + null, + ), + array( + 1, + null, + ), + array( + array( 'speculative-loading' ), + null, + ), + ); + } + + /** + * @covers ::perflab_sanitize_plugin_slug + * + * @dataProvider data_provider_to_test_perflab_sanitize_plugin_slug + * + * @param mixed $slug Slug. + * @param string|null $expected Expected. + */ + public function test_perflab_sanitize_plugin_slug( $slug, ?string $expected ): void { + $this->assertSame( $expected, perflab_sanitize_plugin_slug( $slug ) ); + } }