Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Enqueued_Styles_Size_Check #333

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion includes/Checker/Checks/Enqueued_Scripts_Size_Check.php
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@ protected function check_url( Check_Result $result, $url ) {
$this->add_result_warning_for_file(
$result,
sprintf(
'This script has a size of %1$s which in combination with the other scripts enqueued on %2$s exceeds the script size threshold of %3$s.',
/* translators: 1: style file size. 2: tested URL. 3: threshold file size. */
__( 'This script has a size of %1$s which in combination with the other scripts enqueued on %2$s exceeds the script size threshold of %3$s.', 'plugin-check' ),
size_format( $plugin_script['size'] ),
$url,
size_format( $this->threshold_size )
Expand Down
266 changes: 266 additions & 0 deletions includes/Checker/Checks/Enqueued_Styles_Size_Check.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
<?php
/**
* Class WordPress\Plugin_Check\Checker\Checks\Enqueued_Styles_Size_Check
*
* @package plugin-check
*/

namespace WordPress\Plugin_Check\Checker\Checks;

use Exception;
use WordPress\Plugin_Check\Checker\Check_Categories;
use WordPress\Plugin_Check\Checker\Check_Result;
use WordPress\Plugin_Check\Checker\Preparations\Demo_Posts_Creation_Preparation;
use WordPress\Plugin_Check\Checker\With_Shared_Preparations;
use WordPress\Plugin_Check\Traits\Amend_Check_Result;
use WordPress\Plugin_Check\Traits\Stable_Check;
use WordPress\Plugin_Check\Traits\URL_Aware;

/**
* Check for enqueued style sizes.
*
* @since 1.0.2
*/
class Enqueued_Styles_Size_Check extends Abstract_Runtime_Check implements With_Shared_Preparations {

use Amend_Check_Result;
use Stable_Check;
use URL_Aware;

/**
* Threshold for style size to surface a warning for.
*
* @since 1.0.2
* @var int
*/
private $threshold_size;

/**
* List of viewable post types.
*
* @since 1.0.2
* @var array
*/
private $viewable_post_types;

/**
* Set the threshold size for style sizes to surface warnings.
*
* @since 1.0.2
*
* @param int $threshold_size The threshold in bytes for style size to surface warnings.
*/
public function __construct( $threshold_size = 300000 ) {
$this->threshold_size = $threshold_size;
}

/**
* Gets the categories for the check.
*
* Every check must have at least one category.
*
* @since 1.0.2
*
* @return array The categories for the check.
*/
public function get_categories() {
return array( Check_Categories::CATEGORY_PERFORMANCE );
}

/**
* Runs this preparation step for the environment and returns a cleanup function.
*
* @since 1.0.2
*
* @return callable Cleanup function to revert any changes made here.
*
* @throws Exception Thrown when preparation fails.
*/
public function prepare() {
$orig_styles = isset( $GLOBALS['wp_styles'] ) ? $GLOBALS['wp_styles'] : null;

// Backup the original values for the global state.
$this->backup_globals();

return function () use ( $orig_styles ) {
if ( is_null( $orig_styles ) ) {
unset( $GLOBALS['wp_styles'] );
} else {
$GLOBALS['wp_styles'] = $orig_styles;
}

$this->restore_globals();
};
}

/**
* Returns an array of shared preparations for the check.
*
* @since 1.0.2
*
* @return array Returns a map of $class_name => $constructor_args pairs. If the class does not
* need any constructor arguments, it would just be an empty array.
*/
public function get_shared_preparations() {
$demo_posts = array_map(
static function ( $post_type ) {
return array(
'post_title' => "Demo {$post_type} post",
'post_content' => 'Test content',
'post_type' => $post_type,
'post_status' => 'publish',
);
},
$this->get_viewable_post_types()
);

return array(
Demo_Posts_Creation_Preparation::class => array( $demo_posts ),
);
}

/**
* Runs the check on the plugin and amends results.
*
* @since 1.0.2
*
* @param Check_Result $result The check results to amend and the plugin context.
*/
public function run( Check_Result $result ) {
$this->run_for_urls(
$this->get_urls(),
function ( $url ) use ( $result ) {
$this->check_url( $result, $url );
}
);
}

/**
* Gets the list of URLs to run this check for.
*
* @since 1.0.2
*
* @return array List of URL strings (either full URLs or paths).
*
* @throws Exception Thrown when a post type URL cannot be retrieved.
*/
protected function get_urls() {
$urls = array( home_url() );

foreach ( $this->get_viewable_post_types() as $post_type ) {
$posts = get_posts(
array(
'posts_per_page' => 1,
'post_type' => $post_type,
'post_status' => array( 'publish', 'inherit' ),
)
);

if ( ! isset( $posts[0] ) ) {
throw new Exception(
sprintf(
/* translators: %s: The Post Type name. */
__( 'Unable to retrieve post URL for post type: %s', 'plugin-check' ),
$post_type
)
);
}

$urls[] = get_permalink( $posts[0] );
}

return $urls;
}

/**
* Amends the given result by running the check for the given URL.
*
* @since 1.0.2
*
* @param Check_Result $result The check result to amend, including the plugin context to check.
* @param string $url URL to run the check for.
*
* @throws Exception Thrown when the check fails with a critical error (unrelated to any errors detected as part of
* the check).
*
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
protected function check_url( Check_Result $result, $url ) {
// Reset the WP_Styles instance.
unset( $GLOBALS['wp_styles'] );

// Run enqueue functions wrapped in an output buffer in case of any callbacks printing styles
// directly. This is discouraged, but some plugins or themes are still doing it.
ob_start();
wp_enqueue_scripts();
wp_styles()->do_items();
wp_styles()->do_footer_items();
ob_get_clean();

$plugin_styles = array();
$plugin_style_size = 0;

foreach ( wp_styles()->done as $handle ) {
$style = wp_styles()->registered[ $handle ];

if ( ! $style->src || strpos( $style->src, $result->plugin()->url() ) !== 0 ) {
continue;
}

// Get size of style src.
$style_path = str_replace( $result->plugin()->url(), $result->plugin()->path(), $style->src );
$style_size = function_exists( 'wp_filesize' ) ? wp_filesize( $style_path ) : filesize( $style_path );

// Get size of additional inline styles.
if ( ! empty( $style->extra['after'] ) ) {
foreach ( $style->extra['after'] as $extra ) {
$style_size += ( is_string( $extra ) ) ? mb_strlen( $extra, '8bit' ) : 0;
}
}

if ( ! empty( $style->extra['before'] ) ) {
foreach ( $style->extra['before'] as $extra ) {
$style_size += ( is_string( $extra ) ) ? mb_strlen( $extra, '8bit' ) : 0;
}
}

$plugin_styles[] = array(
'path' => $style_path,
'size' => $style_size,
);
$plugin_style_size += $style_size;
}

if ( $plugin_style_size > $this->threshold_size ) {
foreach ( $plugin_styles as $plugin_style ) {
$this->add_result_warning_for_file(
$result,
sprintf(
/* translators: 1: style file size. 2: tested URL. 3: threshold file size. */
__( 'This style has a size of %1$s which in combination with the other styles enqueued on %2$s exceeds the style size threshold of %3$s.', 'plugin-check' ),
size_format( $plugin_style['size'] ),
$url,
size_format( $this->threshold_size )
),
'EnqueuedStylesSize.StyleSizeGreaterThanThreshold',
$plugin_style['path']
);
}
}
}

