diff --git a/.github/workflows/lint_phpcs.yml b/.github/workflows/lint_phpcs.yml index e28425b5ef..0f4f0ee89b 100644 --- a/.github/workflows/lint_phpcs.yml +++ b/.github/workflows/lint_phpcs.yml @@ -8,6 +8,7 @@ on: - branch-* - feature/* - enhancement/* + - fix/* jobs: run: diff --git a/.github/workflows/lint_phpstan.yml b/.github/workflows/lint_phpstan.yml index 73bf07c680..f9dd138bb2 100644 --- a/.github/workflows/lint_phpstan.yml +++ b/.github/workflows/lint_phpstan.yml @@ -8,6 +8,7 @@ on: - branch-* - feature/* - enhancement/* + - fix/* jobs: run: diff --git a/.github/workflows/test_wprocket.yml b/.github/workflows/test_wprocket.yml index 29a343a960..41455e1120 100644 --- a/.github/workflows/test_wprocket.yml +++ b/.github/workflows/test_wprocket.yml @@ -8,6 +8,7 @@ on: - branch-* - feature/* - enhancement/* + - fix/* jobs: run: diff --git a/.github/workflows/test_wprocket_latest_general_coverage.yml b/.github/workflows/test_wprocket_latest_general_coverage.yml index 7da2e97a2c..7da090ca3e 100644 --- a/.github/workflows/test_wprocket_latest_general_coverage.yml +++ b/.github/workflows/test_wprocket_latest_general_coverage.yml @@ -8,6 +8,7 @@ on: - branch-* - feature/* - enhancement/* + - fix/* jobs: run: diff --git a/.github/workflows/test_wprocket_latest_specific.yml b/.github/workflows/test_wprocket_latest_specific.yml index 04e5ad3fb0..7008cf4edc 100644 --- a/.github/workflows/test_wprocket_latest_specific.yml +++ b/.github/workflows/test_wprocket_latest_specific.yml @@ -8,6 +8,7 @@ on: - branch-* - feature/* - enhancement/* + - fix/* jobs: run: diff --git a/.github/workflows/test_wprocket_php8.yml b/.github/workflows/test_wprocket_php8.yml index 7dea6fc79b..670b776180 100644 --- a/.github/workflows/test_wprocket_php8.yml +++ b/.github/workflows/test_wprocket_php8.yml @@ -8,6 +8,7 @@ on: - branch-* - feature/* - enhancement/* + - fix/* jobs: run: diff --git a/inc/Engine/Admin/Beacon/Beacon.php b/inc/Engine/Admin/Beacon/Beacon.php index 12c4e6e2ef..fc7fcef09f 100644 --- a/inc/Engine/Admin/Beacon/Beacon.php +++ b/inc/Engine/Admin/Beacon/Beacon.php @@ -831,6 +831,16 @@ public function get_suggest( $doc_id ) { 'url' => 'https://fr.docs.wp-rocket.me/article/1836-rendu-differe-automatique/?utm_source=wp_plugin&utm_medium=wp_rocket', ], ], + 'host_fonts_locally' => [ + 'en' => [ + 'id' => '673358b02ddbd952f6241b38', + 'url' => 'https://docs.wp-rocket.me/article/1847-self-host-google-fonts?utm_source=wp_plugin&utm_medium=wp_rocket', + ], + 'fr' => [ + 'id' => '675ab51d46b8d26833b2af82', + 'url' => 'https://fr.docs.wp-rocket.me/article/1852-auto-heberger-google-fonts?utm_source=wp_plugin&utm_medium=wp_rocket', + ], + ], ]; return isset( $suggest[ $doc_id ][ $this->get_user_locale() ] ) diff --git a/inc/Engine/Admin/Settings/Page.php b/inc/Engine/Admin/Settings/Page.php index 45fe923b79..76620c4ff6 100644 --- a/inc/Engine/Admin/Settings/Page.php +++ b/inc/Engine/Admin/Settings/Page.php @@ -846,12 +846,13 @@ private function media_section() { $lazyload_beacon = $this->beacon->get_suggest( 'lazyload' ); $exclude_lazyload = $this->beacon->get_suggest( 'exclude_lazyload' ); $dimensions = $this->beacon->get_suggest( 'image_dimensions' ); + $fonts = $this->beacon->get_suggest( 'host_fonts_locally' ); $this->settings->add_page_section( 'media', [ 'title' => __( 'Media', 'rocket' ), - 'menu_description' => __( 'LazyLoad, image dimensions', 'rocket' ), + 'menu_description' => __( 'LazyLoad, image dimensions, font optimization', 'rocket' ), ] ); @@ -913,7 +914,7 @@ private function media_section() { $this->settings->add_settings_sections( [ - 'lazyload_section' => [ + 'lazyload_section' => [ 'title' => __( 'LazyLoad', 'rocket' ), 'type' => 'fields_container', // translators: %1$s = opening tag, %2$s = closing tag. @@ -926,7 +927,7 @@ private function media_section() { // translators: %1$s = “WP Rocket”, %2$s = a list of plugin names. 'helper' => ! empty( $disable_lazyload ) ? sprintf( __( 'LazyLoad is currently activated in %2$s. If you want to use WP Rocket’s LazyLoad, disable this option in %2$s.', 'rocket' ), WP_ROCKET_PLUGIN_NAME, $disable_lazyload ) : '', ], - 'dimensions_section' => [ + 'dimensions_section' => [ 'title' => __( 'Image Dimensions', 'rocket' ), 'type' => 'fields_container', // translators: %1$s = opening tag, %2$s = closing tag. @@ -934,6 +935,14 @@ private function media_section() { 'help' => $dimensions, 'page' => 'media', ], + 'font_optimization_section' => [ + 'title' => __( 'Fonts', 'rocket' ), + 'type' => 'fields_container', + // translators: %1$s = opening tag, %2$s = closing tag. + 'description' => sprintf( __( 'Download and serve fonts directly from your server. Reduces connections to external servers and minimizes font shifts. %1$sMore info%2$s', 'rocket' ), '', '' ), + 'help' => $fonts, + 'page' => 'media', + ], ] ); @@ -1032,6 +1041,14 @@ private function media_section() { 'default' => 0, 'sanitize_callback' => 'sanitize_checkbox', ], + 'host_fonts_locally' => [ + 'type' => 'checkbox', + 'label' => __( 'Self-host Google Fonts', 'rocket' ), + 'section' => 'font_optimization_section', + 'page' => 'media', + 'default' => 0, + 'sanitize_callback' => 'sanitize_checkbox', + ], ] ); } diff --git a/inc/Engine/Common/AbstractFileSystem.php b/inc/Engine/Common/AbstractFileSystem.php new file mode 100644 index 0000000000..438f75be11 --- /dev/null +++ b/inc/Engine/Common/AbstractFileSystem.php @@ -0,0 +1,129 @@ +filesystem = $filesystem ?? rocket_direct_filesystem(); + } + + /** + * Write to file. + * + * @param string $file_path File path to store the file. + * @param string $content File content(data). + * + * @return bool + */ + protected function write_file( string $file_path, string $content ): bool { + return $this->filesystem->put_contents( $file_path, $content, rocket_get_filesystem_perms( 'file' ) ); + } + + /** + * Get the content of a file + * + * @param string $file The file content to get. + * + * @return string + */ + public function get_file_content( string $file ): string { + if ( ! $this->filesystem->exists( $file ) ) { + return ''; + } + + return $this->filesystem->get_contents( $file ); + } + + /** + * Delete file from a directory + * + * @param string $file_path Path to file that would be deleted. + * + * @return bool + */ + protected function delete_file( string $file_path ): bool { + return $this->filesystem->delete( $file_path, false, 'f' ); + } + + /** + * Checks if the dir path is writable and create dir if it doesn't exist. + * + * @param string $dir_path The directory to check. + * + * @return bool + */ + protected function is_folder_writable( string $dir_path ): bool { + if ( ! $this->filesystem->exists( $dir_path ) ) { + rocket_mkdir_p( $dir_path ); + } + + return $this->filesystem->is_writable( $dir_path ); + } + + /** + * Deletes all files in a given directory + * + * @param string $dir_path The directory path. + * + * @return void + */ + public function delete_all_files_from_directory( $dir_path ): void { + try { + $dir = new RecursiveDirectoryIterator( $dir_path, \FilesystemIterator::SKIP_DOTS ); + + $items = new RecursiveIteratorIterator( $dir, RecursiveIteratorIterator::CHILD_FIRST ); + + foreach ( $items as $item ) { + $this->filesystem->delete( $item ); + } + } catch ( \Exception $e ) { + return; + } + } + + /** + * Converts hash to path with filtered number of levels + * + * @since 3.11.4 + * + * @param string $hash md5 hash string. + * + * @return string + */ + public function hash_to_path( string $hash ): string { + /** + * Filters the number of sub-folders level to create for used CSS storage + * + * @since 3.11.4 + * + * @param int $levels Number of levels. + */ + $levels = wpm_apply_filters_typed( 'integer', 'rocket_used_css_dir_level', 3 ); + + $base = substr( $hash, 0, $levels ); + $remain = substr( $hash, $levels ); + + $path_array = str_split( $base ); + $path_array[] = $remain; + + return implode( '/', $path_array ); + } +} diff --git a/inc/Engine/Common/PerformanceHints/WarmUp/Controller.php b/inc/Engine/Common/PerformanceHints/WarmUp/Controller.php index 73019b2517..ce2a7bc762 100644 --- a/inc/Engine/Common/PerformanceHints/WarmUp/Controller.php +++ b/inc/Engine/Common/PerformanceHints/WarmUp/Controller.php @@ -240,7 +240,7 @@ private function is_mobile(): bool { * @return string */ public function add_wpr_imagedimensions_query_arg( string $url ): string { - if ( empty( $this->factories ) ) { + if ( empty( $this->factories ) && ! $this->options->get( 'remove_unused_css', 0 ) ) { return $url; } diff --git a/inc/Engine/Media/Fonts/Admin/Data.php b/inc/Engine/Media/Fonts/Admin/Data.php new file mode 100644 index 0000000000..585e0a563e --- /dev/null +++ b/inc/Engine/Media/Fonts/Admin/Data.php @@ -0,0 +1,119 @@ +options = $options; + $this->base_path = rocket_get_constant( 'WP_ROCKET_CACHE_ROOT_PATH', '' ) . 'fonts/' . get_current_blog_id() . '/'; + } + + /** + * Schedule data collection. + * + * @return void + */ + public function schedule_data_collection() { + if ( ! $this->is_enabled() ) { + return; + } + + $this->schedule_recurring( time(), WEEK_IN_SECONDS, 'rocket_fonts_data_collection' ); + } + + /** + * Unschedule data collection. + * + * @return void + */ + public function unschedule_data_collection() { + $this->cancel( 'rocket_fonts_data_collection' ); + } + + /** + * Collect data. + * + * @return void + */ + public function collect_data() { + if ( ! $this->is_enabled() ) { + return; + } + + $fonts_data = get_transient( 'rocket_fonts_data_collection' ); + + // If data has been populated, bail out early. + if ( false !== $fonts_data ) { + return; + } + + $fonts = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $this->base_path . 'google-fonts/fonts/' ) ); + + $allowed_extensions = [ + 'woff', + 'woff2', + 'ttf', + 'otf', + ]; + + $total_font_count = 0; + $total_font_size = 0; + + foreach ( $fonts as $file ) { + // check file is not a directory. + if ( $file->isDir() ) { + continue; + } + + $extension = strtolower( pathinfo( $file->getFilename(), PATHINFO_EXTENSION ) ); + + if ( in_array( $extension, $allowed_extensions, true ) ) { + ++$total_font_count; + $total_font_size += $file->getSize(); + } + } + + set_transient( + 'rocket_fonts_data_collection', + [ + 'fonts_total_number' => $total_font_count, + 'fonts_total_size' => size_format( $total_font_size ), + ], + WEEK_IN_SECONDS + ); + } + + /** + * Check if the feature & analytics are enabled. + * + * @return bool + */ + private function is_enabled(): bool { + return $this->options->get( 'host_fonts_locally', 0 ) && $this->options->get( 'analytics_enabled', 0 ); + } +} diff --git a/inc/Engine/Media/Fonts/Admin/Settings.php b/inc/Engine/Media/Fonts/Admin/Settings.php new file mode 100644 index 0000000000..f51f2e2f51 --- /dev/null +++ b/inc/Engine/Media/Fonts/Admin/Settings.php @@ -0,0 +1,35 @@ +sanitize_checkbox( $input, 'host_fonts_locally' ); + + return $input; + } +} diff --git a/inc/Engine/Media/Fonts/Admin/Subscriber.php b/inc/Engine/Media/Fonts/Admin/Subscriber.php new file mode 100644 index 0000000000..12a2fedd83 --- /dev/null +++ b/inc/Engine/Media/Fonts/Admin/Subscriber.php @@ -0,0 +1,101 @@ +settings = $settings; + $this->data = $data; + } + + /** + * Returns an array of events this listens to + * + * @return array + */ + public static function get_subscribed_events(): array { + return [ + 'rocket_first_install_options' => [ 'add_option', 16 ], + 'rocket_input_sanitize' => [ 'sanitize_option', 10, 2 ], + 'admin_init' => 'schedule_data_collection', + 'rocket_fonts_data_collection' => 'collect_data', + 'rocket_deactivation' => 'unschedule_data_collection', + ]; + } + + /** + * Add the images dimensions option to the WP Rocket options array + * + * @param array $options WP Rocket options array. + * + * @return array + */ + public function add_option( array $options ): array { + return $this->settings->add_option( $options ); + } + + /** + * Sanitizes the option value when saving from the settings page + * + * @param array $input Array of sanitized values after being submitted by the form. + * @param Settings $settings Settings class instance. + * + * @return array + */ + public function sanitize_option( array $input, Settings $settings ): array { + return $this->settings->sanitize_option_value( $input, $settings ); + } + + /** + * Schedule data collection + * + * @return void + */ + public function schedule_data_collection() { + $this->data->schedule_data_collection(); + } + + /** + * Unschedule data collection + * + * @return void + */ + public function unschedule_data_collection() { + $this->data->unschedule_data_collection(); + } + + /** + * Collect data + * + * @return void + */ + public function collect_data() { + $this->data->collect_data(); + } +} diff --git a/inc/Engine/Media/Fonts/Clean/Clean.php b/inc/Engine/Media/Fonts/Clean/Clean.php new file mode 100644 index 0000000000..6b55d8522a --- /dev/null +++ b/inc/Engine/Media/Fonts/Clean/Clean.php @@ -0,0 +1,117 @@ +filesystem = $filesystem; + $this->base_path = rocket_get_constant( 'WP_ROCKET_CACHE_ROOT_PATH', '' ) . 'fonts/' . get_current_blog_id() . '/'; + } + + /** + * Clean fonts CSS files stored locally + * + * @return void + */ + public function clean_fonts_css() { + $path = $this->base_path . 'google-fonts/css/'; + + $this->filesystem->delete_all_files_from_directory( $path ); + } + + /** + * Clean fonts files stored locally + * + * @return void + */ + public function clean_fonts() { + $path = $this->base_path . 'google-fonts/fonts/'; + + $this->filesystem->delete_all_files_from_directory( $path ); + } + + /** + * Clean CSS & fonts files stored locally on option change + * + * @param mixed $old_value Old option value. + * @param mixed $value New option value. + * + * @return void + */ + public function clean_on_option_change( $old_value, $value ) { + if ( ! $this->did_setting_change( 'host_fonts_locally', $old_value, $value ) ) { + return; + } + + $this->clean_fonts_css(); + + /** + * Fires when the option to host fonts locally is changed + * + * @since 3.18 + */ + do_action( 'rocket_host_fonts_locally_changed' ); + } + + /** + * Clean CSS & fonts files stored locally on CDN change + * + * @param mixed $old_value Old option value. + * @param mixed $value New option value. + * + * @return void + */ + public function clean_on_cdn_change( $old_value, $value ) { + if ( ! $this->did_setting_change( 'cdn', $old_value, $value ) ) { + return; + } + + if ( ! $this->did_setting_change( 'cdn_cnames', $old_value, $value ) ) { + return; + } + + $this->clean_fonts_css(); + } + + /** + * Checks if the given setting's value changed. + * + * @param string $setting The settings's value to check in the old and new values. + * @param mixed $old_value Old option value. + * @param mixed $value New option value. + * + * @return bool + */ + private function did_setting_change( $setting, $old_value, $value ) { + return ( + array_key_exists( $setting, $old_value ) + && + array_key_exists( $setting, $value ) + && + // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual + $old_value[ $setting ] != $value[ $setting ] + ); + } +} diff --git a/inc/Engine/Media/Fonts/Clean/Subscriber.php b/inc/Engine/Media/Fonts/Clean/Subscriber.php new file mode 100644 index 0000000000..ff963e48e3 --- /dev/null +++ b/inc/Engine/Media/Fonts/Clean/Subscriber.php @@ -0,0 +1,86 @@ +clean = $clean; + } + + /** + * Returns an array of events that this subscriber wants to listen to. + * + * @return array + */ + public static function get_subscribed_events(): array { + return [ + 'rocket_after_clean_domain' => 'clean_fonts_css', + 'switch_theme' => 'clean_fonts', + 'rocket_domain_options_changed' => [ + [ 'clean_fonts_css' ], + [ 'clean_fonts' ], + ], + 'update_option_wp_rocket_settings' => [ + [ 'clean_on_option_change', 10, 2 ], + [ 'clean_on_cdn_change', 11, 2 ], + ], + ]; + } + + /** + * Clean fonts CSS files stored locally + * + * @return void + */ + public function clean_fonts_css() { + $this->clean->clean_fonts_css(); + } + + /** + * Clean fonts files stored locally + * + * @return void + */ + public function clean_fonts() { + $this->clean->clean_fonts(); + } + + /** + * Clean CSS & fonts files stored locally on option change + * + * @param mixed $old_value Old option value. + * @param mixed $value New option value. + * + * @return void + */ + public function clean_on_option_change( $old_value, $value ) { + $this->clean->clean_on_option_change( $old_value, $value ); + } + + /** + * Clean CSS & fonts files stored locally on CDN change + * + * @param mixed $old_value Old option value. + * @param mixed $value New option value. + * + * @return void + */ + public function clean_on_cdn_change( $old_value, $value ) { + $this->clean->clean_on_cdn_change( $old_value, $value ); + } +} diff --git a/inc/Engine/Media/Fonts/Context/OptimizationContext.php b/inc/Engine/Media/Fonts/Context/OptimizationContext.php new file mode 100644 index 0000000000..c2d2eedcae --- /dev/null +++ b/inc/Engine/Media/Fonts/Context/OptimizationContext.php @@ -0,0 +1,25 @@ + 'host_fonts_locally', + 'do_not_optimize' => false, + 'bypass' => false, + ]; + + return $this->run_common_checks( $checks ); + } +} diff --git a/inc/Engine/Media/Fonts/Context/SaasContext.php b/inc/Engine/Media/Fonts/Context/SaasContext.php new file mode 100644 index 0000000000..130e0c7676 --- /dev/null +++ b/inc/Engine/Media/Fonts/Context/SaasContext.php @@ -0,0 +1,23 @@ + 'host_fonts_locally', + ]; + + return $this->run_common_checks( $checks ); + } +} diff --git a/inc/Engine/Media/Fonts/Filesystem.php b/inc/Engine/Media/Fonts/Filesystem.php new file mode 100644 index 0000000000..b8bd8ce039 --- /dev/null +++ b/inc/Engine/Media/Fonts/Filesystem.php @@ -0,0 +1,222 @@ +path = rocket_get_constant( 'WP_ROCKET_CACHE_ROOT_PATH', '' ) . 'fonts/' . get_current_blog_id() . '/'; + } + + /** + * Hashes the url + * + * @param string $url URL to get the hash from. + * + * @return string + */ + private function hash_url( string $url ): string { + return md5( $url ); + } + + /** + * Checks if the file exists + * + * @param string $file Absolute path to the file. + * + * @return bool + */ + public function exists( string $file ): bool { + return $this->filesystem->exists( $file ); + } + + /** + * Writes CSS & fonts locally + * + * @param string $css_url The CSS url to save locally. + * @param string $provider The font provider. + * + * @return bool + */ + public function write_font_css( string $css_url, string $provider ): bool { + $font_provider_path = $this->get_font_provider_path( $provider ); + $css_filepath = $this->get_absolute_path( $font_provider_path, 'css/' . $this->hash_to_path( $this->hash_url( $css_url ) ) . '.css' ); + $fonts_basepath = $this->get_absolute_path( $font_provider_path, 'fonts' ); + + if ( ! rocket_mkdir_p( dirname( $css_filepath ) ) ) { + return false; + } + + $start_time = microtime( true ); + + $css_content = $this->get_remote_content( html_entity_decode( $css_url ) ); + + if ( ! $css_content ) { + return false; + } + + preg_match_all( '/url\((https:\/\/[^)]+)\)/i', $css_content, $matches ); + $font_urls = $matches[1]; + $local_css = $css_content; + + $count_fonts = 0; + $download_average = 0; + + foreach ( $font_urls as $font_url ) { + $font_path = wp_parse_url( $font_url, PHP_URL_PATH ); + + if ( ! $font_path ) { + continue; + } + + $local_path = $fonts_basepath . $font_path; + $local_dir = dirname( $local_path ); + + if ( ! rocket_mkdir_p( $local_dir ) ) { + continue; + } + + if ( ! $this->filesystem->exists( $local_path ) ) { + $download_start = microtime( true ); + + $font_content = $this->get_remote_content( $font_url ); + + if ( ! $font_content ) { + Logger::debug( 'Font download was not successful', [ 'Host Fonts Locally' ] ); + continue; + } + + $this->write_file( $local_path, $font_content ); + + $download_end = microtime( true ); + $download_time = $download_end - $download_start; + + $download_average += $download_time; + + ++$count_fonts; + + Logger::debug( "Font $font_url download duration -- $download_time", [ 'Host Fonts Locally' ] ); + } + + $local_url = content_url( $this->get_fonts_relative_path( $font_provider_path, $font_path ) ); + $local_css = str_replace( $font_url, $local_url, $local_css ); + } + + // This filter is documented in inc/Engine/Optimization/CSSTrait.php. + $local_css = wpm_apply_filters_typed( 'string', 'rocket_css_content', $local_css ); + + $end_time = microtime( true ); + $duration = $end_time - $start_time; + + // Add for test purpose. + Logger::debug( "Font download and optimization duration in seconds -- $duration", [ 'Host Fonts Locally' ] ); + Logger::debug( "Number of fonts downloaded for $css_url -- $count_fonts", [ 'Host Fonts Locally' ] ); + Logger::debug( 'Average download time per font -- ' . ( $count_fonts ? $download_average / $count_fonts : 0 ), [ 'Host Fonts Locally' ] ); + + return $this->write_file( $css_filepath, $local_css ); + } + + /** + * Gets the remote content of the URL + * + * @param string $url URL to request content for. + * + * @return string + */ + private function get_remote_content( string $url ): string { + $response = wp_safe_remote_get( + $url, + [ + 'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + 'httpversion' => '2.0', + ] + ); + + if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { + return ''; + } + + return wp_remote_retrieve_body( $response ); + } + + /** + * Get the absolute path for a file + * + * @param string $font_provider_path Font provider path. + * @param string $path Path to the file. + * + * @return string + */ + private function get_absolute_path( string $font_provider_path, string $path ): string { + return $this->path . $font_provider_path . $path; + } + + /** + * Get the fonts relative paths + * + * @param string $font_provider_path Font provider path. + * @param string $path Path to the file. + * + * @return string + */ + private function get_fonts_relative_path( string $font_provider_path, string $path ): string { + $base_path = $this->path . $font_provider_path . 'fonts'; + $wp_content_dir = rocket_get_constant( 'WP_CONTENT_DIR', '' ); + $relative_path = str_replace( $wp_content_dir, '', $base_path ); + + return $relative_path . $path; + } + + /** + * Get the fonts provider path + * + * @param string $provider The font provider. + * + * @return string + */ + private function get_font_provider_path( string $provider ): string { + $provider = str_replace( '_', '-', $provider ); + + return $provider . '/'; + } + + /** + * Deletes the locally stored fonts for the corresponding url + * + * @since 3.18 + * + * @param string $url The url of the page to be deleted. + * + * @return bool + */ + public function delete_font_css( string $url ): bool { + $dir = $this->get_absolute_path( $this->get_font_provider_path( $url ), $url ); + + return $this->delete_file( $dir ); + } +} diff --git a/inc/Engine/Media/Fonts/FontsTrait.php b/inc/Engine/Media/Fonts/FontsTrait.php new file mode 100644 index 0000000000..e669114488 --- /dev/null +++ b/inc/Engine/Media/Fonts/FontsTrait.php @@ -0,0 +1,58 @@ +optimization_context = $optimization_context; + $this->saas_context = $saas_context; + $this->base_path = rocket_get_constant( 'WP_ROCKET_CACHE_ROOT_PATH', '' ) . 'fonts/' . get_current_blog_id() . '/'; + $this->base_url = rocket_get_constant( 'WP_ROCKET_CACHE_ROOT_URL', '' ) . 'fonts/' . get_current_blog_id() . '/'; + $this->filesystem = $filesystem; + } + + /** + * Rewrites the Google Fonts paths to local ones. + * + * @param string $html HTML content. + * @return string + */ + private function rewrite_fonts( $html ): string { + // For test purposes. + $start_time = microtime( true ); + + $html_nocomments = $this->hide_comments( $html ); + + $v1_fonts = $this->find( '])+)?(?:\s+href\s*=\s*([\'"])(?(?:https?:)?\/\/fonts\.googleapis\.com\/css[^\d](?:(?!\1).)+)\1)(?:\s+[^>]*)?>', $html_nocomments ); + $v2_fonts = $this->find( '])+)?(?:\s+href\s*=\s*([\'"])(?(?:https?:)?\/\/fonts\.googleapis\.com\/css2(?:(?!\1).)+)\1)(?:\s+[^>]*)?>', $html_nocomments ); + + if ( ! $v1_fonts && ! $v2_fonts ) { + Logger::debug( 'No Google Fonts found.', [ 'Host Fonts Locally' ] ); + return $html; + } + + $exclusions = $this->get_exclusions(); + + // Count fonts - for test purposes. + $total_v1 = count( $v1_fonts ); + $total_v2 = count( $v2_fonts ); + $total_fonts = $total_v1 + $total_v2; + + foreach ( $v1_fonts as $font ) { + if ( $this->is_excluded( $font[0], $exclusions ) ) { + continue; + } + $html = $this->replace_font( $font, $html ); + } + + foreach ( $v2_fonts as $font ) { + if ( $this->is_excluded( $font[0], $exclusions ) ) { + continue; + } + $html = $this->replace_font( $font, $html ); + } + + if ( ! $this->error ) { + $html = $this->remove_preconnect_and_prefetch( $html ); + } + + // End time measurement. + $end_time = microtime( true ); + + // Log the total execution time and number of fonts processed, with breakdown. + $duration = $end_time - $start_time; + Logger::debug( "Total execution time for Host Google Fonts Feature in seconds -- $duration. CSS files processed: $total_fonts | Total v1: $total_v1 | Total v2: $total_v2", [ 'Host Fonts Locally' ] ); + + return $html; + } + + /** + * Rewrite fonts for normal optimizations. + * + * @param string $html page HTML. + * @return string + */ + public function rewrite_fonts_for_optimizations( $html ): string { + if ( ! $this->optimization_context->is_allowed() ) { + return $html; + } + return $this->rewrite_fonts( $html ); + } + + /** + * Rewrite fonts for SaaS visits optimizations. + * + * @param string $html page HTML. + * @return string + */ + public function rewrite_fonts_for_saas( $html ): string { + if ( ! $this->saas_context->is_allowed() ) { + return $html; + } + return $this->rewrite_fonts( $html ); + } + + /** + * Replaces the Google Fonts URL with the local one. + * + * @param array $font Font data. + * @param string $html HTML content. + * @param string $font_provider Font provider. + * + * @return string + */ + private function replace_font( array $font, string $html, string $font_provider = 'google-fonts' ): string { + $hash = md5( $font['url'] ); + + if ( $this->filesystem->exists( $this->get_css_path( $hash, $font_provider ) ) ) { + $local = $this->get_optimized_markup( $hash, $font['url'], $font_provider ); + + return str_replace( $font[0], $local, $html ); + } + + if ( ! $this->filesystem->write_font_css( $font['url'], $font_provider ) ) { + $this->error = true; + + return $html; + } + + $local = $this->get_optimized_markup( $hash, $font['url'], $font_provider ); + + return str_replace( $font[0], $local, $html ); + } + + /** + * Returns the optimized markup for Google Fonts + * + * @since 3.18 + * + * @param string $hash Font Url has. + * @param string $original_url Fonts Url. + * @param string $font_provider Fonts provider. + * + * @return string + */ + private function get_optimized_markup( + string $hash, + string $original_url, + string $font_provider + ): string { + $font_provider_path = sprintf( '%s/', $font_provider ); + + $original_url = html_entity_decode( $original_url, ENT_QUOTES ); + $gf_parameters = wp_parse_url( $original_url, PHP_URL_QUERY ); + + /** + * Filters to enable the inline css output. + * + * @since 3.18 + * + * @param bool $enable Tells if we are enabling or not the inline css output. + */ + if ( wpm_apply_filters_typed( 'boolean', 'rocket_host_fonts_locally_inline_css', false ) ) { + $local_css_path = $this->get_css_path( $hash, $font_provider ); + + $inline_css = $this->get_font_inline_css( $local_css_path, $gf_parameters ); + + if ( ! empty( $inline_css ) ) { + return $inline_css; + } + } + + // This filter is documented in inc/classes/optimization/css/class-abstract-css-optimization.php. + $url = wpm_apply_filters_typed( 'string', 'rocket_css_url', $this->base_url . $font_provider_path . 'css/' . $this->filesystem->hash_to_path( $hash ) . '.css' ); + + return sprintf( + '', // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet + $url, + $gf_parameters + ); + } + + /** + * Gets the CSS path for the font. + * + * @param string $hash Font hash. + * @param string $font_provider Font provider. + * + * @return string + */ + private function get_css_path( string $hash, string $font_provider ): string { + $font_provider_path = sprintf( '%s/', $font_provider ); + + return $this->base_path . $font_provider_path . 'css/' . $this->filesystem->hash_to_path( $hash ) . '.css'; + } + + /** + + * Removes preconnect and prefetch links for Google Fonts from the HTML content. + * + * @param string $html HTML content. + * + * @return string Modified HTML content without preconnect and prefetch links. + */ + private function remove_preconnect_and_prefetch( string $html ) { + /** + * Filters the removal of Google preconnect/prefetch links. + * + * @since 3.18 + * + * @param bool $enable_removal Enable or disable removal of Google preconnect/prefetch links. + */ + $remove_links = wpm_apply_filters_typed( 'boolean', 'rocket_remove_font_pre_links', true ); + + if ( ! $remove_links ) { + return $html; + } + + $pattern = '/]*\b(rel\s*=\s*[\'"](?:preconnect|dns-prefetch)[\'"]|href\s*=\s*[\'"](?:https?:)?\/\/(?:fonts\.(?:googleapis|gstatic)\.com)[\'"])[^>]*\b(rel\s*=\s*[\'"](?:preconnect|dns-prefetch)[\'"]|href\s*=\s*[\'"](?:https?:)?\/\/(?:fonts\.(?:googleapis|gstatic)\.com)[\'"])[^>]*>/i'; + + $html = preg_replace( $pattern, '', $html ); + + return $html; + } + + /** + * Disables the preload of Google Fonts. + * + * @param bool $disable Whether to disable the preload of Google Fonts. + * + * @return bool + */ + public function disable_google_fonts_preload( $disable ): bool { + if ( ! $this->optimization_context->is_allowed() ) { + return $disable; + } + + return true; + } + + /** + * Gets the font inline css. + * + * @param string $local_css_path CSS file path. + * @param string $gf_parameters Google Fonts parameters. + * + * @return string + */ + private function get_font_inline_css( string $local_css_path, string $gf_parameters ): string { + $content = $this->filesystem->get_file_content( $local_css_path ); + + if ( empty( $content ) ) { + return ''; + } + + return sprintf( + '', + $gf_parameters, + $content + ); + } +} diff --git a/inc/Engine/Media/Fonts/Frontend/Subscriber.php b/inc/Engine/Media/Fonts/Frontend/Subscriber.php new file mode 100644 index 0000000000..48b522a3f8 --- /dev/null +++ b/inc/Engine/Media/Fonts/Frontend/Subscriber.php @@ -0,0 +1,70 @@ +frontend_controller = $frontend_controller; + } + + /** + * Returns an array of events that this subscriber wants to listen to. + * + * @since 3.18 + * + * @return array + */ + public static function get_subscribed_events(): array { + return [ + 'rocket_buffer' => [ 'rewrite_fonts_for_optimizations', 18 ], + 'rocket_disable_google_fonts_preload' => 'disable_google_fonts_preload', + 'rocket_performance_hints_buffer' => 'rewrite_fonts_for_saas', + ]; + } + + /** + * Rewrites the Google Fonts paths to local ones. + * + * @param string $html HTML content. + * @return string + */ + public function rewrite_fonts_for_optimizations( $html ): string { + return $this->frontend_controller->rewrite_fonts_for_optimizations( $html ); + } + + /** + * Rewrites the Google Fonts paths to local ones for SaaS. + * + * @param string $html HTML content. + * @return string + */ + public function rewrite_fonts_for_saas( $html ): string { + return $this->frontend_controller->rewrite_fonts_for_saas( $html ); + } + + /** + * Disables the preload of Google Fonts. + * + * @param bool $disable Whether to disable the preload of Google Fonts. + * + * @return bool + */ + public function disable_google_fonts_preload( $disable ): bool { + return $this->frontend_controller->disable_google_fonts_preload( $disable ); + } +} diff --git a/inc/Engine/Media/Fonts/ServiceProvider.php b/inc/Engine/Media/Fonts/ServiceProvider.php new file mode 100644 index 0000000000..87902b1de8 --- /dev/null +++ b/inc/Engine/Media/Fonts/ServiceProvider.php @@ -0,0 +1,97 @@ +provides, true ); + } + + /** + * Registers the option array in the container + * + * @return void + */ + public function register(): void { + + $this->getContainer()->add( 'media_fonts_filesystem', Filesystem::class ) + ->addArgument( rocket_direct_filesystem() ); + + $this->getContainer()->add( 'media_fonts_settings', Settings::class ); + $this->getContainer()->add( 'media_fonts_data', Data::class ) + ->addArgument( 'options' ); + $this->getContainer()->addShared( 'media_fonts_admin_subscriber', AdminSubscriber::class ) + ->addArguments( + [ + 'media_fonts_settings', + 'media_fonts_data', + ] + ); + + $this->getContainer()->add( 'media_fonts_clean', Clean::class ) + ->addArgument( 'media_fonts_filesystem' ); + + $this->getContainer()->addShared( 'media_fonts_clean_subscriber', CleanSubscriber::class ) + ->addArgument( 'media_fonts_clean' ); + + $this->getContainer()->add( 'media_fonts_optimization_context', OptimizationContext::class ) + ->addArgument( 'options' ); + $this->getContainer()->add( 'media_fonts_saas_context', SaasContext::class ) + ->addArgument( 'options' ); + + $this->getContainer()->add( 'media_fonts_frontend_controller', FrontendController::class ) + ->addArguments( + [ + 'media_fonts_optimization_context', + 'media_fonts_saas_context', + 'media_fonts_filesystem', + ] + ); + $this->getContainer()->addShared( 'media_fonts_frontend_subscriber', FrontendSubscriber::class ) + ->addArgument( 'media_fonts_frontend_controller' ); + } +} diff --git a/inc/Engine/Optimization/DynamicLists/DynamicLists.php b/inc/Engine/Optimization/DynamicLists/DynamicLists.php index 68fe66710b..2479ff471b 100644 --- a/inc/Engine/Optimization/DynamicLists/DynamicLists.php +++ b/inc/Engine/Optimization/DynamicLists/DynamicLists.php @@ -331,4 +331,15 @@ public function update_lists_from_files() { $provider->data_manager->get_lists(); } } + + /** + * Get the host fonts excluded templates + * + * @return array + */ + public function get_exclude_media_fonts(): array { + $lists = $this->providers['defaultlists']->data_manager->get_lists(); + + return $lists->host_fonts ?? []; + } } diff --git a/inc/Engine/Optimization/DynamicLists/Subscriber.php b/inc/Engine/Optimization/DynamicLists/Subscriber.php index 4a86c1a66c..b737156d30 100644 --- a/inc/Engine/Optimization/DynamicLists/Subscriber.php +++ b/inc/Engine/Optimization/DynamicLists/Subscriber.php @@ -46,6 +46,7 @@ public static function get_subscribed_events() { 'rocket_lrc_exclusions' => 'add_lrc_exclusions', 'wp_rocket_upgrade' => 'update_lists_from_files', 'rocket_before_rollback' => 'maybe_update_lists', + 'rocket_media_fonts_exclusions' => 'add_media_fonts_exclusions', ]; } @@ -226,4 +227,15 @@ public function maybe_update_lists(): void { $this->dynamic_lists->update_lists_from_remote(); } + + /** + * Add the media fonts exclusion to the array + * + * @param array $exclusions Array of Media fonts exclusions. + * + * @return array + */ + public function add_media_fonts_exclusions( array $exclusions ): array { + return array_merge( (array) $exclusions, $this->dynamic_lists->get_exclude_media_fonts() ); + } } diff --git a/inc/Engine/Optimization/GoogleFonts/AbstractGFOptimization.php b/inc/Engine/Optimization/GoogleFonts/AbstractGFOptimization.php index e3b112402b..271f94e6f7 100644 --- a/inc/Engine/Optimization/GoogleFonts/AbstractGFOptimization.php +++ b/inc/Engine/Optimization/GoogleFonts/AbstractGFOptimization.php @@ -3,12 +3,16 @@ namespace WP_Rocket\Engine\Optimization\GoogleFonts; +use WP_Rocket\Engine\Media\Fonts\FontsTrait; + /** * Abstract Optimization Parent Class for Google Fonts Optimizers. * * @since 3.8 */ abstract class AbstractGFOptimization { + use FontsTrait; + /** * Allowed display values. * @@ -111,6 +115,20 @@ protected function get_font_display_value(): string { * @return string */ protected function get_optimized_markup( string $url ): string { + /** + * Filters whether to disable Google Fonts preloading. + * + * @since 3.18 + * + * @param bool $disable_google_fonts_preload Whether to disable Google Fonts preloading. Default false. + */ + if ( wpm_apply_filters_typed( 'boolean', 'rocket_disable_google_fonts_preload', false ) ) { + return sprintf( + '', // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet + $url + ); + } + return sprintf( '', // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet $url diff --git a/inc/Engine/Optimization/GoogleFonts/Combine.php b/inc/Engine/Optimization/GoogleFonts/Combine.php index 1e96d636b3..9308f811ef 100644 --- a/inc/Engine/Optimization/GoogleFonts/Combine.php +++ b/inc/Engine/Optimization/GoogleFonts/Combine.php @@ -57,17 +57,26 @@ public function optimize( $html ): string { $this->has_google_fonts = true; - $num_fonts = count( $fonts ); + $exclusions = $this->get_exclusions(); + + $filtered_fonts = array_filter( + $fonts, + function ( $font ) use ( $exclusions ) { + return ! $this->is_excluded( $font[0], $exclusions ); + } + ); + + $num_fonts = count( $filtered_fonts ); Logger::debug( - "Found {$num_fonts} Google Fonts.", + "Found {$num_fonts} Google Fonts after exclusions.", [ 'GF combine process', - 'tags' => $fonts, + 'tags' => $filtered_fonts, ] ); - $this->parse( $fonts ); + $this->parse( $filtered_fonts ); if ( empty( $this->fonts ) ) { Logger::debug( 'No Google Fonts left to combine.', [ 'GF combine process' ] ); @@ -77,7 +86,7 @@ public function optimize( $html ): string { $html = preg_replace( '@<\/title>@i', '$0' . $this->get_optimized_markup( $this->get_combined_url() ), $html, 1 ); - foreach ( $fonts as $font ) { + foreach ( $filtered_fonts as $font ) { $html = str_replace( $font[0], '', $html ); } diff --git a/inc/Engine/Optimization/GoogleFonts/CombineV2.php b/inc/Engine/Optimization/GoogleFonts/CombineV2.php index faec84ebca..b3878f064a 100644 --- a/inc/Engine/Optimization/GoogleFonts/CombineV2.php +++ b/inc/Engine/Optimization/GoogleFonts/CombineV2.php @@ -40,18 +40,27 @@ public function optimize( $html ): string { $this->has_google_fonts = true; - $num_tags = count( $font_tags ); + $exclusions = $this->get_exclusions(); + + $filtered_tags = array_filter( + $font_tags, + function ( $tag ) use ( $exclusions ) { + return ! $this->is_excluded( $tag[0], $exclusions ); + } + ); + + $num_tags = count( $filtered_tags ); Logger::debug( - "Found {$num_tags} v2 Google Fonts.", + "Found {$num_tags} v2 Google Fonts after exclusions.", [ 'GF combine process', - 'tags' => $font_tags, + 'tags' => $filtered_tags, ] ); $families = []; - foreach ( $font_tags as $tag ) { + foreach ( $filtered_tags as $tag ) { $parsed_families = $this->parse( $tag ); if ( ! empty( $parsed_families ) ) { $processed_tags[] = $tag; diff --git a/inc/Engine/Optimization/GoogleFonts/Subscriber.php b/inc/Engine/Optimization/GoogleFonts/Subscriber.php index 78b79864b6..19232d36e8 100644 --- a/inc/Engine/Optimization/GoogleFonts/Subscriber.php +++ b/inc/Engine/Optimization/GoogleFonts/Subscriber.php @@ -54,7 +54,7 @@ public function __construct( AbstractGFOptimization $combine, AbstractGFOptimiza public static function get_subscribed_events() { return [ 'wp_resource_hints' => [ 'preconnect', 10, 2 ], - 'rocket_buffer' => [ 'process', 1001 ], + 'rocket_buffer' => [ 'process', 17 ], ]; } diff --git a/inc/Engine/Optimization/Minify/CSS/AdminSubscriber.php b/inc/Engine/Optimization/Minify/CSS/AdminSubscriber.php index d4e29e6485..3fe7255c57 100755 --- a/inc/Engine/Optimization/Minify/CSS/AdminSubscriber.php +++ b/inc/Engine/Optimization/Minify/CSS/AdminSubscriber.php @@ -78,6 +78,7 @@ protected function maybe_minify_regenerate( array $new, array $old ) { // phpcs: 'minify_css', 'exclude_css', 'cdn', + 'host_fonts_locally', ]; foreach ( $settings_to_check as $setting ) { diff --git a/inc/Engine/Optimization/RUCSS/Admin/Subscriber.php b/inc/Engine/Optimization/RUCSS/Admin/Subscriber.php index 08814e970a..2962c44b87 100644 --- a/inc/Engine/Optimization/RUCSS/Admin/Subscriber.php +++ b/inc/Engine/Optimization/RUCSS/Admin/Subscriber.php @@ -70,6 +70,7 @@ public static function get_subscribed_events(): array { 'switch_theme' => 'truncate_used_css', 'permalink_structure_changed' => 'truncate_used_css', 'rocket_domain_options_changed' => 'truncate_used_css', + 'rocket_host_fonts_locally_changed' => 'delete_used_css_rows', 'wp_trash_post' => 'delete_used_css_on_update_or_delete', 'delete_post' => 'delete_used_css_on_update_or_delete', 'clean_post_cache' => 'delete_used_css_on_update_or_delete', @@ -206,7 +207,7 @@ public function truncate_used_css() { * * @return void */ - private function delete_used_css_rows() { + public function delete_used_css_rows() { $this->used_css->delete_all_used_css(); if ( 0 < $this->used_css->get_not_completed_count() ) { diff --git a/inc/Engine/Optimization/RUCSS/Controller/Filesystem.php b/inc/Engine/Optimization/RUCSS/Controller/Filesystem.php index 0b7e0b21b8..e9749446b7 100644 --- a/inc/Engine/Optimization/RUCSS/Controller/Filesystem.php +++ b/inc/Engine/Optimization/RUCSS/Controller/Filesystem.php @@ -6,14 +6,15 @@ use WP_Filesystem_Direct; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; +use WP_Rocket\Engine\Common\AbstractFileSystem; -class Filesystem { +class Filesystem extends AbstractFileSystem { /** * WP Filesystem instance * * @var WP_Filesystem_Direct */ - private $filesystem; + protected $filesystem; /** * Path to the used CSS storage @@ -29,8 +30,8 @@ class Filesystem { * @param WP_Filesystem_Direct $filesystem WP Filesystem instance. */ public function __construct( $base_path, $filesystem = null ) { - $this->filesystem = is_null( $filesystem ) ? rocket_direct_filesystem() : $filesystem; - $this->path = $base_path . get_current_blog_id() . '/'; + parent::__construct( is_null( $filesystem ) ? rocket_direct_filesystem() : $filesystem ); + $this->path = $base_path . get_current_blog_id() . '/'; } /** @@ -58,7 +59,7 @@ public function get_used_css( string $hash ): string { return ''; } - $file_contents = $this->filesystem->get_contents( $file ); + $file_contents = $this->get_file_content( $file ); $css = function_exists( 'gzdecode' ) ? gzdecode( $file_contents ) : $file_contents; if ( ! $css ) { @@ -90,7 +91,7 @@ public function write_used_css( string $hash, string $used_css ): bool { return false; } - return $this->filesystem->put_contents( $file, $css, rocket_get_filesystem_perms( 'file' ) ); + return $this->write_file( $file, $css ); } /** @@ -105,7 +106,7 @@ public function write_used_css( string $hash, string $used_css ): bool { public function delete_used_css( string $hash ): bool { $file = $this->get_usedcss_full_path( $hash ); - return $this->filesystem->delete( $file, false, 'f' ); + return $this->delete_file( $file ); } /** @@ -116,17 +117,7 @@ public function delete_used_css( string $hash ): bool { * @return void */ public function delete_all_used_css() { - try { - $dir = new RecursiveDirectoryIterator( $this->path, \FilesystemIterator::SKIP_DOTS ); - - $items = new RecursiveIteratorIterator( $dir, RecursiveIteratorIterator::CHILD_FIRST ); - - foreach ( $items as $item ) { - $this->filesystem->delete( $item ); - } - } catch ( \Exception $e ) { - return; - } + $this->delete_all_files_from_directory( $this->path ); } /** @@ -137,38 +128,6 @@ public function delete_all_used_css() { * @return bool */ public function is_writable_folder() { - if ( ! $this->filesystem->exists( $this->path ) ) { - rocket_mkdir_p( $this->path ); - } - - return $this->filesystem->is_writable( $this->path ); - } - - /** - * Converts hash to path with filtered number of levels - * - * @since 3.11.4 - * - * @param string $hash md5 hash string. - * - * @return string - */ - private function hash_to_path( string $hash ): string { - /** - * Filters the number of sub-folders level to create for used CSS storage - * - * @since 3.11.4 - * - * @param int $levels Number of levels. - */ - $levels = apply_filters( 'rocket_used_css_dir_level', 3 ); - - $base = substr( $hash, 0, $levels ); - $remain = substr( $hash, $levels ); - - $path_array = str_split( $base ); - $path_array[] = $remain; - - return implode( '/', $path_array ); + return $this->is_folder_writable( $this->path ); } } diff --git a/inc/Engine/WPRocketUninstall.php b/inc/Engine/WPRocketUninstall.php index ae30a0ad06..c29345dd31 100644 --- a/inc/Engine/WPRocketUninstall.php +++ b/inc/Engine/WPRocketUninstall.php @@ -113,6 +113,8 @@ class WPRocketUninstall { 'busting', 'critical-css', 'used-css', + 'fonts', + 'background-css', ]; /** diff --git a/inc/Plugin.php b/inc/Plugin.php index 99de360f09..1300cbc134 100644 --- a/inc/Plugin.php +++ b/inc/Plugin.php @@ -53,6 +53,8 @@ use WP_Rocket\Engine\Debug\ServiceProvider as DebugServiceProvider; use WP_Rocket\Engine\Common\PerformanceHints\ServiceProvider as PerformanceHintsServiceProvider; use WP_Rocket\Engine\Optimization\LazyRenderContent\ServiceProvider as LRCServiceProvider; +use WP_Rocket\Engine\Media\Fonts\ServiceProvider as MediaFontsServiceProvider; + /** * Plugin Manager. @@ -308,6 +310,7 @@ private function init_common_subscribers() { $this->container->addServiceProvider( new SaasAdminServiceProvider() ); $this->container->addServiceProvider( new PerformanceHintsServiceProvider() ); $this->container->addServiceProvider( new LRCServiceProvider() ); + $this->container->addServiceProvider( new MediaFontsServiceProvider() ); $common_subscribers = [ 'license_subscriber', @@ -402,6 +405,9 @@ private function init_common_subscribers() { 'lrc_frontend_subscriber', 'taxonomy_subscriber', 'termly_subscriber', + 'media_fonts_frontend_subscriber', + 'media_fonts_admin_subscriber', + 'media_fonts_clean_subscriber', ]; $host_type = HostResolver::get_host_service(); diff --git a/inc/admin/admin.php b/inc/admin/admin.php index b7d4d82733..5789247773 100644 --- a/inc/admin/admin.php +++ b/inc/admin/admin.php @@ -329,6 +329,12 @@ function rocket_analytics_data() { $data['license_type'] = rocket_get_license_type( $customer_data ); } + $media_font_data = get_transient( 'rocket_fonts_data_collection' ); + + if ( false !== $media_font_data ) { + $data = array_merge( $data, $media_font_data ); + } + return $data; } diff --git a/inc/functions/admin.php b/inc/functions/admin.php index 49a8d1449d..e5aeabde0a 100755 --- a/inc/functions/admin.php +++ b/inc/functions/admin.php @@ -428,6 +428,15 @@ function rocket_data_collection_preview_table() { $html .= ''; $html .= ''; + $html .= ''; + $html .= ''; + $html .= sprintf( '%s', __( 'Anonymized WP Rocket statistics:', 'rocket' ) ); + $html .= ''; + $html .= ''; + $html .= sprintf( '%s', __( 'How WP Rocket features function and perform.', 'rocket' ) ); + $html .= ''; + $html .= ''; + $html .= ''; $html .= ''; $html .= sprintf( '%s', __( 'WP Rocket license type', 'rocket' ) ); diff --git a/inc/functions/options.php b/inc/functions/options.php index 423c5ac8dc..1c05119306 100755 --- a/inc/functions/options.php +++ b/inc/functions/options.php @@ -148,7 +148,7 @@ function is_rocket_generate_caching_mobile_files() { // phpcs:ignore WordPress.N * return Array An array of domain names to DNS prefetch */ function rocket_get_dns_prefetch_domains() { - $domains = (array) get_rocket_option( 'dns_prefetch' ); + $domains = (array) get_rocket_option( 'dns_prefetch', [] ); /** * Filter list of domains to prefetch DNS diff --git a/tests/Fixtures/inc/Engine/Common/PerformanceHints/WarmUp/Controller/addWPRImageDimensionQueryArg.php b/tests/Fixtures/inc/Engine/Common/PerformanceHints/WarmUp/Controller/addWPRImageDimensionQueryArg.php index 4be03763c2..92c077c88e 100644 --- a/tests/Fixtures/inc/Engine/Common/PerformanceHints/WarmUp/Controller/addWPRImageDimensionQueryArg.php +++ b/tests/Fixtures/inc/Engine/Common/PerformanceHints/WarmUp/Controller/addWPRImageDimensionQueryArg.php @@ -15,4 +15,12 @@ ], 'expected' => 'http://example.com/?wpr_imagedimensions=1', ], + 'testShouldAddArgumentWhenNoFactoriesAndRUCSSEnabled' => [ + 'config' => [ + 'filter' => [], + 'url' => 'http://example.com', + 'remove_unused_css' => 1, + ], + 'expected' => 'http://example.com/?wpr_imagedimensions=1', + ], ]; diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Admin/Data/collectData.php b/tests/Fixtures/inc/Engine/Media/Fonts/Admin/Data/collectData.php new file mode 100644 index 0000000000..912e491ba4 --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Admin/Data/collectData.php @@ -0,0 +1,88 @@ + [ + 'wp-content' => [ + 'cache' => [ + 'fonts' => [ + '1' => [ + 'google-fonts' => [ + 'fonts' => [ + 's' => [ + 'lato' => [ + 'v24' => [ + 'S6uyw4BMUTPHjx4wXg.woff2' => '', + ], + ], + 'montserrat' => [ + 'v40' => [ + 'memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVI.woff2' => '', + ], + ], + 'oswald' => [ + 'v53' => [ + 'TK3_WkUHHAIjg75cFRf3bXL8LICs1_FvsUZiZQ.woff2' => '', + ], + ], + 'roboto' => [ + 'v32' => [ + 'KFOmCnqEu92Fr1Mu4mxK.woff2' => '', + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], + 'test_data' => [ + 'shouldDoNothingWhenOptionIsDisabled' => [ + 'config' => [ + 'options' => [ + 'host_fonts_locally' => 0, + 'analytics_enabled' => 1, + ], + 'transient' => false, + ], + 'expected' => false, + ], + 'shouldDoNothingWhenAnalyticsDisabled' => [ + 'config' => [ + 'options' => [ + 'host_fonts_locally' => 1, + 'analytics_enabled' => 0, + ], + 'transient' => false, + ], + 'expected' => false, + ], + 'shouldDoNothingWhenDataAlreadyExists' => [ + 'config' => [ + 'options' => [ + 'host_fonts_locally' => 1, + 'analytics_enabled' => 1, + ], + 'transient' => [ + 'fonts_total_number' => 4, + 'fonts_total_size' => '1.2 MB', + ], + ], + 'expected' => false, + ], + 'shouldCollectData' => [ + 'config' => [ + 'options' => [ + 'host_fonts_locally' => 1, + 'analytics_enabled' => 1, + ], + 'transient' => false, + ], + 'expected' => [ + 'fonts_total_number' => 4, + 'fonts_total_size' => '1.2 MB', + ], + ], + ], +]; diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Clean/Clean/cleanOnOptionChange.php b/tests/Fixtures/inc/Engine/Media/Fonts/Clean/Clean/cleanOnOptionChange.php new file mode 100644 index 0000000000..8a4e8ee886 --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Clean/Clean/cleanOnOptionChange.php @@ -0,0 +1,27 @@ + [ + 'old_value' => [], + 'value' => [], + 'expected' => false, + ], + 'testShouldDoNothingWhenOldValueAndNewValueAreTheSame' => [ + 'old_value' => [ + 'host_fonts_locally' => 0, + ], + 'value' => [ + 'host_fonts_locally' => 0, + ], + 'expected' => false, + ], + 'testShouldDeleteAllFilesWhenOldValueAndNewValueAreDifferent' => [ + 'old_value' => [ + 'host_fonts_locally' => 0, + ], + 'value' => [ + 'host_fonts_locally' => 1, + ], + 'expected' => true, + ], +]; diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Context/OptimizationContext/isAllowed.php b/tests/Fixtures/inc/Engine/Media/Fonts/Context/OptimizationContext/isAllowed.php new file mode 100644 index 0000000000..8dad0770f9 --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Context/OptimizationContext/isAllowed.php @@ -0,0 +1,36 @@ + [ + 'config' => [ + 'bypass' => true, + 'do_not_optimize' => false, + 'option' => true, + ], + 'expected' => true, + ], + 'testShouldReturnTrueWhenDoNotOptimize' => [ + 'config' => [ + 'bypass' => false, + 'do_not_optimize' => true, + 'option' => true, + ], + 'expected' => true, + ], + 'testShouldReturnFalseWhenOptionDisabled' => [ + 'config' => [ + 'bypass' => false, + 'do_not_optimize' => true, + 'option' => false, + ], + 'expected' => false, + ], + 'testShouldReturnTrueWhenOptionEnabled' => [ + 'config' => [ + 'bypass' => false, + 'do_not_optimize' => false, + 'option' => true, + ], + 'expected' => true, + ], +]; diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Filesystem/writeFontCss.php b/tests/Fixtures/inc/Engine/Media/Fonts/Filesystem/writeFontCss.php new file mode 100644 index 0000000000..a28fb2840d --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Filesystem/writeFontCss.php @@ -0,0 +1,55 @@ + [ + 'public' => [ + 'wp-content' => [ + 'cache' => [ + 'wp-rocket' => [ + 'fonts' => [ + '1' => [ + 'google-font' => [ + '1' => [ + '5' => [ + '9' => [ + '5' => [ + 'cb6ccb56826a802ed411cef875f0e.css', + 'cb6ccb56826a802ed411cef875f0es' => [ + 'opensans' => [ + 'v18' => 'mem8YaGs126MiZpBA-UFUK0Zdc0.woff2' + ] + ] + ] + ] + ], + ], + ], + ], + ], + ] + ], + ], + ], + ], + 'test_data' => [ + 'shouldWriteFontCss' => [ + 'config' => [ + 'url' => 'https://fonts.googleapis.com/css?family=Open+Sans', + 'css_content' => 'url(https://fonts.gstatic.com/s/opensans/v18/mem8YaGs126MiZpBA-UFUK0Zdc0.woff2);', + 'provider' => 'google-font', + 'local_url' => 'http://example.org/wp-content', + 'response_code' => 200, + 'response' => [ + 'headers' => [], + 'body' => json_encode( (object) [ + 'success' => true, + 'result' => [], + ] ), + 'response' => [], + ], + ], + 'expected' => [ + 'path' => 'vfs://public/wp-content/cache/wp-rocket/font/1/google-font/1/5/9/5/cb6ccb56826a802ed411cef875f0e.css', + ] + ], + ] +]; diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/HTML/expected_v1.php b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/HTML/expected_v1.php new file mode 100644 index 0000000000..d4174e5c84 --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/HTML/expected_v1.php @@ -0,0 +1,33 @@ + + + + + + + + Google Font V1 Template + + + + + +
+
+
+

Hello World

+

Welcome to the world

+
+
+
+ + diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/HTML/expected_v1_v2.php b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/HTML/expected_v1_v2.php new file mode 100644 index 0000000000..bac4f4c3d3 --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/HTML/expected_v1_v2.php @@ -0,0 +1,41 @@ + + + + + + + + Google Font V1 and V2 Template + + + + + +
+
+
+

Hello World

+

Welcome to the world

+

This is a subtitle

+

Enjoy your stay

+
+
+
+ + diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/HTML/expected_v2.php b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/HTML/expected_v2.php new file mode 100644 index 0000000000..1a756bccc4 --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/HTML/expected_v2.php @@ -0,0 +1,33 @@ + + + + + + + + Google Font V2 Template + + + + + +
+
+
+

Hello World

+

Welcome to the world

+
+
+
+ + diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/HTML/input_v1.php b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/HTML/input_v1.php new file mode 100644 index 0000000000..8765447423 --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/HTML/input_v1.php @@ -0,0 +1,35 @@ + + + + + + + + Google Font V1 Template + + + + + + + +
+
+
+

Hello World

+

Welcome to the world

+
+
+
+ + diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/HTML/input_v1_v2.php b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/HTML/input_v1_v2.php new file mode 100644 index 0000000000..cfa1f44954 --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/HTML/input_v1_v2.php @@ -0,0 +1,43 @@ + + + + + + + + Google Font V1 and V2 Template + + + + + + + +
+
+
+

Hello World

+

Welcome to the world

+

This is a subtitle

+

Enjoy your stay

+
+
+
+ + diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/HTML/input_v2.php b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/HTML/input_v2.php new file mode 100644 index 0000000000..8b5f3277fc --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/HTML/input_v2.php @@ -0,0 +1,35 @@ + + + + + + + + Google Font V2 Template + + + + + + + +
+
+
+

Hello World

+

Welcome to the world

+
+
+
+ + diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/rewriteFontsForOptimizations.php b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/rewriteFontsForOptimizations.php new file mode 100644 index 0000000000..3e029b623a --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Controller/rewriteFontsForOptimizations.php @@ -0,0 +1,54 @@ + [ + 'testShouldReturnOriginalWhenNotAllowed' => [ + 'config' => [ + 'is_allowed' => false, + 'write' => false, + ], + 'original' => '', + 'expected' => '', + ], + 'testShouldReturnOriginalWhenNoGoogleFonts' => [ + 'config' => [ + 'is_allowed' => true, + 'write' => false, + ], + 'original' => '', + 'expected' => '', + ], + 'testShouldReturnOriginalWhenWriteFailed' => [ + 'config' => [ + 'is_allowed' => true, + 'write' => false, + ], + 'original' => file_get_contents( __DIR__ . '/HTML/input_v1.php' ), + 'expected' => file_get_contents( __DIR__ . '/HTML/input_v1.php' ), + ], + 'testShouldRewriteV1Font' => [ + 'config' => [ + 'is_allowed' => true, + 'write' => true, + ], + 'original' => file_get_contents( __DIR__ . '/HTML/input_v1.php' ), + 'expected' => file_get_contents( __DIR__ . '/HTML/expected_v1.php' ), + ], + 'testShouldRewriteV2' => [ + 'config' => [ + 'is_allowed' => true, + 'write' => true, + ], + 'original' => file_get_contents( __DIR__ . '/HTML/input_v2.php' ), + 'expected' => file_get_contents( __DIR__ . '/HTML/expected_v2.php' ), + ], + 'testShouldRewriteV1AndV2' => [ + 'config' => [ + 'is_allowed' => true, + 'write' => true, + ], + 'original' => file_get_contents( __DIR__ . '/HTML/input_v1_v2.php' ), + 'expected' => file_get_contents( __DIR__ . '/HTML/expected_v1_v2.php' ), + ], + ], +]; diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v1.php b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v1.php new file mode 100644 index 0000000000..d4174e5c84 --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v1.php @@ -0,0 +1,33 @@ + + + + + + + + Google Font V1 Template + + + + + +
+
+
+

Hello World

+

Welcome to the world

+
+
+
+ + diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v1_excluded_v2.php b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v1_excluded_v2.php new file mode 100644 index 0000000000..415f310ee1 --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v1_excluded_v2.php @@ -0,0 +1,41 @@ + + + + + + + + Google Font V1 and V2 Template + + + + + +
+
+
+

Hello World

+

Welcome to the world

+

This is a subtitle

+

Enjoy your stay

+
+
+
+ + diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v1_style_tag.php b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v1_style_tag.php new file mode 100644 index 0000000000..bcbcee1f97 --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v1_style_tag.php @@ -0,0 +1,33 @@ + + + + + + + + Google Font V1 Template + + + + + +
+
+
+

Hello World

+

Welcome to the world

+
+
+
+ + diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v1_v2.php b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v1_v2.php new file mode 100644 index 0000000000..bac4f4c3d3 --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v1_v2.php @@ -0,0 +1,41 @@ + + + + + + + + Google Font V1 and V2 Template + + + + + +
+
+
+

Hello World

+

Welcome to the world

+

This is a subtitle

+

Enjoy your stay

+
+
+
+ + diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v1_v2_regex.php b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v1_v2_regex.php new file mode 100644 index 0000000000..0d2c2d2350 --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v1_v2_regex.php @@ -0,0 +1,38 @@ + + + + + + + +Google Font V2 Template + + + + + + + +
+
+
+

Hello World

+

Welcome to the world

+
+
+
+ + diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v2.php b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v2.php new file mode 100644 index 0000000000..1a756bccc4 --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v2.php @@ -0,0 +1,33 @@ + + + + + + + + Google Font V2 Template + + + + + +
+
+
+

Hello World

+

Welcome to the world

+
+
+
+ + diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v2_from_combination.php b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v2_from_combination.php new file mode 100644 index 0000000000..d48d6589af --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/expected_v2_from_combination.php @@ -0,0 +1,12 @@ + + + + + Sample Page + + + + + + + diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/input_v1.php b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/input_v1.php new file mode 100644 index 0000000000..e49961654e --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/input_v1.php @@ -0,0 +1,33 @@ + + + + + + + + Google Font V1 Template + + + + + +
+
+
+

Hello World

+

Welcome to the world

+
+
+
+ + diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/input_v1_v2.php b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/input_v1_v2.php new file mode 100644 index 0000000000..34be74bed6 --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/input_v1_v2.php @@ -0,0 +1,41 @@ + + + + + + + + Google Font V1 and V2 Template + + + + + +
+
+
+

Hello World

+

Welcome to the world

+

This is a subtitle

+

Enjoy your stay

+
+
+
+ + diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/input_v1_v2_regex.php b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/input_v1_v2_regex.php new file mode 100644 index 0000000000..8def81e423 --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/input_v1_v2_regex.php @@ -0,0 +1,38 @@ + + + + + + + +Google Font V2 Template + + + + + + + +
+
+
+

Hello World

+

Welcome to the world

+
+
+
+ + diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/input_v2.php b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/input_v2.php new file mode 100644 index 0000000000..2abf18db1b --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/input_v2.php @@ -0,0 +1,33 @@ + + + + + + + + Google Font V2 Template + + + + + +
+
+
+

Hello World

+

Welcome to the world

+
+
+
+ + diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/input_v2_from_combination.php b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/input_v2_from_combination.php new file mode 100644 index 0000000000..00d9bdf377 --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/HTML/input_v2_from_combination.php @@ -0,0 +1,12 @@ + + + + + Sample Page + + + + + + + diff --git a/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/rewriteFontsForOptimizations.php b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/rewriteFontsForOptimizations.php new file mode 100644 index 0000000000..62c0b40858 --- /dev/null +++ b/tests/Fixtures/inc/Engine/Media/Fonts/Frontend/Subscriber/rewriteFontsForOptimizations.php @@ -0,0 +1,163 @@ + [ + 'testShouldReturnOriginalWhenNoGoogleFonts' => [ + 'config' => [ + 'html' => '', + 'host_fonts_locally' => true, + 'locally_inline_css' => false, + ], + 'expected' => [ + 'html' => '' + ], + ], + 'testShouldRewriteV1Font' => [ + 'config' => [ + 'html' => file_get_contents( __DIR__ . '/HTML/input_v1.php' ), + 'host_fonts_locally' => true, + 'locally_inline_css' => false, + 'http' => [ + 'https://fonts.googleapis.com/css?family=Roboto' => [ + 'body' => 'body { font-family: "Roboto"; }', + 'response' => ['code' => 200 ] + ], + 'https://fonts.googleapis.com/css?family=Open+Sans' => [ + 'body' => 'body { font-family: "Open-San"; }', + 'response' => ['code' => 200 ] + ], + ], + ], + 'expected' => [ + 'html' => file_get_contents( __DIR__ . '/HTML/expected_v1.php' ), + ], + ], + 'testShouldRewriteV2Font' => [ + 'config' => [ + 'html' => file_get_contents( __DIR__ . '/HTML/input_v2.php' ), + 'host_fonts_locally' => true, + 'locally_inline_css' => false, + 'http' => [ + 'https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap' => [ + 'body' => 'body { font-family: "Roboto"; }', + 'response' => ['code' => 200 ] + ], + 'https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap' => [ + 'body' => 'body { font-family: "Lato"; }', + 'response' => ['code' => 200 ] + ], + ], + ], + 'expected' => [ + 'html' => file_get_contents( __DIR__ . '/HTML/expected_v2.php' ), + ], + ], + 'testShouldRewriteV1AndV2Fonts' => [ + 'config' => [ + 'html' => file_get_contents( __DIR__ . '/HTML/input_v1_v2.php' ), + 'host_fonts_locally' => true, + 'locally_inline_css' => false, + 'http' => [ + 'https://fonts.googleapis.com/css?family=Roboto|Open+Sans' => [ + 'body' => '.roboto { font-family: "Roboto"; } .open-san { font-family: "Open-San"; }', + 'response' => ['code' => 200 ] + ], + 'https://fonts.googleapis.com/css2?family=Lato:wght@400;700&family=Montserrat:wght@400;700&display=swap' => [ + 'body' => '.lato { font-family: "Lato"; } .montserrat { font-family: "Montserrat"; }', + 'response' => ['code' => 200 ] + ], + ], + ], + 'expected' => [ + 'html' => file_get_contents( __DIR__ . '/HTML/expected_v1_v2.php' ), + ], + ], + 'testShouldRewriteV1AndExcludeV2Fonts' => [ + 'config' => [ + 'html' => file_get_contents( __DIR__ . '/HTML/input_v1_v2.php' ), + 'host_fonts_locally' => true, + 'locally_inline_css' => false, + 'http' => [ + 'https://fonts.googleapis.com/css?family=Roboto|Open+Sans' => [ + 'body' => '.roboto { font-family: "Roboto"; } .open-san { font-family: "Open-San"; }', + 'response' => ['code' => 200 ] + ], + 'https://fonts.googleapis.com/css2?family=Lato:wght@400;700&family=Montserrat:wght@400;700&display=swap' => [ + 'body' => '.lato { font-family: "Lato"; } .montserrat { font-family: "Montserrat"; }', + 'response' => ['code' => 200 ] + ], + ], + 'exclude_locally_host_fonts' => [ + 'Lato', + ] + ], + 'expected' => [ + 'html' => file_get_contents( __DIR__ . '/HTML/expected_v1_excluded_v2.php' ), + ], + ], + 'testShouldRewriteFontV1PathInStyleTag' => [ + 'config' => [ + 'html' => file_get_contents( __DIR__ . '/HTML/input_v1.php' ), + 'host_fonts_locally' => true, + 'http' => [ + 'https://fonts.googleapis.com/css?family=Roboto' => [ + 'body' => 'body { font-family: "Roboto"; }', + 'response' => ['code' => 200 ] + ], + 'https://fonts.googleapis.com/css?family=Open+Sans' => [ + 'body' => 'body { font-family: "Open-San"; }', + 'response' => ['code' => 200 ] + ], + ], + 'locally_inline_css' => true, + ], + 'expected' => [ + 'html' => file_get_contents( __DIR__ . '/HTML/expected_v1_style_tag.php' ), + ], + ], + 'testShouldExcludeFont' => [ + 'config' => [ + 'html' => file_get_contents( __DIR__ . '/HTML/input_v2_from_combination.php' ), + 'host_fonts_locally' => true, + 'locally_inline_css' => false, + 'http' => [ + 'https://fonts.googleapis.com/css2?family=Goldman:wght@700&family=Roboto:ital,wght@0,100;0,400;0,500;1,500;1,900&family=MontSerra:ital,wght@0,100;0,400;0,500;1,500;1,900&family=Comfortaa&display=optional' => [ + 'body' => '', + 'response' => ['code' => 200 ] + ], + ], + 'exclude_locally_host_fonts' => [ + 'Lato', + ] + ], + 'expected' => [ + 'html' => file_get_contents( __DIR__ . '/HTML/expected_v2_from_combination.php' ), + ], + ], + 'testShouldExcludeFontWithRegex' => [ + 'config' => [ + 'html' => file_get_contents( __DIR__ . '/HTML/input_v1_v2_regex.php' ), + 'host_fonts_locally' => true, + 'locally_inline_css' => false, + 'http' => [ + 'https://fonts.googleapis.com/css?family=Roboto|Open+Sans' => [ + 'body' => '.roboto { font-family: "Roboto"; } .open-san { font-family: "Open-San"; }', + 'response' => ['code' => 200 ] + ], + 'https://fonts.googleapis.com/css2?family=Lato:wght@400;700&family=Montserrat:wght@400;700&display=swap' => [ + 'body' => '.lato { font-family: "Lato"; } .montserrat { font-family: "Montserrat"; }', + 'response' => ['code' => 200 ] + ], + ], + 'exclude_locally_host_fonts' => [ + 'family=Rob(.*)o', + 'family(.*)Montserrat(.*)display=swap', + 'astra-google-(.*)-css', + ] + ], + 'expected' => [ + 'html' => file_get_contents( __DIR__ . '/HTML/expected_v1_v2_regex.php' ), + ], + ], + ] +]; diff --git a/tests/Fixtures/inc/Engine/Optimization/GoogleFonts/Combine/optimize.php b/tests/Fixtures/inc/Engine/Optimization/GoogleFonts/Combine/optimize.php index 7e5f9b8993..c78bf59210 100644 --- a/tests/Fixtures/inc/Engine/Optimization/GoogleFonts/Combine/optimize.php +++ b/tests/Fixtures/inc/Engine/Optimization/GoogleFonts/Combine/optimize.php @@ -2,6 +2,10 @@ return [ 'testShouldCombineGoogleFontsWithoutSubsets' => [ + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], 'html' => ' Sample Page @@ -21,6 +25,10 @@ ', ], 'testShouldUseFilteredDisplayValue' => [ + 'config' => [ + 'swap' => 'optional', + 'disable_preload' => false, + ], 'html' => ' Sample Page @@ -38,9 +46,12 @@ ', - 'filtered' => 'optional', ], 'testShouldCombineGoogleFontsWithSubsets' => [ + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], 'html' => ' Sample Page @@ -60,6 +71,10 @@ ', ], 'testShouldCombineGoogleFontsWithoutSubsetsAndNoEnding|' => [ + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], 'html' => ' Sample Page @@ -79,6 +94,10 @@ ', ], 'testShouldCombineGoogleFontsWithoutSubsetsWhenMalformedURL' => [ + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], 'html' => ' Sample Page @@ -98,6 +117,10 @@ ', ], 'testShouldCombineGoogleFontsWithSubsetsWhenMalformedURL' => [ + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], 'html' => ' Sample Page @@ -119,6 +142,10 @@ ', ], 'testShouldOptimizeSingleGoogleFontsWhenNoParam' => [ + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], 'html' => ' Sample Page @@ -138,14 +165,18 @@ ', ], 'testShouldOptimizeSingleGoogleFontsWhenParam' => [ + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], 'html' => ' - - Sample Page - - - - - ', + + Sample Page + + + + + ', 'expected' => ' Sample Page @@ -156,6 +187,10 @@ ', ], 'testShouldOptimizeSingleGoogleFontsWhenInvalidParam' => [ + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], 'html' => ' Sample Page @@ -174,14 +209,18 @@ ', ], 'testShouldOptimizeSingleGoogleFontsWhenEncodedParam' => [ + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], 'html' => ' - - Sample Page - - - - - ', + + Sample Page + + + + + ', 'expected' => ' Sample Page @@ -192,17 +231,21 @@ ', ], 'testShouldCombineGoogleFontsWhenMultipleTitleTags' => [ + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], 'html' => ' - - Sample Page - - - - Sample Title 2 - - - - ', + + Sample Page + + + + Sample Title 2 + + + + ', 'expected' => ' Sample Page @@ -213,6 +256,10 @@ ', ], 'testShouldCombineGoogleFontsWhenTitleTagInsideBody' => [ + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], 'html' => ' Sample Page @@ -234,26 +281,91 @@ ', ], 'testShouldCombineGoogleFontsWhenTitleTagInsideSvgTag' => [ + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], 'html' => ' + + Sample Page + + + + + + +
logo-cacahuete","toggleOpenedIcon":"","closeIcon":"","backIcon":"","dropdownIcon":"","useBreadcrumb":true,"breadcrumbIcon":"","toggleText":"MENU","toggleLoader":true,"backText":"RETOUR","itemIconVisible":"true","itemBadgeVisible":"true","itemDescVisible":"false","loaderColor":"#FCC800","subTrigger":"item"}\'>
+ + ', + 'expected' => ' - Sample Page - - - + Sample Page
logo-cacahuete","toggleOpenedIcon":"","closeIcon":"","backIcon":"","dropdownIcon":"","useBreadcrumb":true,"breadcrumbIcon":"","toggleText":"MENU","toggleLoader":true,"backText":"RETOUR","itemIconVisible":"true","itemBadgeVisible":"true","itemDescVisible":"false","loaderColor":"#FCC800","subTrigger":"item"}\'>
', + ], + 'testShouldOptimizeSingleGoogleFontsNoPreload' => [ + 'config' => [ + 'swap' => false, + 'disable_preload' => true, + ], + 'html' => ' + + Sample Page + + + + + ', + // Expected: Combined HTML. 'expected' => ' - Sample Page + Sample Page - -
logo-cacahuete","toggleOpenedIcon":"","closeIcon":"","backIcon":"","dropdownIcon":"","useBreadcrumb":true,"breadcrumbIcon":"","toggleText":"MENU","toggleLoader":true,"backText":"RETOUR","itemIconVisible":"true","itemBadgeVisible":"true","itemDescVisible":"false","loaderColor":"#FCC800","subTrigger":"item"}\'>
', ], + 'shouldExcludeFontFromCombine' => [ + 'config' => [ + 'swap' => 'optional', + 'disable_preload' => false, + 'exclude_locally_host_fonts' => [ + 'Lato', + ] + ], + 'html' => ' + + + Sample Page + + + + + + + + + ', + 'expected' => ' + + + + Sample Page + + + + + + + + + + ', + ] ]; diff --git a/tests/Fixtures/inc/Engine/Optimization/GoogleFonts/CombineV1V2/optimize.php b/tests/Fixtures/inc/Engine/Optimization/GoogleFonts/CombineV1V2/optimize.php index 620e6f798c..88120d742f 100644 --- a/tests/Fixtures/inc/Engine/Optimization/GoogleFonts/CombineV1V2/optimize.php +++ b/tests/Fixtures/inc/Engine/Optimization/GoogleFonts/CombineV1V2/optimize.php @@ -2,7 +2,11 @@ return [ 'shouldReturnOptimizedTagWhenSingleTagGiven' => [ - 'given' => + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], + 'html' => ' @@ -29,7 +33,11 @@ ' ], 'shouldUseFilteredDisplayValue' => [ - 'given' => + 'config' => [ + 'swap' => 'optional', + 'disable_preload' => false, + ], + 'html' => ' @@ -55,10 +63,13 @@ ' , - 'filtered' => 'optional', ], 'shouldNotCombineMultipleTagsWithTextParam' => [ - 'given' => + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], + 'html' => ' @@ -87,7 +98,11 @@ ' ], 'shouldCombineMultipleTags' => [ - 'given' => + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], + 'html' => ' @@ -115,7 +130,11 @@ ' ], 'shouldCombineMultipleTagsWithMultipleFamiliesInTag' => [ - 'given' => + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], + 'html' => ' @@ -145,7 +164,11 @@ ' ], 'shouldRemovePreconnectWhenNoGoogleFontsPresentOnPage' => [ - 'given' => + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], + 'html' => ' diff --git a/tests/Fixtures/inc/Engine/Optimization/GoogleFonts/CombineV2/optimize.php b/tests/Fixtures/inc/Engine/Optimization/GoogleFonts/CombineV2/optimize.php index 1436ce069d..7c827ede94 100644 --- a/tests/Fixtures/inc/Engine/Optimization/GoogleFonts/CombineV2/optimize.php +++ b/tests/Fixtures/inc/Engine/Optimization/GoogleFonts/CombineV2/optimize.php @@ -2,8 +2,12 @@ return [ 'shouldReturnGivenHTMLWhenNoRelevantTags' => [ - 'given' => - ' + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], + 'html' => + ' Sample Page @@ -11,8 +15,7 @@ - ' - , + ', 'expected' => ' @@ -25,8 +28,12 @@ ' ], 'shouldReturnTagWithFontDisplayWhenSingleTagGiven' => [ - 'given' => - ' + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], + 'html' => + ' Sample Page @@ -34,8 +41,7 @@ - ' - , + ', 'expected' => ' @@ -49,8 +55,11 @@ ' ], 'shouldNotCombineMultipleTagsWithTextParam' => [ - 'given' => - ' + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], + 'html' => ' Sample Page @@ -59,8 +68,7 @@ - ' - , + ', 'expected' => ' @@ -74,8 +82,12 @@ ' ], 'shouldCombineMultipleTags' => [ - 'given' => - ' + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], + 'html' => + ' Sample Page @@ -84,8 +96,7 @@ - ' - , + ', 'expected' => ' @@ -98,8 +109,12 @@ ' ], 'shouldCombineMultipleTagsWithMultipleFamiliesInTag' => [ - 'given' => - ' + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], + 'html' => + ' Sample Page @@ -109,8 +124,7 @@ - ' - , + ', 'expected' => ' @@ -124,8 +138,12 @@ ' ], 'shouldReplaceAnotherFontDisplayValueWithSwap' => [ - 'given' => - ' + 'config' => [ + 'swap' => false, + 'disable_preload' => false, + ], + 'html' => + ' Sample Page @@ -135,8 +153,7 @@ - ' - , + ', 'expected' => ' @@ -150,8 +167,12 @@ ' ], 'shouldReplaceDisplayValueWithFilteredValue' => [ - 'given' => - ' + 'config' => [ + 'swap' => 'optional', + 'disable_preload' => false, + ], + 'html' => + ' Sample Page @@ -161,8 +182,7 @@ - ' - , + ', 'expected' => ' @@ -175,6 +195,71 @@ ' , - 'filtered' => 'optional' ], + 'shouldCombineMultipleTagsNoPreload' => [ + 'config' => [ + 'swap' => false, + 'disable_preload' => true, + ], + 'html' => + ' + + + Sample Page + + + + + + ', + 'expected' => + ' + + + Sample Page + + + + + ' + ], + 'shouldExcludeFontFromCombine' => [ + 'config' => [ + 'swap' => 'optional', + 'disable_preload' => false, + 'exclude_locally_host_fonts' => [ + 'Lato', + ] + ], + 'html' => ' + + + Sample Page + + + + + + + + + ', + 'expected' => ' + + + + Sample Page + + + + + + + + + + ', + ] ]; diff --git a/tests/Fixtures/inc/admin/rocketFirstInstall.php b/tests/Fixtures/inc/admin/rocketFirstInstall.php index 6784e04b54..23a232d6a7 100644 --- a/tests/Fixtures/inc/admin/rocketFirstInstall.php +++ b/tests/Fixtures/inc/admin/rocketFirstInstall.php @@ -77,6 +77,7 @@ $integration[ 'preload_links' ] = 1; $integration[ 'image_dimensions' ] = 0; $integration[ 'exclude_lazyload' ] = []; +$integration['host_fonts_locally'] = 0; return [ 'test_data' => [ diff --git a/tests/Integration/inc/Engine/Media/Fonts/Frontend/Subscriber/rewriteFontsForOptimizations.php b/tests/Integration/inc/Engine/Media/Fonts/Frontend/Subscriber/rewriteFontsForOptimizations.php new file mode 100644 index 0000000000..ed62825df3 --- /dev/null +++ b/tests/Integration/inc/Engine/Media/Fonts/Frontend/Subscriber/rewriteFontsForOptimizations.php @@ -0,0 +1,66 @@ +unregisterAllCallbacksExcept('rocket_buffer', 'rewrite_fonts', 18); + add_filter( 'pre_get_rocket_option_host_fonts_locally', [ $this, 'host_fonts_locally' ] ); + add_filter( 'rocket_host_fonts_locally_inline_css', [ $this, 'locally_inline_css' ] ); + add_filter('rocket_exclude_locally_host_fonts', [ $this, 'exclude_locally_host_fonts' ] ); + $this->setup_http(); + + } + + public function tear_down() { + remove_filter('pre_get_rocket_option_host_fonts_locally', [$this, 'host_fonts_locally']); + remove_filter('rocket_host_fonts_locally_inline_css', [$this, 'locally_inline_css']); + remove_filter('rocket_exclude_locally_host_fonts', [ $this, 'exclude_locally_host_fonts' ] ); + + $this->restoreWpHook('rocket_buffer'); + $this->tear_down_http(); + + + parent::tear_down(); + } + + /** + * @dataProvider providerTestData + */ + public function testShouldReturnAsExpected( $config, $expected ) { + $this->config = $config; + + $this->assertSame( + $expected['html'], + wpm_apply_filters_typed('string', 'rocket_buffer', $config['html']) + ); + } + + public function host_fonts_locally() { + return $this->config['host_fonts_locally']; + } + + public function locally_inline_css() { + return $this->config['locally_inline_css']; + } + + public function exclude_locally_host_fonts() { + return $this->config['exclude_locally_host_fonts'] ?? []; + } +} diff --git a/tests/Integration/inc/Engine/Media/ImageDimensions/AdminSubscriber/addOption.php b/tests/Integration/inc/Engine/Media/ImageDimensions/AdminSubscriber/addOption.php index ffe184e47a..e1d19dcfcd 100644 --- a/tests/Integration/inc/Engine/Media/ImageDimensions/AdminSubscriber/addOption.php +++ b/tests/Integration/inc/Engine/Media/ImageDimensions/AdminSubscriber/addOption.php @@ -18,9 +18,9 @@ public function set_up() { } public function tear_down() { - parent::tear_down(); - $this->restoreWpHook( 'rocket_first_install_options' ); + + parent::tear_down(); } /** diff --git a/tests/Integration/inc/Engine/Optimization/GoogleFonts/Combine/optimize.php b/tests/Integration/inc/Engine/Optimization/GoogleFonts/Combine/optimize.php index 1a97e8b243..5b2e4a013d 100644 --- a/tests/Integration/inc/Engine/Optimization/GoogleFonts/Combine/optimize.php +++ b/tests/Integration/inc/Engine/Optimization/GoogleFonts/Combine/optimize.php @@ -13,11 +13,14 @@ * @group GoogleFonts */ class Test_Optimize extends TestCase { + private $display; + private $disable_preload; - private $filter_value; + protected $config; public function set_up() { parent::set_up(); + $GLOBALS['wp'] = (object) [ 'query_vars' => [], 'request' => 'http://example.org', @@ -25,47 +28,59 @@ public function set_up() { 'embed', ], ]; - $this->unregisterAllCallbacksExcept('rocket_buffer', 'process', 1001 ); + + $this->unregisterAllCallbacksExcept('rocket_buffer', 'process', 17 ); + add_filter('rocket_exclude_locally_host_fonts', [ $this, 'exclude_locally_host_fonts' ] ); // @phpstan-ignore-line } public function tear_down() { remove_filter( 'pre_get_rocket_option_minify_google_fonts', [ $this, 'return_true' ] ); remove_filter( 'rocket_combined_google_fonts_display', [ $this, 'set_display_value' ] ); + remove_filter( 'rocket_disable_google_fonts_preload', [ $this, 'set_disable_preload' ] ); + remove_filter('rocket_exclude_locally_host_fonts', [ $this, 'exclude_locally_host_fonts' ] ); + - unset( $this->filter_value ); $this->restoreWpHook('rocket_buffer'); + parent::tear_down(); } /** * @dataProvider addDataProviderV1 */ - public function testShouldCombineGoogleFontsV1( $original, $combined, $filtered = false ) { - $this->doTest( $original, $combined, $filtered ); + public function testShouldCombineGoogleFontsV1( $config, $original, $combined ) { + $this->config = $config; + $this->doTest( $config, $original, $combined ); } /** * @dataProvider addDataProviderV2 */ - public function testShouldCombineGoogleFontsV2( $original, $combined, $filtered = false ) { - $this->doTest( $original, $combined, $filtered ); + public function testShouldCombineGoogleFontsV2( $config, $original, $combined ) { + $this->config = $config; + $this->doTest( $config, $original, $combined ); } /** * @dataProvider addDataProviderV1V2 */ - public function testShouldCombineGoogleFontsV1V2( $original, $combined, $filtered = false ) { - $this->doTest( $original, $combined, $filtered ); + public function testShouldCombineGoogleFontsV1V2( $config, $original, $combined ) { + $this->config = $config; + $this->doTest( $config, $original, $combined ); } - private function doTest( $original, $expected, $filtered ) { + private function doTest( $config, $original, $expected ) { + $this->display = $config['swap']; + $this->disable_preload = $config['disable_preload']; + add_filter( 'pre_get_rocket_option_minify_google_fonts', [ $this, 'return_true' ] ); - if ( $filtered ) { - $this->filter_value = $filtered; + if ( false !== $config['swap'] ) { add_filter( 'rocket_combined_google_fonts_display', [ $this, 'set_display_value' ] ); } + add_filter( 'rocket_disable_google_fonts_preload', [ $this, 'set_disable_preload' ] ); + $actual = apply_filters( 'rocket_buffer', $original ); $this->assertSame( @@ -86,7 +101,15 @@ public function addDataProviderV1V2() { return $this->getTestData( __DIR__ . 'V1V2', 'optimize' ); } - public function set_display_value( $filtered ) { - return $this->filter_value; + public function set_display_value() { + return $this->display; + } + + public function set_disable_preload() { + return $this->disable_preload; + } + + public function exclude_locally_host_fonts() { + return $this->config['exclude_locally_host_fonts'] ?? []; } } diff --git a/tests/Unit/bootstrap.php b/tests/Unit/bootstrap.php index d2651ce533..6a3a4ad4d7 100644 --- a/tests/Unit/bootstrap.php +++ b/tests/Unit/bootstrap.php @@ -9,7 +9,7 @@ // Set the path and URL to our virtual filesystem. define( 'WP_ROCKET_CACHE_ROOT_PATH', 'vfs://public/wp-content/cache/' ); -define( 'WP_ROCKET_CACHE_ROOT_URL', 'vfs://public/wp-content/cache/' ); +define( 'WP_ROCKET_CACHE_ROOT_URL', 'http://example.org/wp-content/cache/' ); define( 'OBJECT', 'OBJECT' ); /** * The original files need to loaded into memory before we mock them with Patchwork. Add files here before the unit diff --git a/tests/Unit/inc/Engine/Common/PerformanceHints/WarmUp/Controller/addWPRImageDimensionQueryArg.php b/tests/Unit/inc/Engine/Common/PerformanceHints/WarmUp/Controller/addWPRImageDimensionQueryArg.php index 159b35ab0b..8d9b7e4829 100644 --- a/tests/Unit/inc/Engine/Common/PerformanceHints/WarmUp/Controller/addWPRImageDimensionQueryArg.php +++ b/tests/Unit/inc/Engine/Common/PerformanceHints/WarmUp/Controller/addWPRImageDimensionQueryArg.php @@ -36,11 +36,16 @@ protected function setUp(): void { public function testShouldReturnExpected( $config, $expected ) { $this->controller = new Controller( $config['filter'], $this->options, $this->api_client, $this->user, $this->queue ); + $this->options->shouldReceive('get') + ->with( 'remove_unused_css', 0 ) + ->andReturn( $config['remove_unused_css'] ?? 0 ); + Functions\expect( 'add_query_arg' ) ->with( - [ 'wpr_imagedimensions' => 1 ] + [ 'wpr_imagedimensions' => 1 ], + $config['url'] ) - ->andReturn( $expected ); + ->andReturn( $config['url'] . '/?wpr_imagedimensions=1' ); $this->assertSame( $expected, diff --git a/tests/Unit/inc/Engine/Media/Fonts/Admin/Data/collectData.php b/tests/Unit/inc/Engine/Media/Fonts/Admin/Data/collectData.php new file mode 100644 index 0000000000..4d42b8a3b0 --- /dev/null +++ b/tests/Unit/inc/Engine/Media/Fonts/Admin/Data/collectData.php @@ -0,0 +1,57 @@ +justReturn( 1 ); + + $this->options = Mockery::mock( Options_Data::class ); + $this->data = new Data( $this->options ); + } + + /** + * @dataProvider providerTestData + */ + public function testShouldDoExpected( $config, $expected ) { + $this->options->shouldReceive( 'get' ) + ->with( 'host_fonts_locally', 0 ) + ->andReturn( $config['options']['host_fonts_locally'] ); + + $this->options->shouldReceive( 'get') + ->with( 'analytics_enabled', 0 ) + ->andReturn( $config['options']['analytics_enabled'] ); + + Functions\when( 'get_transient' )->justReturn( $config['transient'] ); + + Functions\when( 'size_format' )->justReturn( '1.2 MB' ); + + if ( $expected ) { + Functions\expect( 'set_transient' ) + ->once() + ->with( 'rocket_fonts_data_collection', $expected, WEEK_IN_SECONDS ); + } else { + Functions\expect( 'set_transient' )->never(); + } + + $this->data->collect_data(); + } +} diff --git a/tests/Unit/inc/Engine/Media/Fonts/Clean/Clean/cleanOnOptionChange.php b/tests/Unit/inc/Engine/Media/Fonts/Clean/Clean/cleanOnOptionChange.php new file mode 100644 index 0000000000..b5b1d84f11 --- /dev/null +++ b/tests/Unit/inc/Engine/Media/Fonts/Clean/Clean/cleanOnOptionChange.php @@ -0,0 +1,41 @@ +justReturn( 1 ); + + $this->filesystem = Mockery::mock( Filesystem::class ); + $this->clean = new Clean( $this->filesystem ); + } + + /** + * @dataProvider configTestData + */ + public function testShouldDoExpected( $old_value, $value, $expected ) { + if ( $expected ) { + $this->filesystem->shouldReceive( 'delete_all_files_from_directory' )->once(); + } else { + $this->filesystem->shouldNotReceive( 'delete_all_files_from_directory' ); + } + + $this->clean->clean_on_option_change( $old_value, $value ); + } +} diff --git a/tests/Unit/inc/Engine/Media/Fonts/Context/Context/isAllowed.php b/tests/Unit/inc/Engine/Media/Fonts/Context/Context/isAllowed.php new file mode 100644 index 0000000000..201412162a --- /dev/null +++ b/tests/Unit/inc/Engine/Media/Fonts/Context/Context/isAllowed.php @@ -0,0 +1,36 @@ +donotrocketoptimize = $config['do_not_optimize']; + + $options = Mockery::mock( Options_Data::class ); + $context = new OptimizationContext( $options ); + + Functions\when( 'rocket_bypass' )->justReturn( $config['bypass'] ); + + $options->shouldReceive( 'get' ) + ->with( 'host_fonts_locally', 0 ) + ->andReturn( $config['option'] ); + + $this->assertSame( + $expected, + $context->is_allowed( $config ) + ); + } +} diff --git a/tests/Unit/inc/Engine/Media/Fonts/Filesystem/writeFontCss.php b/tests/Unit/inc/Engine/Media/Fonts/Filesystem/writeFontCss.php new file mode 100644 index 0000000000..e50481bf6d --- /dev/null +++ b/tests/Unit/inc/Engine/Media/Fonts/Filesystem/writeFontCss.php @@ -0,0 +1,54 @@ +justReturn( 1 ); + + $this->stubWpParseUrl(); + } + + /** + * @dataProvider providerTestData + */ + public function testShouldReturnExpected( $config, $expected ) { + + $filesystem = new Filesystem(); + + Functions\when( 'wp_remote_retrieve_body' ) + ->justReturn( $config['css_content'] ); + + Functions\when('rocket_mkdir_p')->alias(function($dir) { + if (!file_exists($dir)) { + mkdir($dir, 0777, true); + } + return true; + }); + + Functions\when( 'wp_safe_remote_get' ) + ->justReturn( $config['response'] ); + + Functions\when( 'wp_remote_retrieve_response_code' ) + ->justReturn( $config['response_code'] ); + + Functions\when( 'content_url' )->justReturn( $config['local_url']); + + $this->assertTrue( $filesystem->write_font_css( $config['url'], $config['provider']) ); + + //$this->assertTrue( $this->filesystem->exists( $expected['path'] ) ); + } +} diff --git a/tests/Unit/inc/Engine/Media/Fonts/Frontend/Controller/rewriteFontsForOptimizations.php b/tests/Unit/inc/Engine/Media/Fonts/Frontend/Controller/rewriteFontsForOptimizations.php new file mode 100644 index 0000000000..5c5945e624 --- /dev/null +++ b/tests/Unit/inc/Engine/Media/Fonts/Frontend/Controller/rewriteFontsForOptimizations.php @@ -0,0 +1,69 @@ +justReturn( 1 ); + + $this->optimization_context = Mockery::mock( OptimizationContext::class ); + $this->saas_context = Mockery::mock( SaasContext::class ); + $this->fonts_filesystem = Mockery::mock( FontsFilesystem::class ); + $this->controller = new Controller( $this->optimization_context, $this->saas_context, $this->fonts_filesystem ); + + $this->stubWpParseUrl(); + } + + /** + * @dataProvider providerTestData + */ + public function testShouldDoExpected( $config, $original, $expected ) { + $this->optimization_context->shouldReceive('is_allowed') + ->once() + ->andReturn( $config['is_allowed'] ); + + $this->fonts_filesystem->shouldReceive( 'exists' ) + ->andReturn( false ); + + $this->fonts_filesystem->shouldReceive( 'write_font_css' ) + ->andReturn( $config['write'] ); + $this->fonts_filesystem->shouldReceive( 'hash_to_path' ) + ->andReturnUsing( function( $hash ) { + $levels = 3; + + $base = substr( $hash, 0, $levels ); + $remain = substr( $hash, $levels ); + + $path_array = str_split( $base ); + $path_array[] = $remain; + + return implode( '/', $path_array ); + } ); + + $this->assertSame( + $this->format_the_html( $expected ), + $this->format_the_html( $this->controller->rewrite_fonts_for_optimizations( $original ) ) + ); + } +} diff --git a/tests/Unit/inc/Engine/Optimization/GoogleFonts/Combine/optimize.php b/tests/Unit/inc/Engine/Optimization/GoogleFonts/Combine/optimize.php index b2c8e05ff4..4d54ad5fc5 100644 --- a/tests/Unit/inc/Engine/Optimization/GoogleFonts/Combine/optimize.php +++ b/tests/Unit/inc/Engine/Optimization/GoogleFonts/Combine/optimize.php @@ -1,12 +1,11 @@ alias( function( $url, $component ) { - return parse_url( $url, $component ); - } ); + public function testShouldCombineGoogleFonts( $config, $html, $expected ) { + $this->stubWpParseUrl(); Functions\when( 'wp_parse_args' )->alias( function( $value ) { parse_str( $value, $r ); @@ -40,12 +37,20 @@ public function testShouldCombineGoogleFonts( $html, $expected, $filtered = fals return str_replace( [ '&', '&' ], '&', $url ); } ); - if ( false !== $filtered ) { - Filters\expectApplied('rocket_combined_google_fonts_display') - ->with('swap', Mockery::type(AbstractGFOptimization::class)) - ->andReturn( $filtered ); + if ( false !== $config['swap'] ) { + Filters\expectApplied( 'rocket_combined_google_fonts_display' ) + ->with('swap', Mockery::type( AbstractGFOptimization::class ) ) + ->andReturn( $config['swap'] ); } + + Filters\expectApplied( 'rocket_disable_google_fonts_preload' ) + ->andReturn( $config['disable_preload'] ); + + Filters\expectApplied( 'rocket_exclude_locally_host_fonts' ) + ->andReturn( $config['exclude_locally_host_fonts'] ?? [] ); + + $combine = new Combine(); $this->assertSame( diff --git a/tests/Unit/inc/Engine/Optimization/GoogleFonts/CombineV2/optimize.php b/tests/Unit/inc/Engine/Optimization/GoogleFonts/CombineV2/optimize.php index 5fda6ddfc4..b9f57421b4 100644 --- a/tests/Unit/inc/Engine/Optimization/GoogleFonts/CombineV2/optimize.php +++ b/tests/Unit/inc/Engine/Optimization/GoogleFonts/CombineV2/optimize.php @@ -1,12 +1,11 @@ alias( function ( $url, $component ) { - return parse_url( $url, $component ); - } ); + public function testShouldCombineV2GoogleFonts( $config, $html, $expected ) { + $this->stubWpParseUrl(); Functions\when( 'wp_parse_args' )->alias( function ( $value ) { parse_str( $value, $r ); @@ -39,12 +36,20 @@ public function testShouldCombineV2GoogleFonts( $html, $expected, $filtered = fa return str_replace( [ '&', '&' ], '&', $url ); } ); - if ( false !== $filtered ) { - Filters\expectApplied('rocket_combined_google_fonts_display') - ->with('swap', Mockery::type(AbstractGFOptimization::class)) - ->andReturn( $filtered ); + if ( false !== $config['swap'] ) { + Filters\expectApplied( 'rocket_combined_google_fonts_display' ) + ->with('swap', Mockery::type( AbstractGFOptimization::class ) ) + ->andReturn( $config['swap'] ); + } + + if ( false !== $config['disable_preload'] ) { + Filters\expectApplied( 'rocket_disable_google_fonts_preload' ) + ->andReturn( $config['disable_preload'] ); } + Filters\expectApplied( 'rocket_exclude_locally_host_fonts' ) + ->andReturn( $config['exclude_locally_host_fonts'] ?? [] ); + $combiner = new CombineV2(); $this->assertSame( diff --git a/wp-rocket.php b/wp-rocket.php index f052da3521..c515de6293 100755 --- a/wp-rocket.php +++ b/wp-rocket.php @@ -3,7 +3,7 @@ * Plugin Name: WP Rocket * Plugin URI: https://wp-rocket.me * Description: The best WordPress performance plugin. - * Version: 3.18 + * Version: 3.18-alpha2 * Requires at least: 5.8 * Requires PHP: 7.3 * Code Name: Iego @@ -20,7 +20,7 @@ defined( 'ABSPATH' ) || exit; // Rocket defines. -define( 'WP_ROCKET_VERSION', '3.18' ); +define( 'WP_ROCKET_VERSION', '3.18-alpha2' ); define( 'WP_ROCKET_WP_VERSION', '5.8' ); define( 'WP_ROCKET_WP_VERSION_TESTED', '6.3.1' ); define( 'WP_ROCKET_PHP_VERSION', '7.3' ); @@ -82,7 +82,7 @@ define( 'CHMOD_WP_ROCKET_CACHE_DIRS', 0755 ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals } if ( ! defined( 'WP_ROCKET_LASTVERSION' ) ) { - define( 'WP_ROCKET_LASTVERSION', '3.16.4' ); + define( 'WP_ROCKET_LASTVERSION', '3.17.3.1' ); } /**