ntinue; } $mapping = array_merge( $mapping, $file_mapping ); } else { $this->logger::debug( "Load lazy css files $url", $this->generate_log_context() ); $mapping = array_merge( $mapping, $this->load_existing_mapping( $url ) ); } $cached_url = $this->generate_asset_url( $url ); $html = str_replace( $css_files_mapping[ $url ], $cached_url, $html ); } foreach ( $css_files_mapping as $url => $placeholder ) { $html = str_replace( $placeholder, $url, $html ); } $html = $this->restore_html_comments( $html ); $data['html'] = $html; if ( ! key_exists( 'lazyloaded_images', $data ) ) { $data['lazyloaded_images'] = []; } $data['lazyloaded_images'] = array_merge( $data['lazyloaded_images'], $mapping ); $data['lazyloaded_images'] = array_unique( $data['lazyloaded_images'], SORT_REGULAR ); return $data; } /** * Add the lazy tag to the current HTML. * * @param array $data Data sent. * @return array */ public function add_lazy_tag( array $data ): array { if ( ! key_exists( 'html', $data ) || ! key_exists( 'lazyloaded_images', $data ) ) { $this->logger::debug( 'Add lazy tag bailed out', $this->generate_log_context( [ 'data' => $data, ] ) ); return $data; } $lazyload_images = $data['lazyloaded_images']; /** * Lazyload background CSS excluded urls. * * @param array $excluded Excluded URLs. * @param array $urls List of Urls processed. */ $loaded = apply_filters( 'rocket_css_image_lazyload_images_load', [], $lazyload_images ); $tags = $this->tag_generator->generate( $lazyload_images, $loaded ); $this->logger::debug( 'Add lazy tag generated', $this->generate_log_context( [ 'data' => $tags, ] ) ); $data['html'] = str_replace( '', "$tags", $data['html'] ); return $data; } /** * Generate lazy CSS for a file. * * @param string $url Url from the CSS. * @return array */ protected function generate_css_file( string $url ) { $path = $this->file_resolver->resolve( $url ); if ( ! $path ) { $path = $url; } $content = $this->fetcher->fetch( $path, $this->cache->generate_path( $url ) ); if ( ! $content ) { return []; } $output = $this->generate_content( $content, $this->cache->generate_url( $url ) ); if ( count( $output->get_urls() ) === 0 ) { return []; } if ( ! $this->cache->set( $this->format_url( $url ), $output->get_content() ) ) { return []; } $this->cache->set( $this->get_mapping_file_url( $url ), json_encode( $output->get_urls() ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode return $output->get_urls(); } /** * Generate lazy content for a certain content. * * @param string $content Content to generate lazy for. * @param string $url URL of the file we are extracting content from. * @return LazyloadedContent */ protected function generate_content( string $content, string $url = '' ): LazyloadedContent { $urls = $this->extractor->extract( $content, $url ); $formatted_urls = []; foreach ( $urls as $url_tags ) { $url_tags = $this->add_hashes( $url_tags ); $content = $this->rule_formatter->format( $content, $url_tags ); $formatted_urls = array_merge( $formatted_urls, $this->mapping_formatter->format( $url_tags ) ); } return $this->lazyloaded_content_factory->make_lazyloaded_content( $formatted_urls, $content ); } /** * Load existing mapping for a URL. * * @param string $url Url we load mapping for. * @return array */ protected function load_existing_mapping( string $url ) { $content = $this->cache->get( $this->get_mapping_file_url( $url ) ); $urls = json_decode( $content, true ); if ( ! $urls ) { return []; } return $urls; } /** * Create the lazyload file for inline CSS. * * @param array $data Data sent. * @return array */ public function create_lazy_inline_css( array $data ): array { if ( ! key_exists( 'html', $data ) || ! key_exists( 'css_inline', $data ) ) { $this->logger::debug( 'Create lazy css inline bailed out', $this->generate_log_context( [ 'data' => $data, ] ) ); return $data; } $html = $data['html']; if ( ! key_exists( 'lazyloaded_images', $data ) ) { $data['lazyloaded_images'] = []; } foreach ( $data['css_inline'] as $content ) { $output = $this->generate_content( $content ); $html = str_replace( $content, $output->get_content(), $html ); $data['lazyloaded_images'] = array_merge( $data['lazyloaded_images'], $output->get_urls() ); } $data['html'] = $html; return $data; } /** * Format a URL. * * @param string $url URL to format. * @return string */ protected function format_url( string $url ): string { return strtok( $url, '?' ); } /** * Check of the string is excluded. * * @param string $string String to check. * @return bool */ protected function is_excluded( string $string ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.stringFound $values = [ $string, ]; $parsed_url_host = wp_parse_url( $string, PHP_URL_HOST ); if ( ! $parsed_url_host ) { $values [] = rocket_get_home_url() . $string; } /** * Filters the src used to prevent lazy load from being applied. * * @param array $excluded_src An array of excluded src. */ $excluded_values = apply_filters( 'rocket_lazyload_excluded_src', [] ); if ( ! is_array( $excluded_values ) ) { $excluded_values = (array) $excluded_values; } if ( empty( $excluded_values ) ) { return false; } foreach ( $values as $value ) { foreach ( $excluded_values as $excluded_value ) { if ( strpos( $value, $excluded_value ) !== false ) { return true; } } } return false; } /** * Is the feature activated. * * @return bool */ protected function is_activated(): bool { return (bool) $this->options->get( 'lazyload_css_bg_img', false ); } /** * Add lazyload_excluded_src to excluded filters. * * @param array $excluded Excluded URLs. * @param array $urls List of Urls processed. * @return mixed */ public function exclude_rocket_lazyload_excluded_src( $excluded, $urls ) { /** * Filters the src used to prevent lazy load from being applied. * * @param array $excluded_src An array of excluded src. */ $excluded_values = apply_filters( 'rocket_lazyload_excluded_src', [] ); if ( ! is_array( $excluded_values ) ) { $excluded_values = (array) $excluded_values; } if ( empty( $excluded_values ) ) { return $excluded; } foreach ( $urls as $url ) { foreach ( $excluded_values as $excluded_value ) { if ( strpos( $url['selector'], $excluded_value ) !== false || strpos( $url['style'], $excluded_value ) !== false ) { $excluded[] = $url; break; } } } return $excluded; } /** * Add hashes. * * @param array $tags Tags. * @return array */ protected function add_hashes( array $tags ): array { return array_map( function ( $url_tag ) { /** * Lazyload CSS hash. * * @param string $hash Lazyload CSS hash. */ $url_tag['hash'] = apply_filters( 'rocket_lazyload_css_hash', wp_generate_uuid4(), $url_tag ); return $url_tag; }, $tags ); } /** * Return mapping file URL. * * @param string $url Resource URL. * @return string */ protected function get_mapping_file_url( string $url ): string { return $this->format_url( $url ) . '.json'; } /** * Add timestamp to URL. * * @param string $url Asset Url. * * @return string */ protected function generate_asset_url( string $url ): string { $parsed_query = wp_parse_url( $url, PHP_URL_QUERY ); $queries = []; if ( $parsed_query ) { parse_str( $parsed_query, $queries ); } $queries['wpr_t'] = current_time( 'timestamp' ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested $cached_url = $this->cache->generate_url( $this->format_url( $url ) ); $this->logger::debug( "Generated url lazy css files $url", $this->generate_log_context( [ 'data' => $cached_url, ] ) ); return add_query_arg( $queries, $cached_url ); } /** * Generate the context for logs. * * @param array $data Data to pass to logs. * @return array */ protected function generate_log_context( array $data = [] ): array { return array_merge( $data, [ 'type' => 'lazyload_css_bg_images', ] ); } /** * Protect URL inside the content. * * @param string $content Content to protect. * @param array $css_files CSS files from the content. * @return ProtectedContent */ protected function protect_css_urls( string $content, array $css_files ): ProtectedContent { usort( $css_files, function ( $url1, $url2 ) { return strlen( $url1 ) < strlen( $url2 ) ? 1 : -1; } ); $css_files_mapping = []; foreach ( $css_files as $url ) { $placeholder = uniqid( 'url_bg_css_' ); $content = str_replace( $url, $placeholder, $content ); $css_files_mapping[ $url ] = $placeholder; } return $this->lazyloaded_content_factory->make_protected_content( $css_files_mapping, $content ); } /** * Exclude SVG from lazy loaded images. * * @param array $urls URLs to exclude. * @return array */ public function remove_svg_from_lazyload_css( array $urls ): array { $urls[] = 'data:image/svg+xml'; return $urls; } }