/**
* Returns an array of viewable post types.
*
* @since 1.0.2
*
* @return array Array of viewable post type slugs.
*/
private function get_viewable_post_types() {
if ( ! is_array( $this->viewable_post_types ) ) {
$this->viewable_post_types = array_filter( get_post_types(), 'is_post_type_viewable' );
}

return $this->viewable_post_types;
}
}
1 change: 1 addition & 0 deletions includes/Checker/Default_Check_Repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ private function register_default_checks() {
array(
'i18n_usage' => new Checks\I18n_Usage_Check(),
'enqueued_scripts_size' => new Checks\Enqueued_Scripts_Size_Check(),
'enqueued_styles_size' => new Checks\Enqueued_Styles_Size_Check(),
'code_obfuscation' => new Checks\Code_Obfuscation_Check(),
'file_type' => new Checks\File_Type_Check(),
'plugin_header_text_domain' => new Checks\Plugin_Header_Text_Domain_Check(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php
/**
* File contains errors for the enqueued style sizes check.
*/

add_action(
'wp_enqueue_scripts',
function() {
// Style size is 33 bytes.
wp_enqueue_style(
'plugin_check_test_style',
plugin_dir_url( __FILE__ ) . 'test-style.css'
);

wp_add_inline_style(
'plugin_check_test_style',
'*{outline:none;}'
);
}
);

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a { text-decoration: underline; }
Loading
Loading