Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

440 lines (327 sloc) 12.852 kb
<?php
/**
*
* Recursive file importing
*
*/
class csscrush_importer {
public static function save ( $data ) {
$process = csscrush::$process;
$options = $process->options;
// No saving if caching is disabled, return early
if ( ! $options->cache ) {
return;
}
// Write to config
$process->cacheData[ $process->outputFileName ] = $data;
// Save config changes
csscrush::io_call( 'saveCacheData' );
}
public static function hostfile () {
$config = csscrush::$config;
$process = csscrush::$process;
$options = $process->options;
$regex = csscrush_regex::$patt;
$hostfile = $process->input;
// Keep track of all import file info for later logging
$mtimes = array();
$filenames = array();
$stream = '';
$prepend_file_contents = '';
// The prepend file.
if ( $prepend_file = csscrush_util::find( 'Prepend-local.css', 'Prepend.css' ) ) {
$prepend_file_contents = file_get_contents( $prepend_file );
$process->currentFile = 'file://' . $prepend_file;
// If there's a parsing error inside the prepend file, wipe $prepend_file_contents.
if ( ! csscrush::prepareStream( $prepend_file_contents ) ) {
$prepend_file_contents = '';
}
}
// Resolve main input: string or file.
if ( isset( $hostfile->string ) ) {
$stream .= $hostfile->string;
$process->currentFile = 'inline-css';
}
else {
$stream .= file_get_contents( $hostfile->path );
$process->currentFile = 'file://' . $hostfile->path;
}
// If there's a parsing error go no further.
if ( ! csscrush::prepareStream( $stream ) ) {
return $stream;
}
// Not forgetting to prepend the prepend file contents.
$stream = $prepend_file_contents . $stream;
// If rewriting URLs as absolute we need to do some extra work
if ( $options->rewrite_import_urls === 'absolute' ) {
// Normalize the @import statements in this case
foreach ( csscrush_regex::matchAll( $regex->import, $stream ) as $match ) {
$full_match = $match[0][0];
$normalized_import_statement = self::normalizeImportStatement( $full_match );
$stream = str_replace( $full_match, $normalized_import_statement, $stream );
}
// Convert URLs to URL tokens by setting an empty prefix
csscrush::$storage->misc->rewriteUrlPrefix = '';
$stream = self::rewriteUrls( $stream );
}
// This may be set non-zero during the script if an absolute @import URL is encountered
$search_offset = 0;
// Recurses until the nesting heirarchy is flattened and all files are combined
while ( preg_match( $regex->import, $stream, $match, PREG_OFFSET_CAPTURE, $search_offset ) ) {
$full_match = $match[0][0]; // Full match
$match_start = $match[0][1]; // Full match offset
$match_end = $match_start + strlen( $full_match );
$pre_statement = substr( $stream, 0, $match_start );
$post_statement = substr( $stream, $match_end );
// If just stripping the import statements
if ( isset( $hostfile->importIgnore ) ) {
$stream = $pre_statement . $post_statement;
continue;
}
// The media context (if specified) at position 3 in the match
$media_context = trim( $match[3][0] );
// The url may be at position 1 or 2 in the match depending on the syntax used
$url = trim( $match[1][0] );
if ( ! $url ) {
$url = trim( $match[2][0] );
}
// Url may be a string token
if ( preg_match( $regex->stringToken, $url ) ) {
$import_url_token = new csscrush_string( $url );
$url = $import_url_token->value;
}
// Pass over absolute urls
// Move the search pointer forward
if ( preg_match( $regex->absoluteUrl, $url ) ) {
$search_offset = $match_end;
continue;
}
// Create import object
$import = (object) array();
$import->url = $url;
$import->mediaContext = $media_context;
$import->hostDir = $process->inputDir;
// Check to see if the url is root relative
// Flatten import path for convenience
if ( strpos( $import->url, '/' ) === 0 ) {
$import->path = realpath( $config->docRoot . $import->url );
}
else {
$import->path = realpath( "$hostfile->dir/$import->url" );
}
// Get the import contents, if unsuccessful just continue with the import line removed
if ( ! ( $import->content = @file_get_contents( $import->path ) ) ) {
csscrush::log( "Import file '$import->url' not found" );
$stream = $pre_statement . $post_statement;
continue;
}
// Import file opened successfully so we process it:
// - We need to resolve import statement urls in all imported files since
// they will be brought inline with the hostfile
$process->currentFile = 'file://' . $import->path;
// If there are unmatched brackets inside the import, strip it.
if ( ! csscrush::prepareStream( $import->content ) ) {
$stream = $pre_statement . $post_statement;
continue;
}
$import->dir = dirname( $import->url );
// Store import file info for cache validation
$mtimes[] = filemtime( $import->path );
$filenames[] = $import->url;
// Alter all the url strings to be paths relative to the hostfile:
// - Match all @import statements in the import content
// - Store the replacements we might find
$replacements = array();
foreach ( csscrush_regex::matchAll( $regex->import, $import->content ) as $match ) {
$full_match = $match[0][0];
// Url match may be at one of 2 positions
$url_match = $match[1][1] == -1 ? $match[2][0] : $match[1][0];
// Url may be a string token
if ( $url_match_token = preg_match( $regex->stringToken, $url_match ) ) {
// Store the token
$url_match_token = new csscrush_string( $url_match );
// Set $url_match to the actual value
$url_match = $url_match_token->value;
}
// Search and replace on the statement url
$search = $url_match;
$replace = "$import->dir/$url_match";
// Try to resolve absolute paths
// On failure strip the @import statement
if ( strpos( $url_match, '/' ) === 0 ) {
$replace = self::resolveAbsolutePath( $url_match );
if ( ! $replace ) {
$search = $full_match;
$replace = '';
}
}
// The full revised statement for replacement
$statement = $full_match;
if ( $url_match_token && ! empty( $replace ) ) {
// Alter the stored token on internal hash table
$url_match_token->update( $replace );
}
else {
// Trim the statement and set the resolved path
$statement = trim( str_replace( $search, $replace, $full_match ) );
}
// Normalise import statement to be without url() syntax:
// - So relative urls can be targeted later
$statement = self::normalizeImportStatement( $statement );
if ( $full_match !== $statement ) {
$replacements[ $full_match ] = $statement;
}
}
// If we've stored any altered @import strings then we need to apply them
if ( $replacements ) {
$import->content = csscrush_util::strReplaceHash( $import->content, $replacements );
}
// Optionally rewrite relative url and custom function data-uri references
if ( $options->rewrite_import_urls ) {
$import->content = self::rewriteImportUrls( $import );
}
// Add media context if it exists
if ( $import->mediaContext ) {
$import->content = "@media $import->mediaContext {{$import->content}}";
}
$stream = $pre_statement . $import->content . $post_statement;
} // End while
// Save only if the hostfile object is associated with a real file
if ( $hostfile->path ) {
self::save( array(
'imports' => $filenames,
'datem_sum' => array_sum( $mtimes ) + $hostfile->mtime,
'options' => $options,
));
}
return $stream;
}
protected static function normalizeImportStatement ( $import_statement ) {
if ( preg_match( '!(\s)url\(\s*([^\)]+)\)!', $import_statement, $m ) ) {
list( $full_match, $the_space, $the_url ) = $m;
$the_url = rtrim( $the_url );
if ( preg_match( csscrush_regex::$patt->stringToken, $the_url ) ) {
// @import url( ___s34___ ) screen and ( max-width: 500px );
// @import url( ___s34___ );
$import_statement = str_replace( $full_match, $the_space . $the_url, $import_statement );
}
else {
// @import url( some/path/styles.css );
$string_label = csscrush::tokenLabelCreate( 's' );
csscrush::$storage->tokens->strings[ $string_label ] = '"' . $the_url . '"';
$import_statement = str_replace( $full_match, $the_space . $string_label, $import_statement );
}
}
return $import_statement;
}
protected static function resolveAbsolutePath ( $url ) {
$config = csscrush::$config;
$process = csscrush::$process;
if ( ! file_exists ( $config->docRoot . $url ) ) {
return false;
}
// Move upwards '..' by the number of slashes in baseURL to get a relative path
$url = str_repeat( '../', substr_count( $process->inputDirUrl, '/' ) ) . substr( $url, 1 );
return $url;
}
protected static function rewriteImportUrls ( $import ) {
$stream = $import->content;
// Normalise the paths
$host_dir = csscrush_util::normalizePath( $import->hostDir, true );
$import_dir = csscrush_util::normalizePath( dirname( $import->path ), true );
csscrush::$storage->misc->rewriteUrlPrefix = '';
$url_prefix = '';
if ( $import_dir === $host_dir ) {
// Do nothing if files are in the same directory
return $stream;
}
elseif ( strpos( $import_dir, $host_dir ) === false ) {
// Import directory is higher than the host directory
// Split the directory paths into arrays so we can compare segment by segment
$host_segs = preg_split( '!/+!', $host_dir, null, PREG_SPLIT_NO_EMPTY );
$import_segs = preg_split( '!/+!', $import_dir, null, PREG_SPLIT_NO_EMPTY );
// Shift the segments until they are on different branches
while ( @( $host_segs[0] == $import_segs[0] ) ) {
array_shift( $host_segs );
array_shift( $import_segs );
// csscrush::log( array( $host_segs, $import_segs ) );
}
// Count the remaining $host_segs to get the offset
$level_diff = count( $host_segs );
$url_prefix = str_repeat( '../', $level_diff ) . implode( '/', $import_segs );
}
else {
// Import directory is lower than host directory
// easy, url_prefix is the difference
$url_prefix = substr( $import_dir, strlen( $host_dir ) + 1 );
}
if ( empty( $url_prefix ) ) {
return $stream;
}
// Add the directory seperator ending (if needed)
if ( substr( $url_prefix, -1 ) !== '/' ) {
$url_prefix .= '/';
}
// Make $url_prefix accessible in callback scope
csscrush::$storage->misc->rewriteUrlPrefix = $url_prefix;
// Search for all relative url and data-uri references in the content
// and prepend $relative_url_prefix
return self::rewriteUrls( $stream );
}
protected static function rewriteUrls ( $stream ) {
$url_function_patt = '!
([^a-z-]) # the preceeding character
(data-uri|url) # the function name
\(\s*([^\)]+)\s*\) # the url
!xi';
$stream = preg_replace_callback( $url_function_patt,
array( 'self', 'cb_rewriteUrls' ), $stream );
return $stream;
}
protected static function cb_rewriteUrls ( $match ) {
$regex = csscrush_regex::$patt;
$storage = csscrush::$storage;
// The relative url prefix
$relative_url_prefix = $storage->misc->rewriteUrlPrefix;
list( $full_match, $before, $function, $url ) = $match;
$url = trim( $url );
// If the url is a string token we'll need to restore it as a string token later
if ( $url_is_string = preg_match( $regex->stringToken, $url ) ) {
$url_string = new csscrush_string( $url );
$url = $url_string->value;
}
// Normalise the path
$url = csscrush_util::normalizePath( $url );
// No rewrite if:
// - $url is an empty string
// - $url path is absolute or begins with slash
// - $url begins with a variable, e.g '$('
// - $url is a data uri
if (
$url === '' ||
strpos( $url, '/' ) === 0 ||
strpos( $url, '$(' ) === 0 ||
strpos( $url, 'data:' ) === 0 ||
preg_match( $regex->absoluteUrl, $url )
) {
// Token or not, it's ok to return the full match
// if $url is a root relative or absolute ref
return $full_match;
}
// Prepend the relative url prefix
$url = $relative_url_prefix . $url;
// If the path comes via the css url function convert it to a URL token
if ( $function == 'url' ) {
$label = csscrush::tokenLabelCreate( 'u' );
csscrush::$storage->tokens->urls[ $label ] = $url;
$url = $label;
}
elseif ( $url_is_string ) {
// Restore quotes if $url was a string token, update the token and return it
$url_string->update( $url_string->quoteMark . $url . $url_string->quoteMark );
$url = $url_string->token;
}
// Reconstruct the match and return
return "$before$function($url)";
}
}
Jump to Line
Something went wrong with that request. Please try again.