old_styles = new WP_Styles(); } else { $this->old_styles = $styles; } // Unset all the object properties except our private copy of the styles object. // We have to unset everything so that the overload methods talk to $this->old_styles->whatever // instead of $this->whatever. foreach ( array_keys( get_object_vars( $this ) ) as $key ) { if ( 'old_styles' === $key ) { continue; } unset( $this->$key ); } $this->dependency_path_mapping = new Page_Optimize_Dependency_Path_Mapping( apply_filters( 'page_optimize_site_url', $this->base_url ) ); } protected function has_inline_style( $handle ) { $after_output = $this->get_data( $handle, 'after' ); if ( ! empty( $after_output ) ) { return true; } return false; } protected function css_has_import( $path ) { static $cache = array(); if ( empty( $path ) ) { return false; } $key = $path; if ( array_key_exists( $key, $cache ) ) { return $cache[ $key ]; } $fh = @fopen( $path, 'rb' ); if ( ! $fh ) { $cache[ $key ] = false; return false; } $found = false; $tail = ''; // Scan in chunks to keep memory bounded for large stylesheets. while ( ! feof( $fh ) ) { $chunk = fread( $fh, 32768 ); if ( false === $chunk || '' === $chunk ) { break; } $haystack = $tail . $chunk; if ( false !== stripos( $haystack, '@import' ) ) { $found = true; break; } // Keep a short overlap so "@import" across chunk boundaries isn't missed. $tail = substr( $haystack, -7 ); } fclose( $fh ); $cache[ $key ] = $found; return $found; } function do_items( $handles = false, $group = false ) { $handles = false === $handles ? $this->queue : (array) $handles; $stylesheets = array(); $siteurl = apply_filters( 'page_optimize_site_url', $this->base_url ); $this->all_deps( $handles ); $concat_group = null; foreach ( $this->to_do as $key => $handle ) { $css_realpath = null; // 1a. Skip invalid dependencies. if ( ! isset( $this->registered[ $handle ] ) ) { unset( $this->to_do[ $key ] ); continue; } $obj = $this->registered[ $handle ]; // 1b. Skip virtual (src-less) dependencies. if ( empty( $obj->src ) ) { if ( null !== $concat_group ) { $stylesheets[] = $concat_group; $concat_group = null; } $stylesheets[] = array( 'type' => 'do_item', 'handle' => $handle, ); unset( $this->to_do[ $key ] ); continue; } $obj->src = apply_filters( 'style_loader_src', $obj->src, $obj->handle ); // Core is kind of broken and returns "true" for src of "colors" handle // http://core.trac.wordpress.org/attachment/ticket/16827/colors-hacked-fixed.diff // http://core.trac.wordpress.org/ticket/20729 $css_url = $obj->src; if ( 'colors' == $obj->handle && true === $css_url ) { $css_url = wp_style_loader_src( $css_url, $obj->handle ); } $css_url_parsed = parse_url( $css_url ); $css_path = ( is_array( $css_url_parsed ) && isset( $css_url_parsed['path'] ) ) ? $css_url_parsed['path'] : ''; $extra = $obj->extra; // 2. Determine if this stylesheet can be concatenated. // Don't concat by default $do_concat = false; // Only try to concat static css files if ( $css_path && false !== strpos( $css_path, '.css' ) ) { $do_concat = true; } else { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { echo sprintf( "\n\n", esc_html( $handle ), esc_html( $obj->src ) ); } } // Don't try to concat styles which are loaded conditionally (like IE stuff) if ( isset( $extra['conditional'] ) ) { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { echo sprintf( "\n\n", esc_html( $handle ) ); } $do_concat = false; } // Don't concat rtl stuff for now until concat supports it correctly if ( $do_concat && 'rtl' === $this->text_direction && ! empty( $extra['rtl'] ) ) { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { echo sprintf( "\n\n", esc_html( $handle ) ); } $do_concat = false; } // Don't try to concat externally hosted scripts $is_internal_uri = $this->dependency_path_mapping->is_internal_uri( $css_url ); if ( $do_concat && ! $is_internal_uri ) { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { echo sprintf( "\n\n", esc_html( $handle ), esc_url( $css_url ) ); } $do_concat = false; } if ( $do_concat ) { // Resolve paths and concat styles that exist in the filesystem $css_realpath = $this->dependency_path_mapping->dependency_src_to_fs_path( $css_url ); if ( false === $css_realpath ) { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { echo sprintf( "\n\n", esc_html( $handle ), esc_html( $css_realpath ) ); } $do_concat = false; } } // Skip concating CSS from exclusion list $exclude_list = page_optimize_css_exclude_list(); foreach ( $exclude_list as $exclude ) { if ( $do_concat && $handle === $exclude ) { $do_concat = false; if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { echo sprintf( "\n\n", esc_html( $handle ) ); } } } // Allow plugins to disable concatenation of certain stylesheets. $filtered_concat = apply_filters( 'css_do_concat', $do_concat, $handle ); if ( $do_concat && ! $filtered_concat ) { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { echo sprintf( "\n\n", esc_html( $handle ) ); } } $do_concat = $filtered_concat; /** * 3. Add to the stylesheets output list. * * We keep a running $concat_group that accumulates consecutive concat-eligible styles * sharing the same media type. The group is finalized and appended to $stylesheets when: * - A non-concat style appears * - The media type changes * - An inline style is attached to the current style * This makes sure output order matches registration order. **/ if ( true === $do_concat ) { $media = $obj->args; if ( empty( $media ) ) { $media = 'all'; } // Media type changed - finish the current group. if ( null !== $concat_group && $concat_group['media'] !== $media ) { $stylesheets[] = $concat_group; $concat_group = null; } // If a non-first stylesheet in a group contains @import, the concat service hoists it, // reordering CSS. Split groups so @import stylesheets are always first in their group. if ( null !== $concat_group && ! empty( $css_realpath ) && $this->css_has_import( $css_realpath ) ) { $stylesheets[] = $concat_group; $concat_group = null; } // Start a new group if needed. if ( null === $concat_group ) { $concat_group = array( 'type' => 'concat', 'media' => $media, 'paths' => array(), 'handles' => array(), ); } // Add this stylesheet to the current group. $concat_group['paths'][] = $css_path; $concat_group['handles'][] = $handle; $this->done[] = $handle; // Inline styles must print right after their , so break the group // because we can't concat directly anything after this CSS. if ( $this->has_inline_style( $handle ) ) { $stylesheets[] = $concat_group; $concat_group = null; } } else { // Non-concat item - finish any current open group to preserve order. if ( null !== $concat_group ) { $stylesheets[] = $concat_group; $concat_group = null; } // Add the non-concat item. $stylesheets[] = array( 'type' => 'do_item', 'handle' => $handle, ); } unset( $this->to_do[ $key ] ); } if ( null !== $concat_group ) { $stylesheets[] = $concat_group; } foreach ( $stylesheets as $css_array ) { if ( 'do_item' === $css_array['type'] ) { if ( $this->do_item( $css_array['handle'], $group ) ) { $this->done[] = $css_array['handle']; } } elseif ( 'concat' === $css_array['type'] && isset( $css_array['paths'] ) ) { $media = $css_array['media']; $css = $css_array['paths']; $handles = $css_array['handles']; if ( count( $css ) > 1 ) { $fs_paths = array(); foreach ( $css as $css_uri_path ) { $fs_paths[] = $this->dependency_path_mapping->uri_path_to_fs_path( $css_uri_path ); } $mtime = max( array_map( 'filemtime', $fs_paths ) ); if ( page_optimize_use_concat_base_dir() ) { $path_str = implode( ',', array_map( 'page_optimize_remove_concat_base_prefix', $fs_paths ) ); } else { $path_str = implode( ',', $css ); } $path_str = "$path_str?m=$mtime"; if ( $this->allow_gzip_compression ) { $path_64 = base64_encode( gzcompress( $path_str ) ); if ( strlen( $path_str ) > ( strlen( $path_64 ) + 1 ) ) { $path_str = '-' . $path_64; } } $href = $siteurl . "/_static/??" . $path_str; } else { $href = Page_Optimize_Utils::cache_bust_mtime( $css[0], $siteurl ); } $css_id = "$media-css-" . md5( $href ); if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { $tag = "\n"; } else { $tag = "\n"; } $tag = apply_filters( 'page_optimize_style_loader_tag', $tag, $handles, $href, $media ); if ( is_array( $handles ) && count( $handles ) === 1 ) { $tag = apply_filters( 'style_loader_tag', $tag, $handles[0], $href, $media ); } echo $tag; array_map( array( $this, 'print_inline_style' ), $handles ); } } return $this->done; } function __isset( $key ) { return isset( $this->old_styles->$key ); } function __unset( $key ) { unset( $this->old_styles->$key ); } function &__get( $key ) { return $this->old_styles->$key; } function __set( $key, $value ) { $this->old_styles->$key = $value; } } function page_optimize_css_concat_init() { global $wp_styles; $wp_styles = new Page_Optimize_CSS_Concat( $wp_styles ); $wp_styles->allow_gzip_compression = ALLOW_GZIP_COMPRESSION; } if ( page_optimize_should_concat_css() ) { add_action( 'init', 'page_optimize_css_concat_init' ); }