From 7458ecb19647622123ab75a146834a589dc328f9 Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Tue, 30 Jun 2009 19:49:45 +0000 Subject: [PATCH 01/25] Tagging for release From bf6ea29c394abd2211352a59474ba02095ccee7e Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Tue, 30 Jun 2009 19:50:42 +0000 Subject: [PATCH 02/25] Not for release (I should branch these) --- min_extras/README.txt | 15 - min_extras/ab_tests/README.txt | 10 - min_extras/ab_tests/_delimiter | 3 - min_extras/ab_tests/ideal_php/before.php | 25 - min_extras/ab_tests/minify/before.js | 3408 ----------------- min_extras/ab_tests/minify/test_Files.php | 15 - .../ab_tests/minify/test_Files_Memcache.php | 35 - min_extras/ab_tests/minify/test_Groups.php | 15 - min_extras/ab_tests/minify/test_Version1.php | 13 - min_extras/ab_tests/minify/test_memcache.php | 19 - min_extras/ab_tests/mod_deflate/.htaccess-dev | 30 - min_extras/ab_tests/mod_deflate/before.js | 374 -- min_extras/ab_tests/results_summary.txt | 37 - min_extras/ab_tests/test_all.bat | 67 - min_extras/ab_tests/test_memcache.bat | 13 - min_extras/ab_tests/type-map/.htaccess-dev | 40 - min_extras/ab_tests/type-map/before.js | 374 -- min_extras/ab_tests/type-map/before.js.var | 14 - min_extras/ab_tests/type-map/before.js.zc | Bin 15999 -> 0 bytes min_extras/ab_tests/type-map/before.js.zd | Bin 15993 -> 0 bytes min_extras/ab_tests/type-map/before.js.zg | Bin 16011 -> 0 bytes min_extras/ab_tests/v1.0/minify.php | 500 --- min_extras/config.php | 11 - min_extras/examples/1/_groupsSources.php | 11 - min_extras/examples/1/index.php | 50 - min_extras/examples/1/m.php | 14 - min_extras/examples/2/_groupsSources.php | 11 - min_extras/examples/2/index.php | 94 - min_extras/examples/2/m.php | 14 - min_extras/examples/index.php | 3 - min_extras/examples/lib.js | 6 - min_extras/examples/test space.js | 5 - min_extras/examples/test.css | 41 - min_extras/tools/encodeFile.php | 34 - min_extras/tools/minifyFile.php | 50 - min_extras/tools/minifyTextarea.php | 98 - min_extras/tools/minifyUrl.php | 166 - min_extras/tools/testRewriteUri.php | 59 - mincache/.htaccess | 44 - mincache/.htaccess-dev | 45 - mincache/README.txt | 69 - mincache/config.php | 15 - mincache/gen.php | 101 - mincache/src/test.css | 6 - 44 files changed, 5954 deletions(-) delete mode 100644 min_extras/README.txt delete mode 100644 min_extras/ab_tests/README.txt delete mode 100644 min_extras/ab_tests/_delimiter delete mode 100644 min_extras/ab_tests/ideal_php/before.php delete mode 100644 min_extras/ab_tests/minify/before.js delete mode 100644 min_extras/ab_tests/minify/test_Files.php delete mode 100644 min_extras/ab_tests/minify/test_Files_Memcache.php delete mode 100644 min_extras/ab_tests/minify/test_Groups.php delete mode 100644 min_extras/ab_tests/minify/test_Version1.php delete mode 100644 min_extras/ab_tests/minify/test_memcache.php delete mode 100644 min_extras/ab_tests/mod_deflate/.htaccess-dev delete mode 100644 min_extras/ab_tests/mod_deflate/before.js delete mode 100644 min_extras/ab_tests/results_summary.txt delete mode 100644 min_extras/ab_tests/test_all.bat delete mode 100644 min_extras/ab_tests/test_memcache.bat delete mode 100644 min_extras/ab_tests/type-map/.htaccess-dev delete mode 100644 min_extras/ab_tests/type-map/before.js delete mode 100644 min_extras/ab_tests/type-map/before.js.var delete mode 100644 min_extras/ab_tests/type-map/before.js.zc delete mode 100644 min_extras/ab_tests/type-map/before.js.zd delete mode 100644 min_extras/ab_tests/type-map/before.js.zg delete mode 100644 min_extras/ab_tests/v1.0/minify.php delete mode 100644 min_extras/config.php delete mode 100644 min_extras/examples/1/_groupsSources.php delete mode 100644 min_extras/examples/1/index.php delete mode 100644 min_extras/examples/1/m.php delete mode 100644 min_extras/examples/2/_groupsSources.php delete mode 100644 min_extras/examples/2/index.php delete mode 100644 min_extras/examples/2/m.php delete mode 100644 min_extras/examples/index.php delete mode 100644 min_extras/examples/lib.js delete mode 100644 min_extras/examples/test space.js delete mode 100644 min_extras/examples/test.css delete mode 100644 min_extras/tools/encodeFile.php delete mode 100644 min_extras/tools/minifyFile.php delete mode 100644 min_extras/tools/minifyTextarea.php delete mode 100644 min_extras/tools/minifyUrl.php delete mode 100644 min_extras/tools/testRewriteUri.php delete mode 100644 mincache/.htaccess delete mode 100644 mincache/.htaccess-dev delete mode 100644 mincache/README.txt delete mode 100644 mincache/config.php delete mode 100644 mincache/gen.php delete mode 100644 mincache/src/test.css diff --git a/min_extras/README.txt b/min_extras/README.txt deleted file mode 100644 index 025946f..0000000 --- a/min_extras/README.txt +++ /dev/null @@ -1,15 +0,0 @@ -!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -DO NOT leave this directory on a site in production. Some scripts within may be -resource intensive and some allow file uploads. -!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - -This directory contains testing scripts and a few example applications built -with the Minify library classes. - -ab_tests/ - Windows batch files used to benchmark various Minify and Apache - configurations using Apache ab. - -tools/ - Two utility web apps that upload a file, alter it, and send it back to the - user. One applies HTTP encoding, the other minifies CSS/JS/HTML. diff --git a/min_extras/ab_tests/README.txt b/min_extras/ab_tests/README.txt deleted file mode 100644 index be242ef..0000000 --- a/min_extras/ab_tests/README.txt +++ /dev/null @@ -1,10 +0,0 @@ -This is a little AB testing setup for Windows. Before committing any non-trivial -change, these tests should be run so "results_summary.txt" will be an up-to-date -document of each revision's performance. - -Before testing make sure all cache locations are correct. - -Double-click "test_all.bat" to run the tests. At the end, "results_summary.txt" -will be opened in notepad. For more details see "results.txt". - -Delete "results.txt" before committing. We don't need this much info in svn. \ No newline at end of file diff --git a/min_extras/ab_tests/_delimiter b/min_extras/ab_tests/_delimiter deleted file mode 100644 index 091a8cd..0000000 --- a/min_extras/ab_tests/_delimiter +++ /dev/null @@ -1,3 +0,0 @@ - --------------------------------------------------------------------------------- - diff --git a/min_extras/ab_tests/ideal_php/before.php b/min_extras/ab_tests/ideal_php/before.php deleted file mode 100644 index fc5c77d..0000000 --- a/min_extras/ab_tests/ideal_php/before.php +++ /dev/null @@ -1,25 +0,0 @@ -)[^>]*$|^#(\w+)$/; - -// Is it a simple selector -var isSimple = /^.[^:#\[\.]*$/; - -jQuery.fn = jQuery.prototype = { - init: function( selector, context ) { - // Make sure that a selection was provided - selector = selector || document; - - // Handle $(DOMElement) - if ( selector.nodeType ) { - this[0] = selector; - this.length = 1; - return this; - - // Handle HTML strings - } else if ( typeof selector == "string" ) { - // Are we dealing with HTML string or an ID? - var match = quickExpr.exec( selector ); - - // Verify a match, and that no context was specified for #id - if ( match && (match[1] || !context) ) { - - // HANDLE: $(html) -> $(array) - if ( match[1] ) - selector = jQuery.clean( [ match[1] ], context ); - - // HANDLE: $("#id") - else { - var elem = document.getElementById( match[3] ); - - // Make sure an element was located - if ( elem ) - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id != match[3] ) - return jQuery().find( selector ); - - // Otherwise, we inject the element directly into the jQuery object - else { - this[0] = elem; - this.length = 1; - return this; - } - - else - selector = []; - } - - // HANDLE: $(expr, [context]) - // (which is just equivalent to: $(content).find(expr) - } else - return new jQuery( context ).find( selector ); - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) - return new jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector ); - - return this.setArray( - // HANDLE: $(array) - selector.constructor == Array && selector || - - // HANDLE: $(arraylike) - // Watch for when an array-like object, contains DOM nodes, is passed in as the selector - (selector.jquery || selector.length && selector != window && !selector.nodeType && selector[0] != undefined && selector[0].nodeType) && jQuery.makeArray( selector ) || - - // HANDLE: $(*) - [ selector ] ); - }, - - // The current version of jQuery being used - jquery: "1.2.3", - - // The number of elements contained in the matched element set - size: function() { - return this.length; - }, - - // The number of elements contained in the matched element set - length: 0, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num == undefined ? - - // Return a 'clean' array - jQuery.makeArray( this ) : - - // Return just the object - this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - // Build a new jQuery matched element set - var ret = jQuery( elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Force the current matched set of elements to become - // the specified array of elements (destroying the stack in the process) - // You should use pushStack() in order to do this, but maintain the stack - setArray: function( elems ) { - // Resetting the length to 0, then using the native Array push - // is a super-fast way to populate an object with array-like properties - this.length = 0; - Array.prototype.push.apply( this, elems ); - - return this; - }, - - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function( callback, args ) { - return jQuery.each( this, callback, args ); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - var ret = -1; - - // Locate the position of the desired element - this.each(function(i){ - if ( this == elem ) - ret = i; - }); - - return ret; - }, - - attr: function( name, value, type ) { - var options = name; - - // Look for the case where we're accessing a style value - if ( name.constructor == String ) - if ( value == undefined ) - return this.length && jQuery[ type || "attr" ]( this[0], name ) || undefined; - - else { - options = {}; - options[ name ] = value; - } - - // Check to see if we're setting style values - return this.each(function(i){ - // Set all the styles - for ( name in options ) - jQuery.attr( - type ? - this.style : - this, - name, jQuery.prop( this, options[ name ], type, i, name ) - ); - }); - }, - - css: function( key, value ) { - // ignore negative width and height values - if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) - value = undefined; - return this.attr( key, value, "curCSS" ); - }, - - text: function( text ) { - if ( typeof text != "object" && text != null ) - return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); - - var ret = ""; - - jQuery.each( text || this, function(){ - jQuery.each( this.childNodes, function(){ - if ( this.nodeType != 8 ) - ret += this.nodeType != 1 ? - this.nodeValue : - jQuery.fn.text( [ this ] ); - }); - }); - - return ret; - }, - - wrapAll: function( html ) { - if ( this[0] ) - // The elements to wrap the target around - jQuery( html, this[0].ownerDocument ) - .clone() - .insertBefore( this[0] ) - .map(function(){ - var elem = this; - - while ( elem.firstChild ) - elem = elem.firstChild; - - return elem; - }) - .append(this); - - return this; - }, - - wrapInner: function( html ) { - return this.each(function(){ - jQuery( this ).contents().wrapAll( html ); - }); - }, - - wrap: function( html ) { - return this.each(function(){ - jQuery( this ).wrapAll( html ); - }); - }, - - append: function() { - return this.domManip(arguments, true, false, function(elem){ - if (this.nodeType == 1) - this.appendChild( elem ); - }); - }, - - prepend: function() { - return this.domManip(arguments, true, true, function(elem){ - if (this.nodeType == 1) - this.insertBefore( elem, this.firstChild ); - }); - }, - - before: function() { - return this.domManip(arguments, false, false, function(elem){ - this.parentNode.insertBefore( elem, this ); - }); - }, - - after: function() { - return this.domManip(arguments, false, true, function(elem){ - this.parentNode.insertBefore( elem, this.nextSibling ); - }); - }, - - end: function() { - return this.prevObject || jQuery( [] ); - }, - - find: function( selector ) { - var elems = jQuery.map(this, function(elem){ - return jQuery.find( selector, elem ); - }); - - return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ? - jQuery.unique( elems ) : - elems ); - }, - - clone: function( events ) { - // Do the clone - var ret = this.map(function(){ - if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) { - // IE copies events bound via attachEvent when - // using cloneNode. Calling detachEvent on the - // clone will also remove the events from the orignal - // In order to get around this, we use innerHTML. - // Unfortunately, this means some modifications to - // attributes in IE that are actually only stored - // as properties will not be copied (such as the - // the name attribute on an input). - var clone = this.cloneNode(true), - container = document.createElement("div"); - container.appendChild(clone); - return jQuery.clean([container.innerHTML])[0]; - } else - return this.cloneNode(true); - }); - - // Need to set the expando to null on the cloned set if it exists - // removeData doesn't work here, IE removes it from the original as well - // this is primarily for IE but the data expando shouldn't be copied over in any browser - var clone = ret.find("*").andSelf().each(function(){ - if ( this[ expando ] != undefined ) - this[ expando ] = null; - }); - - // Copy the events from the original to the clone - if ( events === true ) - this.find("*").andSelf().each(function(i){ - if (this.nodeType == 3) - return; - var events = jQuery.data( this, "events" ); - - for ( var type in events ) - for ( var handler in events[ type ] ) - jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data ); - }); - - // Return the cloned set - return ret; - }, - - filter: function( selector ) { - return this.pushStack( - jQuery.isFunction( selector ) && - jQuery.grep(this, function(elem, i){ - return selector.call( elem, i ); - }) || - - jQuery.multiFilter( selector, this ) ); - }, - - not: function( selector ) { - if ( selector.constructor == String ) - // test special case where just one selector is passed in - if ( isSimple.test( selector ) ) - return this.pushStack( jQuery.multiFilter( selector, this, true ) ); - else - selector = jQuery.multiFilter( selector, this ); - - var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; - return this.filter(function() { - return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; - }); - }, - - add: function( selector ) { - return !selector ? this : this.pushStack( jQuery.merge( - this.get(), - selector.constructor == String ? - jQuery( selector ).get() : - selector.length != undefined && (!selector.nodeName || jQuery.nodeName(selector, "form")) ? - selector : [selector] ) ); - }, - - is: function( selector ) { - return selector ? - jQuery.multiFilter( selector, this ).length > 0 : - false; - }, - - hasClass: function( selector ) { - return this.is( "." + selector ); - }, - - val: function( value ) { - if ( value == undefined ) { - - if ( this.length ) { - var elem = this[0]; - - // We need to handle select boxes special - if ( jQuery.nodeName( elem, "select" ) ) { - var index = elem.selectedIndex, - values = [], - options = elem.options, - one = elem.type == "select-one"; - - // Nothing was selected - if ( index < 0 ) - return null; - - // Loop through all the selected options - for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { - var option = options[ i ]; - - if ( option.selected ) { - // Get the specifc value for the option - value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value; - - // We don't need an array for one selects - if ( one ) - return value; - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - - // Everything else, we just grab the value - } else - return (this[0].value || "").replace(/\r/g, ""); - - } - - return undefined; - } - - return this.each(function(){ - if ( this.nodeType != 1 ) - return; - - if ( value.constructor == Array && /radio|checkbox/.test( this.type ) ) - this.checked = (jQuery.inArray(this.value, value) >= 0 || - jQuery.inArray(this.name, value) >= 0); - - else if ( jQuery.nodeName( this, "select" ) ) { - var values = value.constructor == Array ? - value : - [ value ]; - - jQuery( "option", this ).each(function(){ - this.selected = (jQuery.inArray( this.value, values ) >= 0 || - jQuery.inArray( this.text, values ) >= 0); - }); - - if ( !values.length ) - this.selectedIndex = -1; - - } else - this.value = value; - }); - }, - - html: function( value ) { - return value == undefined ? - (this.length ? - this[0].innerHTML : - null) : - this.empty().append( value ); - }, - - replaceWith: function( value ) { - return this.after( value ).remove(); - }, - - eq: function( i ) { - return this.slice( i, i + 1 ); - }, - - slice: function() { - return this.pushStack( Array.prototype.slice.apply( this, arguments ) ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map(this, function(elem, i){ - return callback.call( elem, i, elem ); - })); - }, - - andSelf: function() { - return this.add( this.prevObject ); - }, - - data: function( key, value ){ - var parts = key.split("."); - parts[1] = parts[1] ? "." + parts[1] : ""; - - if ( value == null ) { - var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); - - if ( data == undefined && this.length ) - data = jQuery.data( this[0], key ); - - return data == null && parts[1] ? - this.data( parts[0] ) : - data; - } else - return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){ - jQuery.data( this, key, value ); - }); - }, - - removeData: function( key ){ - return this.each(function(){ - jQuery.removeData( this, key ); - }); - }, - - domManip: function( args, table, reverse, callback ) { - var clone = this.length > 1, elems; - - return this.each(function(){ - if ( !elems ) { - elems = jQuery.clean( args, this.ownerDocument ); - - if ( reverse ) - elems.reverse(); - } - - var obj = this; - - if ( table && jQuery.nodeName( this, "table" ) && jQuery.nodeName( elems[0], "tr" ) ) - obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") ); - - var scripts = jQuery( [] ); - - jQuery.each(elems, function(){ - var elem = clone ? - jQuery( this ).clone( true )[0] : - this; - - // execute all scripts after the elements have been injected - if ( jQuery.nodeName( elem, "script" ) ) { - scripts = scripts.add( elem ); - } else { - // Remove any inner scripts for later evaluation - if ( elem.nodeType == 1 ) - scripts = scripts.add( jQuery( "script", elem ).remove() ); - - // Inject the elements into the document - callback.call( obj, elem ); - } - }); - - scripts.each( evalScript ); - }); - } -}; - -// Give the init function the jQuery prototype for later instantiation -jQuery.prototype.init.prototype = jQuery.prototype; - -function evalScript( i, elem ) { - if ( elem.src ) - jQuery.ajax({ - url: elem.src, - async: false, - dataType: "script" - }); - - else - jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); - - if ( elem.parentNode ) - elem.parentNode.removeChild( elem ); -} - -jQuery.extend = jQuery.fn.extend = function() { - // copy reference to target object - var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; - - // Handle a deep copy situation - if ( target.constructor == Boolean ) { - deep = target; - target = arguments[1] || {}; - // skip the boolean and the target - i = 2; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target != "object" && typeof target != "function" ) - target = {}; - - // extend jQuery itself if only one argument is passed - if ( length == 1 ) { - target = this; - i = 0; - } - - for ( ; i < length; i++ ) - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) - // Extend the base object - for ( var name in options ) { - // Prevent never-ending loop - if ( target === options[ name ] ) - continue; - - // Recurse if we're merging object values - if ( deep && options[ name ] && typeof options[ name ] == "object" && target[ name ] && !options[ name ].nodeType ) - target[ name ] = jQuery.extend( target[ name ], options[ name ] ); - - // Don't bring in undefined values - else if ( options[ name ] != undefined ) - target[ name ] = options[ name ]; - - } - - // Return the modified object - return target; -}; - -var expando = "jQuery" + (new Date()).getTime(), uuid = 0, windowData = {}; - -// exclude the following css properties to add px -var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i; - -jQuery.extend({ - noConflict: function( deep ) { - window.$ = _$; - - if ( deep ) - window.jQuery = _jQuery; - - return jQuery; - }, - - // See test/unit/core.js for details concerning this function. - isFunction: function( fn ) { - return !!fn && typeof fn != "string" && !fn.nodeName && - fn.constructor != Array && /function/i.test( fn + "" ); - }, - - // check if an element is in a (or is an) XML document - isXMLDoc: function( elem ) { - return elem.documentElement && !elem.body || - elem.tagName && elem.ownerDocument && !elem.ownerDocument.body; - }, - - // Evalulates a script in a global context - globalEval: function( data ) { - data = jQuery.trim( data ); - - if ( data ) { - // Inspired by code by Andrea Giammarchi - // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html - var head = document.getElementsByTagName("head")[0] || document.documentElement, - script = document.createElement("script"); - - script.type = "text/javascript"; - if ( jQuery.browser.msie ) - script.text = data; - else - script.appendChild( document.createTextNode( data ) ); - - head.appendChild( script ); - head.removeChild( script ); - } - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); - }, - - cache: {}, - - data: function( elem, name, data ) { - elem = elem == window ? - windowData : - elem; - - var id = elem[ expando ]; - - // Compute a unique ID for the element - if ( !id ) - id = elem[ expando ] = ++uuid; - - // Only generate the data cache if we're - // trying to access or manipulate it - if ( name && !jQuery.cache[ id ] ) - jQuery.cache[ id ] = {}; - - // Prevent overriding the named cache with undefined values - if ( data != undefined ) - jQuery.cache[ id ][ name ] = data; - - // Return the named cache data, or the ID for the element - return name ? - jQuery.cache[ id ][ name ] : - id; - }, - - removeData: function( elem, name ) { - elem = elem == window ? - windowData : - elem; - - var id = elem[ expando ]; - - // If we want to remove a specific section of the element's data - if ( name ) { - if ( jQuery.cache[ id ] ) { - // Remove the section of cache data - delete jQuery.cache[ id ][ name ]; - - // If we've removed all the data, remove the element's cache - name = ""; - - for ( name in jQuery.cache[ id ] ) - break; - - if ( !name ) - jQuery.removeData( elem ); - } - - // Otherwise, we want to remove all of the element's data - } else { - // Clean up the element expando - try { - delete elem[ expando ]; - } catch(e){ - // IE has trouble directly removing the expando - // but it's ok with using removeAttribute - if ( elem.removeAttribute ) - elem.removeAttribute( expando ); - } - - // Completely remove the data cache - delete jQuery.cache[ id ]; - } - }, - - // args is for internal usage only - each: function( object, callback, args ) { - if ( args ) { - if ( object.length == undefined ) { - for ( var name in object ) - if ( callback.apply( object[ name ], args ) === false ) - break; - } else - for ( var i = 0, length = object.length; i < length; i++ ) - if ( callback.apply( object[ i ], args ) === false ) - break; - - // A special, fast, case for the most common use of each - } else { - if ( object.length == undefined ) { - for ( var name in object ) - if ( callback.call( object[ name ], name, object[ name ] ) === false ) - break; - } else - for ( var i = 0, length = object.length, value = object[0]; - i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} - } - - return object; - }, - - prop: function( elem, value, type, i, name ) { - // Handle executable functions - if ( jQuery.isFunction( value ) ) - value = value.call( elem, i ); - - // Handle passing in a number to a CSS property - return value && value.constructor == Number && type == "curCSS" && !exclude.test( name ) ? - value + "px" : - value; - }, - - className: { - // internal only, use addClass("class") - add: function( elem, classNames ) { - jQuery.each((classNames || "").split(/\s+/), function(i, className){ - if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) - elem.className += (elem.className ? " " : "") + className; - }); - }, - - // internal only, use removeClass("class") - remove: function( elem, classNames ) { - if (elem.nodeType == 1) - elem.className = classNames != undefined ? - jQuery.grep(elem.className.split(/\s+/), function(className){ - return !jQuery.className.has( classNames, className ); - }).join(" ") : - ""; - }, - - // internal only, use is(".class") - has: function( elem, className ) { - return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; - } - }, - - // A method for quickly swapping in/out CSS properties to get correct calculations - swap: function( elem, options, callback ) { - var old = {}; - // Remember the old values, and insert the new ones - for ( var name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - callback.call( elem ); - - // Revert the old values - for ( var name in options ) - elem.style[ name ] = old[ name ]; - }, - - css: function( elem, name, force ) { - if ( name == "width" || name == "height" ) { - var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; - - function getWH() { - val = name == "width" ? elem.offsetWidth : elem.offsetHeight; - var padding = 0, border = 0; - jQuery.each( which, function() { - padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; - border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; - }); - val -= Math.round(padding + border); - } - - if ( jQuery(elem).is(":visible") ) - getWH(); - else - jQuery.swap( elem, props, getWH ); - - return Math.max(0, val); - } - - return jQuery.curCSS( elem, name, force ); - }, - - curCSS: function( elem, name, force ) { - var ret; - - // A helper method for determining if an element's values are broken - function color( elem ) { - if ( !jQuery.browser.safari ) - return false; - - var ret = document.defaultView.getComputedStyle( elem, null ); - return !ret || ret.getPropertyValue("color") == ""; - } - - // We need to handle opacity special in IE - if ( name == "opacity" && jQuery.browser.msie ) { - ret = jQuery.attr( elem.style, "opacity" ); - - return ret == "" ? - "1" : - ret; - } - // Opera sometimes will give the wrong display answer, this fixes it, see #2037 - if ( jQuery.browser.opera && name == "display" ) { - var save = elem.style.outline; - elem.style.outline = "0 solid black"; - elem.style.outline = save; - } - - // Make sure we're using the right name for getting the float value - if ( name.match( /float/i ) ) - name = styleFloat; - - if ( !force && elem.style && elem.style[ name ] ) - ret = elem.style[ name ]; - - else if ( document.defaultView && document.defaultView.getComputedStyle ) { - - // Only "float" is needed here - if ( name.match( /float/i ) ) - name = "float"; - - name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); - - var getComputedStyle = document.defaultView.getComputedStyle( elem, null ); - - if ( getComputedStyle && !color( elem ) ) - ret = getComputedStyle.getPropertyValue( name ); - - // If the element isn't reporting its values properly in Safari - // then some display: none elements are involved - else { - var swap = [], stack = []; - - // Locate all of the parent display: none elements - for ( var a = elem; a && color(a); a = a.parentNode ) - stack.unshift(a); - - // Go through and make them visible, but in reverse - // (It would be better if we knew the exact display type that they had) - for ( var i = 0; i < stack.length; i++ ) - if ( color( stack[ i ] ) ) { - swap[ i ] = stack[ i ].style.display; - stack[ i ].style.display = "block"; - } - - // Since we flip the display style, we have to handle that - // one special, otherwise get the value - ret = name == "display" && swap[ stack.length - 1 ] != null ? - "none" : - ( getComputedStyle && getComputedStyle.getPropertyValue( name ) ) || ""; - - // Finally, revert the display styles back - for ( var i = 0; i < swap.length; i++ ) - if ( swap[ i ] != null ) - stack[ i ].style.display = swap[ i ]; - } - - // We should always get a number back from opacity - if ( name == "opacity" && ret == "" ) - ret = "1"; - - } else if ( elem.currentStyle ) { - var camelCase = name.replace(/\-(\w)/g, function(all, letter){ - return letter.toUpperCase(); - }); - - ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; - - // From the awesome hack by Dean Edwards - // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 - - // If we're not dealing with a regular pixel number - // but a number that has a weird ending, we need to convert it to pixels - if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { - // Remember the original values - var style = elem.style.left, runtimeStyle = elem.runtimeStyle.left; - - // Put in the new values to get a computed value out - elem.runtimeStyle.left = elem.currentStyle.left; - elem.style.left = ret || 0; - ret = elem.style.pixelLeft + "px"; - - // Revert the changed values - elem.style.left = style; - elem.runtimeStyle.left = runtimeStyle; - } - } - - return ret; - }, - - clean: function( elems, context ) { - var ret = []; - context = context || document; - // !context.createElement fails in IE with an error but returns typeof 'object' - if (typeof context.createElement == 'undefined') - context = context.ownerDocument || context[0] && context[0].ownerDocument || document; - - jQuery.each(elems, function(i, elem){ - if ( !elem ) - return; - - if ( elem.constructor == Number ) - elem = elem.toString(); - - // Convert html string into DOM nodes - if ( typeof elem == "string" ) { - // Fix "XHTML"-style tags in all browsers - elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ - return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? - all : - front + ">"; - }); - - // Trim whitespace, otherwise indexOf won't work as expected - var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div"); - - var wrap = - // option or optgroup - !tags.indexOf("", "" ] || - - !tags.indexOf("", "" ] || - - tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && - [ 1, "", "
" ] || - - !tags.indexOf("", "" ] || - - // matched above - (!tags.indexOf("", "" ] || - - !tags.indexOf("", "" ] || - - // IE can't serialize and - - \ No newline at end of file diff --git a/min_extras/examples/1/m.php b/min_extras/examples/1/m.php deleted file mode 100644 index 84c8277..0000000 --- a/min_extras/examples/1/m.php +++ /dev/null @@ -1,14 +0,0 @@ - $groupsSources - ,'setExpires' => time() + 86400 * 365 -)); diff --git a/min_extras/examples/2/_groupsSources.php b/min_extras/examples/2/_groupsSources.php deleted file mode 100644 index efb9085..0000000 --- a/min_extras/examples/2/_groupsSources.php +++ /dev/null @@ -1,11 +0,0 @@ - array( - "{$base}/lib.js" - ,"{$base}/test space.js" - ) - ,'css' => array("{$base}/test.css") -); -unset($base); \ No newline at end of file diff --git a/min_extras/examples/2/index.php b/min_extras/examples/2/index.php deleted file mode 100644 index 5fdcda5..0000000 --- a/min_extras/examples/2/index.php +++ /dev/null @@ -1,94 +0,0 @@ - - - - - Minify Example 2 - - - - - - -

Note: You should always enable caching using -Minify::setCache(). For the examples this can be set in -config.php.

- - -

Minify Example 2: Minifying Everything

- -

In this example, external Javascript and CSS minification is identical to -example 1, but here Minify is also used to minify and serve the HTML, including -the contents of all <style> and <script> -elements.

- -

As the document is XHTML, Minify_HTML places the 2nd <script> -element in a CDATA section because it contains "<". The output is valid XHTML.

- -

Validate XHTML

- -

Minify tests

-
    -
  • FAILPASS
  • -
  • FAIL
  • -
  • FAIL
  • -
- -

Test client cache

-

When you click here to reload the page, your browser should -not have to re-download any files.

- -

extras index »

- - - - - -lastModified - ,$cssBuild->lastModified -); - -Minify::serve('Page', array( - 'content' => $content - ,'id' => __FILE__ - ,'lastModifiedTime' => $pageLastUpdate - // also minify the CSS/JS inside the HTML - ,'minifyAll' => true -)); \ No newline at end of file diff --git a/min_extras/examples/2/m.php b/min_extras/examples/2/m.php deleted file mode 100644 index 84c8277..0000000 --- a/min_extras/examples/2/m.php +++ /dev/null @@ -1,14 +0,0 @@ - $groupsSources - ,'setExpires' => time() + 86400 * 365 -)); diff --git a/min_extras/examples/index.php b/min_extras/examples/index.php deleted file mode 100644 index e5762fe..0000000 --- a/min_extras/examples/index.php +++ /dev/null @@ -1,3 +0,0 @@ - *:first-child {margin-top:0;} - -h1 { - color: #00cc00; - font-size: 20px; -} -h2 { - color: #009900; - font-size: 17px; -} -h1, h2 {margin:1.3em 0 .3em;} - -p { - margin:0 0 1em; -} - -ul, li { - padding:0; - margin:0; - display:block; - font-family: monospace; -} - -#cssFail span { - display: none; -} \ No newline at end of file diff --git a/min_extras/tools/encodeFile.php b/min_extras/tools/encodeFile.php deleted file mode 100644 index 6d0992d..0000000 --- a/min_extras/tools/encodeFile.php +++ /dev/null @@ -1,34 +0,0 @@ - file_get_contents($_FILES['subject']['tmp_name']) - ,'method' => $_POST['method'] - )); - header('Content-Type: application/octet-stream'); - header('Content-Transfer-Encoding: binary'); - header("Content-Disposition: attachment; filename=\"{$_FILES['subject']['name']}." - . ($_POST['method'] == 'deflate' - ? 'zd' - : ($_POST['method'] == 'gzip' - ? 'zg' - : 'zc' - ) - ) . '"'); - $he->encode(9); - echo $he->getContent(); - exit(); -} - -?> -
-

Encode
-as - - -

-
\ No newline at end of file diff --git a/min_extras/tools/minifyFile.php b/min_extras/tools/minifyFile.php deleted file mode 100644 index 3b4e691..0000000 --- a/min_extras/tools/minifyFile.php +++ /dev/null @@ -1,50 +0,0 @@ - array('Minify_CSS', 'minify') - ,'jsMinifier' => array('JSMin', 'minify') - ); - } - $func = array('Minify_' . $type, 'minify'); - - $out = call_user_func($func, file_get_contents($_FILES['subject']['tmp_name']), $arg2); - - header('Content-Type: application/octet-stream'); - header('Content-Transfer-Encoding: binary'); - header('Content-Disposition: attachment; filename="' - . preg_replace('/\\.(\w+)$/', '.min.$1', $_FILES['subject']['name']) - . '"'); - - //@unlink($_FILES['subject']['tmp_name']); - echo $out; - exit(); -} - -?> -
-

Minify
- -

-
\ No newline at end of file diff --git a/min_extras/tools/minifyTextarea.php b/min_extras/tools/minifyTextarea.php deleted file mode 100644 index 110b463..0000000 --- a/min_extras/tools/minifyTextarea.php +++ /dev/null @@ -1,98 +0,0 @@ -]*>)@i' - ,'$1' - ,$textIn - ); - } - - $sourceSpec['content'] = $textIn; - $sourceSpec['id'] = 'foo'; - if (isset($_POST['minJs'])) { - $sourceSpec['minifyOptions']['jsMinifier'] = array('JSMin', 'minify'); - require 'JSMin.php'; - } - if (isset($_POST['minCss'])) { - $sourceSpec['minifyOptions']['cssMinifier'] = array('Minify_CSS', 'minify'); - require 'Minify/CSS.php'; - } - $source = new Minify_Source($sourceSpec); - require_once 'Minify/Logger.php'; - require_once 'FirePHP.php'; - Minify_Logger::setLogger(FirePHP::getInstance(true)); - Minify::serve('Files', array( - 'files' => $source - ,'contentType' => Minify::TYPE_HTML - )); - exit(); -} - -$classes = array('Minify_HTML', 'Minify_CSS', 'JSMin', 'JSMinPlus'); - -if (isset($_POST['method']) && in_array($_POST['method'], $classes)) { - // easier to just require them all - require 'Minify/HTML.php'; - require 'Minify/CSS.php'; - require 'JSMin.php'; - require 'JSMinPlus.php'; - - $arg2 = null; - if ($_POST['method'] === 'Minify_HTML') { - $arg2 = array( - 'cssMinifier' => array('Minify_CSS', 'minify') - ,'jsMinifier' => array('JSMin', 'minify') - ); - } - $func = array($_POST['method'], 'minify'); - $inOutBytes[0] = strlen($textIn); - $textOut = call_user_func($func, $textIn, $arg2); - $inOutBytes[1] = strlen($textOut); -} - -header('Content-Type: text/html; charset=utf-8'); -?> -minifyTextarea - - Bytes in{$inOutBytes[0]} (after line endings normalized to \\n) - Bytes out{$inOutBytes[1]} (" . round(100 * $inOutBytes[1] / $inOutBytes[0]) . "%) - - "; -} -?> -
-

-

Minify with: - - - -

-

...or this HTML to the browser. Also minify: - : -. - -

-
diff --git a/min_extras/tools/minifyUrl.php b/min_extras/tools/minifyUrl.php deleted file mode 100644 index dd6cd7b..0000000 --- a/min_extras/tools/minifyUrl.php +++ /dev/null @@ -1,166 +0,0 @@ - $type - ,'sent' => $sentType - ,'charset' => $charset - ); -} - -if (isset($_POST['url'])) { - - require '../config.php'; - - $url = trim(getPost('url')); - $ua = trim(getPost('ua')); - $cook = trim(getPost('cook')); - - if (! preg_match('@^https?://@', $url)) { - die('HTTP(s) only.'); - } - - $httpOpts = array( - 'max_redirects' => 0 - ,'timeout' => 3 - ); - if ($ua !== '') { - $httpOpts['user_agent'] = $ua; - } - if ($cook !== '') { - $httpOpts['header'] = "Cookie: {$cook}\r\n"; - } - $ctx = stream_context_create(array( - 'http' => $httpOpts - )); - - // fetch - if (! ($fp = @fopen($url, 'r', false, $ctx))) { - die('Couldn\'t open URL.'); - } - $meta = stream_get_meta_data($fp); - $content = stream_get_contents($fp); - fclose($fp); - - // get type info - $type = sniffType($meta['wrapper_data']); - if (! $type['minify']) { - die('Unrecognized Content-Type: ' . $type['sent']); - } - - require 'Minify.php'; - require 'Minify/HTML.php'; - require 'Minify/CSS.php'; - require 'JSMin.php'; - - if ($type['minify'] === 'text/html' - && isset($_POST['addBase']) - && ! preg_match('@]*>)@i' - ,'$1' - ,$content - ); - } - - $sourceSpec['content'] = $content; - $sourceSpec['id'] = 'foo'; - - if ($type['minify'] === 'text/html') { - if (isset($_POST['minJs'])) { - $sourceSpec['minifyOptions']['jsMinifier'] = array('JSMin', 'minify'); - require 'JSMin.php'; - } - if (isset($_POST['minCss'])) { - $sourceSpec['minifyOptions']['cssMinifier'] = array('Minify_CSS', 'minify'); - require 'Minify/CSS.php'; - } - } - - $source = new Minify_Source($sourceSpec); - - $sendType = 'text/plain'; - if ($type['minify'] === 'text/html' && ! isset($_POST['asText'])) { - $sendType = $type['sent']; - } - if ($type['charset']) { - $sendType .= ';charset=' . $type['charset']; - } - header('Content-Type: ' . $sendType); - // using combine instead of serve because it allows us to specify a - // Content-Type like application/xhtml+xml IF we need to - echo Minify::combine(array($source), array( - 'contentType' => $type['minify'] - )); - exit(); -} - -header('Content-Type: text/html; charset=utf-8'); - -$ua = get_magic_quotes_gpc() - ? stripslashes($_SERVER['HTTP_USER_AGENT']) - : $_SERVER['HTTP_USER_AGENT']; - -?> -Minify URL - -

Warning! Please do not place this application on a public site. This should be used -only for testing.

- -

Fetch and Minify a URL

-

This tool will retrieve the contents of a URL and minify it. -The fetched resource Content-Type will determine the minifier used.

- -
-

-

- -
HTML options -

If the resource above is sent with an (x)HTML Content-Type, the following options will apply:

-
    -
  • -
  • -
  • -
  • -
-
- -
Retreival options -
    -
  • -
  • -
-
- -
diff --git a/min_extras/tools/testRewriteUri.php b/min_extras/tools/testRewriteUri.php deleted file mode 100644 index 9cc917f..0000000 --- a/min_extras/tools/testRewriteUri.php +++ /dev/null @@ -1,59 +0,0 @@ -"; -} - -// validate user POST (no arrays and fix slashes) -if (! empty($_POST)) { - foreach ($_POST as $name => $val) { - if (! is_string($val)) { - unset($_POST[$name]); - continue; - } - if (get_magic_quotes_gpc()) { - $_POST[$name] = stripslashes($val); - } - } -} - -$defaultCurrentDir = dirname(__FILE__); -$defaultDocRoot = realpath($_SERVER['DOCUMENT_ROOT']); -$defaultSymLink = '//symlinkPath'; -$defaultSymTarget = ($defaultCurrentDir[0] === '/') ? '/tmp' : 'C:\\WINDOWS\\Temp'; -$defaultCss = "url(hello.gif)\nurl(../hello.gif)\nurl(../../hello.gif)\nurl(up/hello.gif)"; - -$out = ''; - -if (isset($_POST['css'])) { - require '../config.php'; - require 'Minify/CSS/UriRewriter.php'; - $symlinks = array(); - if ('' !== ($target = getPost('symTarget'))) { - $symlinks[getPost('symLink')] = $target; - } - $css = Minify_CSS_UriRewriter::rewrite( - getPost('css') - , getPost('currentDir') - , getPost('docRoot') - , $symlinks - ); - $out = "
" . h($css) . '
'; -} - -?> -

Test Minify_CSS_UriRewriter::rewrite()

-
-
-
-
-

-

-
- \ No newline at end of file diff --git a/mincache/.htaccess b/mincache/.htaccess deleted file mode 100644 index d56271f..0000000 --- a/mincache/.htaccess +++ /dev/null @@ -1,44 +0,0 @@ - -# turn off MultiViews if enabled -Options -MultiViews - -# register .var as type-map and zd for deflate encoding -AddHandler type-map .mjs .mcss -AddEncoding deflate .zd - -# Necessary to add charset while using type-map -AddType application/x-javascript;charset=utf-8 js -AddType text/css;charset=utf-8 css - -# Below we remove the ETag header and set a far-off Expires -# header. Since clients will aggressively cache, make sure -# to modify the URL (querystring or via mod_rewrite) when -# the resource changes - -# remove ETag -FileETag None - -# requires mod_expires -ExpiresActive On -# sets Expires and Cache-Control: max-age, but not "public" -ExpiresDefault "access plus 1 year" - -# requires mod_headers -# adds the "public" to Cache-Control. -# -# If you get 500 errors, you may need to comment this out -# -Header set Cache-Control "public, max-age=31536000" - -# mod_rewrite reqd. -RewriteEngine On - -# remove any rev number -RewriteRule ^(.*)_\d+\.m(js|css)$ $1.m$2 - -# if mincache is NOT directly within document root, you must -# specify the path to it -RewriteCond %{DOCUMENT_ROOT}/mincache/$1 !-f - -# any unfound files go to PHP -RewriteRule ^(.*)$ gen.php?req=$1 [L,QSA] diff --git a/mincache/.htaccess-dev b/mincache/.htaccess-dev deleted file mode 100644 index 8ba411f..0000000 --- a/mincache/.htaccess-dev +++ /dev/null @@ -1,45 +0,0 @@ -# for Steve's test setup. You may delete this file - - - - -# turn off MultiViews if enabled -Options -MultiViews - -# register .var as type-map and zd for deflate encoding -AddHandler type-map .mjs .mcss -AddEncoding deflate .zd - -# Necessary to add charset while using type-map -AddType application/x-javascript;charset=utf-8 js -AddType text/css;charset=utf-8 css - -# Below we remove the ETag header and set a far-off Expires -# header. Since clients will aggressively cache, make sure -# to modify the URL (querystring or via mod_rewrite) when -# the resource changes - -# remove ETag -FileETag None - -# requires mod_expires -ExpiresActive On -# sets Expires and Cache-Control: max-age, but not "public" -ExpiresDefault "access plus 1 year" - -# requires mod_headers -# adds the "public" to Cache-Control. -Header set Cache-Control "public, max-age=31536000" - -# mod_rewrite reqd. -RewriteEngine On - -# remove any rev number -RewriteRule ^(.*)_\d+\.m(js|css)$ $1.m$2 - -# if mincache is NOT directly within document root, you must -# specify the path to it -RewriteCond %{DOCUMENT_ROOT}/_3rd_party/minify/mincache/$1 !-f - -# any unfound files go to PHP -RewriteRule ^(.*)$ gen.php?req=$1 [L,QSA] diff --git a/mincache/README.txt b/mincache/README.txt deleted file mode 100644 index 3e4a263..0000000 --- a/mincache/README.txt +++ /dev/null @@ -1,69 +0,0 @@ -The files in this directory represent a new way to serve minified files via the -Apache file server. This will yield much better performance than Minify can -deliver when all requests are routed through PHP. - - -REQUIREMENTS - -Apache server with mod_negotiation and mod_rewrite - - -GETTING STARTED - -The "min" directory must already be set up within the DOCUMENT_ROOT. - -Place the "mincache" directory in DOCUMENT_ROOT. - -Request this URL: http://yourserver.com/mincache/f/test.mcss -(It should return a CSS file with a "Success" comment.) - -Place CSS/JS source files in "/mincache/src" and/or specify groups in -"/min/groupsConfig.php" - - -UPDATING FILES - -After changing any source files: - -1. Delete any associated ".mcss" or ".mjs" files inside the "f" and "g" - directories. (the next request will trigger PHP to rebuild them.) - -2. Add or update a revision number within the URIs in your HTML files. This is - recommended because we send a far-future Expires header and you want users - to see your changes. - E.g. /mincache/f/one,two.mjs => /mincache/f/one,two_2.mjs - /mincache/g/grpName_123.mcss => /mincache/g/grpName_124.mcss - - -HOW THIS WORKS - -In your HTML files you'll refer to minify URIs like: - /mincache/f/one,two_12345.mjs - /mincache/g/groupName_12345.mcss - -.htaccess will internally strip the revision numbers (_\d+) from the URIs. - -If the files exist, they'll be served directly by Apache and with deflate -encoding where supported. - -If not, .htaccess will call /mincache/gen.php to generate the files. - - -URIS IN THE "/f" DIRECTORY - -For these URIs, Minify will combine files from a single specified source -directory, "/mincache/src" by default. E.g.: - /mincache/f/one,two.mcss = /mincache/src/one.css - + /mincache/src/two.css - /mincache/f/jQuery.1.3,site.mjs = /mincache/src/jQuery.1.3.js - + /mincache/src/site.js - - -URIS IN THE "/g" DIRECTORY - -(To be implemented) - - -QUESTIONS? - -http://groups.google.com/group/minify \ No newline at end of file diff --git a/mincache/config.php b/mincache/config.php deleted file mode 100644 index 84307b7..0000000 --- a/mincache/config.php +++ /dev/null @@ -1,15 +0,0 @@ - $m[1] - ,'group' => $m[2] - ,'ext' => $m[3] - ,'plainFilename' => "{$m[2]}.{$m[3]}" - ,'deflatedFilename' => "{$m[2]}.{$m[3]}.zd" - ,'typeMap' => MINCACHE_DIR . '/' . $m[0] - ,'plainFile' => MINCACHE_DIR . "/{$m[1]}/{$m[2]}.{$m[3]}" - ,'deflatedFile' => MINCACHE_DIR . "/{$m[1]}/{$m[2]}.{$m[3]}.zd" - ,'ctype' => ($m[3] === 'js' ? 'application/x-javascript' : 'text/css') -); - -// load configs -require MINIFY_MIN_DIR . '/config.php'; -require MINCACHE_DIR . '/config.php'; - -// setup include path -set_include_path($min_libPath . PATH_SEPARATOR . get_include_path()); - -require 'Minify.php'; - -if ($min_documentRoot) { - $_SERVER['DOCUMENT_ROOT'] = $min_documentRoot; -} elseif (0 === stripos(PHP_OS, 'win')) { - Minify::setDocRoot(); // IIS may need help -} - -$min_serveOptions['minifierOptions']['text/css']['symlinks'] = $min_symlinks; - -$sources = array(); - -// URIs in "/f" -if ($spec['mode'] === 'f') { - $files = explode(',', $spec['group']); - foreach ($files as $file) { - $file = realpath($mincache_servePath . '/' . $file . '.' . $spec['ext']); - if ($file !== false - && is_file($file) - && dirname($file) === realpath($mincache_servePath)) { - // file OK - $sources[] = $file; - } else { - send404(); - } - } - $output = Minify::serve('Files', array( - 'files' => $sources - ,'quiet' => true - ,'encodeMethod' => '' - ,'lastModifiedTime' => 0 - )); - if (! $output['success']) { - send404(); - } -} - -$typeMap = "URI: {$spec['deflatedFilename']}\n" - . "Content-Type: {$spec['ctype']}; qs=0.9\n" - . "Content-Encoding: deflate\n" - . "\n" - . "URI: {$spec['plainFilename']}\n" - . "Content-Type: {$spec['ctype']}; qs=0.6\n"; - -error_reporting(0); -if (false === file_put_contents($spec['plainFile'], $output['content']) - || false === file_put_contents($spec['deflatedFile'], gzdeflate($output['content'])) - || false === file_put_contents($spec['typeMap'], $typeMap)) { - echo "/* File writing failed. Your mincache directories /f and /g must be writable by PHP. */\n"; - exit(); -} - -unset($output['headers']['Last-Modified'], - $output['headers']['ETag']); -foreach ($output['headers'] as $name => $value) { - header("{$name}: {$value}"); -} - -echo $output['content']; diff --git a/mincache/src/test.css b/mincache/src/test.css deleted file mode 100644 index 7026c51..0000000 --- a/mincache/src/test.css +++ /dev/null @@ -1,6 +0,0 @@ -/*! - Success! Minify wrote this file to disk. - If you wish, you may delete "/mincache/f/test.*" and "/mincache/src/test.css". - - */ -This file is { content: 'valid css.' ; } From 269578533e4650dac83896fd58210a5a3f489210 Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Sat, 8 May 2010 17:50:36 +0000 Subject: [PATCH 03/25] Branching for work on 2.1.3 From 5205a42f9900aaa93f09f4669f23b715dfe71ea6 Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Sat, 8 May 2010 19:38:26 +0000 Subject: [PATCH 04/25] Workaround for Issue 144 --- min/lib/JSMin.php | 37 ++++++++++++++++--- min_unit_tests/_test_files/js/issue144.js | 1 + min_unit_tests/_test_files/js/issue144.min.js | 1 + min_unit_tests/test_JSMin.php | 7 ++++ 4 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 min_unit_tests/_test_files/js/issue144.js create mode 100644 min_unit_tests/_test_files/js/issue144.min.js diff --git a/min/lib/JSMin.php b/min/lib/JSMin.php index 770e1c6..e53f99c 100644 --- a/min/lib/JSMin.php +++ b/min/lib/JSMin.php @@ -1,6 +1,10 @@ + * $minifiedJs = JSMin::minify($js); + * * * This is a direct port of jsmin.c to PHP with a few PHP performance tweaks and * modifications to preserve some comments (see below). Also, rather than using @@ -64,7 +68,7 @@ class JSMin { protected $inputLength = 0; protected $lookAhead = null; protected $output = ''; - + /** * Minify Javascript * @@ -73,16 +77,37 @@ class JSMin { */ public static function minify($js) { + // look out for syntax like "++ +" and "- ++" + $p = '\\+'; + $m = '\\-'; + if (preg_match("/([$p$m])(?:\\1 [$p$m]| (?:$p$p|$m$m))/", $js)) { + // likely pre-minified and would be broken by JSMin + return $js; + } + if (function_exists('mb_strlen') + && (ini_get('mbstring.func_overload') !== '') + && ((int)ini_get('mbstring.func_overload') & 2)) { + $enc = mb_internal_encoding(); + mb_internal_encoding('8bit'); + $jsmin = new JSMin($js); + $ret = $jsmin->min(); + mb_internal_encoding($enc); + return $ret; + } $jsmin = new JSMin($js); return $jsmin->min(); } - - /** - * Setup process + + /* + * Don't create a JSMin instance, instead use the static function minify, + * which checks for mb_string function overloading and avoids errors + * trying to re-minify the output of Closure Compiler + * + * @private */ public function __construct($input) { - $this->input = str_replace("\r\n", "\n", $input); + $this->input = str_replace("\r\n", "\n", $input); $this->inputLength = strlen($this->input); } diff --git a/min_unit_tests/_test_files/js/issue144.js b/min_unit_tests/_test_files/js/issue144.js new file mode 100644 index 0000000..cccb826 --- /dev/null +++ b/min_unit_tests/_test_files/js/issue144.js @@ -0,0 +1 @@ +if(!a.id)a.id="dp"+ ++this.uuid; \ No newline at end of file diff --git a/min_unit_tests/_test_files/js/issue144.min.js b/min_unit_tests/_test_files/js/issue144.min.js new file mode 100644 index 0000000..cccb826 --- /dev/null +++ b/min_unit_tests/_test_files/js/issue144.min.js @@ -0,0 +1 @@ +if(!a.id)a.id="dp"+ ++this.uuid; \ No newline at end of file diff --git a/min_unit_tests/test_JSMin.php b/min_unit_tests/test_JSMin.php index 56d07a2..a492324 100644 --- a/min_unit_tests/test_JSMin.php +++ b/min_unit_tests/test_JSMin.php @@ -19,6 +19,13 @@ function test_JSMin() echo "---Source: " .strlen($src). " bytes\n\n{$src}\n\n\n"; } + $src = file_get_contents($thisDir . '/_test_files/js/issue144.js'); + $minExpected = file_get_contents($thisDir . '/_test_files/js/issue144.min.js'); + $minOutput = JSMin::minify($src); + + $passed = assertTrue($minExpected == $minOutput, 'JSMin : Don\'t minify files with + ++ (Issue 144)'); + + $src = file_get_contents($thisDir . '/_test_files/js/issue74.js'); $minExpected = file_get_contents($thisDir . '/_test_files/js/issue74.min.js'); $minOutput = JSMin::minify($src); From f4c688b33812cd8d4173227d25ead59886f9adc1 Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Sun, 9 May 2010 16:43:47 +0000 Subject: [PATCH 05/25] Work on: Issue 125, Issue 126, Issue 132, Issue 134, Issue 138, Issue 139, Issue 147, Issue 149, Issue 151, Issue 162, Issue 166 --- min/builder/_index.js | 3 +- min/builder/index.php | 30 ++++-- min/config.php | 19 ++-- min/groupsConfig.php | 20 +--- min/index.php | 6 +- min/lib/HTTP/ConditionalGet.php | 2 +- min/lib/HTTP/Encoder.php | 70 ++++++------ min/lib/JSMin.php | 6 +- min/lib/Minify.php | 102 +++++++++++------- min/lib/Minify/CSS/Compressor.php | 2 +- min/lib/Minify/Controller/Base.php | 3 +- min/lib/Minify/Controller/MinApp.php | 24 +++-- min/lib/Minify/Lines.php | 18 ++-- min/lib/Minify/YUICompressor.php | 13 ++- .../_test_files/css/vladmirated.min.css | 2 +- min_unit_tests/_test_files/js/issue141.min.js | 3 + min_unit_tests/_test_files/js/issue144.js | 3 +- min_unit_tests/_test_files/js/issue144.min.js | 3 +- min_unit_tests/_test_files/minify/issue143.js | 6 ++ .../_test_files/minify/lines_bugs.js | 12 ++- .../_test_files/minify/lines_output.js | 13 ++- min_unit_tests/_test_files/minify/minified.js | 3 +- min_unit_tests/test_HTTP_Encoder.php | 15 ++- min_unit_tests/test_JSMin.php | 6 +- min_unit_tests/test_Minify.php | 4 +- min_unit_tests/test_environment.php | 3 +- 26 files changed, 236 insertions(+), 155 deletions(-) create mode 100644 min_unit_tests/_test_files/js/issue141.min.js create mode 100644 min_unit_tests/_test_files/minify/issue143.js diff --git a/min/builder/_index.js b/min/builder/_index.js index 8e5313a..bbaadb4 100644 --- a/min/builder/_index.js +++ b/min/builder/_index.js @@ -197,6 +197,7 @@ var MUB = { * Runs on DOMready */ ,init : function () { + $('#jsDidntLoad').hide(); $('#app').show(); $('#sources').html(''); $('#add button').click(MUB.addButtonClick); @@ -239,4 +240,4 @@ var MUB = { MUB.checkRewrite(); } }; -window.onload = MUB.init; \ No newline at end of file +$(MUB.init); \ No newline at end of file diff --git a/min/builder/index.php b/min/builder/index.php index 1b20982..18ec939 100644 --- a/min/builder/index.php +++ b/min/builder/index.php @@ -17,11 +17,10 @@ if (! $min_enableBuilder) { ob_start(); ?> - - - - Minify URI Builder - - +.topWarning a {color:#fff;} + + +

Uh Oh. Minify was unable to + serve the Javascript for this app. Enable + FirePHP debugging and request the Minify URL directly. +

Note: Please set $min_cachePath @@ -149,11 +153,15 @@ $(function () { }); - + array('//js/file1.js', '//js/file2.js'), // 'css' => array('//css/file1.css', '//css/file2.css'), - // custom source example - /*'js2' => array( - dirname(__FILE__) . '/../min_unit_tests/_test_files/js/before.js', - // do NOT process this file - new Minify_Source(array( - 'filepath' => dirname(__FILE__) . '/../min_unit_tests/_test_files/js/before.js', - 'minifier' => create_function('$a', 'return $a;') - )) - ),//*/ - - /*'js3' => array( - dirname(__FILE__) . '/../min_unit_tests/_test_files/js/before.js', - // do NOT process this file - new Minify_Source(array( - 'filepath' => dirname(__FILE__) . '/../min_unit_tests/_test_files/js/before.js', - 'minifier' => array('Minify_Packer', 'minify') - )) - ),//*/ ); \ No newline at end of file diff --git a/min/index.php b/min/index.php index 51c3525..288cbf3 100644 --- a/min/index.php +++ b/min/index.php @@ -30,6 +30,10 @@ if ($min_documentRoot) { } $min_serveOptions['minifierOptions']['text/css']['symlinks'] = $min_symlinks; +// auto-add targets to allowDirs +foreach ($min_symlinks as $uri => $target) { + $min_serveOptions['minApp']['allowDirs'][] = $target; +} if ($min_allowDebugFlag && isset($_GET['debug'])) { $min_serveOptions['debug'] = true; @@ -63,4 +67,4 @@ if (isset($_GET['f']) || isset($_GET['g'])) { } else { header("Location: /"); exit(); -} +} \ No newline at end of file diff --git a/min/lib/HTTP/ConditionalGet.php b/min/lib/HTTP/ConditionalGet.php index 823db05..4a0225a 100644 --- a/min/lib/HTTP/ConditionalGet.php +++ b/min/lib/HTTP/ConditionalGet.php @@ -337,7 +337,7 @@ class HTTP_ConditionalGet { // IE has tacked on extra data to this header, strip it $ifModifiedSince = substr($ifModifiedSince, 0, $semicolon); } - if ($ifModifiedSince == self::gmtDate($this->_lmTime)) { + if (strtotime($ifModifiedSince) >= $this->_lmTime) { // Apache 2.2's behavior. If there was no ETag match, send the // non-encoded version of the ETag value. $this->_headers['ETag'] = $this->normalizeEtag($this->_etag); diff --git a/min/lib/HTTP/Encoder.php b/min/lib/HTTP/Encoder.php index 66c2678..4c02fc9 100644 --- a/min/lib/HTTP/Encoder.php +++ b/min/lib/HTTP/Encoder.php @@ -33,11 +33,11 @@ * * * For more control over headers, use getHeaders() and getData() and send your - * own output. - * - * Note: If you don't need header mgmt, use PHP's native gzencode, gzdeflate, - * and gzcompress functions for gzip, deflate, and compress-encoding - * respectively. + * own output. + * + * Note: If you don't need header mgmt, use PHP's native gzencode, gzdeflate, + * and gzcompress functions for gzip, deflate, and compress-encoding + * respectively. * * @package Minify * @subpackage HTTP @@ -59,7 +59,7 @@ class HTTP_Encoder { * * @var bool */ - public static $encodeToIe6 = false; + public static $encodeToIe6 = true; /** @@ -88,7 +88,7 @@ class HTTP_Encoder { * * @return null */ - public function __construct($spec) + public function __construct($spec) { $this->_content = $spec['content']; $this->_headers['Content-Length'] = (string)strlen($this->_content); @@ -111,7 +111,7 @@ class HTTP_Encoder { * * return string */ - public function getContent() + public function getContent() { return $this->_content; } @@ -130,7 +130,7 @@ class HTTP_Encoder { * * @return array */ - public function getHeaders() + public function getHeaders() { return $this->_headers; } @@ -146,7 +146,7 @@ class HTTP_Encoder { * * @return null */ - public function sendHeaders() + public function sendHeaders() { foreach ($this->_headers as $name => $val) { header($name . ': ' . $val); @@ -164,7 +164,7 @@ class HTTP_Encoder { * * @return null */ - public function sendAll() + public function sendAll() { $this->sendHeaders(); echo $this->_content; @@ -181,21 +181,21 @@ class HTTP_Encoder { * be non 0. The methods are favored in order of gzip, deflate, then * compress. Deflate is always smallest and generally faster, but is * rarely sent by servers, so client support could be buggier. - * + * * @param bool $allowCompress allow the older compress encoding * - * @param bool $allowDeflate allow the more recent deflate encoding + * @param bool $allowDeflate allow the more recent deflate encoding * * @return array two values, 1st is the actual encoding method, 2nd is the * alias of that method to use in the Content-Encoding header (some browsers * call gzip "x-gzip" etc.) */ - public static function getAcceptedEncoding($allowCompress = true, $allowDeflate = true) + public static function getAcceptedEncoding($allowCompress = true, $allowDeflate = true) { // @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html if (! isset($_SERVER['HTTP_ACCEPT_ENCODING']) - || self::_isBuggyIe()) + || self::isBuggyIe()) { return array('', ''); } @@ -244,16 +244,18 @@ class HTTP_Encoder { * this fails, false is returned. * * The header "Vary: Accept-Encoding" is added. If encoding is successful, - * the Content-Length header is updated, and Content-Encoding is also added. - * + * the Content-Length header is updated, and Content-Encoding is also added. + * * @param int $compressionLevel given to zlib functions. If not given, the * class default will be used. * * @return bool success true if the content was actually compressed */ - public function encode($compressionLevel = null) + public function encode($compressionLevel = null) { - $this->_headers['Vary'] = 'Accept-Encoding'; + if (! self::isBuggyIe()) { + $this->_headers['Vary'] = 'Accept-Encoding'; + } if (null === $compressionLevel) { $compressionLevel = self::$compressionLevel; } @@ -262,9 +264,9 @@ class HTTP_Encoder { || !extension_loaded('zlib')) { return false; - } - if ($this->_encodeMethod[0] === 'deflate') { - $encoded = gzdeflate($this->_content, $compressionLevel); + } + if ($this->_encodeMethod[0] === 'deflate') { + $encoded = gzdeflate($this->_content, $compressionLevel); } elseif ($this->_encodeMethod[0] === 'gzip') { $encoded = gzencode($this->_content, $compressionLevel); } else { @@ -285,7 +287,7 @@ class HTTP_Encoder { * This is a convenience method for common use of the class * * @param string $content - * + * * @param int $compressionLevel given to zlib functions. If not given, the * class default will be used. * @@ -296,20 +298,18 @@ class HTTP_Encoder { if (null === $compressionLevel) { $compressionLevel = self::$compressionLevel; } - $he = new HTTP_Encoder(array('content' => $content)); - $ret = $he->encode($compressionLevel); + $he = new HTTP_Encoder(array('content' => $content)); + $ret = $he->encode($compressionLevel); $he->sendAll(); return $ret; - } - - protected $_content = ''; - protected $_headers = array(); - protected $_encodeMethod = array('', ''); + } /** - * Is the browser an IE version earlier than 6 SP2? + * Is the browser an IE version earlier than 6 SP2? + * + * @return bool */ - protected static function _isBuggyIe() + public static function isBuggyIe() { $ua = $_SERVER['HTTP_USER_AGENT']; // quick escape for non-IEs @@ -318,9 +318,13 @@ class HTTP_Encoder { return false; } // no regex = faaast - $version = (float)substr($ua, 30); + $version = (float)substr($ua, 30); return self::$encodeToIe6 ? ($version < 6 || ($version == 6 && false === strpos($ua, 'SV1'))) : ($version < 7); } + + protected $_content = ''; + protected $_headers = array(); + protected $_encodeMethod = array('', ''); } diff --git a/min/lib/JSMin.php b/min/lib/JSMin.php index e53f99c..74d3422 100644 --- a/min/lib/JSMin.php +++ b/min/lib/JSMin.php @@ -171,7 +171,7 @@ class JSMin { } if (ord($this->a) <= self::ORD_LF) { throw new JSMin_UnterminatedStringException( - 'Unterminated String: ' . var_export($str, true)); + "Unterminated String: {$str}"); } $str .= $this->a; if ($this->a === '\\') { @@ -198,7 +198,7 @@ class JSMin { $pattern .= $this->a; } elseif (ord($this->a) <= self::ORD_LF) { throw new JSMin_UnterminatedRegExpException( - 'Unterminated RegExp: '. var_export($pattern, true)); + "Unterminated RegExp: {$pattern}"); } $this->output .= $this->a; } @@ -310,7 +310,7 @@ class JSMin { return ' '; } } elseif ($get === null) { - throw new JSMin_UnterminatedCommentException('Unterminated Comment: ' . var_export('/*' . $comment, true)); + throw new JSMin_UnterminatedCommentException("Unterminated Comment: /*{$comment}"); } $comment .= $get; } diff --git a/min/lib/Minify.php b/min/lib/Minify.php index 2c0ca34..ba40d7e 100644 --- a/min/lib/Minify.php +++ b/min/lib/Minify.php @@ -35,6 +35,7 @@ class Minify { // there is some debate over the ideal JS Content-Type, but this is the // Apache default and what Yahoo! uses.. const TYPE_JS = 'application/x-javascript'; + const URL_DEBUG = 'http://code.google.com/p/minify/wiki/Debugging'; /** * How many hours behind are the file modification times of uploaded files? @@ -179,9 +180,7 @@ class Minify { if (! $controller->sources) { // invalid request! if (! self::$_options['quiet']) { - header(self::$_options['badRequestHeader']); - echo self::$_options['badRequestHeader']; - return; + self::_errorExit(self::$_options['badRequestHeader'], self::URL_DEBUG); } else { list(,$statusCode) = explode(' ', self::$_options['badRequestHeader']); return array( @@ -202,6 +201,7 @@ class Minify { // determine encoding if (self::$_options['encodeOutput']) { + $sendVary = true; if (self::$_options['encodeMethod'] !== null) { // controller specifically requested this $contentEncoding = self::$_options['encodeMethod']; @@ -212,6 +212,7 @@ class Minify { // 'x-gzip' while our internal encodeMethod is 'gzip'. Calling // getAcceptedEncoding(false, false) leaves out compress and deflate as options. list(self::$_options['encodeMethod'], $contentEncoding) = HTTP_Encoder::getAcceptedEncoding(false, false); + $sendVary = ! HTTP_Encoder::isBuggyIe(); } } else { self::$_options['encodeMethod'] = ''; // identity (no encoding) @@ -261,7 +262,7 @@ class Minify { } // check server cache - if (null !== self::$_cache) { + if (null !== self::$_cache && ! self::$_options['debug']) { // using cache // the goal is to use only the cache methods to sniff the length and // output the content, as they do not require ever loading the file into @@ -276,7 +277,15 @@ class Minify { $cacheContentLength = self::$_cache->getSize($fullCacheId); } else { // generate & cache content - $content = self::_combineMinify(); + try { + $content = self::_combineMinify(); + } catch (Exception $e) { + self::$_controller->log($e->getMessage()); + if (! self::$_options['quiet']) { + self::_errorExit(self::$_options['errorHeader'], self::URL_DEBUG); + } + throw $e; + } self::$_cache->store($cacheId, $content); if (function_exists('gzencode')) { self::$_cache->store($cacheId . '.gz', gzencode($content, self::$_options['encodeLevel'])); @@ -285,7 +294,15 @@ class Minify { } else { // no cache $cacheIsReady = false; - $content = self::_combineMinify(); + try { + $content = self::_combineMinify(); + } catch (Exception $e) { + self::$_controller->log($e->getMessage()); + if (! self::$_options['quiet']) { + self::_errorExit(self::$_options['errorHeader'], self::URL_DEBUG); + } + throw $e; + } } if (! $cacheIsReady && self::$_options['encodeMethod']) { // still need to encode @@ -293,16 +310,22 @@ class Minify { } // add headers + $hasMbOverload = (function_exists('mb_strlen') + && (ini_get('mbstring.func_overload') !== '') + && ((int)ini_get('mbstring.func_overload') & 2)); $headers['Content-Length'] = $cacheIsReady ? $cacheContentLength - : strlen($content); + : ($hasMbOverload + ? mb_strlen($content, '8bit') + : strlen($content) + ); $headers['Content-Type'] = self::$_options['contentTypeCharset'] ? self::$_options['contentType'] . '; charset=' . self::$_options['contentTypeCharset'] : self::$_options['contentType']; if (self::$_options['encodeMethod'] !== '') { $headers['Content-Encoding'] = $contentEncoding; } - if (self::$_options['encodeOutput']) { + if (self::$_options['encodeOutput'] && $sendVary) { $headers['Vary'] = 'Accept-Encoding'; } @@ -369,9 +392,9 @@ class Minify { && 0 === strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/') ) { $_SERVER['DOCUMENT_ROOT'] = rtrim(substr( - $_SERVER['PATH_TRANSLATED'] + $_SERVER['SCRIPT_FILENAME'] ,0 - ,strlen($_SERVER['PATH_TRANSLATED']) - strlen($_SERVER['SCRIPT_NAME']) + ,strlen($_SERVER['SCRIPT_FILENAME']) - strlen($_SERVER['SCRIPT_NAME']) ), '\\'); if ($unsetPathInfo) { unset($_SERVER['PATH_INFO']); @@ -396,6 +419,18 @@ class Minify { */ protected static $_options = null; + protected static function _errorExit($header, $url) + { + $url = htmlspecialchars($url, ENT_QUOTES); + list(,$h1) = explode(' ', $header, 2); + $h1 = htmlspecialchars($h1); + header($header); + header('Content-Type: text/html; charset=utf-8'); + echo "

$h1

"; + echo "

Please see $url.

"; + exit(); + } + /** * Set up sources to use Minify_Lines * @@ -440,37 +475,26 @@ class Minify { ? self::$_options['minifiers'][$type] : false; - if (Minify_Source::haveNoMinifyPrefs(self::$_controller->sources)) { - // all source have same options/minifier, better performance - // to combine, then minify once - foreach (self::$_controller->sources as $source) { + // minify each source with its own options and minifier, then combine. + // Here we used to combine all first but this was probably + // bad for PCRE performance, esp. in CSS. + foreach (self::$_controller->sources as $source) { + // allow the source to override our minifier and options + $minifier = (null !== $source->minifier) + ? $source->minifier + : $defaultMinifier; + $options = (null !== $source->minifyOptions) + ? array_merge($defaultOptions, $source->minifyOptions) + : $defaultOptions; + if ($minifier) { + self::$_controller->loadMinifier($minifier); + // get source content and minify it + $pieces[] = call_user_func($minifier, $source->getContent(), $options); + } else { $pieces[] = $source->getContent(); } - $content = implode($implodeSeparator, $pieces); - if ($defaultMinifier) { - self::$_controller->loadMinifier($defaultMinifier); - $content = call_user_func($defaultMinifier, $content, $defaultOptions); - } - } else { - // minify each source with its own options and minifier, then combine - foreach (self::$_controller->sources as $source) { - // allow the source to override our minifier and options - $minifier = (null !== $source->minifier) - ? $source->minifier - : $defaultMinifier; - $options = (null !== $source->minifyOptions) - ? array_merge($defaultOptions, $source->minifyOptions) - : $defaultOptions; - if ($minifier) { - self::$_controller->loadMinifier($minifier); - // get source content and minify it - $pieces[] = call_user_func($minifier, $source->getContent(), $options); - } else { - $pieces[] = $source->getContent(); - } - } - $content = implode($implodeSeparator, $pieces); } + $content = implode($implodeSeparator, $pieces); if ($type === self::TYPE_CSS && false !== strpos($content, '@import')) { $content = self::_handleCssImports($content); @@ -512,7 +536,7 @@ class Minify { { if (self::$_options['bubbleCssImports']) { // bubble CSS imports - preg_match_all('/@import.*?;/', $css, $imports); + preg_match_all('/@import.*?;/', $css, $imports); $css = implode('', $imports[0]) . preg_replace('/@import.*?;/', '', $css); } else if ('' !== self::$importWarning) { // remove comments so we don't mistake { in a comment as a block diff --git a/min/lib/Minify/CSS/Compressor.php b/min/lib/Minify/CSS/Compressor.php index a348286..5223aa2 100644 --- a/min/lib/Minify/CSS/Compressor.php +++ b/min/lib/Minify/CSS/Compressor.php @@ -108,7 +108,7 @@ class Minify_CSS_Compressor { \\s* : \\s* - (\\b|[#\'"]) # 3 = first character of a value + (\\b|[#\'"-]) # 3 = first character of a value /x', '$1$2:$3', $css); // remove ws in selectors diff --git a/min/lib/Minify/Controller/Base.php b/min/lib/Minify/Controller/Base.php index 84889b3..736cf59 100644 --- a/min/lib/Minify/Controller/Base.php +++ b/min/lib/Minify/Controller/Base.php @@ -55,6 +55,7 @@ abstract class Minify_Controller_Base { // if you override this, the response code MUST be directly after // the first space. ,'badRequestHeader' => 'HTTP/1.0 400 Bad Request' + ,'errorHeader' => 'HTTP/1.0 500 Internal Server Error' // callback function to see/modify content of all sources ,'postprocessor' => null @@ -195,7 +196,7 @@ abstract class Minify_Controller_Base { * @param string $msg * @return null */ - protected function log($msg) { + public function log($msg) { require_once 'Minify/Logger.php'; Minify_Logger::log($msg); } diff --git a/min/lib/Minify/Controller/MinApp.php b/min/lib/Minify/Controller/MinApp.php index 9582d29..f3c38f4 100644 --- a/min/lib/Minify/Controller/MinApp.php +++ b/min/lib/Minify/Controller/MinApp.php @@ -28,7 +28,7 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { 'allowDirs' => '//' ,'groupsOnly' => false ,'groups' => array() - ,'maxFiles' => 10 + ,'noMinPattern' => '@[-\\.]min\\.(?:js|css)$@i' // matched against basename ) ,(isset($options['minApp']) ? $options['minApp'] : array()) ); @@ -58,9 +58,7 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { } $file = realpath($file); if (is_file($file)) { - $sources[] = new Minify_Source(array( - 'filepath' => $file - )); + $sources[] = $this->_getFileSource($file, $cOptions); } else { $this->log("The path \"{$file}\" could not be found (or was not a file)"); return $options; @@ -84,8 +82,8 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { return $options; } $files = explode(',', $_GET['f']); - if (count($files) > $cOptions['maxFiles'] || $files != array_unique($files)) { - $this->log("Too many or duplicate files specified"); + if ($files != array_unique($files)) { + $this->log("Duplicate files specified"); return $options; } if (isset($_GET['b'])) { @@ -116,9 +114,7 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { $this->log("Path \"{$path}\" failed Minify_Controller_Base::_fileIsSafe()"); return $options; } else { - $sources[] = new Minify_Source(array( - 'filepath' => $file - )); + $sources[] = $this->_getFileSource($file, $cOptions); } } } @@ -129,4 +125,14 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { } return $options; } + + protected function _getFileSource($file, $cOptions) + { + $spec['filepath'] = $file; + if ($cOptions['noMinPattern'] + && preg_match($cOptions['noMinPattern'], basename($file))) { + $spec['minifier'] = ''; + } + return new Minify_Source($spec); + } } diff --git a/min/lib/Minify/Lines.php b/min/lib/Minify/Lines.php index 6f94fb6..ca8afa1 100644 --- a/min/lib/Minify/Lines.php +++ b/min/lib/Minify/Lines.php @@ -26,9 +26,9 @@ class Minify_Lines { * * 'id': (optional) string to identify file. E.g. file name/path * - * 'currentDir': (default null) if given, this is assumed to be the - * directory of the current CSS file. Using this, minify will rewrite - * all relative URIs in import/url declarations to correctly point to + * 'currentDir': (default null) if given, this is assumed to be the + * directory of the current CSS file. Using this, minify will rewrite + * all relative URIs in import/url declarations to correctly point to * the desired files, and prepend a comment with debugging information about * this process. * @@ -40,10 +40,16 @@ class Minify_Lines { ? $options['id'] : ''; $content = str_replace("\r\n", "\n", $content); + + // Hackily rewrite strings with XPath expressions that are + // likely to throw off our dumb parser (for Prototype 1.6.1). + $content = str_replace('"/*"', '"/"+"*"', $content); + $content = preg_replace('@([\'"])(\\.?//?)\\*@', '$1$2$1+$1*', $content); + $lines = explode("\n", $content); $numLines = count($lines); // determine left padding - $padTo = strlen($numLines); + $padTo = strlen((string) $numLines); // e.g. 103 lines = 3 digits $inComment = false; $i = 0; $newLines = array(); @@ -64,7 +70,7 @@ class Minify_Lines { $content = Minify_CSS_UriRewriter::rewrite( $content ,$options['currentDir'] - ,isset($options['docRoot']) ? $options['docRoot'] : $_SERVER['DOCUMENT_ROOT'] + ,isset($options['docRoot']) ? $options['docRoot'] : $_SERVER['DOCUMENT_ROOT'] ,isset($options['symlinks']) ? $options['symlinks'] : array() ); $content = "/* Minify_CSS_UriRewriter::\$debugText\n\n" @@ -82,7 +88,7 @@ class Minify_Lines { * * @param bool $inComment was the parser in a comment at the * beginning of the line? - * + * * @return bool */ private static function _eolInComment($line, $inComment) diff --git a/min/lib/Minify/YUICompressor.php b/min/lib/Minify/YUICompressor.php index 7cb61ad..c7af51f 100644 --- a/min/lib/Minify/YUICompressor.php +++ b/min/lib/Minify/YUICompressor.php @@ -128,11 +128,14 @@ class Minify_YUICompressor { private static function _prepare() { - if (! is_file(self::$jarFile) - || ! is_dir(self::$tempDir) - || ! is_writable(self::$tempDir) - ) { - throw new Exception('Minify_YUICompressor : $jarFile and $tempDir must be set.'); + if (! is_file(self::$jarFile)) { + throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not a valid file.'); + } + if (! is_dir(self::$tempDir)) { + throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not a valid direcotry.'); + } + if (! is_writable(self::$tempDir)) { + throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not writable.'); } } } diff --git a/min_unit_tests/_test_files/css/vladmirated.min.css b/min_unit_tests/_test_files/css/vladmirated.min.css index eaffaee..a59e500 100644 --- a/min_unit_tests/_test_files/css/vladmirated.min.css +++ b/min_unit_tests/_test_files/css/vladmirated.min.css @@ -25,7 +25,7 @@ p{padding:0px;margin:0px;padding-bottom:5px}#post_nav{text-align:left;padding-bo a{text-decoration:none;font-family:Verdana;font-size:12px;color:#108eed}#post_nav a:hover, .post_nav_2 p a:hover{text-decoration:underline;color:#FF8000}#rightcol #about{padding-bottom:10px}#rightcol #r_news input{color:#333;font-size:12px}#header{height:200px;width:100%;background-image:url('images/header_bg.jpg');background-repeat:x-repeat}#header -img{float:right;margin-right: -3px;z-index:100}.tags{text-transform:lowercase;color:#333;font-family:arial;font-size:12px;border-top:2px dotted #EEE;width:300px;padding-top:20px;padding-bottom:0px;margin-top:0px;padding-left:20px;padding-right:20px}.tags +img{float:right;margin-right:-3px;z-index:100}.tags{text-transform:lowercase;color:#333;font-family:arial;font-size:12px;border-top:2px dotted #EEE;width:300px;padding-top:20px;padding-bottom:0px;margin-top:0px;padding-left:20px;padding-right:20px}.tags a{color:#108eed}.tags p{text-align:left;margin:0px;padding:0px}blockquote strong{font-family:verdana;display:block;margin-top:10px;color:#F00;font-style:italic;text-align:right}blockquote{margin:0px;background-color:#eee;border:2px diff --git a/min_unit_tests/_test_files/js/issue141.min.js b/min_unit_tests/_test_files/js/issue141.min.js new file mode 100644 index 0000000..10aa56e --- /dev/null +++ b/min_unit_tests/_test_files/js/issue141.min.js @@ -0,0 +1,3 @@ +// The MinApp controller should cause this file to not be minified +// since the basename of the filepath matches the default noMinPattern + diff --git a/min_unit_tests/_test_files/js/issue144.js b/min_unit_tests/_test_files/js/issue144.js index cccb826..41b72f1 100644 --- a/min_unit_tests/_test_files/js/issue144.js +++ b/min_unit_tests/_test_files/js/issue144.js @@ -1 +1,2 @@ -if(!a.id)a.id="dp"+ ++this.uuid; \ No newline at end of file +// JSMin should not alter this file +if(!a.id)a.id="dp"+ ++this.uuid; diff --git a/min_unit_tests/_test_files/js/issue144.min.js b/min_unit_tests/_test_files/js/issue144.min.js index cccb826..41b72f1 100644 --- a/min_unit_tests/_test_files/js/issue144.min.js +++ b/min_unit_tests/_test_files/js/issue144.min.js @@ -1 +1,2 @@ -if(!a.id)a.id="dp"+ ++this.uuid; \ No newline at end of file +// JSMin should not alter this file +if(!a.id)a.id="dp"+ ++this.uuid; diff --git a/min_unit_tests/_test_files/minify/issue143.js b/min_unit_tests/_test_files/minify/issue143.js new file mode 100644 index 0000000..d726a73 --- /dev/null +++ b/min_unit_tests/_test_files/minify/issue143.js @@ -0,0 +1,6 @@ +/* + * This file is to intentionally throw a JSMin exception + */ +function HelloWorld() { + return /regexp; +} diff --git a/min_unit_tests/_test_files/minify/lines_bugs.js b/min_unit_tests/_test_files/minify/lines_bugs.js index 3215540..cf56b58 100644 --- a/min_unit_tests/_test_files/minify/lines_bugs.js +++ b/min_unit_tests/_test_files/minify/lines_bugs.js @@ -1,2 +1,10 @@ -var triggerBug = {_default: "*/*"}; -var essentialFunctionality = true; +// sections from Prototype 1.6.1 +var xpath = ".//*[local-name()='ul' or local-name()='UL']" + + "//*[local-name()='li' or local-name()='LI']"; +this.matcher = ['.//*']; +xpath = { + descendant: "//*", + child: "/*", + f: 0 +}; +document._getElementsByXPath('.//*' + cond, element); \ No newline at end of file diff --git a/min_unit_tests/_test_files/minify/lines_output.js b/min_unit_tests/_test_files/minify/lines_output.js index 424308c..0c7a63a 100644 --- a/min_unit_tests/_test_files/minify/lines_output.js +++ b/min_unit_tests/_test_files/minify/lines_output.js @@ -29,9 +29,16 @@ ; /* lines_bugs.js */ -/* 1 */ var triggerBug = {_default: "*/*"}; -/* 2 */ var essentialFunctionality = true; -/* 3 */ +/* 1 */ // sections from Prototype 1.6.1 +/* 2 */ var xpath = ".//"+"*[local-name()='ul' or local-name()='UL']" + +/* 3 */ "//"+"*[local-name()='li' or local-name()='LI']"; +/* 4 */ this.matcher = ['.//'+'*']; +/* 5 */ xpath = { +/* 6 */ descendant: "//"+"*", +/* 7 */ child: "/"+"*", +/* 8 */ f: 0 +/* 9 */ }; +/* 10 */ document._getElementsByXPath('.//'+'*' + cond, element); ; /* QueryString.js */ diff --git a/min_unit_tests/_test_files/minify/minified.js b/min_unit_tests/_test_files/minify/minified.js index 5a490ed..3883520 100644 --- a/min_unit_tests/_test_files/minify/minified.js +++ b/min_unit_tests/_test_files/minify/minified.js @@ -1,5 +1,6 @@ (function(){var -reMailto=/^mailto:my_name_is_(\S+)_and_the_domain_is_(\S+)$/,reRemoveTitleIf=/^my name is/,oo=window.onload,fixHrefs=function(){var i=0,l,m;while(l=document.links[i++]){if(m=l.href.match(reMailto)){l.href='mailto:'+m[1]+'@'+m[2];if(reRemoveTitleIf.test(l.title)){l.title='';}}}};window.onload=function(){oo&&oo();fixHrefs();};})();;var MrClay=window.MrClay||{};MrClay.QueryString=function(){var parse=function(str){var assignments=str.split('&'),obj={},propValue;for(var i=0,l=assignments.length;i2||-1!=propValue[0].indexOf('+')||propValue[0]==''){continue;} +reMailto=/^mailto:my_name_is_(\S+)_and_the_domain_is_(\S+)$/,reRemoveTitleIf=/^my name is/,oo=window.onload,fixHrefs=function(){var i=0,l,m;while(l=document.links[i++]){if(m=l.href.match(reMailto)){l.href='mailto:'+m[1]+'@'+m[2];if(reRemoveTitleIf.test(l.title)){l.title='';}}}};window.onload=function(){oo&&oo();fixHrefs();};})(); +;var MrClay=window.MrClay||{};MrClay.QueryString=function(){var parse=function(str){var assignments=str.split('&'),obj={},propValue;for(var i=0,l=assignments.length;i2||-1!=propValue[0].indexOf('+')||propValue[0]==''){continue;} if(propValue.length==1){propValue[1]=propValue[0];} obj[unescape(propValue[0])]=unescape(propValue[1].replace(/\+/g,' '));} return obj;};function construct_(spec){spec=spec||window;if(typeof spec=='object'){this.window=spec;spec=spec.location.search.substr(1);}else{this.window=window;} diff --git a/min_unit_tests/test_HTTP_Encoder.php b/min_unit_tests/test_HTTP_Encoder.php index 9573e80..106d146 100644 --- a/min_unit_tests/test_HTTP_Encoder.php +++ b/min_unit_tests/test_HTTP_Encoder.php @@ -128,14 +128,19 @@ function test_HTTP_Encoder() , "(off by ". abs($ret - $test['exp']) . " bytes)\n\n"; } } - + + HTTP_Encoder::$encodeToIe6 = true; $_SERVER['HTTP_ACCEPT_ENCODING'] = 'identity'; - $he = new HTTP_Encoder(array( - 'content' => 'Hello' - )); + $he = new HTTP_Encoder(array('content' => 'Hello')); $he->encode(); $headers = $he->getHeaders(); - assertTrue(isset($headers['Vary']), 'HTTP_Encoder : Vary always sent'); + assertTrue(isset($headers['Vary']), 'HTTP_Encoder : Vary always sent to good browsers'); + + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'; + $he = new HTTP_Encoder(array('content' => 'Hello')); + $he->encode(); + $headers = $he->getHeaders(); + assertTrue(! isset($headers['Vary']), 'HTTP_Encoder : Vary not sent to bad IE (Issue 126)'); } test_HTTP_Encoder(); diff --git a/min_unit_tests/test_JSMin.php b/min_unit_tests/test_JSMin.php index a492324..43a8302 100644 --- a/min_unit_tests/test_JSMin.php +++ b/min_unit_tests/test_JSMin.php @@ -40,15 +40,15 @@ function test_JSMin() test_JSMin_exception('"Hello' ,'Unterminated String' ,'JSMin_UnterminatedStringException' - ,"Unterminated String: '\"Hello'"); + ,"Unterminated String: \"Hello"); test_JSMin_exception("return /regexp\n}" ,'Unterminated RegExp' ,'JSMin_UnterminatedRegExpException' - ,"Unterminated RegExp: '/regexp\n'"); + ,"Unterminated RegExp: /regexp\n"); test_JSMin_exception("/* Comment " ,'Unterminated Comment' ,'JSMin_UnterminatedCommentException' - ,"Unterminated Comment: '/* Comment '"); + ,"Unterminated Comment: /* Comment "); } } diff --git a/min_unit_tests/test_Minify.php b/min_unit_tests/test_Minify.php index 7075570..9a4e6ec 100644 --- a/min_unit_tests/test_Minify.php +++ b/min_unit_tests/test_Minify.php @@ -53,7 +53,7 @@ function test_Minify() ,'Minify : cache, and minifier classes aren\'t loaded for 304s' ); - // Test minifying JS and serving with Expires header + // Test JS and Expires $content = preg_replace('/\\r\\n?/', "\n", file_get_contents($minifyTestPath . '/minified.js')); $lastModified = max( @@ -96,7 +96,7 @@ function test_Minify() // test for Issue 73 Minify::setCache(null); - $expected = ";function h(){}"; + $expected = "\n;function h(){}"; $output = Minify::serve('Files', array( 'files' => array( $minifyTestPath . '/issue73_1.js' diff --git a/min_unit_tests/test_environment.php b/min_unit_tests/test_environment.php index f801569..53f0d23 100644 --- a/min_unit_tests/test_environment.php +++ b/min_unit_tests/test_environment.php @@ -79,7 +79,8 @@ function test_environment() break; } } - if ($passed && stream_get_contents($fp) !== 'World!') { + $streamContents = stream_get_contents($fp); + if ($passed && $streamContents !== 'World!') { $passed = false; } assertTrue( From 02dbf14c8ef751d4eb041ed925c2c1812d5eaf5f Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Mon, 10 May 2010 07:44:40 +0000 Subject: [PATCH 06/25] Work on: Issue 120, Issue 170, Issue 152, Issue 125, Issue 134, Issue 143 Broke some tests --- README.txt | 10 +-- min/.htaccess | 13 +++- min/README.txt | 8 +-- min/builder/_index.js | 15 ++-- min/builder/bm2.js | 14 ++++ min/builder/index.php | 83 ++++++++++++++------- min/config.php | 27 ++++--- min/index.php | 11 ++- min/lib/HTTP/ConditionalGet.php | 17 +++-- min/lib/JSMin.php | 10 ++- min/lib/Minify.php | 21 ++++-- min/lib/Minify/Controller/Base.php | 9 +++ min/lib/Minify/Controller/MinApp.php | 10 ++- min/lib/Minify/Controller/Page.php | 5 ++ min_unit_tests/.htaccess | 4 ++ min_unit_tests/test_Minify.php | 10 +-- min_unit_tests/test_environment.php | 103 ++++++++++++++++++--------- 17 files changed, 259 insertions(+), 111 deletions(-) create mode 100644 min/builder/bm2.js create mode 100644 min_unit_tests/.htaccess diff --git a/README.txt b/README.txt index 3899b99..43978d5 100644 --- a/README.txt +++ b/README.txt @@ -16,12 +16,14 @@ See UPGRADING.txt for instructions. INSTALLATION AND USAGE: 1. Place the /min/ directory as a child of your DOCUMENT_ROOT -directory: i.e. you will have: /home/user/www/public_html/min +directory: i.e. you will have: /home/user/www/min 2. Open http://yourdomain/min/ in a web browser. This will forward you to the Minify URI Builder application, which will help you quickly start using Minify to serve content on your site. +See the User Guide: http://code.google.com/p/minify/wiki/UserGuide + UNIT TESTING: @@ -36,12 +38,6 @@ components with more verbose output.) 3. Remove /min_unit_tests/ from your DOCUMENT_ROOT when you are done. -EXTRAS: - -The min_extras folder contains files for benchmarking using Apache ab on Windows -and a couple single-use tools. DO NOT place this on your production server. - - FILE ENCODINGS Minify *should* work fine with files encoded in UTF-8 or other 8-bit diff --git a/min/.htaccess b/min/.htaccess index 42f13eb..06c1161 100644 --- a/min/.htaccess +++ b/min/.htaccess @@ -1,4 +1,13 @@ RewriteEngine on -RewriteRule ^([a-z]=.*) index.php?$1 [L,NE] - \ No newline at end of file + +# You may need RewriteBase on some servers +#RewriteBase /min + +# rewrite URLs like "/min/f=..." to "/min/?f=..." +RewriteRule ^([bfg]=.*) index.php?$1 [L,NE] + + +# In case AddOutputFilterByType has been added +SetEnv no-gzip + diff --git a/min/README.txt b/min/README.txt index a7cf774..fab1bb0 100644 --- a/min/README.txt +++ b/min/README.txt @@ -66,14 +66,14 @@ to the /js and /themes/default directories, use: $min_serveOptions['minApp']['allowDirs'] = array('//js', '//themes/default'); -GROUPS: FASTER PERFORMANCE AND BETTER URLS +GROUPS: NICER URLS -For the best performance, edit groupsConfig.php to pre-specify groups of files +For nicer URLs, edit groupsConfig.php to pre-specify groups of files to be combined under preset keys. E.g., here's an example configuration in groupsConfig.php: -return array( - 'js' => array('//js/Class.js', '//js/email.js') +return array( + 'js' => array('//js/Class.js', '//js/email.js') ); This pre-selects the following files to be combined under the key "js": diff --git a/min/builder/_index.js b/min/builder/_index.js index bbaadb4..90c3afd 100644 --- a/min/builder/_index.js +++ b/min/builder/_index.js @@ -202,7 +202,7 @@ var MUB = { $('#sources').html(''); $('#add button').click(MUB.addButtonClick); // make easier to copy text out of - $('#uriHtml, #groupConfig').click(function () { + $('#uriHtml, #groupConfig, #symlinkOpt').click(function () { this.select(); }).focus(function () { this.select(); @@ -223,10 +223,9 @@ var MUB = { return false; }).attr({title:'Add file +'}); } else { - // copy bookmarklet code into href - var bmUri = location.pathname.replace(/\/[^\/]*$/, '/bm.js').substr(1); + // setup bookmarklet 1 $.ajax({ - url : '../?f=' + bmUri + url : '../?f=' + location.pathname.replace(/\/[^\/]*$/, '/bm.js').substr(1) ,success : function (code) { $('#bm')[0].href = code .replace('%BUILDER_URL%', location.href) @@ -237,6 +236,14 @@ var MUB = { $.browser.msie && $('#getBm p:last').append(' Sorry, not supported in MSIE!'); MUB.addButtonClick(); } + // setup bookmarklet 2 + $.ajax({ + url : '../?f=' + location.pathname.replace(/\/[^\/]*$/, '/bm2.js').substr(1) + ,success : function (code) { + $('#bm2')[0].href = code.replace(/\n/g, ' '); + } + ,dataType : 'text' + }); MUB.checkRewrite(); } }; diff --git a/min/builder/bm2.js b/min/builder/bm2.js new file mode 100644 index 0000000..236402e --- /dev/null +++ b/min/builder/bm2.js @@ -0,0 +1,14 @@ +javascript:(function(){ + var d = document + ,c = d.cookie + ,m = c.match(/\bminDebug=([^; ]+)/) + ,v = m ? decodeURIComponent(m[1]) : '' + ,p = prompt('Debug Minify URIs on "' + location.hostname + '" \ncontaining: (empty to disable)', v) + ; + if (p === null) return; + p = p.replace(/\s+/g, ''); + v = (p === '') + ? 'minDebug=; expires=Fri, 27 Jul 2001 02:47:11 UTC; path=/' + : 'minDebug=' + encodeURIComponent(p) + '; path=/'; + d.cookie = v; +})(); \ No newline at end of file diff --git a/min/builder/index.php b/min/builder/index.php index 18ec939..093954b 100644 --- a/min/builder/index.php +++ b/min/builder/index.php @@ -8,6 +8,20 @@ if (phpversion() < 5) { $encodeOutput = (function_exists('gzdeflate') && !ini_get('zlib.output_compression')); +// recommend $min_symlinks setting for Apache UserDir +$symlinkOption = ''; +if (0 === strpos($_SERVER["SERVER_SOFTWARE"], 'Apache/') + && preg_match('@^/\\~(\\w+)/@', $_SERVER['REQUEST_URI'], $m) +) { + $userDir = DIRECTORY_SEPARATOR . $m[1] . DIRECTORY_SEPARATOR; + if (false !== strpos(__FILE__, $userDir)) { + $sm = array(); + $sm["//~{$m[1]}"] = dirname(dirname(__FILE__)); + $array = str_replace('array (', 'array(', var_export($sm, 1)); + $symlinkOption = "\$min_symlinks = $array;"; + } +} + require dirname(__FILE__) . '/../config.php'; if (! $min_enableBuilder) { @@ -18,8 +32,8 @@ if (! $min_enableBuilder) { ob_start(); ?> - Minify URI Builder + + +
Note: It looks like you're running Minify in a user + directory. You may need the following option in /min/config.php to have URIs + correctly rewritten in CSS output: +
+
+ +

Uh Oh. Minify was unable to - serve the Javascript for this app. Enable - FirePHP debugging and request the Minify URL directly. + serve the Javascript for this app. To troubleshoot this, + enable FirePHP debugging + and request the Minify URL directly. Hopefully the + FirePHP console will report the cause of the error.

-

Note: Please set $min_cachePath -in /min/config.php to improve performance.

+

Note: You can set $min_cachePath +in /min/config.php to slightly improve performance.

Note: Your webserver does not seem to @@ -111,14 +135,21 @@ in your list, and move any others to the top of the first file in your list

If you desire, you can use Minify URIs in imports and they will not be touched by Minify. E.g. @import "/min/?g=css2";

+

Debug Mode

+

When /min/config.php has $min_allowDebugFlag = true; + you can get debug output by appending &debug to a Minify URL, or + by sending the cookie minDebug=<match>, where minDebug=<match> + should match the Minify URIs you'd like to debug. This bookmarklet will allow you to + set this cookie.

+

Minify Debug (right-click, add to bookmarks)

+
-

Need help? Search or post to the Minify discussion list.

-

This app is minified :) view -source

+

Need help? Check the wiki, + or post to the discussion + list.

+

This app is minified :)

@@ -163,9 +194,22 @@ $(function () { ob_get_contents() +// setup Minify +set_include_path(dirname(__FILE__) . '/../lib' . PATH_SEPARATOR . get_include_path()); +require 'Minify.php'; +if (0 === stripos(PHP_OS, 'win')) { + Minify::setDocRoot(); // we may be on IIS +} +Minify::setCache( + isset($min_cachePath) ? $min_cachePath : '' + ,$min_cacheFileLocking +); +Minify::$uploaderHoursBehind = $min_uploaderHoursBehind; + +Minify::serve('Page', array( + 'content' => $content ,'id' => __FILE__ ,'lastModifiedTime' => max( // regenerate cache if either of these change @@ -174,17 +218,4 @@ $serveOpts = array( ) ,'minifyAll' => true ,'encodeOutput' => $encodeOutput -); -ob_end_clean(); - -set_include_path(dirname(__FILE__) . '/../lib' . PATH_SEPARATOR . get_include_path()); - -require 'Minify.php'; - -if (0 === stripos(PHP_OS, 'win')) { - Minify::setDocRoot(); // we may be on IIS -} -Minify::setCache(isset($min_cachePath) ? $min_cachePath : null); -Minify::$uploaderHoursBehind = $min_uploaderHoursBehind; - -Minify::serve('Page', $serveOpts); +)); diff --git a/min/config.php b/min/config.php index 927d2fe..7d7be0a 100644 --- a/min/config.php +++ b/min/config.php @@ -7,22 +7,12 @@ */ -/** - * In 'debug' mode, Minify can combine files with no minification and - * add comments to indicate line #s of the original files. - * - * To allow debugging, set this option to true and add "&debug=1" to - * a URI. E.g. /min/?f=script1.js,script2.js&debug=1 - */ -$min_allowDebugFlag = false; - - /** * Set to true to log messages to FirePHP (Firefox Firebug addon). * Set to false for no error logging (Minify may be slightly faster). * @link http://www.firephp.org/ * - * If you want to use a custom error logger, set this to your logger + * If you want to use a custom error logger, set this to your logger * instance. Your object should have a method log(string $message). * * @todo cache system does not have error logging yet. @@ -30,6 +20,21 @@ $min_allowDebugFlag = false; $min_errorLogger = false; +/** + * To allow debugging, you must set this option to true. + * + * Once true, you can send the cookie minDebug to request debug mode output. The + * cookie value should match the URIs you'd like to debug. E.g. to debug + * /min/f=file1.js send the cookie minDebug=file1.js + * You can manually enable debugging by appending "&debug" to a URI. + * E.g. /min/?f=script1.js,script2.js&debug + * + * In 'debug' mode, Minify combines files with no minification and adds comments + * to indicate line #s of the original files. + */ +$min_allowDebugFlag = false; + + /** * Allow use of the Minify URI Builder app. If you no longer need * this, set to false. diff --git a/min/index.php b/min/index.php index 288cbf3..352313f 100644 --- a/min/index.php +++ b/min/index.php @@ -35,8 +35,15 @@ foreach ($min_symlinks as $uri => $target) { $min_serveOptions['minApp']['allowDirs'][] = $target; } -if ($min_allowDebugFlag && isset($_GET['debug'])) { - $min_serveOptions['debug'] = true; +if ($min_allowDebugFlag) { + if (! empty($_COOKIE['minDebug']) + && false !== strpos($_SERVER['REQUEST_URI'], $_COOKIE['minDebug'])) { + $min_serveOptions['debug'] = true; + } + // allow GET to override + if (isset($_GET['debug'])) { + $min_serveOptions['debug'] = true; + } } if ($min_errorLogger) { diff --git a/min/lib/HTTP/ConditionalGet.php b/min/lib/HTTP/ConditionalGet.php index 4a0225a..26e413e 100644 --- a/min/lib/HTTP/ConditionalGet.php +++ b/min/lib/HTTP/ConditionalGet.php @@ -75,9 +75,8 @@ class HTTP_ConditionalGet { /** * @param array $spec options * - * 'isPublic': (bool) if true, the Cache-Control header will contain - * "public", allowing proxies to cache the content. Otherwise "private" will - * be sent, allowing only browser caching. (default false) + * 'isPublic': (bool) if false, the Cache-Control header will contain + * "private", allowing only browser caching. (default false) * * 'lastModifiedTime': (int) if given, both ETag AND Last-Modified headers * will be sent with content. This is recommended. @@ -150,7 +149,10 @@ class HTTP_ConditionalGet { } elseif (isset($spec['contentHash'])) { // Use the hash as the ETag $this->_setEtag($spec['contentHash'] . $etagAppend, $scope); } - $this->_headers['Cache-Control'] = "max-age={$maxAge}, {$scope}"; + $privacy = ($scope === 'private') + ? ', private' + : ''; + $this->_headers['Cache-Control'] = "max-age={$maxAge}{$privacy}"; // invalidate cache if disabled, otherwise check $this->cacheIsValid = (isset($spec['invalidate']) && $spec['invalidate']) ? false @@ -332,11 +334,8 @@ class HTTP_ConditionalGet { if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { return false; } - $ifModifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE']; - if (false !== ($semicolon = strrpos($ifModifiedSince, ';'))) { - // IE has tacked on extra data to this header, strip it - $ifModifiedSince = substr($ifModifiedSince, 0, $semicolon); - } + // strip off IE's extra data (semicolon) + list($ifModifiedSince) = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE'], 2); if (strtotime($ifModifiedSince) >= $this->_lmTime) { // Apache 2.2's behavior. If there was no ETag match, send the // non-encoded version of the ETag value. diff --git a/min/lib/JSMin.php b/min/lib/JSMin.php index 74d3422..288a0e2 100644 --- a/min/lib/JSMin.php +++ b/min/lib/JSMin.php @@ -171,7 +171,8 @@ class JSMin { } if (ord($this->a) <= self::ORD_LF) { throw new JSMin_UnterminatedStringException( - "Unterminated String: {$str}"); + "JSMin: Unterminated String at byte " + . $this->inputIndex . ": {$str}"); } $str .= $this->a; if ($this->a === '\\') { @@ -198,7 +199,8 @@ class JSMin { $pattern .= $this->a; } elseif (ord($this->a) <= self::ORD_LF) { throw new JSMin_UnterminatedRegExpException( - "Unterminated RegExp: {$pattern}"); + "JSMin: Unterminated RegExp at byte " + . $this->inputIndex .": {$pattern}"); } $this->output .= $this->a; } @@ -310,7 +312,9 @@ class JSMin { return ' '; } } elseif ($get === null) { - throw new JSMin_UnterminatedCommentException("Unterminated Comment: /*{$comment}"); + throw new JSMin_UnterminatedCommentException( + "JSMin: Unterminated comment at byte " + . $this->inputIndex . ": /*{$comment}"); } $comment .= $get; } diff --git a/min/lib/Minify.php b/min/lib/Minify.php index ba40d7e..5cb2405 100644 --- a/min/lib/Minify.php +++ b/min/lib/Minify.php @@ -227,6 +227,8 @@ class Minify { ); if (self::$_options['maxAge'] > 0) { $cgOptions['maxAge'] = self::$_options['maxAge']; + } elseif (self::$_options['debug']) { + $cgOptions['invalidate'] = true; } $cg = new HTTP_ConditionalGet($cgOptions); if ($cg->cacheIsValid) { @@ -267,7 +269,7 @@ class Minify { // the goal is to use only the cache methods to sniff the length and // output the content, as they do not require ever loading the file into // memory. - $cacheId = 'minify_' . self::_getCacheId(); + $cacheId = self::_getCacheId(); $fullCacheId = (self::$_options['encodeMethod']) ? $cacheId . '.gz' : $cacheId; @@ -489,7 +491,12 @@ class Minify { if ($minifier) { self::$_controller->loadMinifier($minifier); // get source content and minify it - $pieces[] = call_user_func($minifier, $source->getContent(), $options); + try { + $pieces[] = call_user_func($minifier, $source->getContent(), $options); + } catch (Exception $e) { + throw new Exception("Exception in " . $source->getId() . + ": " . $e->getMessage()); + } } else { $pieces[] = $source->getContent(); } @@ -515,17 +522,23 @@ class Minify { * * Any settings that could affect output are taken into consideration * + * @param string $prefix + * * @return string */ - protected static function _getCacheId() + protected static function _getCacheId($prefix = 'minify') { - return md5(serialize(array( + $name = preg_replace('/[^a-zA-Z0-9\\.=_,]/', '', self::$_controller->selectionId); + $name = preg_replace('/\\.+/', '.', $name); + $name = substr($name, 0, 250 - 34 - strlen($prefix)); + $md5 = md5(serialize(array( Minify_Source::getDigest(self::$_controller->sources) ,self::$_options['minifiers'] ,self::$_options['minifierOptions'] ,self::$_options['postprocessor'] ,self::$_options['bubbleCssImports'] ))); + return "{$prefix}_{$name}_{$md5}"; } /** diff --git a/min/lib/Minify/Controller/Base.php b/min/lib/Minify/Controller/Base.php index 736cf59..1e59ed5 100644 --- a/min/lib/Minify/Controller/Base.php +++ b/min/lib/Minify/Controller/Base.php @@ -144,6 +144,15 @@ abstract class Minify_Controller_Base { */ public $sources = array(); + /** + * The setupSources() method may choose to set this, making it easier to + * recognize a particular set of sources/settings in the cache folder. It + * will be filtered and truncated to make the final cache id <= 250 bytes. + * + * @var string short name to place inside cache id + */ + public $selectionId = ''; + /** * Mix in default controller options with user-given options * diff --git a/min/lib/Minify/Controller/MinApp.php b/min/lib/Minify/Controller/MinApp.php index f3c38f4..98b4e2c 100644 --- a/min/lib/Minify/Controller/MinApp.php +++ b/min/lib/Minify/Controller/MinApp.php @@ -40,7 +40,8 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { $this->log("A group configuration for \"{$_GET['g']}\" was not set"); return $options; } - + + $this->selectionId = "g=" . $_GET['g']; $files = $cOptions['groups'][$_GET['g']]; // if $files is a single object, casting will break it if (is_object($files)) { @@ -70,7 +71,7 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { // respond to. Ideally there should be only one way to reference a file. if (// verify at least one file, files are single comma separated, // and are all same extension - ! preg_match('/^[^,]+\\.(css|js)(?:,[^,]+\\.\\1)*$/', $_GET['f']) + ! preg_match('/^[^,]+\\.(css|js)(?:,[^,]+\\.\\1)*$/', $_GET['f'], $m) // no "//" || strpos($_GET['f'], '//') !== false // no "\" @@ -81,11 +82,13 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { $this->log("GET param 'f' invalid (see MinApp.php line 63)"); return $options; } + $ext = ".{$m[1]}"; $files = explode(',', $_GET['f']); if ($files != array_unique($files)) { $this->log("Duplicate files specified"); return $options; } + if (isset($_GET['b'])) { // check for validity if (preg_match('@^[^/]+(?:/[^/]+)*$@', $_GET['b']) @@ -104,6 +107,7 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { foreach ((array)$cOptions['allowDirs'] as $allowDir) { $allowDirs[] = realpath(str_replace('//', $_SERVER['DOCUMENT_ROOT'] . '/', $allowDir)); } + $basenames = array(); // just for cache id foreach ($files as $file) { $path = $_SERVER['DOCUMENT_ROOT'] . $base . $file; $file = realpath($path); @@ -115,8 +119,10 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { return $options; } else { $sources[] = $this->_getFileSource($file, $cOptions); + $basenames[] = basename($file, $ext); } } + $this->selectionId = implode(',', $basenames) . $ext; } if ($sources) { $this->sources = $sources; diff --git a/min/lib/Minify/Controller/Page.php b/min/lib/Minify/Controller/Page.php index fa4599a..de471e1 100644 --- a/min/lib/Minify/Controller/Page.php +++ b/min/lib/Minify/Controller/Page.php @@ -40,14 +40,19 @@ class Minify_Controller_Page extends Minify_Controller_Base { $sourceSpec = array( 'filepath' => $options['file'] ); + $f = $options['file']; } else { // strip controller options $sourceSpec = array( 'content' => $options['content'] ,'id' => $options['id'] ); + $f = $options['id']; unset($options['content'], $options['id']); } + // something like "builder,index.php" or "directory,file.html" + $this->selectionId = strtr(substr($f, 1 + strlen(dirname(dirname($f)))), '/\\', ',,'); + if (isset($options['minifyAll'])) { // this will be the 2nd argument passed to Minify_HTML::minify() $sourceSpec['minifyOptions'] = array( diff --git a/min_unit_tests/.htaccess b/min_unit_tests/.htaccess new file mode 100644 index 0000000..4e05c4c --- /dev/null +++ b/min_unit_tests/.htaccess @@ -0,0 +1,4 @@ + +# In case AddOutputFilterByType has been added +SetEnv no-gzip + diff --git a/min_unit_tests/test_Minify.php b/min_unit_tests/test_Minify.php index 9a4e6ec..95e5d64 100644 --- a/min_unit_tests/test_Minify.php +++ b/min_unit_tests/test_Minify.php @@ -29,7 +29,7 @@ function test_Minify() 'Vary' => 'Accept-Encoding', 'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified), 'ETag' => "\"pub{$lastModified}\"", - 'Cache-Control' => 'max-age=1800, public', + 'Cache-Control' => 'max-age=1800', '_responseCode' => 'HTTP/1.0 304 Not Modified', ) ); @@ -49,7 +49,7 @@ function test_Minify() assertTrue( ! class_exists('Minify_CSS', false) - && ! class_exists('Minify_Cache', false) + && ! class_exists('Minify_Cache_File', false) ,'Minify : cache, and minifier classes aren\'t loaded for 304s' ); @@ -70,11 +70,13 @@ function test_Minify() 'Vary' => 'Accept-Encoding', 'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified), 'ETag' => "\"pub{$lastModified}\"", - 'Cache-Control' => 'max-age=86400, public', + 'Cache-Control' => 'max-age=86400', 'Content-Length' => strlen($content), 'Content-Type' => 'application/x-javascript; charset=utf-8', ) ); + unset($_SERVER['HTTP_IF_NONE_MATCH']); + unset($_SERVER['HTTP_IF_MODIFIED_SINCE']); $output = Minify::serve('Files', array( 'files' => array( $minifyTestPath . '/email.js' @@ -185,7 +187,7 @@ function test_Minify() 'Vary' => 'Accept-Encoding', 'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified), 'ETag' => "\"pub{$lastModified}\"", - 'Cache-Control' => 'max-age=0, public', + 'Cache-Control' => 'max-age=0', 'Content-Length' => strlen($expectedContent), 'Content-Type' => 'text/css; charset=utf-8', ) diff --git a/min_unit_tests/test_environment.php b/min_unit_tests/test_environment.php index 53f0d23..3d4162c 100644 --- a/min_unit_tests/test_environment.php +++ b/min_unit_tests/test_environment.php @@ -1,5 +1,7 @@ array( - 'method' => "GET", - 'header' => "Accept-Encoding: deflate, gzip\r\n" - ) - ))); - - $meta = stream_get_meta_data($fp); - - $passed = true; - foreach ($meta['wrapper_data'] as $i => $header) { - if ((preg_match('@^Content-Length: (\\d+)$@i', $header, $m) && $m[1] !== '6') - || preg_match('@^Content-Encoding:@i', $header, $m) - ) { - $passed = false; - break; - } - } - $streamContents = stream_get_contents($fp); - if ($passed && $streamContents !== 'World!') { - $passed = false; - } - assertTrue( - $passed - ,'environment : PHP/server does not auto-HTTP-encode content' + + $testJs = _test_environment_getHello($thisUrl . '?hello=js'); + $passed = assertTrue( + $testJs['length'] == 6 + ,'environment : PHP/server should not auto-encode application/x-javascript output' ); - fclose($fp); - - if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { - if (! $passed) { - echo "\nReturned content should be 6 bytes and not HTTP encoded.\n" - . "Headers returned by: {$thisUrl}?hello=1\n\n"; - var_export($meta['wrapper_data']); + + $testCss = _test_environment_getHello($thisUrl . '?hello=css'); + $passed = $passed && assertTrue( + $testCss['length'] == 6 + ,'environment : PHP/server should not auto-encode text/css output' + ); + + $testHtml = _test_environment_getHello($thisUrl . '?hello=html'); + $passed = $passed && assertTrue( + $testHtml['length'] == 6 + ,'environment : PHP/server should not auto-encode text/html output' + ); + + if (! $passed) { + $testFake = _test_environment_getHello($thisUrl . '?hello=faketype'); + if ($testFake['length'] == 6) { + echo "!NOTE: environment : Server does not auto-encode arbitrary types. This\n" + . " may indicate that the auto-encoding is caused by Apache's \n" + . " AddOutputFilterByType."; } } } +function _test_environment_getHello($url) +{ + $fp = fopen($url, 'r', false, stream_context_create(array( + 'http' => array( + 'method' => "GET", + 'timeout' => '10', + 'header' => "Accept-Encoding: deflate, gzip\r\n", + ) + ))); + $meta = stream_get_meta_data($fp); + $encoding = ''; + $length = 0; + foreach ($meta['wrapper_data'] as $i => $header) { + if (preg_match('@^Content-Length:\\s*(\\d+)$@i', $header, $m)) { + $length = $m[1]; + } elseif (preg_match('@^Content-Encoding:\\s*(\\S+)$@i', $header, $m)) { + if ($m[1] !== 'identity') { + $encoding = $m[1]; + } + } + } + $streamContents = stream_get_contents($fp); + fclose($fp); + + if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { + if ($length != 6) { + echo "\nReturned content should be 6 bytes and not HTTP encoded.\n" + . "Headers returned by: {$url}\n\n"; + var_export($meta['wrapper_data']); + echo "\n\n"; + } + } + + return array( + 'length' => $length + ,'encoding' => $encoding + ,'bytes' => $streamContents + ); +} + test_environment(); From 19e79ff7fb00cc847e40eeba628c24f91e64fefa Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Sun, 16 May 2010 01:27:33 +0000 Subject: [PATCH 07/25] Fixed tests & classes in case of mbstring.func_overload & 2 (Issue 132) Added ability to match multiple strings in URIs for debugging --- min/builder/_index.js | 3 + min/builder/bm2.js | 5 +- min/index.php | 10 +++- min/lib/HTTP/Encoder.php | 12 +++- min/lib/JSMin.php | 33 ++++++----- min/lib/Minify.php | 7 +-- min/lib/Minify/Cache/APC.php | 9 ++- min/lib/Minify/Cache/Memcache.php | 9 ++- min/lib/Minify/Controller/Version1.php | 2 +- min_unit_tests/_inc.php | 15 ++++- min_unit_tests/_test_files/js/issue132.js | 7 +++ min_unit_tests/_test_files/js/issue132.min.js | 1 + .../_test_files/minify/QueryString.js | 2 +- min_unit_tests/_test_files/minify/email.js | 2 +- min_unit_tests/test_HTTP_Encoder.php | 59 +++++++++++++++++-- min_unit_tests/test_JSMin.php | 37 +++++++----- min_unit_tests/test_JSMinPlus.php | 47 +++------------ min_unit_tests/test_Minify.php | 16 +++-- min_unit_tests/test_Minify_CSS.php | 6 +- .../test_Minify_CSS_UriRewriter.php | 8 +-- min_unit_tests/test_Minify_Cache_APC.php | 4 +- min_unit_tests/test_Minify_Cache_File.php | 46 +++++++-------- min_unit_tests/test_Minify_Cache_Memcache.php | 4 +- .../test_Minify_CommentPreserver.php | 4 +- min_unit_tests/test_Minify_HTML.php | 20 +++---- .../test_Minify_ImportProcessor.php | 16 ++--- min_unit_tests/test_Minify_Lines.php | 4 +- 27 files changed, 235 insertions(+), 153 deletions(-) create mode 100644 min_unit_tests/_test_files/js/issue132.js create mode 100644 min_unit_tests/_test_files/js/issue132.min.js diff --git a/min/builder/_index.js b/min/builder/_index.js index 90c3afd..cf40a3e 100644 --- a/min/builder/_index.js +++ b/min/builder/_index.js @@ -1,3 +1,6 @@ +/*! + * Minify URI Builder + */ var MUB = { _uid : 0 ,_minRoot : '/min/?' diff --git a/min/builder/bm2.js b/min/builder/bm2.js index 236402e..db92172 100644 --- a/min/builder/bm2.js +++ b/min/builder/bm2.js @@ -3,10 +3,11 @@ javascript:(function(){ ,c = d.cookie ,m = c.match(/\bminDebug=([^; ]+)/) ,v = m ? decodeURIComponent(m[1]) : '' - ,p = prompt('Debug Minify URIs on "' + location.hostname + '" \ncontaining: (empty to disable)', v) + ,p = prompt('Debug Minify URIs on ' + location.hostname + ' which contain:' + + '\n(empty for none, space = OR)', v) ; if (p === null) return; - p = p.replace(/\s+/g, ''); + p = p.replace(/^\s+|\s+$/, ''); v = (p === '') ? 'minDebug=; expires=Fri, 27 Jul 2001 02:47:11 UTC; path=/' : 'minDebug=' + encodeURIComponent(p) + '; path=/'; diff --git a/min/index.php b/min/index.php index 352313f..4b295ba 100644 --- a/min/index.php +++ b/min/index.php @@ -36,9 +36,13 @@ foreach ($min_symlinks as $uri => $target) { } if ($min_allowDebugFlag) { - if (! empty($_COOKIE['minDebug']) - && false !== strpos($_SERVER['REQUEST_URI'], $_COOKIE['minDebug'])) { - $min_serveOptions['debug'] = true; + if (! empty($_COOKIE['minDebug'])) { + foreach (preg_split('/\\s+/', $_COOKIE['minDebug']) as $debugUri) { + if (false !== strpos($_SERVER['REQUEST_URI'], $debugUri)) { + $min_serveOptions['debug'] = true; + break; + } + } } // allow GET to override if (isset($_GET['debug'])) { diff --git a/min/lib/HTTP/Encoder.php b/min/lib/HTTP/Encoder.php index 4c02fc9..49bedd7 100644 --- a/min/lib/HTTP/Encoder.php +++ b/min/lib/HTTP/Encoder.php @@ -90,8 +90,13 @@ class HTTP_Encoder { */ public function __construct($spec) { + $this->_useMbStrlen = (function_exists('mb_strlen') + && (ini_get('mbstring.func_overload') !== '') + && ((int)ini_get('mbstring.func_overload') & 2)); $this->_content = $spec['content']; - $this->_headers['Content-Length'] = (string)strlen($this->_content); + $this->_headers['Content-Length'] = $this->_useMbStrlen + ? (string)mb_strlen($this->_content, '8bit') + : (string)strlen($this->_content); if (isset($spec['type'])) { $this->_headers['Content-Type'] = $spec['type']; } @@ -275,7 +280,9 @@ class HTTP_Encoder { if (false === $encoded) { return false; } - $this->_headers['Content-Length'] = strlen($encoded); + $this->_headers['Content-Length'] = $this->_useMbStrlen + ? (string)mb_strlen($encoded, '8bit') + : (string)strlen($encoded); $this->_headers['Content-Encoding'] = $this->_encodeMethod[1]; $this->_content = $encoded; return true; @@ -327,4 +334,5 @@ class HTTP_Encoder { protected $_content = ''; protected $_headers = array(); protected $_encodeMethod = array('', ''); + protected $_useMbStrlen = false; } diff --git a/min/lib/JSMin.php b/min/lib/JSMin.php index 288a0e2..ae4ff92 100644 --- a/min/lib/JSMin.php +++ b/min/lib/JSMin.php @@ -84,16 +84,6 @@ class JSMin { // likely pre-minified and would be broken by JSMin return $js; } - if (function_exists('mb_strlen') - && (ini_get('mbstring.func_overload') !== '') - && ((int)ini_get('mbstring.func_overload') & 2)) { - $enc = mb_internal_encoding(); - mb_internal_encoding('8bit'); - $jsmin = new JSMin($js); - $ret = $jsmin->min(); - mb_internal_encoding($enc); - return $ret; - } $jsmin = new JSMin($js); return $jsmin->min(); } @@ -107,8 +97,7 @@ class JSMin { */ public function __construct($input) { - $this->input = str_replace("\r\n", "\n", $input); - $this->inputLength = strlen($this->input); + $this->input = $input; } /** @@ -119,6 +108,15 @@ class JSMin { if ($this->output !== '') { // min already run return $this->output; } + + $mbIntEnc = null; + if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) { + $mbIntEnc = mb_internal_encoding(); + mb_internal_encoding('8bit'); + } + $this->input = str_replace("\r\n", "\n", $this->input); + $this->inputLength = strlen($this->input); + $this->action(self::ACTION_DELETE_A_B); while ($this->a !== null) { @@ -131,8 +129,11 @@ class JSMin { } elseif ($this->a === "\n") { if ($this->b === ' ') { $command = self::ACTION_DELETE_A_B; - } elseif (false === strpos('{[(+-', $this->b) - && ! $this->isAlphaNum($this->b)) { + // in case of mbstring.func_overload & 2, must check for null b, + // otherwise mb_strpos will give WARNING + } elseif ($this->b === null + || (false === strpos('{[(+-', $this->b) + && ! $this->isAlphaNum($this->b))) { $command = self::ACTION_DELETE_A; } } elseif (! $this->isAlphaNum($this->a)) { @@ -145,6 +146,10 @@ class JSMin { $this->action($command); } $this->output = trim($this->output); + + if ($mbIntEnc !== null) { + mb_internal_encoding($mbIntEnc); + } return $this->output; } diff --git a/min/lib/Minify.php b/min/lib/Minify.php index 5cb2405..fabde72 100644 --- a/min/lib/Minify.php +++ b/min/lib/Minify.php @@ -29,7 +29,7 @@ require_once 'Minify/Source.php'; */ class Minify { - const VERSION = '2.1.3'; + const VERSION = '2.1.4'; const TYPE_CSS = 'text/css'; const TYPE_HTML = 'text/html'; // there is some debate over the ideal JS Content-Type, but this is the @@ -312,12 +312,9 @@ class Minify { } // add headers - $hasMbOverload = (function_exists('mb_strlen') - && (ini_get('mbstring.func_overload') !== '') - && ((int)ini_get('mbstring.func_overload') & 2)); $headers['Content-Length'] = $cacheIsReady ? $cacheContentLength - : ($hasMbOverload + : ((function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) ? mb_strlen($content, '8bit') : strlen($content) ); diff --git a/min/lib/Minify/Cache/APC.php b/min/lib/Minify/Cache/APC.php index ca84d29..24ab046 100644 --- a/min/lib/Minify/Cache/APC.php +++ b/min/lib/Minify/Cache/APC.php @@ -54,9 +54,12 @@ class Minify_Cache_APC { */ public function getSize($id) { - return $this->_fetch($id) - ? strlen($this->_data) - : false; + if (! $this->_fetch($id)) { + return false; + } + return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) + ? mb_strlen($this->_data, '8bit') + : strlen($this->_data); } /** diff --git a/min/lib/Minify/Cache/Memcache.php b/min/lib/Minify/Cache/Memcache.php index 2b81e7a..72bf454 100644 --- a/min/lib/Minify/Cache/Memcache.php +++ b/min/lib/Minify/Cache/Memcache.php @@ -60,9 +60,12 @@ class Minify_Cache_Memcache { */ public function getSize($id) { - return $this->_fetch($id) - ? strlen($this->_data) - : false; + if (! $this->_fetch($id)) { + return false; + } + return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) + ? mb_strlen($this->_data, '8bit') + : strlen($this->_data); } /** diff --git a/min/lib/Minify/Controller/Version1.php b/min/lib/Minify/Controller/Version1.php index 1861aab..5279d36 100644 --- a/min/lib/Minify/Controller/Version1.php +++ b/min/lib/Minify/Controller/Version1.php @@ -7,7 +7,7 @@ require_once 'Minify/Controller/Base.php'; /** - * Controller class for emulating version 1 of minify.php + * Controller class for emulating version 1 of minify.php (mostly a proof-of-concept) * * * Minify::serve('Version1'); diff --git a/min_unit_tests/_inc.php b/min_unit_tests/_inc.php index bf416d4..711009a 100644 --- a/min_unit_tests/_inc.php +++ b/min_unit_tests/_inc.php @@ -24,7 +24,7 @@ if ($min_errorLogger && true !== $min_errorLogger) { // custom logger error_reporting(E_ALL | E_STRICT); ini_set('display_errors', 1); -header('Content-Type: text/plain'); +header('Content-Type: text/plain;charset=utf-8'); $thisDir = dirname(__FILE__); @@ -47,4 +47,17 @@ function assertTrue($test, $message) return (bool)$test; } +/** + * Get number of bytes in a string regardless of mbstring.func_overload + * + * @param string $str + * @return int + */ +function countBytes($str) +{ + return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) + ? mb_strlen($str, '8bit') + : strlen($str); +} + ob_start(); \ No newline at end of file diff --git a/min_unit_tests/_test_files/js/issue132.js b/min_unit_tests/_test_files/js/issue132.js new file mode 100644 index 0000000..4ccfb15 --- /dev/null +++ b/min_unit_tests/_test_files/js/issue132.js @@ -0,0 +1,7 @@ +// from jQuery tablesorter +ts.addParser({ + id: "currency", + is: function(s) { + return /^[£$€?.]/.test(s); + }, +}); diff --git a/min_unit_tests/_test_files/js/issue132.min.js b/min_unit_tests/_test_files/js/issue132.min.js new file mode 100644 index 0000000..7215187 --- /dev/null +++ b/min_unit_tests/_test_files/js/issue132.min.js @@ -0,0 +1 @@ +ts.addParser({id:"currency",is:function(s){return /^[£$€?.]/.test(s);},}); \ No newline at end of file diff --git a/min_unit_tests/_test_files/minify/QueryString.js b/min_unit_tests/_test_files/minify/QueryString.js index e56a244..d926d64 100644 --- a/min_unit_tests/_test_files/minify/QueryString.js +++ b/min_unit_tests/_test_files/minify/QueryString.js @@ -1,4 +1,4 @@ -var MrClay = window.MrClay || {}; +var MrClay = window.MrClay || {}; /** * Simplified access to/manipulation of the query string diff --git a/min_unit_tests/_test_files/minify/email.js b/min_unit_tests/_test_files/minify/email.js index 761062d..b725379 100644 --- a/min_unit_tests/_test_files/minify/email.js +++ b/min_unit_tests/_test_files/minify/email.js @@ -1,4 +1,4 @@ -// http://mrclay.org/ +// http://mrclay.org/ (function(){ var reMailto = /^mailto:my_name_is_(\S+)_and_the_domain_is_(\S+)$/, diff --git a/min_unit_tests/test_HTTP_Encoder.php b/min_unit_tests/test_HTTP_Encoder.php index 106d146..665bdd2 100644 --- a/min_unit_tests/test_HTTP_Encoder.php +++ b/min_unit_tests/test_HTTP_Encoder.php @@ -94,10 +94,11 @@ function test_HTTP_Encoder() } // test compression of varied content (HTML,JS, & CSS) + $variedContent = file_get_contents($thisDir . '/_test_files/html/before.html') . file_get_contents($thisDir . '/_test_files/css/subsilver.css') . file_get_contents($thisDir . '/_test_files/js/jquery-1.2.3.js'); - $variedLength = strlen($variedContent); + $variedLength = countBytes($variedContent); $encodingTests = array( array('method' => 'deflate', 'inv' => 'gzinflate', 'exp' => 32157) @@ -111,7 +112,7 @@ function test_HTTP_Encoder() ,'method' => $test['method'] )); $e->encode(9); - $ret = strlen($e->getContent()); + $ret = countBytes($e->getContent()); // test uncompression $roundTrip = @call_user_func($test['inv'], $e->getContent()); @@ -154,15 +155,31 @@ function _gzdecode($data) // http://www.php.net/manual/en/function.gzdecode.php#82930 function _phpman_gzdecode($data, &$filename='', &$error='', $maxlength=null) { + $mbIntEnc = null; + $hasMbOverload = (function_exists('mb_strlen') + && (ini_get('mbstring.func_overload') !== '') + && ((int)ini_get('mbstring.func_overload') & 2)); + if ($hasMbOverload) { + $mbIntEnc = mb_internal_encoding(); + mb_internal_encoding('8bit'); + } + + $len = strlen($data); if ($len < 18 || strcmp(substr($data,0,2),"\x1f\x8b")) { $error = "Not in GZIP format."; + if ($mbIntEnc !== null) { + mb_internal_encoding($mbIntEnc); + } return null; // Not GZIP format (See RFC 1952) } $method = ord(substr($data,2,1)); // Compression method $flags = ord(substr($data,3,1)); // Flags if ($flags & 31 != $flags) { $error = "Reserved bits not allowed."; + if ($mbIntEnc !== null) { + mb_internal_encoding($mbIntEnc); + } return null; } // NOTE: $mtime may be negative (PHP integer limitations) @@ -176,11 +193,17 @@ function _phpman_gzdecode($data, &$filename='', &$error='', $maxlength=null) if ($flags & 4) { // 2-byte length prefixed EXTRA data in header if ($len - $headerlen - 2 < 8) { + if ($mbIntEnc !== null) { + mb_internal_encoding($mbIntEnc); + } return false; // invalid } $extralen = unpack("v",substr($data,8,2)); $extralen = $extralen[1]; if ($len - $headerlen - 2 - $extralen < 8) { + if ($mbIntEnc !== null) { + mb_internal_encoding($mbIntEnc); + } return false; // invalid } $extra = substr($data,10,$extralen); @@ -191,10 +214,16 @@ function _phpman_gzdecode($data, &$filename='', &$error='', $maxlength=null) if ($flags & 8) { // C-style string if ($len - $headerlen - 1 < 8) { + if ($mbIntEnc !== null) { + mb_internal_encoding($mbIntEnc); + } return false; // invalid } $filenamelen = strpos(substr($data,$headerlen),chr(0)); if ($filenamelen === false || $len - $headerlen - $filenamelen - 1 < 8) { + if ($mbIntEnc !== null) { + mb_internal_encoding($mbIntEnc); + } return false; // invalid } $filename = substr($data,$headerlen,$filenamelen); @@ -205,10 +234,16 @@ function _phpman_gzdecode($data, &$filename='', &$error='', $maxlength=null) if ($flags & 16) { // C-style string COMMENT data in header if ($len - $headerlen - 1 < 8) { + if ($mbIntEnc !== null) { + mb_internal_encoding($mbIntEnc); + } return false; // invalid } $commentlen = strpos(substr($data,$headerlen),chr(0)); if ($commentlen === false || $len - $headerlen - $commentlen - 1 < 8) { + if ($mbIntEnc !== null) { + mb_internal_encoding($mbIntEnc); + } return false; // Invalid header format } $comment = substr($data,$headerlen,$commentlen); @@ -218,6 +253,9 @@ function _phpman_gzdecode($data, &$filename='', &$error='', $maxlength=null) if ($flags & 2) { // 2-bytes (lowest order) of CRC32 on header present if ($len - $headerlen - 2 < 8) { + if ($mbIntEnc !== null) { + mb_internal_encoding($mbIntEnc); + } return false; // invalid } $calccrc = crc32(substr($data,0,$headerlen)) & 0xffff; @@ -225,6 +263,9 @@ function _phpman_gzdecode($data, &$filename='', &$error='', $maxlength=null) $headercrc = $headercrc[1]; if ($headercrc != $calccrc) { $error = "Header checksum failed."; + if ($mbIntEnc !== null) { + mb_internal_encoding($mbIntEnc); + } return false; // Bad header CRC } $headerlen += 2; @@ -238,6 +279,9 @@ function _phpman_gzdecode($data, &$filename='', &$error='', $maxlength=null) $bodylen = $len-$headerlen-8; if ($bodylen < 1) { // IMPLEMENTATION BUG! + if ($mbIntEnc !== null) { + mb_internal_encoding($mbIntEnc); + } return null; } $body = substr($data,$headerlen,$bodylen); @@ -250,6 +294,9 @@ function _phpman_gzdecode($data, &$filename='', &$error='', $maxlength=null) break; default: $error = "Unknown compression method."; + if ($mbIntEnc !== null) { + mb_internal_encoding($mbIntEnc); + } return false; } } // zero-byte body content is allowed @@ -259,7 +306,11 @@ function _phpman_gzdecode($data, &$filename='', &$error='', $maxlength=null) $lenOK = $isize == strlen($data); if (!$lenOK || !$crcOK) { $error = ( $lenOK ? '' : 'Length check FAILED. ') . ( $crcOK ? '' : 'Checksum FAILED.'); - return false; + $ret = false; } - return $data; + $ret = $data; + if ($mbIntEnc !== null) { + mb_internal_encoding($mbIntEnc); + } + return $ret; } \ No newline at end of file diff --git a/min_unit_tests/test_JSMin.php b/min_unit_tests/test_JSMin.php index 43a8302..ef3a978 100644 --- a/min_unit_tests/test_JSMin.php +++ b/min_unit_tests/test_JSMin.php @@ -10,45 +10,52 @@ function test_JSMin() $src = file_get_contents($thisDir . '/_test_files/js/before.js'); $minExpected = file_get_contents($thisDir . '/_test_files/js/before.min.js'); $minOutput = JSMin::minify($src); - $passed = assertTrue($minExpected == $minOutput, 'JSMin : Overall'); - if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { - echo "\n---Output: " .strlen($minOutput). " bytes\n\n{$minOutput}\n\n"; - echo "---Expected: " .strlen($minExpected). " bytes\n\n{$minExpected}\n\n"; - echo "---Source: " .strlen($src). " bytes\n\n{$src}\n\n\n"; + echo "\n---Output: " .countBytes($minOutput). " bytes\n\n{$minOutput}\n\n"; + echo "---Expected: " .countBytes($minExpected). " bytes\n\n{$minExpected}\n\n"; + echo "---Source: " .countBytes($src). " bytes\n\n{$src}\n\n\n"; } $src = file_get_contents($thisDir . '/_test_files/js/issue144.js'); $minExpected = file_get_contents($thisDir . '/_test_files/js/issue144.min.js'); $minOutput = JSMin::minify($src); - $passed = assertTrue($minExpected == $minOutput, 'JSMin : Don\'t minify files with + ++ (Issue 144)'); + if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) { + $src = file_get_contents($thisDir . '/_test_files/js/issue132.js'); + $minExpected = file_get_contents($thisDir . '/_test_files/js/issue132.min.js'); + $minOutput = JSMin::minify($src); + $passed = assertTrue($minExpected == $minOutput, 'JSMin : mbstring.func_overload shouldn\'t cause failure (Issue 132)'); + if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { + echo "\n---Output: " .countBytes($minOutput). " bytes\n\n{$minOutput}\n\n"; + echo "---Expected: " .countBytes($minExpected). " bytes\n\n{$minExpected}\n\n"; + echo "---Source: " .countBytes($src). " bytes\n\n{$src}\n\n\n"; + } + } $src = file_get_contents($thisDir . '/_test_files/js/issue74.js'); $minExpected = file_get_contents($thisDir . '/_test_files/js/issue74.min.js'); $minOutput = JSMin::minify($src); - $passed = assertTrue($minExpected == $minOutput, 'JSMin : Quotes in RegExp literals (Issue 74)'); - if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { - echo "\n---Output: " .strlen($minOutput). " bytes\n\n{$minOutput}\n\n"; - echo "---Expected: " .strlen($minExpected). " bytes\n\n{$minExpected}\n\n"; - echo "---Source: " .strlen($src). " bytes\n\n{$src}\n\n\n"; - + echo "\n---Output: " .countBytes($minOutput). " bytes\n\n{$minOutput}\n\n"; + echo "---Expected: " .countBytes($minExpected). " bytes\n\n{$minExpected}\n\n"; + echo "---Source: " .countBytes($src). " bytes\n\n{$src}\n\n\n"; + + // only test exceptions on this page test_JSMin_exception('"Hello' ,'Unterminated String' ,'JSMin_UnterminatedStringException' - ,"Unterminated String: \"Hello"); + ,"JSMin: Unterminated String at byte 6: \"Hello"); test_JSMin_exception("return /regexp\n}" ,'Unterminated RegExp' ,'JSMin_UnterminatedRegExpException' - ,"Unterminated RegExp: /regexp\n"); + ,"JSMin: Unterminated RegExp at byte 15: /regexp\n"); test_JSMin_exception("/* Comment " ,'Unterminated Comment' ,'JSMin_UnterminatedCommentException' - ,"Unterminated Comment: /* Comment "); + ,"JSMin: Unterminated comment at byte 11: /* Comment "); } } diff --git a/min_unit_tests/test_JSMinPlus.php b/min_unit_tests/test_JSMinPlus.php index eaae8bd..880f9eb 100644 --- a/min_unit_tests/test_JSMinPlus.php +++ b/min_unit_tests/test_JSMinPlus.php @@ -15,9 +15,9 @@ function test_JSMinPlus() $passed = assertTrue($minExpected == $minOutput, 'JSMinPlus : Conditional Comments'); if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { - echo "\n---Output: " .strlen($minOutput). " bytes\n\n{$minOutput}\n\n"; - echo "---Expected: " .strlen($minExpected). " bytes\n\n{$minExpected}\n\n"; - echo "---Source: " .strlen($src). " bytes\n\n{$src}\n\n\n"; + echo "\n---Output: " .countBytes($minOutput). " bytes\n\n{$minOutput}\n\n"; + echo "---Expected: " .countBytes($minExpected). " bytes\n\n{$minExpected}\n\n"; + echo "---Source: " .countBytes($src). " bytes\n\n{$src}\n\n\n"; } return; @@ -30,9 +30,9 @@ function test_JSMinPlus() $passed = assertTrue($minExpected == $minOutput, 'JSMinPlus : Overall'); if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { - echo "\n---Output: " .strlen($minOutput). " bytes\n\n{$minOutput}\n\n"; - echo "---Expected: " .strlen($minExpected). " bytes\n\n{$minExpected}\n\n"; - echo "---Source: " .strlen($src). " bytes\n\n{$src}\n\n\n"; + echo "\n---Output: " .countBytes($minOutput). " bytes\n\n{$minOutput}\n\n"; + echo "---Expected: " .countBytes($minExpected). " bytes\n\n{$minExpected}\n\n"; + echo "---Source: " .countBytes($src). " bytes\n\n{$src}\n\n\n"; } $src = file_get_contents($thisDir . '/_test_files/js/issue74.js'); @@ -42,39 +42,10 @@ function test_JSMinPlus() $passed = assertTrue($minExpected == $minOutput, 'JSMinPlus : Quotes in RegExp literals (Issue 74)'); if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { - echo "\n---Output: " .strlen($minOutput). " bytes\n\n{$minOutput}\n\n"; - echo "---Expected: " .strlen($minExpected). " bytes\n\n{$minExpected}\n\n"; - echo "---Source: " .strlen($src). " bytes\n\n{$src}\n\n\n"; - /* - test_JSMin_exception('"Hello' - ,'Unterminated String' - ,'JSMin_UnterminatedStringException' - ,"Unterminated String: '\"Hello'"); - test_JSMin_exception("return /regexp\n}" - ,'Unterminated RegExp' - ,'JSMin_UnterminatedRegExpException' - ,"Unterminated RegExp: '/regexp\n'"); - test_JSMin_exception("/* Comment " - ,'Unterminated Comment' - ,'JSMin_UnterminatedCommentException' - ,"Unterminated Comment: '/* Comment '"); - //*/ + echo "\n---Output: " .countBytes($minOutput). " bytes\n\n{$minOutput}\n\n"; + echo "---Expected: " .countBytes($minExpected). " bytes\n\n{$minExpected}\n\n"; + echo "---Source: " .countBytes($src). " bytes\n\n{$src}\n\n\n"; } } -/*function test_JSMin_exception($js, $label, $expClass, $expMessage) { - $eClass = $eMsg = ''; - try { - JSMin::minify($js); - } catch (Exception $e) { - $eClass = get_class($e); - $eMsg = $e->getMessage(); - } - $passed = assertTrue($eClass === $expClass && $eMsg === $expMessage, - 'JSMin : throw on ' . $label); - if (! $passed && __FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { - echo "\n ---" , $e, "\n\n"; - } -}//*/ - test_JSMinPlus(); diff --git a/min_unit_tests/test_Minify.php b/min_unit_tests/test_Minify.php index 95e5d64..f45be60 100644 --- a/min_unit_tests/test_Minify.php +++ b/min_unit_tests/test_Minify.php @@ -71,7 +71,7 @@ function test_Minify() 'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified), 'ETag' => "\"pub{$lastModified}\"", 'Cache-Control' => 'max-age=86400', - 'Content-Length' => strlen($content), + 'Content-Length' => countBytes($content), 'Content-Type' => 'application/x-javascript; charset=utf-8', ) ); @@ -170,9 +170,17 @@ function test_Minify() } } - // Test minifying CSS and responding with Etag/Last-Modified + // Test Issue 132 + if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) { + $output = Minify::serve('Files', array( + 'files' => array(dirname(__FILE__) . '/_test_files/js/issue132.js') + ,'quiet' => true + ,'encodeOutput' => false + )); + $passed = assertTrue($output['headers']['Content-Length'] == 77, 'Minify : Issue 132 : mbstring.func_overload shouldn\'t cause incorrect Content-Length'); + } - Minify::setCache(null); + // Test minifying CSS and responding with Etag/Last-Modified // don't allow conditional headers unset($_SERVER['HTTP_IF_NONE_MATCH'], $_SERVER['HTTP_IF_MODIFIED_SINCE']); @@ -188,7 +196,7 @@ function test_Minify() 'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified), 'ETag' => "\"pub{$lastModified}\"", 'Cache-Control' => 'max-age=0', - 'Content-Length' => strlen($expectedContent), + 'Content-Length' => countBytes($expectedContent), 'Content-Type' => 'text/css; charset=utf-8', ) ); diff --git a/min_unit_tests/test_Minify_CSS.php b/min_unit_tests/test_Minify_CSS.php index 96e1875..14f53a4 100644 --- a/min_unit_tests/test_Minify_CSS.php +++ b/min_unit_tests/test_Minify_CSS.php @@ -41,10 +41,10 @@ function test_CSS() $passed = assertTrue($minExpected === $minOutput, 'Minify_CSS : ' . $item); if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { - echo "\n---Output: " .strlen($minOutput). " bytes\n\n{$minOutput}\n\n"; + echo "\n---Output: " .countBytes($minOutput). " bytes\n\n{$minOutput}\n\n"; if (!$passed) { - echo "---Expected: " .strlen($minExpected). " bytes\n\n{$minExpected}\n\n"; - echo "---Source: " .strlen($src). " bytes\n\n{$src}\n\n\n"; + echo "---Expected: " .countBytes($minExpected). " bytes\n\n{$minExpected}\n\n"; + echo "---Source: " .countBytes($src). " bytes\n\n{$src}\n\n\n"; } } } diff --git a/min_unit_tests/test_Minify_CSS_UriRewriter.php b/min_unit_tests/test_Minify_CSS_UriRewriter.php index 55f09b0..7217a3d 100644 --- a/min_unit_tests/test_Minify_CSS_UriRewriter.php +++ b/min_unit_tests/test_Minify_CSS_UriRewriter.php @@ -20,9 +20,9 @@ function test_Minify_CSS_UriRewriter() $passed = assertTrue($expected === $actual, 'Minify_CSS_UriRewriter'); if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { echo "\n---Input:\n\n{$in}\n"; - echo "\n---Output: " .strlen($actual). " bytes\n\n{$actual}\n\n"; + echo "\n---Output: " .countBytes($actual). " bytes\n\n{$actual}\n\n"; if (!$passed) { - echo "---Expected: " .strlen($expected). " bytes\n\n{$expected}\n\n\n"; + echo "---Expected: " .countBytes($expected). " bytes\n\n{$expected}\n\n\n"; } // show debugging only when test run directly @@ -42,9 +42,9 @@ function test_Minify_CSS_UriRewriter() $passed = assertTrue($exp === $actual, 'Minify_CSS_UriRewriter : Issue 99'); if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { echo "\n---Input:\n\n{$in}\n"; - echo "\n---Output: " .strlen($actual). " bytes\n\n{$actual}\n\n"; + echo "\n---Output: " .countBytes($actual). " bytes\n\n{$actual}\n\n"; if (!$passed) { - echo "---Expected: " .strlen($exp). " bytes\n\n{$exp}\n\n\n"; + echo "---Expected: " .countBytes($exp). " bytes\n\n{$exp}\n\n\n"; } // show debugging only when test run directly diff --git a/min_unit_tests/test_Minify_Cache_APC.php b/min_unit_tests/test_Minify_Cache_APC.php index 74a1b2c..1761add 100644 --- a/min_unit_tests/test_Minify_Cache_APC.php +++ b/min_unit_tests/test_Minify_Cache_APC.php @@ -9,14 +9,14 @@ function test_Minify_Cache_APC() if (! function_exists('apc_store')) { return; } - $data = str_repeat(md5('testing'), 160); + $data = str_repeat(md5(time()) . 'í', 100); // 3400 bytes in UTF-8 $id = 'Minify_test_cache'; $cache = new Minify_Cache_APC(); assertTrue(true === $cache->store($id, $data), $prefix . 'store'); - assertTrue(strlen($data) === $cache->getSize($id), $prefix . 'getSize'); + assertTrue(countBytes($data) === $cache->getSize($id), $prefix . 'getSize'); assertTrue(true === $cache->isValid($id, $_SERVER['REQUEST_TIME'] - 10), $prefix . 'isValid'); diff --git a/min_unit_tests/test_Minify_Cache_File.php b/min_unit_tests/test_Minify_Cache_File.php index 2463e5b..00e2c26 100644 --- a/min_unit_tests/test_Minify_Cache_File.php +++ b/min_unit_tests/test_Minify_Cache_File.php @@ -7,7 +7,7 @@ function test_Minify_Cache_File() { global $minifyCachePath; - $data = str_repeat(md5(time()), 160); + $data = str_repeat(md5(time()) . 'í', 100); // 3400 bytes in UTF-8 $id = 'Minify_test_cache_noLock'; $prefix = 'Minify_Cache_File : '; @@ -16,8 +16,8 @@ function test_Minify_Cache_File() echo "NOTE: Minify_Cache_File : path is set to: '" . $cache->getPath() . "'.\n"; assertTrue(true === $cache->store($id, $data), $prefix . 'store'); - - assertTrue(strlen($data) === $cache->getSize($id), $prefix . 'getSize'); + + assertTrue(countBytes($data) === $cache->getSize($id), $prefix . 'getSize'); assertTrue(true === $cache->isValid($id, $_SERVER['REQUEST_TIME'] - 10), $prefix . 'isValid'); @@ -28,26 +28,26 @@ function test_Minify_Cache_File() assertTrue($data === $displayed, $prefix . 'display'); - assertTrue($data === $cache->fetch($id), $prefix . 'fetch'); - - // test with locks - - $id = 'Minify_test_cache_withLock'; - $cache = new Minify_Cache_File($minifyCachePath, true); - - assertTrue(true === $cache->store($id, $data), $prefix . 'store w/ lock'); - - assertTrue(strlen($data) === $cache->getSize($id), $prefix . 'getSize'); - - assertTrue(true === $cache->isValid($id, $_SERVER['REQUEST_TIME'] - 10), $prefix . 'isValid'); - - ob_start(); - $cache->display($id); - $displayed = ob_get_contents(); - ob_end_clean(); - - assertTrue($data === $displayed, $prefix . 'display w/ lock'); - + assertTrue($data === $cache->fetch($id), $prefix . 'fetch'); + + // test with locks + + $id = 'Minify_test_cache_withLock'; + $cache = new Minify_Cache_File($minifyCachePath, true); + + assertTrue(true === $cache->store($id, $data), $prefix . 'store w/ lock'); + + assertTrue(countBytes($data) === $cache->getSize($id), $prefix . 'getSize'); + + assertTrue(true === $cache->isValid($id, $_SERVER['REQUEST_TIME'] - 10), $prefix . 'isValid'); + + ob_start(); + $cache->display($id); + $displayed = ob_get_contents(); + ob_end_clean(); + + assertTrue($data === $displayed, $prefix . 'display w/ lock'); + assertTrue($data === $cache->fetch($id), $prefix . 'fetch w/ lock'); } diff --git a/min_unit_tests/test_Minify_Cache_Memcache.php b/min_unit_tests/test_Minify_Cache_Memcache.php index b0223aa..a8979c6 100644 --- a/min_unit_tests/test_Minify_Cache_Memcache.php +++ b/min_unit_tests/test_Minify_Cache_Memcache.php @@ -14,14 +14,14 @@ function test_Minify_Cache_Memcache() return; } - $data = str_repeat(md5('testing'), 160); + $data = str_repeat(md5(time()) . 'í', 100); // 3400 bytes in UTF-8 $id = 'Minify_test_cache'; $cache = new Minify_Cache_Memcache($mc); assertTrue(true === $cache->store($id, $data), $prefix . 'store'); - assertTrue(strlen($data) === $cache->getSize($id), $prefix . 'getSize'); + assertTrue(countBytes($data) === $cache->getSize($id), $prefix . 'getSize'); assertTrue(true === $cache->isValid($id, $_SERVER['REQUEST_TIME'] - 10), $prefix . 'isValid'); diff --git a/min_unit_tests/test_Minify_CommentPreserver.php b/min_unit_tests/test_Minify_CommentPreserver.php index e4e0e9f..537a738 100644 --- a/min_unit_tests/test_Minify_CommentPreserver.php +++ b/min_unit_tests/test_Minify_CommentPreserver.php @@ -19,9 +19,9 @@ function test_Minify_CommentPreserver() $actual = Minify_CommentPreserver::process($in, '_test_MCP_processor'); $passed = assertTrue($expected === $actual, 'Minify_CommentPreserver'); if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { - echo "\n---Output: " .strlen($actual). " bytes\n\n{$actual}\n\n"; + echo "\n---Output: " .countBytes($actual). " bytes\n\n{$actual}\n\n"; if (!$passed) { - echo "---Expected: " .strlen($expected). " bytes\n\n{$expected}\n\n\n"; + echo "---Expected: " .countBytes($expected). " bytes\n\n{$expected}\n\n\n"; } } } diff --git a/min_unit_tests/test_Minify_HTML.php b/min_unit_tests/test_Minify_HTML.php index 69aeff5..fbc2a02 100644 --- a/min_unit_tests/test_Minify_HTML.php +++ b/min_unit_tests/test_Minify_HTML.php @@ -23,12 +23,12 @@ function test_HTML() if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { if ($passed) { - echo "\n---Source: ", strlen($src), " bytes\n" - , "---Output: ", strlen($minOutput), " bytes (", round($time * 1000), " ms)\n\n{$minOutput}\n\n\n"; + echo "\n---Source: ", countBytes($src), " bytes\n" + , "---Output: ", countBytes($minOutput), " bytes (", round($time * 1000), " ms)\n\n{$minOutput}\n\n\n"; } else { - echo "\n---Output: ", strlen($minOutput), " bytes (", round($time * 1000), " ms)\n\n{$minOutput}\n\n" - , "---Expected: ", strlen($minExpected), " bytes\n\n{$minExpected}\n\n" - , "---Source: ", strlen($src), " bytes\n\n{$src}\n\n\n"; + echo "\n---Output: ", countBytes($minOutput), " bytes (", round($time * 1000), " ms)\n\n{$minOutput}\n\n" + , "---Expected: ", countBytes($minExpected), " bytes\n\n{$minExpected}\n\n" + , "---Source: ", countBytes($src), " bytes\n\n{$src}\n\n\n"; } } @@ -46,12 +46,12 @@ function test_HTML() if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { if ($passed) { - echo "\n---Source: ", strlen($src), " bytes\n" - , "---Output: ", strlen($minOutput), " bytes (", round($time * 1000), " ms)\n\n{$minOutput}\n\n\n"; + echo "\n---Source: ", countBytes($src), " bytes\n" + , "---Output: ", countBytes($minOutput), " bytes (", round($time * 1000), " ms)\n\n{$minOutput}\n\n\n"; } else { - echo "\n---Output: ", strlen($minOutput), " bytes (", round($time * 1000), " ms)\n\n{$minOutput}\n\n" - , "---Expected: ", strlen($minExpected), " bytes\n\n{$minExpected}\n\n" - , "---Source: ", strlen($src), " bytes\n\n{$src}\n\n\n"; + echo "\n---Output: ", countBytes($minOutput), " bytes (", round($time * 1000), " ms)\n\n{$minOutput}\n\n" + , "---Expected: ", countBytes($minExpected), " bytes\n\n{$minExpected}\n\n" + , "---Source: ", countBytes($src), " bytes\n\n{$src}\n\n\n"; } } } diff --git a/min_unit_tests/test_Minify_ImportProcessor.php b/min_unit_tests/test_Minify_ImportProcessor.php index 52b19ff..59abfca 100644 --- a/min_unit_tests/test_Minify_ImportProcessor.php +++ b/min_unit_tests/test_Minify_ImportProcessor.php @@ -27,18 +27,18 @@ function test_Minify_ImportProcessor() $passed = assertTrue($expected === $actual, 'ImportProcessor'); if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { - echo "\n---Output: " .strlen($actual). " bytes\n\n{$actual}\n\n"; + echo "\n---Output: " .countBytes($actual). " bytes\n\n{$actual}\n\n"; if (!$passed) { - echo "---Expected: " .strlen($expected). " bytes\n\n{$expected}\n\n\n"; + echo "---Expected: " .countBytes($expected). " bytes\n\n{$expected}\n\n\n"; } } - $expectedIncludes = array ( - realpath($linDir . '/input.css') - ,realpath($linDir . '/adjacent.css') - ,realpath($linDir . '/../css/styles.css') - ,realpath($linDir . '/1/tv.css') - ,realpath($linDir . '/1/adjacent.css') + $expectedIncludes = array ( + realpath($linDir . '/input.css') + ,realpath($linDir . '/adjacent.css') + ,realpath($linDir . '/../css/styles.css') + ,realpath($linDir . '/1/tv.css') + ,realpath($linDir . '/1/adjacent.css') ); $passed = assertTrue($expectedIncludes === Minify_ImportProcessor::$filesIncluded diff --git a/min_unit_tests/test_Minify_Lines.php b/min_unit_tests/test_Minify_Lines.php index c518fce..40d531f 100644 --- a/min_unit_tests/test_Minify_Lines.php +++ b/min_unit_tests/test_Minify_Lines.php @@ -26,9 +26,9 @@ function test_Lines() $passed = assertTrue($exp === $ret['content'], 'Minify_Lines'); if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { - echo "\n---Output: " .strlen($ret['content']). " bytes\n\n{$ret['content']}\n\n"; + echo "\n---Output: " .countBytes($ret['content']). " bytes\n\n{$ret['content']}\n\n"; if (!$passed) { - echo "---Expected: " .strlen($exp). " bytes\n\n{$exp}\n\n\n"; + echo "---Expected: " .countBytes($exp). " bytes\n\n{$exp}\n\n\n"; } } } From b41a908dbf9341e5d3b7393c196ab1511540b905 Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Sun, 16 May 2010 16:19:35 +0000 Subject: [PATCH 08/25] Work on better HTML helper --- min/builder/test.php | 43 +++++ min/lib/Minify/HTML/Helper.php | 184 +++++++++++++++++++++ min/utils.php | 10 +- min_unit_tests/test_Minify_HTML_Helper.php | 48 ++++++ 4 files changed, 280 insertions(+), 5 deletions(-) create mode 100644 min/builder/test.php create mode 100644 min/lib/Minify/HTML/Helper.php create mode 100644 min_unit_tests/test_Minify_HTML_Helper.php diff --git a/min/builder/test.php b/min/builder/test.php new file mode 100644 index 0000000..08fb498 --- /dev/null +++ b/min/builder/test.php @@ -0,0 +1,43 @@ +realpath(DOCUMENT_ROOT) failed. You may need " + . "to set \$min_documentRoot manually (hopefully realpath() is not " + . "broken in your environment).

"; + } + if (0 !== strpos(realpath(__FILE__), realpath($_SERVER['DOCUMENT_ROOT']))) { + echo "

DOCUMENT_ROOT doesn't contain this file. You may " + . " need to set \$min_documentRoot manually

"; + } + if (isset($_SERVER['SUBDOMAIN_DOCUMENT_ROOT'])) { + echo "

\$_SERVER['SUBDOMAIN_DOCUMENT_ROOT'] is set. " + . "You may need to set \$min_documentRoot to this in config.php

"; + } + +} + +//*/ \ No newline at end of file diff --git a/min/lib/Minify/HTML/Helper.php b/min/lib/Minify/HTML/Helper.php new file mode 100644 index 0000000..b06631f --- /dev/null +++ b/min/lib/Minify/HTML/Helper.php @@ -0,0 +1,184 @@ + + */ +class Minify_HTML_Helper { + public $rewriteWorks = true; + public $minAppUri = '/min'; + + public static function groupUri($key, $farExpires = true, $debug = false, $charset = 'UTF-8') + { + $h = new self; + $h->setGroup($key, $farExpires); + $uri = $h->getRawUri($farExpires, $debug); + return htmlspecialchars($uri, ENT_QUOTES, $charset); + } + + public static function filesUri($files, $farExpires = true, $debug = false, $charset = 'UTF-8') + { + $h = new self; + $h->setFiles($files, $farExpires); + $uri = $h->getRawUri($farExpires, $debug); + return htmlspecialchars($uri, ENT_QUOTES, $charset); + } + + /* + * Get URI (not html-escaped) to minify a group/set of files + */ + public function getRawUri($farExpires = true, $debug = false) + { + $path = rtrim($this->minAppUri, '/') . '/'; + if (! $this->rewriteWorks) { + $path .= '?'; + } + if (null === $this->_groupKey) { + // @todo: implement shortest uri + $path .= "f=" . $this->_fileList; + } else { + $path .= "g=" . $this->_groupKey; + } + if ($debug) { + $path .= "&debug"; + } elseif ($farExpires && $this->_lastModified) { + $path .= "&" . $this->_lastModified; + } + return $path; + } + + public function setFiles($files, $checkLastModified = true) + { + $this->_groupKey = null; + if ($checkLastModified) { + $this->_sniffLastModified($files); + } + // normalize paths like in /min/f= + foreach ($files as $k => $file) { + if (0 === strpos($file, '//')) { + $file = substr($file, 2); + } elseif (0 === strpos($file, '/') + || 1 === strpos($file, ':\\')) { + $file = substr($file, strlen($_SERVER['DOCUMENT_ROOT']) + 1); + } + $file = strtr($file, '\\', '/'); + $files[$k] = $file; + } + $this->_fileList = implode(',', $files); + } + + public function setGroup($key, $checkLastModified = true) + { + $this->_groupKey = $key; + if ($checkLastModified) { + $gcFile = (null === $this->_groupsConfigFile) + ? dirname(dirname(dirname(dirname(__FILE__)))) . '/groupsConfig.php' + : $this->_groupsConfigFile; + if (is_file($gcFile)) { + $gc = (require $gcFile); + if (isset($gc[$key])) { + $this->_sniffLastModified($gc[$key]); + } + } + } + } + + public function setPathToMin($path) + { + if (0 === strpos($path, '.')) { + // relative path + $path = dirname(__FILE__) . "/" . $path; + } + $file = realpath(rtrim($path, '/\\') . '/groupsConfig.php'); + if (! $file) { + return false; + } + $this->_groupsConfigFile = $file; + return true; + } + + + protected $_groupKey = null; // if present, URI will be like g=... + protected $_fileList = ''; + protected $_groupsConfigArray = array(); + protected $_groupsConfigFile = null; + protected $_lastModified = null; + + protected function _sniffLastModified($sources) + { + $max = 0; + foreach ((array)$sources as $source) { + if ($source instanceof Minify_Source) { + $max = max($max, $source->lastModified); + } elseif (is_string($source)) { + if (0 === strpos($source, '//')) { + $source = $_SERVER['DOCUMENT_ROOT'] . substr($source, 1); + } + if (is_file($source)) { + $max = max($max, filemtime($source)); + } + } + } + $this->_lastModified = $max; + } + + /** + * In a given array of strings, find the character they all have at + * a particular index + * @param Array arr array of strings + * @param Number pos index to check + * @return mixed a common char or '' if any do not match + */ + protected static function _getCommonCharAtPos($arr, $pos) { + $l = count($arr); + $c = $arr[0][$pos]; + if ($c === '' || $l === 1) + return $c; + for ($i = 1; $i < l; ++$i) + if ($arr[$i][$pos] !== $c) + return ''; + return $c; + } + + /** + * Get the shortest URI to minify the set of source files + * @param Array sources URIs + *//* + ,getBestUri : function (sources) { + var pos = 0 + ,base = '' + ,c; + while (true) { + c = MUB.getCommonCharAtPos(sources, pos); + if (c === '') + break; + else + base += c; + ++pos; + } + base = base.replace(/[^\/]+$/, ''); + var uri = MUB._minRoot + 'f=' + sources.join(','); + if (base.charAt(base.length - 1) === '/') { + // we have a base dir! + var basedSources = sources + ,i + ,l = sources.length; + for (i = 0; i < l; ++i) { + basedSources[i] = sources[i].substr(base.length); + } + base = base.substr(0, base.length - 1); + var bUri = MUB._minRoot + 'b=' + base + '&f=' + basedSources.join(','); + //window.console && console.log([uri, bUri]); + uri = uri.length < bUri.length + ? uri + : bUri; + } + return uri; + }//*/ +} diff --git a/min/utils.php b/min/utils.php index c735941..018020a 100644 --- a/min/utils.php +++ b/min/utils.php @@ -1,6 +1,6 @@ uri( '/' . basename(dirname(__FILE__)) . $path - ,$forceAmpersand + ,$modRewriteWorking ); } diff --git a/min_unit_tests/test_Minify_HTML_Helper.php b/min_unit_tests/test_Minify_HTML_Helper.php new file mode 100644 index 0000000..e2dfff1 --- /dev/null +++ b/min_unit_tests/test_Minify_HTML_Helper.php @@ -0,0 +1,48 @@ +lastModified == filemtime($file1) + ,'Minify_Build : single file path'); + + $b = new Minify_Build(array($file1, $file2)); + assertTrue($maxTime == $b->lastModified + ,'Minify_Build : multiple file paths'); + + require_once 'Minify.php'; + $b = new Minify_Build(array( + $file1 + ,new Minify_Source(array('filepath' => $file2)) + )); + + assertTrue($maxTime == $b->lastModified + ,'Minify_Build : file path and a Minify_Source'); + assertTrue($b->uri('/path') == "/path?{$maxTime}" + ,'Minify_Build : uri() with no querystring'); + assertTrue($b->uri('/path?hello') == "/path?hello&{$maxTime}" + ,'Minify_Build : uri() with existing querystring'); + //*/ +} + +test_Minify_HTML_Helper(); \ No newline at end of file From f2ebbeb5261da685e9d63360041d48f903425a75 Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Mon, 17 May 2010 02:30:51 +0000 Subject: [PATCH 09/25] More work on HTML helpers --- min/lib/Minify/HTML/Helper.php | 169 +++++++++--------- min/utils.php | 130 ++++++-------- .../_test_files/htmlHelper_groupsConfig.php | 8 + min_unit_tests/test_Minify_HTML_Helper.php | 74 +++++--- min_unit_tests/test_all.php | 1 + 5 files changed, 202 insertions(+), 180 deletions(-) create mode 100644 min_unit_tests/_test_files/htmlHelper_groupsConfig.php diff --git a/min/lib/Minify/HTML/Helper.php b/min/lib/Minify/HTML/Helper.php index b06631f..807fdc9 100644 --- a/min/lib/Minify/HTML/Helper.php +++ b/min/lib/Minify/HTML/Helper.php @@ -13,25 +13,46 @@ class Minify_HTML_Helper { public $rewriteWorks = true; public $minAppUri = '/min'; + public $groupsConfigFile = ''; - public static function groupUri($key, $farExpires = true, $debug = false, $charset = 'UTF-8') + /* + * Get an HTML-escaped Minify URI for a group or set of files + * + * @param mixed $keyOrFiles a group key or array of filepaths/URIs + * @param array $opts options: + * 'farExpires' : (default true) append a modified timestamp for cache revving + * 'debug' : (default false) append debug flag + * 'charset' : (default 'UTF-8') for htmlspecialchars + * 'minAppUri' : (default '/min') URI of min directory + * 'rewriteWorks' : (default true) does mod_rewrite work in min app? + * 'groupsConfigFile' : specify if different + * @return string + */ + public static function getUri($keyOrFiles, $opts = array()) { + $opts = array_merge(array( // default options + 'farExpires' => true + ,'debug' => false + ,'charset' => 'UTF-8' + ,'minAppUri' => '/min' + ,'rewriteWorks' => true + ,'groupsConfigFile' => '' + ), $opts); $h = new self; - $h->setGroup($key, $farExpires); - $uri = $h->getRawUri($farExpires, $debug); - return htmlspecialchars($uri, ENT_QUOTES, $charset); - } - - public static function filesUri($files, $farExpires = true, $debug = false, $charset = 'UTF-8') - { - $h = new self; - $h->setFiles($files, $farExpires); - $uri = $h->getRawUri($farExpires, $debug); - return htmlspecialchars($uri, ENT_QUOTES, $charset); + $h->minAppUri = $opts['minAppUri']; + $h->rewriteWorks = $opts['rewriteWorks']; + $h->groupsConfigFile = $opts['groupsConfigFile']; + if (is_array($keyOrFiles)) { + $h->setFiles($keyOrFiles, $opts['farExpires']); + } else { + $h->setGroup($keyOrFiles, $opts['farExpires']); + } + $uri = $h->getRawUri($opts['farExpires'], $opts['debug']); + return htmlspecialchars($uri, ENT_QUOTES, $opts['charset']); } /* - * Get URI (not html-escaped) to minify a group/set of files + * Get non-HTML-escaped URI to minify the specified files */ public function getRawUri($farExpires = true, $debug = false) { @@ -41,7 +62,7 @@ class Minify_HTML_Helper { } if (null === $this->_groupKey) { // @todo: implement shortest uri - $path .= "f=" . $this->_fileList; + $path = self::_getShortestUri($this->_filePaths, $path); } else { $path .= "g=" . $this->_groupKey; } @@ -57,7 +78,7 @@ class Minify_HTML_Helper { { $this->_groupKey = null; if ($checkLastModified) { - $this->_sniffLastModified($files); + $this->_lastModified = self::getLastModified($files); } // normalize paths like in /min/f= foreach ($files as $k => $file) { @@ -70,51 +91,30 @@ class Minify_HTML_Helper { $file = strtr($file, '\\', '/'); $files[$k] = $file; } - $this->_fileList = implode(',', $files); + $this->_filePaths = $files; } public function setGroup($key, $checkLastModified = true) { $this->_groupKey = $key; if ($checkLastModified) { - $gcFile = (null === $this->_groupsConfigFile) - ? dirname(dirname(dirname(dirname(__FILE__)))) . '/groupsConfig.php' - : $this->_groupsConfigFile; - if (is_file($gcFile)) { - $gc = (require $gcFile); + if (! $this->groupsConfigFile) { + $this->groupsConfigFile = dirname(dirname(dirname(dirname(__FILE__)))) . '/groupsConfig.php'; + } + if (is_file($this->groupsConfigFile)) { + $gc = (require $this->groupsConfigFile); if (isset($gc[$key])) { - $this->_sniffLastModified($gc[$key]); + $this->_lastModified = self::getLastModified($gc[$key]); } } } } - - public function setPathToMin($path) + + public static function getLastModified($sources, $lastModified = 0) { - if (0 === strpos($path, '.')) { - // relative path - $path = dirname(__FILE__) . "/" . $path; - } - $file = realpath(rtrim($path, '/\\') . '/groupsConfig.php'); - if (! $file) { - return false; - } - $this->_groupsConfigFile = $file; - return true; - } - - - protected $_groupKey = null; // if present, URI will be like g=... - protected $_fileList = ''; - protected $_groupsConfigArray = array(); - protected $_groupsConfigFile = null; - protected $_lastModified = null; - - protected function _sniffLastModified($sources) - { - $max = 0; + $max = $lastModified; foreach ((array)$sources as $source) { - if ($source instanceof Minify_Source) { + if (is_object($source) && isset($source->lastModified)) { $max = max($max, $source->lastModified); } elseif (is_string($source)) { if (0 === strpos($source, '//')) { @@ -125,14 +125,20 @@ class Minify_HTML_Helper { } } } - $this->_lastModified = $max; + return $max; } + protected $_groupKey = null; // if present, URI will be like g=... + protected $_filePaths = array(); + protected $_lastModified = null; + + /** * In a given array of strings, find the character they all have at * a particular index - * @param Array arr array of strings - * @param Number pos index to check + * + * @param array $arr array of strings + * @param int $pos index to check * @return mixed a common char or '' if any do not match */ protected static function _getCommonCharAtPos($arr, $pos) { @@ -140,7 +146,7 @@ class Minify_HTML_Helper { $c = $arr[0][$pos]; if ($c === '' || $l === 1) return $c; - for ($i = 1; $i < l; ++$i) + for ($i = 1; $i < $l; ++$i) if ($arr[$i][$pos] !== $c) return ''; return $c; @@ -148,37 +154,40 @@ class Minify_HTML_Helper { /** * Get the shortest URI to minify the set of source files - * @param Array sources URIs - *//* - ,getBestUri : function (sources) { - var pos = 0 - ,base = '' - ,c; + * + * @param array $paths root-relative URIs of files + * @param string $minRoot root-relative URI of the "min" application + */ + protected static function _getShortestUri($paths, $minRoot = '/min/') { + $pos = 0; + $base = ''; + $c; while (true) { - c = MUB.getCommonCharAtPos(sources, pos); - if (c === '') + $c = self::_getCommonCharAtPos($paths, $pos); + if ($c === '') { break; - else - base += c; - ++pos; - } - base = base.replace(/[^\/]+$/, ''); - var uri = MUB._minRoot + 'f=' + sources.join(','); - if (base.charAt(base.length - 1) === '/') { - // we have a base dir! - var basedSources = sources - ,i - ,l = sources.length; - for (i = 0; i < l; ++i) { - basedSources[i] = sources[i].substr(base.length); + } else { + $base .= $c; } - base = base.substr(0, base.length - 1); - var bUri = MUB._minRoot + 'b=' + base + '&f=' + basedSources.join(','); - //window.console && console.log([uri, bUri]); - uri = uri.length < bUri.length - ? uri - : bUri; + ++$pos; } - return uri; - }//*/ + $base = preg_replace('@[^/]+$@', '', $base); + $uri = $minRoot . 'f=' . implode(',', $paths); + + if (substr($base, -1) === '/') { + // we have a base dir! + $basedPaths = $paths; + $l = count($paths); + for ($i = 0; $i < $l; ++$i) { + $basedPaths[$i] = substr($paths[$i], strlen($base)); + } + $base = substr($base, 0, strlen($base) - 1); + $bUri = $minRoot . 'b=' . $base . '&f=' . implode(',', $basedPaths); + + $uri = strlen($uri) < strlen($bUri) + ? $uri + : $bUri; + } + return $uri; + } } diff --git a/min/utils.php b/min/utils.php index 018020a..a18d7ad 100644 --- a/min/utils.php +++ b/min/utils.php @@ -2,89 +2,73 @@ /** * Utility functions for generating URIs in HTML files * - * Before including this file, /min/lib must be in your include_path. - * * @package Minify */ -require_once 'Minify/Build.php'; +require_once dirname(__FILE__) . '/lib/Minify/HTML/Helper.php'; -/** - * Get a timestamped URI to a minified resource using the default Minify install +/* + * Get an HTML-escaped Minify URI for a group or set of files. By default, URIs + * will contain timestamps to allow far-future Expires headers. * * - * - * + * + * + * * * - * If you do not want ampersands as HTML entities, set Minify_Build::$ampersand = "&" - * before using this function. - * - * @param string $group a key from groupsConfig.php - * @param boolean $modRewriteWorking (default false) Set to true if the RewriteRule - * directives in .htaccess are functional. This will remove the "?" from URIs, making them - * more cacheable by proxies. + * @param mixed $keyOrFiles a group key or array of file paths/URIs + * @param array $opts options: + * 'farExpires' : (default true) append a modified timestamp for cache revving + * 'debug' : (default false) append debug flag + * 'charset' : (default 'UTF-8') for htmlspecialchars + * 'minAppUri' : (default '/min') URI of min directory + * 'rewriteWorks' : (default true) does mod_rewrite work in min app? + * 'groupsConfigFile' : specify if different * @return string - */ -function Minify_groupUri($group, $modRewriteWorking = false) -{ - $path = $modRewriteWorking - ? "/g={$group}" - : "/?g={$group}"; - return _Minify_getBuild($group)->uri( - '/' . basename(dirname(__FILE__)) . $path - ,$modRewriteWorking - ); -} - - -/** - * Get the last modification time of the source js/css files used by Minify to - * build the page. - * - * If you're caching the output of Minify_groupUri(), you'll want to rebuild - * the cache if it's older than this timestamp. - * - * - * // simplistic HTML cache system - * $file = '/path/to/cache/file'; - * if (! file_exists($file) || filemtime($file) < Minify_groupsMtime(array('js', 'css'))) { - * // (re)build cache - * $page = buildPage(); // this calls Minify_groupUri() for js and css - * file_put_contents($file, $page); - * echo $page; - * exit(); - * } - * readfile($file); - * - * - * @param array $groups an array of keys from groupsConfig.php - * @return int Unix timestamp of the latest modification - */ -function Minify_groupsMtime($groups) -{ - $max = 0; - foreach ((array)$groups as $group) { - $max = max($max, _Minify_getBuild($group)->lastModified); - } - return $max; -} - -/** - * @param string $group a key from groupsConfig.php - * @return Minify_Build - * @private */ -function _Minify_getBuild($group) +function Minify_getUri($keyOrFiles, $opts = array()) { - static $builds = array(); - static $gc = false; - if (false === $gc) { - $gc = (require dirname(__FILE__) . '/groupsConfig.php'); - } - if (! isset($builds[$group])) { - $builds[$group] = new Minify_Build($gc[$group]); - } - return $builds[$group]; + return Minify_HTML_Helper::getUri($keyOrFiles, $opts); +} + + +/** + * Get the last modification time of several source js/css files. If you're + * caching the output of Minify_getUri(), you might want to know if one of the + * dependent source files has changed so you can update the HTML. + * + * Since this makes a bunch of stat() calls, you might not want to check this + * on every request. + * + * @param array $keysAndFiles group keys and/or file paths/URIs. + * @return int latest modification time of all given keys/files + */ +function Minify_mtime($keysAndFiles, $groupsConfigFile = null) +{ + $gc = null; + if (! $groupsConfigFile) { + $groupsConfigFile = dirname(__FILE__) . '/groupsConfig.php'; + } + $sources = array(); + foreach ($keysAndFiles as $keyOrFile) { + if (is_object($keyOrFile) + || 0 === strpos($keyOrFile, '/') + || 1 === strpos($keyOrFile, ':\\')) { + // a file/source obj + $sources[] = $keyOrFile; + } else { + if (! $gc) { + $gc = (require $groupsConfigFile); + } + foreach ($gc[$keyOrFile] as $source) { + $sources[] = $source; + } + } + } + return Minify_HTML_Helper::getLastModified($sources); } diff --git a/min_unit_tests/_test_files/htmlHelper_groupsConfig.php b/min_unit_tests/_test_files/htmlHelper_groupsConfig.php new file mode 100644 index 0000000..7f5153d --- /dev/null +++ b/min_unit_tests/_test_files/htmlHelper_groupsConfig.php @@ -0,0 +1,8 @@ + array( + '//_test_files/css/paths_prepend.css' + ,'//_test_files/css/styles.css' + ) +); diff --git a/min_unit_tests/test_Minify_HTML_Helper.php b/min_unit_tests/test_Minify_HTML_Helper.php index e2dfff1..19abb8b 100644 --- a/min_unit_tests/test_Minify_HTML_Helper.php +++ b/min_unit_tests/test_Minify_HTML_Helper.php @@ -7,42 +7,62 @@ function test_Minify_HTML_Helper() { global $thisDir; + $realDocRoot = $_SERVER['DOCUMENT_ROOT']; + $_SERVER['DOCUMENT_ROOT'] = $thisDir; + $file1 = $thisDir . '/_test_files/css/paths_prepend.css'; $file2 = $thisDir . '/_test_files/css/styles.css'; $maxTime = max(filemtime($file1), filemtime($file2)); - $path1 = '/' . dirname($_SERVER['SCRIPT_NAME']) . '/_test_files/css/paths_prepend.css'; - $path2 = '/' . dirname($_SERVER['SCRIPT_NAME']) . '/_test_files/css/styles.css'; + $uri1 = '//_test_files/css/paths_prepend.css'; + $uri2 = '//_test_files/css/styles.css'; - echo Minify_HTML_Helper::filesUri(array($path1, $path2)) . "\n"; - echo Minify_HTML_Helper::filesUri(array($file1, $file2)) . "\n"; - echo Minify_HTML_Helper::groupUri('notRealGroup') . "\n"; + $expected = "/min/b=_test_files/css&f=paths_prepend.css,styles.css&{$maxTime}"; + $actual = Minify_HTML_Helper::getUri(array($uri1, $uri2)); + $passed = assertTrue($actual === $expected, 'Minify_HTML_Helper : given URIs'); + $expected = "/min/b=_test_files/css&f=paths_prepend.css,styles.css&{$maxTime}"; + $actual = Minify_HTML_Helper::getUri(array($file1, $file2)); + $passed = assertTrue($actual === $expected, 'Minify_HTML_Helper : given filepaths'); - //echo Minify_HTML_Helper::filesUri(array($file1, $file2)); + $expected = "/min/g=notRealGroup&debug"; + $actual = Minify_HTML_Helper::getUri('notRealGroup', array('debug' => true)); + $passed = assertTrue($actual === $expected, 'Minify_HTML_Helper : non-existent group & debug'); - /* - $b = new Minify_Build($file1); - assertTrue($b->lastModified == filemtime($file1) - ,'Minify_Build : single file path'); - - $b = new Minify_Build(array($file1, $file2)); - assertTrue($maxTime == $b->lastModified - ,'Minify_Build : multiple file paths'); - - require_once 'Minify.php'; - $b = new Minify_Build(array( - $file1 - ,new Minify_Source(array('filepath' => $file2)) + $expected = "/myApp/min/?g=css&{$maxTime}"; + $actual = Minify_HTML_Helper::getUri('css', array( + 'rewriteWorks' => false + ,'minAppUri' => '/myApp/min/' + ,'groupsConfigFile' => $thisDir . '/_test_files/htmlHelper_groupsConfig.php' )); - - assertTrue($maxTime == $b->lastModified - ,'Minify_Build : file path and a Minify_Source'); - assertTrue($b->uri('/path') == "/path?{$maxTime}" - ,'Minify_Build : uri() with no querystring'); - assertTrue($b->uri('/path?hello') == "/path?hello&{$maxTime}" - ,'Minify_Build : uri() with existing querystring'); - //*/ + $passed = assertTrue($actual === $expected, 'Minify_HTML_Helper : existing group'); + + $utilsFile = dirname(dirname(__FILE__)) . '/min/utils.php'; + if (is_file($utilsFile)) { + require_once $utilsFile; + + $fiveSecondsAgo = $_SERVER['REQUEST_TIME'] - 5; + $obj = new stdClass(); + $obj->lastModified = $fiveSecondsAgo; + + $output = Minify_mtime(array( + $uri1 + ,$uri2 + ,$obj + )); + $passed = assertTrue($output === $fiveSecondsAgo, 'utils.php : Minify_mtime w/ files & obj'); + + $obj = new stdClass(); + $obj->lastModified = strtotime('2000-01-01'); + $output = Minify_mtime(array( + $obj + ,'css' + ), $thisDir . '/_test_files/htmlHelper_groupsConfig.php'); + $passed = assertTrue($output === $maxTime, 'utils.php : Minify_mtime w/ obj & group'); + + } + + $_SERVER['DOCUMENT_ROOT'] = $realDocRoot; } test_Minify_HTML_Helper(); \ No newline at end of file diff --git a/min_unit_tests/test_all.php b/min_unit_tests/test_all.php index 3b7921d..7defc64 100644 --- a/min_unit_tests/test_all.php +++ b/min_unit_tests/test_all.php @@ -2,6 +2,7 @@ require 'test_Minify.php'; require 'test_Minify_Build.php'; +require 'test_Minify_HTML_Helper.php'; require 'test_Minify_Cache_APC.php'; require 'test_Minify_Cache_File.php'; require 'test_Minify_Cache_Memcache.php'; From a8f8897fbbb44073dbe9f56cfa79c3874c903b51 Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Mon, 17 May 2010 04:31:48 +0000 Subject: [PATCH 10/25] Work on Issue 160, Issue 169, Issue 178, Issue 68 --- min/groupsConfig.php | 1 - min/lib/Minify/Cache/File.php | 82 +++++++++------- min/lib/Minify/Controller/Base.php | 23 +++++ min/lib/Minify/Controller/MinApp.php | 142 ++++++++++++++++++++------- min/lib/Minify/Logger.php | 2 + 5 files changed, 177 insertions(+), 73 deletions(-) diff --git a/min/groupsConfig.php b/min/groupsConfig.php index 4062d1d..c900776 100644 --- a/min/groupsConfig.php +++ b/min/groupsConfig.php @@ -14,5 +14,4 @@ return array( // 'js' => array('//js/file1.js', '//js/file2.js'), // 'css' => array('//css/file1.css', '//css/file2.css'), - ); \ No newline at end of file diff --git a/min/lib/Minify/Cache/File.php b/min/lib/Minify/Cache/File.php index 8744a7e..9603303 100644 --- a/min/lib/Minify/Cache/File.php +++ b/min/lib/Minify/Cache/File.php @@ -11,11 +11,11 @@ class Minify_Cache_File { if (! $path) { require_once 'Solar/Dir.php'; $path = rtrim(Solar_Dir::tmp(), DIRECTORY_SEPARATOR); - } + } $this->_locking = $fileLocking; $this->_path = $path; } - + /** * Write data to cache. * @@ -27,20 +27,23 @@ class Minify_Cache_File { */ public function store($id, $data) { - $flag = $this->_locking - ? LOCK_EX - : null; - if (is_file($this->_path . '/' . $id)) { - @unlink($this->_path . '/' . $id); - } - if (! @file_put_contents($this->_path . '/' . $id, $data, $flag)) { - return false; + $flag = $this->_locking + ? LOCK_EX + : null; + $file = $this->_path . '/' . $id; + if (is_file($file)) { + @unlink($file); + } + if (! @file_put_contents($file, $data, $flag)) { + $this->_log("Minify_Cache_File: Write failed to '$file'"); + return false; + } + // write control + if ($data !== $this->fetch($id)) { + @unlink($file); + $this->_log("Minify_Cache_File: Post-write read failed for '$file'"); + return false; } - // write control - if ($data !== $this->fetch($id)) { - @unlink($file); - return false; - } return true; } @@ -78,15 +81,15 @@ class Minify_Cache_File { */ public function display($id) { - if ($this->_locking) { - $fp = fopen($this->_path . '/' . $id, 'rb'); - flock($fp, LOCK_SH); - fpassthru($fp); - flock($fp, LOCK_UN); - fclose($fp); - } else { - readfile($this->_path . '/' . $id); - } + if ($this->_locking) { + $fp = fopen($this->_path . '/' . $id, 'rb'); + flock($fp, LOCK_SH); + fpassthru($fp); + flock($fp, LOCK_UN); + fclose($fp); + } else { + readfile($this->_path . '/' . $id); + } } /** @@ -98,15 +101,15 @@ class Minify_Cache_File { */ public function fetch($id) { - if ($this->_locking) { - $fp = fopen($this->_path . '/' . $id, 'rb'); - flock($fp, LOCK_SH); - $ret = stream_get_contents($fp); - flock($fp, LOCK_UN); - fclose($fp); - return $ret; - } else { - return file_get_contents($this->_path . '/' . $id); + if ($this->_locking) { + $fp = fopen($this->_path . '/' . $id, 'rb'); + flock($fp, LOCK_SH); + $ret = stream_get_contents($fp); + flock($fp, LOCK_UN); + fclose($fp); + return $ret; + } else { + return file_get_contents($this->_path . '/' . $id); } } @@ -119,7 +122,18 @@ class Minify_Cache_File { { return $this->_path; } + + /** + * Send message to the Minify logger + * @param string $msg + * @return null + */ + protected function _log($msg) + { + require_once 'Minify/Logger.php'; + Minify_Logger::log($msg); + } private $_path = null; - private $_locking = null; + private $_locking = null; } diff --git a/min/lib/Minify/Controller/Base.php b/min/lib/Minify/Controller/Base.php index 1e59ed5..3bebecf 100644 --- a/min/lib/Minify/Controller/Base.php +++ b/min/lib/Minify/Controller/Base.php @@ -118,6 +118,8 @@ abstract class Minify_Controller_Base { * be in subdirectories of these directories. * * @return bool file is safe + * + * @deprecated use checkAllowDirs, checkNotHidden instead */ public static function _fileIsSafe($file, $safeDirs) { @@ -135,7 +137,28 @@ abstract class Minify_Controller_Base { list($revExt) = explode('.', strrev($base)); return in_array(strrev($revExt), array('js', 'css', 'html', 'txt')); } + + public static function checkAllowDirs($file, $allowDirs, $uri) + { + foreach ((array)$allowDirs as $allowDir) { + if (strpos($file, $allowDir) === 0) { + return true; + } + } + throw new Exception("File '$file' is outside \$allowDirs. If the path is" + . " resolved via an alias/symlink, look into the \$min_symlinks option." + . " E.g. \$min_symlinks = array('/" . dirname($uri) . "' => '" . dirname($file) . "');"); + } + + public static function checkNotHidden($file) + { + $b = basename($file); + if (0 === strpos($b, '.')) { + throw new Exception("Filename '$b' starts with period (may be hidden)"); + } + } + /** * @var array instances of Minify_Source, which provide content and * any individual minification needs. diff --git a/min/lib/Minify/Controller/MinApp.php b/min/lib/Minify/Controller/MinApp.php index 98b4e2c..ec7d8e0 100644 --- a/min/lib/Minify/Controller/MinApp.php +++ b/min/lib/Minify/Controller/MinApp.php @@ -34,38 +34,56 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { ); unset($options['minApp']); $sources = array(); + $this->selectionId = ''; + $missingUri = ''; + if (isset($_GET['g'])) { - // try groups - if (! isset($cOptions['groups'][$_GET['g']])) { - $this->log("A group configuration for \"{$_GET['g']}\" was not set"); + // add group(s) + $this->selectionId .= 'g=' . $_GET['g']; + $keys = explode(',', $_GET['g']); + if ($keys != array_unique($keys)) { + $this->log("Duplicate group key found."); return $options; } - - $this->selectionId = "g=" . $_GET['g']; - $files = $cOptions['groups'][$_GET['g']]; - // if $files is a single object, casting will break it - if (is_object($files)) { - $files = array($files); - } elseif (! is_array($files)) { - $files = (array)$files; - } - foreach ($files as $file) { - if ($file instanceof Minify_Source) { - $sources[] = $file; - continue; - } - if (0 === strpos($file, '//')) { - $file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1); - } - $file = realpath($file); - if (is_file($file)) { - $sources[] = $this->_getFileSource($file, $cOptions); - } else { - $this->log("The path \"{$file}\" could not be found (or was not a file)"); + foreach (explode(',', $_GET['g']) as $key) { + if (! isset($cOptions['groups'][$key])) { + $this->log("A group configuration for \"{$key}\" was not found"); return $options; } + $files = $cOptions['groups'][$key]; + // if $files is a single object, casting will break it + if (is_object($files)) { + $files = array($files); + } elseif (! is_array($files)) { + $files = (array)$files; + } + foreach ($files as $file) { + if ($file instanceof Minify_Source) { + $sources[] = $file; + continue; + } + if (0 === strpos($file, '//')) { + $file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1); + } + $file = realpath($file); + if ($file && is_file($file)) { + $sources[] = $this->_getFileSource($file, $cOptions); + } else { + $this->log("The path \"{$file}\" could not be found (or was not a file)"); + return $options; + } + } + if ($sources) { + try { + $this->checkType($sources[0]); + } catch (Exception $e) { + $this->log($e->getMessage()); + return $options; + } + } } - } elseif (! $cOptions['groupsOnly'] && isset($_GET['f'])) { + } + if (! $cOptions['groupsOnly'] && isset($_GET['f'])) { // try user files // The following restrictions are to limit the URLs that minify will // respond to. Ideally there should be only one way to reference a file. @@ -83,12 +101,17 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { return $options; } $ext = ".{$m[1]}"; + try { + $this->checkType($m[1]); + } catch (Exception $e) { + $this->log($e->getMessage()); + return $options; + } $files = explode(',', $_GET['f']); if ($files != array_unique($files)) { $this->log("Duplicate files specified"); return $options; } - if (isset($_GET['b'])) { // check for validity if (preg_match('@^[^/]+(?:/[^/]+)*$@', $_GET['b']) @@ -109,22 +132,42 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { } $basenames = array(); // just for cache id foreach ($files as $file) { - $path = $_SERVER['DOCUMENT_ROOT'] . $base . $file; + $uri = $base . $file; + $path = $_SERVER['DOCUMENT_ROOT'] . $uri; $file = realpath($path); - if (false === $file) { - $this->log("Path \"{$path}\" failed realpath()"); - return $options; - } elseif (! parent::_fileIsSafe($file, $allowDirs)) { - $this->log("Path \"{$path}\" failed Minify_Controller_Base::_fileIsSafe()"); - return $options; - } else { - $sources[] = $this->_getFileSource($file, $cOptions); - $basenames[] = basename($file, $ext); + if (false === $file || ! is_file($file)) { + if (! $missingUri) { + $missingUri = $uri; + continue; + } else { + $this->log("At least two files missing: '$missingUri', '$uri'"); + return $options; + } } + try { + parent::checkNotHidden($file); + parent::checkAllowDirs($file, $allowDirs, $uri); + } catch (Exception $e) { + $this->log($e->getMessage()); + return $options; + } + $sources[] = $this->_getFileSource($file, $cOptions); + $basenames[] = basename($file, $ext); } - $this->selectionId = implode(',', $basenames) . $ext; + if ($this->selectionId) { + $this->selectionId .= '_f='; + } + $this->selectionId .= implode(',', $basenames) . $ext; } if ($sources) { + if ($missingUri) { + array_unshift($sources, new Minify_Source(array( + 'id' => 'missingFile' + ,'lastModified' => 0 + ,'content' => "/* Minify: missing file '" . ltrim($missingUri, '/') . "' */\n" + ,'minifier' => '' + ))); + } $this->sources = $sources; } else { $this->log("No sources to serve"); @@ -141,4 +184,27 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { } return new Minify_Source($spec); } + + protected $_type = null; + + /* + * Make sure that only source files of a single type are registered + */ + public function checkType($sourceOrExt) + { + if ($sourceOrExt === 'js') { + $type = Minify::TYPE_JS; + } elseif ($sourceOrExt === 'css') { + $type = Minify::TYPE_CSS; + } elseif ($sourceOrExt->contentType !== null) { + $type = $sourceOrExt->contentType; + } else { + return; + } + if ($this->_type === null) { + $this->_type = $type; + } elseif ($this->_type !== $type) { + throw new Exception('Content-Type mismatch'); + } + } } diff --git a/min/lib/Minify/Logger.php b/min/lib/Minify/Logger.php index 7844eea..8eb72f4 100644 --- a/min/lib/Minify/Logger.php +++ b/min/lib/Minify/Logger.php @@ -9,6 +9,8 @@ * * @package Minify * @author Stephen Clay + * + * @todo lose this singleton! pass log object in Minify::serve and distribute to others */ class Minify_Logger { From 15de686195768ab162e1dafdaa4d1aa8b8fb1446 Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Thu, 8 Jul 2010 01:30:12 +0000 Subject: [PATCH 11/25] Issue 184 fix --- min/lib/Minify/YUICompressor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/min/lib/Minify/YUICompressor.php b/min/lib/Minify/YUICompressor.php index c7af51f..3e77e96 100644 --- a/min/lib/Minify/YUICompressor.php +++ b/min/lib/Minify/YUICompressor.php @@ -110,7 +110,7 @@ class Minify_YUICompressor { ); $cmd = self::$javaExecutable . ' -jar ' . escapeshellarg(self::$jarFile) . " --type {$type}" - . (preg_match('/^[a-zA-Z\\-]+$/', $o['charset']) + . (preg_match('/^[a-zA-Z0-9\\-]+$/', $o['charset']) ? " --charset {$o['charset']}" : '') . (is_numeric($o['line-break']) && $o['line-break'] >= 0 From 5e0a7b1bf969520563fe164ad11f3953809a4ef0 Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Thu, 8 Jul 2010 04:25:05 +0000 Subject: [PATCH 12/25] Prepping 2.1.4 beta release. Issue 185, More helpful Builder messages. --- HISTORY.txt | 15 +++++ README.txt | 8 +++ min/README.txt | 25 ++++++-- min/builder/index.php | 64 ++++++++++++++----- min/config.php | 2 +- min/lib/Minify/CommentPreserver.php | 7 +- min/lib/Minify/Controller/Base.php | 4 +- .../_test_files/css/comments.min.css | 2 +- .../_test_files/html/before.min.html | 2 +- .../_test_files/html/before2.min.html | 2 +- .../_test_files/minify/minified.css | 2 +- .../test_Minify_CommentPreserver.php | 8 +-- 12 files changed, 103 insertions(+), 38 deletions(-) diff --git a/HISTORY.txt b/HISTORY.txt index 95a46c8..beee56f 100644 --- a/HISTORY.txt +++ b/HISTORY.txt @@ -1,5 +1,20 @@ Minify Release History +Version 2.1.4 + * Cookie/bookmarklet-based debug mode. No HTML editing! + * Allows 1 file to be missing w/o complete failure + * Combine multiple groups and files in single URI + * More useful HTML helpers for writing versioned URIs + * More detailed error logging, including minifier exceptions + * Builder offers more helpful messages/PHP environment warnings + * Bypass minification based on filename pattern. e.g. foo.min.js / foo-min.css + * JSMin won't choke on common Closure compiler syntaxes (i+ ++j) + * Better caching in IE6 + * Cache ids are influenced by group/file names + * Debug mode for Javascript doesn't break on common XPath strings (Prototype 1.6) + * Removed annoying maxFiles limit + * mbstring.func_overload usage is safer + Version 2.1.3 * HTTP fixes * ETag generation now valid (different when gzipped) diff --git a/README.txt b/README.txt index 43978d5..5d2e232 100644 --- a/README.txt +++ b/README.txt @@ -8,6 +8,14 @@ and tell clients to cache the file for a period of time. More info: http://code.google.com/p/minify/ +WORDPRESS USER? + +These WP plugins integrate Minify into WordPress's style and script hooks to +get you set up faster. + http://wordpress.org/extend/plugins/wp-minify/ + http://wordpress.org/extend/plugins/w3-total-cache/ + + UPGRADING See UPGRADING.txt for instructions. diff --git a/min/README.txt b/min/README.txt index fab1bb0..7e6fdb1 100644 --- a/min/README.txt +++ b/min/README.txt @@ -82,7 +82,7 @@ This pre-selects the following files to be combined under the key "js": You can now serve these files with this simple URL: http://example.com/min/?g=js - + GROUPS: SPECIFYING FILES OUTSIDE THE DOC_ROOT @@ -99,6 +99,14 @@ return array( ); +COMBINE MULTIPLE GROUPS AND FILES IN ONE URL + +E.g.: http://example.com/min/?g=js&f=more/scripts.js + +Separate group keys with commas: + http://example.com/min/?g=baseCss,css1&f=moreStyles.css + + FAR-FUTURE EXPIRES HEADERS Minify can send far-future (one year) Expires headers. To enable this you must @@ -106,15 +114,20 @@ add a number to the querystring (e.g. /min/?g=js&1234 or /min/f=file.js&1234) and alter it whenever a source file is changed. If you have a build process you can use a build/source control revision number. -If you serve files as a group, you can use the utility function Minify_groupUri() -to get a "versioned" Minify URI for use in your HTML. E.g.: +You can alternately use the utility function Minify_getUri() to get a "versioned" +Minify URI for use in your HTML. E.g.: "; +$jsUri = Minify_getUri('js'); // a key in groupsConfig.php +echo ""; + +$cssUri = Minify_getUri(array( + '//css/styles1.css' + ,'//css/styles2.css' +)); // a list of files +echo ""; DEBUG MODE diff --git a/min/builder/index.php b/min/builder/index.php index 093954b..bd3de96 100644 --- a/min/builder/index.php +++ b/min/builder/index.php @@ -29,12 +29,34 @@ if (! $min_enableBuilder) { exit(); } +$setIncludeSuccess = set_include_path(dirname(__FILE__) . '/../lib' . PATH_SEPARATOR . get_include_path()); +// we do it this way because we want the builder to work after the user corrects +// include_path. (set_include_path returning FALSE is OK). +try { + require_once 'Solar/Dir.php'; +} catch (Exception $e) { + if (! $setIncludeSuccess) { + echo "Minify: set_include_path() failed. You may need to set your include_path " + ."outside of PHP code, e.g., in php.ini."; + } else { + echo $e->getMessage(); + } + exit(); +} +require 'Minify.php'; + +$cachePathCode = ''; +if (! isset($min_cachePath)) { + $detectedTmp = rtrim(Solar_Dir::tmp(), DIRECTORY_SEPARATOR); + $cachePathCode = "\$min_cachePath = " . var_export($detectedTmp, 1) . ';'; +} + ob_start(); ?> Minify URI Builder - -
Note: It looks like you're running Minify in a user +
Note: It looks like you're running Minify in a user directory. You may need the following option in /min/config.php to have URIs correctly rewritten in CSS output:
@@ -62,15 +84,17 @@ b {color:#c00}

Uh Oh. Minify was unable to - serve the Javascript for this app. To troubleshoot this, + serve Javascript for this app. To troubleshoot this, enable FirePHP debugging and request the Minify URL directly. Hopefully the FirePHP console will report the cause of the error.

- -

Note: You can set $min_cachePath -in /min/config.php to slightly improve performance.

+ +

Note: was discovered as a usable temp directory.
To + slightly improve performance you can hardcode this in /min/config.php: +

Note: Your webserver does not seem to @@ -138,8 +162,8 @@ by Minify. E.g. @import "/min/?g=css2";<

Debug Mode

When /min/config.php has $min_allowDebugFlag = true; you can get debug output by appending &debug to a Minify URL, or - by sending the cookie minDebug=<match>, where minDebug=<match> - should match the Minify URIs you'd like to debug. This bookmarklet will allow you to + by sending the cookie minDebug=<match>, where <match> + should be a string in the Minify URIs you'd like to debug. This bookmarklet will allow you to set this cookie.

Minify Debug (right-click, add to bookmarks)

@@ -149,13 +173,20 @@ by Minify. E.g. @import "/min/?g=css2";<

Need help? Check the wiki, or post to the discussion list.

-

This app is minified :)

+

Powered by Minify

- - - + - @@ -179,11 +180,10 @@ by Minify. E.g. @import "/min/?g=css2";< {$ws2}" - : "{$ws1}{$openScript}{$js}{$ws2}" - ); - } - - protected function _removeCdata($str) - { - return (false !== strpos($str, ''), '', $str) - : $str; - } - - protected function _needsCdata($str) - { - return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str)); - } -} + + */ +class Minify_HTML { + + /** + * "Minify" an HTML page + * + * @param string $html + * + * @param array $options + * + * 'cssMinifier' : (optional) callback function to process content of STYLE + * elements. + * + * 'jsMinifier' : (optional) callback function to process content of SCRIPT + * elements. Note: the type attribute is ignored. + * + * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If + * unset, minify will sniff for an XHTML doctype. + * + * @return string + */ + public static function minify($html, $options = array()) { + $min = new Minify_HTML($html, $options); + return $min->process(); + } + + + /** + * Create a minifier object + * + * @param string $html + * + * @param array $options + * + * 'cssMinifier' : (optional) callback function to process content of STYLE + * elements. + * + * 'jsMinifier' : (optional) callback function to process content of SCRIPT + * elements. Note: the type attribute is ignored. + * + * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If + * unset, minify will sniff for an XHTML doctype. + * + * @return null + */ + public function __construct($html, $options = array()) + { + $this->_html = str_replace("\r\n", "\n", trim($html)); + if (isset($options['xhtml'])) { + $this->_isXhtml = (bool)$options['xhtml']; + } + if (isset($options['cssMinifier'])) { + $this->_cssMinifier = $options['cssMinifier']; + } + if (isset($options['jsMinifier'])) { + $this->_jsMinifier = $options['jsMinifier']; + } + } + + + /** + * Minify the markeup given in the constructor + * + * @return string + */ + public function process() + { + if ($this->_isXhtml === null) { + $this->_isXhtml = (false !== strpos($this->_html, '_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']); + $this->_placeholders = array(); + + // replace SCRIPTs (and minify) with placeholders + $this->_html = preg_replace_callback( + '/(\\s*)]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i' + ,array($this, '_removeScriptCB') + ,$this->_html); + + // replace STYLEs (and minify) with placeholders + $this->_html = preg_replace_callback( + '/\\s*]*>)([\\s\\S]*?)<\\/style>\\s*/i' + ,array($this, '_removeStyleCB') + ,$this->_html); + + // remove HTML comments (not containing IE conditional comments). + $this->_html = preg_replace_callback( + '//' + ,array($this, '_commentCB') + ,$this->_html); + + // replace PREs with placeholders + $this->_html = preg_replace_callback('/\\s*]*?>[\\s\\S]*?<\\/pre>)\\s*/i' + ,array($this, '_removePreCB') + ,$this->_html); + + // replace TEXTAREAs with placeholders + $this->_html = preg_replace_callback( + '/\\s*]*?>[\\s\\S]*?<\\/textarea>)\\s*/i' + ,array($this, '_removeTextareaCB') + ,$this->_html); + + // trim each line. + // @todo take into account attribute values that span multiple lines. + $this->_html = preg_replace('/^\\s+|\\s+$/m', '', $this->_html); + + // remove ws around block/undisplayed elements + $this->_html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body' + .'|caption|center|cite|col(?:group)?|dd|dir|div|dl|dt|fieldset|form' + .'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta' + .'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)' + .'|ul)\\b[^>]*>)/i', '$1', $this->_html); + + // remove ws outside of all elements + $this->_html = preg_replace( + '/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?$1$2$3<' + ,$this->_html); + + // use newlines before 1st attribute in open tags (to limit line lengths) + $this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html); + + // fill placeholders + $this->_html = str_replace( + array_keys($this->_placeholders) + ,array_values($this->_placeholders) + ,$this->_html + ); + return $this->_html; + } + + protected function _commentCB($m) + { + return (0 === strpos($m[1], '[') || false !== strpos($m[1], '_replacementHash . count($this->_placeholders) . '%'; + $this->_placeholders[$placeholder] = $content; + return $placeholder; + } + + protected $_isXhtml = null; + protected $_replacementHash = null; + protected $_placeholders = array(); + protected $_cssMinifier = null; + protected $_jsMinifier = null; + + protected function _removePreCB($m) + { + return $this->_reservePlace("_reservePlace("\\s*$)/', '', $css); + + // remove CDATA section markers + $css = $this->_removeCdata($css); + + // minify + $minifier = $this->_cssMinifier + ? $this->_cssMinifier + : 'trim'; + $css = call_user_func($minifier, $css); + + return $this->_reservePlace($this->_needsCdata($css) + ? "{$openStyle}/**/" + : "{$openStyle}{$css}" + ); + } + + protected function _removeScriptCB($m) + { + $openScript = "\\s*$)/', '', $js); + + // remove CDATA section markers + $js = $this->_removeCdata($js); + + // minify + $minifier = $this->_jsMinifier + ? $this->_jsMinifier + : 'trim'; + $js = call_user_func($minifier, $js); + + return $this->_reservePlace($this->_needsCdata($js) + ? "{$ws1}{$openScript}/**/{$ws2}" + : "{$ws1}{$openScript}{$js}{$ws2}" + ); + } + + protected function _removeCdata($str) + { + return (false !== strpos($str, ''), '', $str) + : $str; + } + + protected function _needsCdata($str) + { + return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str)); + } +} diff --git a/min_unit_tests/_test_files/css/styles.css b/min_unit_tests/_test_files/css/styles.css index bf46c0a..0c3a892 100644 --- a/min_unit_tests/_test_files/css/styles.css +++ b/min_unit_tests/_test_files/css/styles.css @@ -1,3 +1,5 @@ +@charset "utf-8"; + /* some CSS to try to exercise things in general */ @import url( /more.css ); diff --git a/min_unit_tests/_test_files/html/before.min.html b/min_unit_tests/_test_files/html/before.min.html index cb56d4f..6b1373f 100644 --- a/min_unit_tests/_test_files/html/before.min.html +++ b/min_unit_tests/_test_files/html/before.min.html @@ -20,10 +20,12 @@ rel="alternate" type="application/rss+xml" title="RSS" href="http://www.csszengarden.com/zengarden.xml" />

Browser != IE

+

Browser != IE

+title="Cascading Style Sheets">CSS
+Design
 	White  space  is  important   here!
 		

Browser != IE

+

Browser != IE

+title="Cascading Style Sheets">CSS
+Design
 	White  space  is  important   here!
 		

Date: Sat, 16 Oct 2010 00:10:24 +0000 Subject: [PATCH 17/25] added test for storing gzencoded string in memcache --- min_unit_tests/test_Minify_Cache_Memcache.php | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/min_unit_tests/test_Minify_Cache_Memcache.php b/min_unit_tests/test_Minify_Cache_Memcache.php index a8979c6..2321045 100644 --- a/min_unit_tests/test_Minify_Cache_Memcache.php +++ b/min_unit_tests/test_Minify_Cache_Memcache.php @@ -6,33 +6,47 @@ require_once 'Minify/Cache/Memcache.php'; function test_Minify_Cache_Memcache() { $prefix = 'Minify_Cache_Memcache : '; + $thisFileActive = (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])); + if (! function_exists('memcache_set')) { + if ($thisFileActive) { + echo "NOTE: {$prefix}PHP lacks memcache support\n"; + } return; } $mc = new Memcache; if (! @$mc->connect('localhost', 11211)) { + if ($thisFileActive) { + echo "NOTE: {$prefix}Could not connect to localhost:11211\n"; + } return; } $data = str_repeat(md5(time()) . 'í', 100); // 3400 bytes in UTF-8 - $id = 'Minify_test_cache'; - + $id = 'Minify_test_memcache'; $cache = new Minify_Cache_Memcache($mc); - + assertTrue(true === $cache->store($id, $data), $prefix . 'store'); - + assertTrue(countBytes($data) === $cache->getSize($id), $prefix . 'getSize'); - + assertTrue(true === $cache->isValid($id, $_SERVER['REQUEST_TIME'] - 10), $prefix . 'isValid'); - + ob_start(); $cache->display($id); $displayed = ob_get_contents(); ob_end_clean(); - + assertTrue($data === $displayed, $prefix . 'display'); - + assertTrue($data === $cache->fetch($id), $prefix . 'fetch'); + + if (function_exists('gzencode')) { + $data = gzencode($data); + $id .= ".gz"; + $cache->store($id, $data); + assertTrue($data === $cache->fetch($id), $prefix . 'store/fetch gzencoded string'); + } } test_Minify_Cache_Memcache(); \ No newline at end of file From 356b6fb834069aaa35fef6716ec2825ea180afbb Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Fri, 29 Oct 2010 04:10:39 +0000 Subject: [PATCH 18/25] Issue 196, Issue 201, Issue 198 --- min/lib/Minify.php | 2 +- min/lib/Minify/Controller/MinApp.php | 4 +--- min/lib/Minify/JS/ClosureCompiler.php | 23 +++++++++++++++-------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/min/lib/Minify.php b/min/lib/Minify.php index 6d1a165..58b9b88 100644 --- a/min/lib/Minify.php +++ b/min/lib/Minify.php @@ -529,7 +529,7 @@ class Minify { { $name = preg_replace('/[^a-zA-Z0-9\\.=_,]/', '', self::$_controller->selectionId); $name = preg_replace('/\\.+/', '.', $name); - $name = substr($name, 0, 250 - 34 - strlen($prefix)); + $name = substr($name, 0, 200 - 34 - strlen($prefix)); $md5 = md5(serialize(array( Minify_Source::getDigest(self::$_controller->sources) ,self::$_options['minifiers'] diff --git a/min/lib/Minify/Controller/MinApp.php b/min/lib/Minify/Controller/MinApp.php index b28eb39..986ef2c 100644 --- a/min/lib/Minify/Controller/MinApp.php +++ b/min/lib/Minify/Controller/MinApp.php @@ -86,7 +86,7 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { if (! $cOptions['groupsOnly'] && isset($_GET['f'])) { // try user files // The following restrictions are to limit the URLs that minify will - // respond to. Ideally there should be only one way to reference a file. + // respond to. if (// verify at least one file, files are single comma separated, // and are all same extension ! preg_match('/^[^,]+\\.(css|js)(?:,[^,]+\\.\\1)*$/', $_GET['f'], $m) @@ -94,8 +94,6 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { || strpos($_GET['f'], '//') !== false // no "\" || strpos($_GET['f'], '\\') !== false - // no "./" - || preg_match('/(?:^|[^\\.])\\.\\//', $_GET['f']) ) { $this->log("GET param 'f' was invalid"); return $options; diff --git a/min/lib/Minify/JS/ClosureCompiler.php b/min/lib/Minify/JS/ClosureCompiler.php index 5b032ba..66cc6db 100644 --- a/min/lib/Minify/JS/ClosureCompiler.php +++ b/min/lib/Minify/JS/ClosureCompiler.php @@ -10,6 +10,8 @@ * @link http://code.google.com/closure/compiler/ * @package Minify * @author Stephen Clay + * + * @todo can use a stream wrapper to unit test this? */ class Minify_JS_ClosureCompiler { const URL = 'http://closure-compiler.appspot.com/compile'; @@ -42,16 +44,16 @@ class Minify_JS_ClosureCompiler { public function min($js) { - $content = $this->_getPostContent($js); + $postBody = $this->_buildPostBody($js); $bytes = (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) - ? mb_strlen($content, '8bit') - : strlen($content); + ? mb_strlen($postBody, '8bit') + : strlen($postBody); if ($bytes > 200000) { throw new Minify_JS_ClosureCompiler_Exception( 'POST content larger than 200000 bytes' ); } - $response = $this->_getResponse($content); + $response = $this->_getResponse($postBody); if (preg_match('/^Error\(\d\d?\):/', $response)) { if (is_callable($this->_fallbackFunc)) { $response = "/* Received errors from Closure Compiler API:\n$response" @@ -62,7 +64,7 @@ class Minify_JS_ClosureCompiler { } } if ($response === '') { - $errors = $this->_getResponse($this->_getPostContent($js, true)); + $errors = $this->_getResponse($this->_buildPostBody($js, true)); throw new Minify_JS_ClosureCompiler_Exception($errors); } return $response; @@ -70,13 +72,13 @@ class Minify_JS_ClosureCompiler { protected $_fallbackFunc = null; - protected function _getResponse($content) + protected function _getResponse($postBody) { $contents = file_get_contents(self::URL, false, stream_context_create(array( 'http' => array( 'method' => 'POST', 'header' => 'Content-type: application/x-www-form-urlencoded', - 'content' => $content, + 'content' => $postBody, 'max_redirects' => 0, 'timeout' => 15, ) @@ -89,7 +91,7 @@ class Minify_JS_ClosureCompiler { return trim($contents); } - protected function _getPostContent($js, $returnErrors = false) + protected function _buildPostBody($js, $returnErrors = false) { return http_build_query(array( 'js_code' => $js, @@ -99,6 +101,11 @@ class Minify_JS_ClosureCompiler { ), null, '&'); } + /** + * Default fallback function if CC API fails + * @param string $js + * @return string + */ protected function _fallback($js) { require_once 'JSMin.php'; From d8d1f89ca05b030c44565173846704a7418de7bf Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Sun, 19 Dec 2010 21:00:27 +0000 Subject: [PATCH 19/25] Issue 214 (remove traversals in prepended links) --- min/lib/Minify/CSS/UriRewriter.php | 196 ++++++++++-------- .../css_uriRewriter/exp_prepend.css | 14 ++ .../css_uriRewriter/exp_prepend2.css | 14 ++ .../test_Minify_CSS_UriRewriter.php | 41 +++- 4 files changed, 176 insertions(+), 89 deletions(-) create mode 100644 min_unit_tests/_test_files/css_uriRewriter/exp_prepend.css create mode 100644 min_unit_tests/_test_files/css_uriRewriter/exp_prepend2.css diff --git a/min/lib/Minify/CSS/UriRewriter.php b/min/lib/Minify/CSS/UriRewriter.php index 824c6bb..1404371 100644 --- a/min/lib/Minify/CSS/UriRewriter.php +++ b/min/lib/Minify/CSS/UriRewriter.php @@ -12,13 +12,6 @@ */ class Minify_CSS_UriRewriter { - /** - * Defines which class to call as part of callbacks, change this - * if you extend Minify_CSS_UriRewriter - * @var string - */ - protected static $className = 'Minify_CSS_UriRewriter'; - /** * rewrite() and rewriteRelative() append debugging information here * @var string @@ -26,7 +19,7 @@ class Minify_CSS_UriRewriter { public static $debugText = ''; /** - * Rewrite file relative URIs as root relative in CSS files + * In CSS content, rewrite file relative URIs as root relative * * @param string $css * @@ -83,7 +76,7 @@ class Minify_CSS_UriRewriter { } /** - * Prepend a path to relative URIs in CSS files + * In CSS content, prepend a path to relative URIs * * @param string $css * @@ -107,73 +100,8 @@ class Minify_CSS_UriRewriter { return $css; } - /** - * @var string directory of this stylesheet - */ - private static $_currentDir = ''; - - /** - * @var string DOC_ROOT - */ - private static $_docRoot = ''; - - /** - * @var array directory replacements to map symlink targets back to their - * source (within the document root) E.g. '/var/www/symlink' => '/var/realpath' - */ - private static $_symlinks = array(); - - /** - * @var string path to prepend - */ - private static $_prependPath = null; - - private static function _trimUrls($css) - { - return preg_replace('/ - url\\( # url( - \\s* - ([^\\)]+?) # 1 = URI (assuming does not contain ")") - \\s* - \\) # ) - /x', 'url($1)', $css); - } - - private static function _processUriCB($m) - { - // $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/' - $isImport = ($m[0][0] === '@'); - // determine URI and the quote character (if any) - if ($isImport) { - $quoteChar = $m[1]; - $uri = $m[2]; - } else { - // $m[1] is either quoted or not - $quoteChar = ($m[1][0] === "'" || $m[1][0] === '"') - ? $m[1][0] - : ''; - $uri = ($quoteChar === '') - ? $m[1] - : substr($m[1], 1, strlen($m[1]) - 2); - } - // analyze URI - if ('/' !== $uri[0] // root-relative - && false === strpos($uri, '//') // protocol (non-data) - && 0 !== strpos($uri, 'data:') // data protocol - ) { - // URI is file-relative: rewrite depending on options - $uri = (self::$_prependPath !== null) - ? (self::$_prependPath . $uri) - : self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks); - } - return $isImport - ? "@import {$quoteChar}{$uri}{$quoteChar}" - : "url({$quoteChar}{$uri}{$quoteChar})"; - } - - /** - * Rewrite a file relative URI as root relative + * Get a root relative URI from a file relative URI * * * Minify_CSS_UriRewriter::rewriteRelative( @@ -219,16 +147,16 @@ class Minify_CSS_UriRewriter { self::$debugText .= "file-relative URI : {$uri}\n" . "path prepended : {$path}\n"; - // "unresolve" a symlink back to doc root - foreach ($symlinks as $link => $target) { - if (0 === strpos($path, $target)) { - // replace $target with $link + // "unresolve" a symlink back to doc root + foreach ($symlinks as $link => $target) { + if (0 === strpos($path, $target)) { + // replace $target with $link $path = $link . substr($path, strlen($target)); - self::$debugText .= "symlink unresolved : {$path}\n"; + self::$debugText .= "symlink unresolved : {$path}\n"; - break; - } + break; + } } // strip doc root $path = substr($path, strlen($realDocRoot)); @@ -239,18 +167,35 @@ class Minify_CSS_UriRewriter { $uri = strtr($path, '/\\', '//'); - // remove /./ and /../ where possible - $uri = str_replace('/./', '/', $uri); - // inspired by patch from Oleg Cherniy - do { - $uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed); - } while ($changed); + $uri = self::removeDots($uri); self::$debugText .= "traversals removed : {$uri}\n\n"; return $uri; } + + /** + * Remove instances of "./" and "../" where possible from a root-relative URI + * @param string $uri + * @return string + */ + public static function removeDots($uri) + { + $uri = str_replace('/./', '/', $uri); + // inspired by patch from Oleg Cherniy + do { + $uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed); + } while ($changed); + return $uri; + } + /** + * Defines which class to call as part of callbacks, change this + * if you extend Minify_CSS_UriRewriter + * @var string + */ + protected static $className = 'Minify_CSS_UriRewriter'; + /** * Get realpath with any trailing slash removed. If realpath() fails, * just remove the trailing slash. @@ -267,4 +212,79 @@ class Minify_CSS_UriRewriter { } return rtrim($path, '/\\'); } + + /** + * @var string directory of this stylesheet + */ + private static $_currentDir = ''; + + /** + * @var string DOC_ROOT + */ + private static $_docRoot = ''; + + /** + * @var array directory replacements to map symlink targets back to their + * source (within the document root) E.g. '/var/www/symlink' => '/var/realpath' + */ + private static $_symlinks = array(); + + /** + * @var string path to prepend + */ + private static $_prependPath = null; + + private static function _trimUrls($css) + { + return preg_replace('/ + url\\( # url( + \\s* + ([^\\)]+?) # 1 = URI (assuming does not contain ")") + \\s* + \\) # ) + /x', 'url($1)', $css); + } + + private static function _processUriCB($m) + { + // $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/' + $isImport = ($m[0][0] === '@'); + // determine URI and the quote character (if any) + if ($isImport) { + $quoteChar = $m[1]; + $uri = $m[2]; + } else { + // $m[1] is either quoted or not + $quoteChar = ($m[1][0] === "'" || $m[1][0] === '"') + ? $m[1][0] + : ''; + $uri = ($quoteChar === '') + ? $m[1] + : substr($m[1], 1, strlen($m[1]) - 2); + } + // analyze URI + if ('/' !== $uri[0] // root-relative + && false === strpos($uri, '//') // protocol (non-data) + && 0 !== strpos($uri, 'data:') // data protocol + ) { + // URI is file-relative: rewrite depending on options + if (self::$_prependPath === null) { + $uri = self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks); + } else { + $uri = self::$_prependPath . $uri; + if ($uri[0] === '/') { + $root = ''; + $rootRelative = $uri; + $uri = $root . self::removeDots($rootRelative); + } elseif (preg_match('@^((https?\:)?//([^/]+))/@', $uri, $m) && (false !== strpos($m[3], '.'))) { + $root = $m[1]; + $rootRelative = substr($uri, strlen($root)); + $uri = $root . self::removeDots($rootRelative); + } + } + } + return $isImport + ? "@import {$quoteChar}{$uri}{$quoteChar}" + : "url({$quoteChar}{$uri}{$quoteChar})"; + } } diff --git a/min_unit_tests/_test_files/css_uriRewriter/exp_prepend.css b/min_unit_tests/_test_files/css_uriRewriter/exp_prepend.css new file mode 100644 index 0000000..87b7ecb --- /dev/null +++ b/min_unit_tests/_test_files/css_uriRewriter/exp_prepend.css @@ -0,0 +1,14 @@ +@import "http://cnd.com/A/B/foo.css"; +@import 'http://cnd.com/A/B/bar/foo.css' print; +@import 'http://cnd.com/A/bar/foo.css' print; +@import 'http://cnd.com/foo.css' print; +@import '/css/foo.css'; /* abs, should not alter */ +@import 'http://foo.com/css/foo.css'; /* abs, should not alter */ +@import url(http://cnd.com/A/foo.css) tv, projection; +@import url("/css/foo.css"); /* abs, should not alter */ +@import url(/css2/foo.css); /* abs, should not alter */ +@import url(data:image/gif;base64,AAAA); /* data, should not alter */ +foo {background:url('http://cnd.com/A/B/bar/foo.png')} +foo {background:url('http://foo.com/css/foo.css');} /* abs, should not alter */ +foo {background:url("//foo.com/css/foo.css");} /* protocol relative, should not alter */ +foo {background:url(data:image/gif;base64,AAAA);} /* data, should not alter */ \ No newline at end of file diff --git a/min_unit_tests/_test_files/css_uriRewriter/exp_prepend2.css b/min_unit_tests/_test_files/css_uriRewriter/exp_prepend2.css new file mode 100644 index 0000000..ceea4ec --- /dev/null +++ b/min_unit_tests/_test_files/css_uriRewriter/exp_prepend2.css @@ -0,0 +1,14 @@ +@import "//cnd.com/A/B/foo.css"; +@import '//cnd.com/A/B/bar/foo.css' print; +@import '//cnd.com/A/bar/foo.css' print; +@import '//cnd.com/foo.css' print; +@import '/css/foo.css'; /* abs, should not alter */ +@import 'http://foo.com/css/foo.css'; /* abs, should not alter */ +@import url(//cnd.com/A/foo.css) tv, projection; +@import url("/css/foo.css"); /* abs, should not alter */ +@import url(/css2/foo.css); /* abs, should not alter */ +@import url(data:image/gif;base64,AAAA); /* data, should not alter */ +foo {background:url('//cnd.com/A/B/bar/foo.png')} +foo {background:url('http://foo.com/css/foo.css');} /* abs, should not alter */ +foo {background:url("//foo.com/css/foo.css");} /* protocol relative, should not alter */ +foo {background:url(data:image/gif;base64,AAAA);} /* data, should not alter */ \ No newline at end of file diff --git a/min_unit_tests/test_Minify_CSS_UriRewriter.php b/min_unit_tests/test_Minify_CSS_UriRewriter.php index 7217a3d..40eba91 100644 --- a/min_unit_tests/test_Minify_CSS_UriRewriter.php +++ b/min_unit_tests/test_Minify_CSS_UriRewriter.php @@ -17,7 +17,7 @@ function test_Minify_CSS_UriRewriter() ,$thisDir // use DOCUMENT_ROOT = '/full/path/to/min_unit_tests' ); - $passed = assertTrue($expected === $actual, 'Minify_CSS_UriRewriter'); + $passed = assertTrue($expected === $actual, 'Minify_CSS_UriRewriter : rewrite'); if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { echo "\n---Input:\n\n{$in}\n"; echo "\n---Output: " .countBytes($actual). " bytes\n\n{$actual}\n\n"; @@ -29,6 +29,45 @@ function test_Minify_CSS_UriRewriter() echo "--- Minify_CSS_UriRewriter::\$debugText\n\n" , Minify_CSS_UriRewriter::$debugText; } + + + Minify_CSS_UriRewriter::$debugText = ''; + $in = file_get_contents($thisDir . '/_test_files/css_uriRewriter/in.css'); + $expected = file_get_contents($thisDir . '/_test_files/css_uriRewriter/exp_prepend.css'); + $actual = Minify_CSS_UriRewriter::prepend($in, 'http://cnd.com/A/B/'); + + $passed = assertTrue($expected === $actual, 'Minify_CSS_UriRewriter : prepend1'); + if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { + echo "\n---Input:\n\n{$in}\n"; + echo "\n---Output: " .countBytes($actual). " bytes\n\n{$actual}\n\n"; + if (!$passed) { + echo "---Expected: " .countBytes($expected). " bytes\n\n{$expected}\n\n\n"; + } + + // show debugging only when test run directly + echo "--- Minify_CSS_UriRewriter::\$debugText\n\n" + , Minify_CSS_UriRewriter::$debugText; + } + + + Minify_CSS_UriRewriter::$debugText = ''; + $in = file_get_contents($thisDir . '/_test_files/css_uriRewriter/in.css'); + $expected = file_get_contents($thisDir . '/_test_files/css_uriRewriter/exp_prepend2.css'); + $actual = Minify_CSS_UriRewriter::prepend($in, '//cnd.com/A/B/'); + + $passed = assertTrue($expected === $actual, 'Minify_CSS_UriRewriter : prepend2'); + if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { + echo "\n---Input:\n\n{$in}\n"; + echo "\n---Output: " .countBytes($actual). " bytes\n\n{$actual}\n\n"; + if (!$passed) { + echo "---Expected: " .countBytes($expected). " bytes\n\n{$expected}\n\n\n"; + } + + // show debugging only when test run directly + echo "--- Minify_CSS_UriRewriter::\$debugText\n\n" + , Minify_CSS_UriRewriter::$debugText; + } + Minify_CSS_UriRewriter::$debugText = ''; $in = '../../../../assets/skins/sam/sprite.png'; From b07880e17a2b986ea76d32d5a22f0c3d8f316523 Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Sun, 19 Dec 2010 21:12:53 +0000 Subject: [PATCH 20/25] Fix for Issue 211 --- min/lib/Minify/YUICompressor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/min/lib/Minify/YUICompressor.php b/min/lib/Minify/YUICompressor.php index 3e77e96..cd93b0e 100644 --- a/min/lib/Minify/YUICompressor.php +++ b/min/lib/Minify/YUICompressor.php @@ -110,7 +110,7 @@ class Minify_YUICompressor { ); $cmd = self::$javaExecutable . ' -jar ' . escapeshellarg(self::$jarFile) . " --type {$type}" - . (preg_match('/^[a-zA-Z0-9\\-]+$/', $o['charset']) + . (preg_match('/^[\\da-zA-Z0-9\\-]+$/', $o['charset']) ? " --charset {$o['charset']}" : '') . (is_numeric($o['line-break']) && $o['line-break'] >= 0 From 8adf606cc641fa7d0e681dc2edce80461082822a Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Mon, 20 Dec 2010 14:43:31 +0000 Subject: [PATCH 21/25] Fix for Issue 210, start work on port of YUIC CssCompressor --- min/lib/Minify/CSS/Compressor.php | 19 +- min/lib/Minify/YUI/CssCompressor.java | 291 ++++++++++++++++++ min/lib/Minify/YUI/CssCompressor.php | 171 ++++++++++ min/lib/Minify/YUI/Java/Matcher.php | 81 +++++ min/lib/Minify/YUI/Java/String.php | 64 ++++ min_unit_tests/_test_files/css/issue210.css | 1 + .../_test_files/css/issue210.min.css | 1 + min_unit_tests/_test_files/yuic/README | 6 + .../_test_files/yuic/background-position.css | 2 + .../yuic/background-position.css.min | 1 + .../_test_files/yuic/border-none.css | 5 + .../_test_files/yuic/border-none.css.min | 1 + .../_test_files/yuic/box-model-hack.css | 9 + .../_test_files/yuic/box-model-hack.css.min | 1 + .../_test_files/yuic/bug2527974.css | 10 + .../_test_files/yuic/bug2527974.css.min | 1 + .../_test_files/yuic/bug2527991.css | 19 ++ .../_test_files/yuic/bug2527991.css.min | 1 + .../_test_files/yuic/bug2527998.css | 4 + .../_test_files/yuic/bug2527998.css.min | 1 + .../_test_files/yuic/bug2528034.css | 5 + .../_test_files/yuic/bug2528034.css.min | 1 + .../_test_files/yuic/charset-media.css | 9 + .../_test_files/yuic/charset-media.css.min | 1 + min_unit_tests/_test_files/yuic/color.css | 7 + min_unit_tests/_test_files/yuic/color.css.min | 1 + min_unit_tests/_test_files/yuic/comment.css | 3 + .../_test_files/yuic/comment.css.min | 1 + .../_test_files/yuic/concat-charset.css | 15 + .../_test_files/yuic/concat-charset.css.min | 1 + min_unit_tests/_test_files/yuic/decimals.css | 3 + .../_test_files/yuic/decimals.css.min | 1 + .../_test_files/yuic/dollar-header.css | 7 + .../_test_files/yuic/dollar-header.css.min | 3 + min_unit_tests/_test_files/yuic/font-face.css | 6 + .../_test_files/yuic/font-face.css.min | 1 + min_unit_tests/_test_files/yuic/ie5mac.css | 5 + .../_test_files/yuic/ie5mac.css.min | 1 + .../_test_files/yuic/media-empty-class.css | 16 + .../yuic/media-empty-class.css.min | 1 + .../_test_files/yuic/media-multi.css | 3 + .../_test_files/yuic/media-multi.css.min | 1 + .../_test_files/yuic/media-test.css | 3 + .../_test_files/yuic/media-test.css.min | 1 + .../_test_files/yuic/opacity-filter.css | 14 + .../_test_files/yuic/opacity-filter.css.min | 1 + .../_test_files/yuic/preserve-new-line.css | 6 + .../yuic/preserve-new-line.css.min | 3 + .../_test_files/yuic/preserve-strings.css | 7 + .../_test_files/yuic/preserve-strings.css.min | 1 + .../_test_files/yuic/pseudo-first.css | 16 + .../_test_files/yuic/pseudo-first.css.min | 1 + min_unit_tests/_test_files/yuic/pseudo.css | 4 + .../_test_files/yuic/pseudo.css.min | 1 + .../_test_files/yuic/special-comments.css | 13 + .../_test_files/yuic/special-comments.css.min | 9 + .../yuic/star-underscore-hacks.css | 5 + .../yuic/star-underscore-hacks.css.min | 1 + .../_test_files/yuic/string-in-comment.css | 8 + .../yuic/string-in-comment.css.min | 1 + .../_test_files/yuic/webkit-transform.css | 2 + .../_test_files/yuic/webkit-transform.css.min | 1 + min_unit_tests/_test_files/yuic/zeros.css | 6 + min_unit_tests/_test_files/yuic/zeros.css.min | 1 + 64 files changed, 876 insertions(+), 10 deletions(-) create mode 100644 min/lib/Minify/YUI/CssCompressor.java create mode 100644 min/lib/Minify/YUI/CssCompressor.php create mode 100644 min/lib/Minify/YUI/Java/Matcher.php create mode 100644 min/lib/Minify/YUI/Java/String.php create mode 100644 min_unit_tests/_test_files/css/issue210.css create mode 100644 min_unit_tests/_test_files/css/issue210.min.css create mode 100644 min_unit_tests/_test_files/yuic/README create mode 100644 min_unit_tests/_test_files/yuic/background-position.css create mode 100644 min_unit_tests/_test_files/yuic/background-position.css.min create mode 100644 min_unit_tests/_test_files/yuic/border-none.css create mode 100644 min_unit_tests/_test_files/yuic/border-none.css.min create mode 100644 min_unit_tests/_test_files/yuic/box-model-hack.css create mode 100644 min_unit_tests/_test_files/yuic/box-model-hack.css.min create mode 100644 min_unit_tests/_test_files/yuic/bug2527974.css create mode 100644 min_unit_tests/_test_files/yuic/bug2527974.css.min create mode 100644 min_unit_tests/_test_files/yuic/bug2527991.css create mode 100644 min_unit_tests/_test_files/yuic/bug2527991.css.min create mode 100644 min_unit_tests/_test_files/yuic/bug2527998.css create mode 100644 min_unit_tests/_test_files/yuic/bug2527998.css.min create mode 100644 min_unit_tests/_test_files/yuic/bug2528034.css create mode 100644 min_unit_tests/_test_files/yuic/bug2528034.css.min create mode 100644 min_unit_tests/_test_files/yuic/charset-media.css create mode 100644 min_unit_tests/_test_files/yuic/charset-media.css.min create mode 100644 min_unit_tests/_test_files/yuic/color.css create mode 100644 min_unit_tests/_test_files/yuic/color.css.min create mode 100644 min_unit_tests/_test_files/yuic/comment.css create mode 100644 min_unit_tests/_test_files/yuic/comment.css.min create mode 100644 min_unit_tests/_test_files/yuic/concat-charset.css create mode 100644 min_unit_tests/_test_files/yuic/concat-charset.css.min create mode 100644 min_unit_tests/_test_files/yuic/decimals.css create mode 100644 min_unit_tests/_test_files/yuic/decimals.css.min create mode 100644 min_unit_tests/_test_files/yuic/dollar-header.css create mode 100644 min_unit_tests/_test_files/yuic/dollar-header.css.min create mode 100644 min_unit_tests/_test_files/yuic/font-face.css create mode 100644 min_unit_tests/_test_files/yuic/font-face.css.min create mode 100644 min_unit_tests/_test_files/yuic/ie5mac.css create mode 100644 min_unit_tests/_test_files/yuic/ie5mac.css.min create mode 100644 min_unit_tests/_test_files/yuic/media-empty-class.css create mode 100644 min_unit_tests/_test_files/yuic/media-empty-class.css.min create mode 100644 min_unit_tests/_test_files/yuic/media-multi.css create mode 100644 min_unit_tests/_test_files/yuic/media-multi.css.min create mode 100644 min_unit_tests/_test_files/yuic/media-test.css create mode 100644 min_unit_tests/_test_files/yuic/media-test.css.min create mode 100644 min_unit_tests/_test_files/yuic/opacity-filter.css create mode 100644 min_unit_tests/_test_files/yuic/opacity-filter.css.min create mode 100644 min_unit_tests/_test_files/yuic/preserve-new-line.css create mode 100644 min_unit_tests/_test_files/yuic/preserve-new-line.css.min create mode 100644 min_unit_tests/_test_files/yuic/preserve-strings.css create mode 100644 min_unit_tests/_test_files/yuic/preserve-strings.css.min create mode 100644 min_unit_tests/_test_files/yuic/pseudo-first.css create mode 100644 min_unit_tests/_test_files/yuic/pseudo-first.css.min create mode 100644 min_unit_tests/_test_files/yuic/pseudo.css create mode 100644 min_unit_tests/_test_files/yuic/pseudo.css.min create mode 100644 min_unit_tests/_test_files/yuic/special-comments.css create mode 100644 min_unit_tests/_test_files/yuic/special-comments.css.min create mode 100644 min_unit_tests/_test_files/yuic/star-underscore-hacks.css create mode 100644 min_unit_tests/_test_files/yuic/star-underscore-hacks.css.min create mode 100644 min_unit_tests/_test_files/yuic/string-in-comment.css create mode 100644 min_unit_tests/_test_files/yuic/string-in-comment.css.min create mode 100644 min_unit_tests/_test_files/yuic/webkit-transform.css create mode 100644 min_unit_tests/_test_files/yuic/webkit-transform.css.min create mode 100644 min_unit_tests/_test_files/yuic/zeros.css create mode 100644 min_unit_tests/_test_files/yuic/zeros.css.min diff --git a/min/lib/Minify/CSS/Compressor.php b/min/lib/Minify/CSS/Compressor.php index 5223aa2..3c96c10 100644 --- a/min/lib/Minify/CSS/Compressor.php +++ b/min/lib/Minify/CSS/Compressor.php @@ -236,15 +236,14 @@ class Minify_CSS_Compressor { */ protected function _fontFamilyCB($m) { - $m[1] = preg_replace('/ - \\s* - ( - "[^"]+" # 1 = family in double qutoes - |\'[^\']+\' # or 1 = family in single quotes - |[\\w\\-]+ # or 1 = unquoted family - ) - \\s* - /x', '$1', $m[1]); - return 'font-family:' . $m[1] . $m[2]; + $pieces = preg_split('@(\'[^\']+\'|"[^"]+")@', $m[1], null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + foreach ($pieces as $i => $piece) { + if ($piece[0] !== '"' && $piece[0] !== "'") { + $piece = trim(preg_replace('@\\s+@', ' ', $piece)); + $piece = preg_replace('@\\s*,\\s*@', ',', $piece); + $pieces[$i] = $piece; + } + } + return 'font-family:' . implode('', $pieces) . $m[2]; } } diff --git a/min/lib/Minify/YUI/CssCompressor.java b/min/lib/Minify/YUI/CssCompressor.java new file mode 100644 index 0000000..ed699b8 --- /dev/null +++ b/min/lib/Minify/YUI/CssCompressor.java @@ -0,0 +1,291 @@ +/* + * YUI Compressor + * Author: Julien Lecomte - http://www.julienlecomte.net/ + * Author: Isaac Schlueter - http://foohack.com/ + * Author: Stoyan Stefanov - http://phpied.com/ + * Copyright (c) 2009 Yahoo! Inc. All rights reserved. + * The copyrights embodied in the content of this file are licensed + * by Yahoo! Inc. under the BSD (revised) open source license. + */ + +package com.yahoo.platform.yui.compressor; + +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.util.ArrayList; + +public class CssCompressor { + + private StringBuffer srcsb = new StringBuffer(); + + public CssCompressor(Reader in) throws IOException { + // Read the stream... + int c; + while ((c = in.read()) != -1) { + srcsb.append((char) c); + } + } + + public void compress(Writer out, int linebreakpos) + throws IOException { + + Pattern p; + Matcher m; + String css = srcsb.toString(); + StringBuffer sb = new StringBuffer(css); + + int startIndex = 0; + int endIndex = 0; + int i = 0; + int max = 0; + ArrayList preservedTokens = new ArrayList(0); + ArrayList comments = new ArrayList(0); + String token; + int totallen = css.length(); + String placeholder; + + + // collect all comment blocks... + while ((startIndex = sb.indexOf("/*", startIndex)) >= 0) { + endIndex = sb.indexOf("*/", startIndex + 2); + if (endIndex < 0) { + endIndex = totallen; + } + + token = sb.substring(startIndex + 2, endIndex); + comments.add(token); + sb.replace(startIndex + 2, endIndex, "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + (comments.size() - 1) + "___"); + startIndex += 2; + } + css = sb.toString(); + + // preserve strings so their content doesn't get accidentally minified + sb = new StringBuffer(); + p = Pattern.compile("(\"([^\\\\\"]|\\\\.|\\\\)*\")|(\'([^\\\\\']|\\\\.|\\\\)*\')"); + m = p.matcher(css); + while (m.find()) { + token = m.group(); + char quote = token.charAt(0); + token = token.substring(1, token.length() - 1); + + // maybe the string contains a comment-like substring? + // one, maybe more? put'em back then + if (token.indexOf("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_") >= 0) { + for (i = 0, max = comments.size(); i < max; i += 1) { + token = token.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", comments.get(i).toString()); + } + } + + // minify alpha opacity in filter strings + token = token.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity="); + + preservedTokens.add(token); + String preserver = quote + "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___" + quote; + m.appendReplacement(sb, preserver); + } + m.appendTail(sb); + css = sb.toString(); + + + // strings are safe, now wrestle the comments + for (i = 0, max = comments.size(); i < max; i += 1) { + + token = comments.get(i).toString(); + placeholder = "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___"; + + // ! in the first position of the comment means preserve + // so push to the preserved tokens while stripping the ! + if (token.startsWith("!")) { + preservedTokens.add(token); + css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___"); + continue; + } + + // \ in the last position looks like hack for Mac/IE5 + // shorten that to /*\*/ and the next one to /**/ + if (token.endsWith("\\")) { + preservedTokens.add("\\"); + css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___"); + i = i + 1; // attn: advancing the loop + preservedTokens.add(""); + css = css.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___"); + continue; + } + + // keep empty comments after child selectors (IE7 hack) + // e.g. html >/**/ body + if (token.length() == 0) { + startIndex = css.indexOf(placeholder); + if (startIndex > 2) { + if (css.charAt(startIndex - 3) == '>') { + preservedTokens.add(""); + css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___"); + } + } + } + + // in all other cases kill the comment + css = css.replace("/*" + placeholder + "*/", ""); + } + + + // Normalize all whitespace strings to single spaces. Easier to work with that way. + css = css.replaceAll("\\s+", " "); + + // Remove the spaces before the things that should not have spaces before them. + // But, be careful not to turn "p :link {...}" into "p:link{...}" + // Swap out any pseudo-class colons with the token, and then swap back. + sb = new StringBuffer(); + p = Pattern.compile("(^|\\})(([^\\{:])+:)+([^\\{]*\\{)"); + m = p.matcher(css); + while (m.find()) { + String s = m.group(); + s = s.replaceAll(":", "___YUICSSMIN_PSEUDOCLASSCOLON___"); + s = s.replaceAll( "\\\\", "\\\\\\\\" ).replaceAll( "\\$", "\\\\\\$" ); + m.appendReplacement(sb, s); + } + m.appendTail(sb); + css = sb.toString(); + // Remove spaces before the things that should not have spaces before them. + css = css.replaceAll("\\s+([!{};:>+\\(\\)\\],])", "$1"); + // bring back the colon + css = css.replaceAll("___YUICSSMIN_PSEUDOCLASSCOLON___", ":"); + + // retain space for special IE6 cases + css = css.replaceAll(":first\\-(line|letter)(\\{|,)", ":first-$1 $2"); + + // no space after the end of a preserved comment + css = css.replaceAll("\\*/ ", "*/"); + + // If there is a @charset, then only allow one, and push to the top of the file. + css = css.replaceAll("^(.*)(@charset \"[^\"]*\";)", "$2$1"); + css = css.replaceAll("^(\\s*@charset [^;]+;\\s*)+", "$1"); + + // Put the space back in some cases, to support stuff like + // @media screen and (-webkit-min-device-pixel-ratio:0){ + css = css.replaceAll("\\band\\(", "and ("); + + // Remove the spaces after the things that should not have spaces after them. + css = css.replaceAll("([!{}:;>+\\(\\[,])\\s+", "$1"); + + // remove unnecessary semicolons + css = css.replaceAll(";+}", "}"); + + // Replace 0(px,em,%) with 0. + css = css.replaceAll("([\\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", "$1$2"); + + // Replace 0 0 0 0; with 0. + css = css.replaceAll(":0 0 0 0(;|})", ":0$1"); + css = css.replaceAll(":0 0 0(;|})", ":0$1"); + css = css.replaceAll(":0 0(;|})", ":0$1"); + + + // Replace background-position:0; with background-position:0 0; + // same for transform-origin + sb = new StringBuffer(); + p = Pattern.compile("(?i)(background-position|transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin):0(;|})"); + m = p.matcher(css); + while (m.find()) { + m.appendReplacement(sb, m.group(1).toLowerCase() + ":0 0" + m.group(2)); + } + m.appendTail(sb); + css = sb.toString(); + + // Replace 0.6 to .6, but only when preceded by : or a white-space + css = css.replaceAll("(:|\\s)0+\\.(\\d+)", "$1.$2"); + + // Shorten colors from rgb(51,102,153) to #336699 + // This makes it more likely that it'll get further compressed in the next step. + p = Pattern.compile("rgb\\s*\\(\\s*([0-9,\\s]+)\\s*\\)"); + m = p.matcher(css); + sb = new StringBuffer(); + while (m.find()) { + String[] rgbcolors = m.group(1).split(","); + StringBuffer hexcolor = new StringBuffer("#"); + for (i = 0; i < rgbcolors.length; i++) { + int val = Integer.parseInt(rgbcolors[i]); + if (val < 16) { + hexcolor.append("0"); + } + hexcolor.append(Integer.toHexString(val)); + } + m.appendReplacement(sb, hexcolor.toString()); + } + m.appendTail(sb); + css = sb.toString(); + + // Shorten colors from #AABBCC to #ABC. Note that we want to make sure + // the color is not preceded by either ", " or =. Indeed, the property + // filter: chroma(color="#FFFFFF"); + // would become + // filter: chroma(color="#FFF"); + // which makes the filter break in IE. + p = Pattern.compile("([^\"'=\\s])(\\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])"); + m = p.matcher(css); + sb = new StringBuffer(); + while (m.find()) { + // Test for AABBCC pattern + if (m.group(3).equalsIgnoreCase(m.group(4)) && + m.group(5).equalsIgnoreCase(m.group(6)) && + m.group(7).equalsIgnoreCase(m.group(8))) { + m.appendReplacement(sb, (m.group(1) + m.group(2) + "#" + m.group(3) + m.group(5) + m.group(7)).toLowerCase()); + } else { + m.appendReplacement(sb, m.group().toLowerCase()); + } + } + m.appendTail(sb); + css = sb.toString(); + + // border: none -> border:0 + sb = new StringBuffer(); + p = Pattern.compile("(?i)(border|border-top|border-right|border-bottom|border-right|outline|background):none(;|})"); + m = p.matcher(css); + while (m.find()) { + m.appendReplacement(sb, m.group(1).toLowerCase() + ":0" + m.group(2)); + } + m.appendTail(sb); + css = sb.toString(); + + // shorter opacity IE filter + css = css.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity="); + + // Remove empty rules. + css = css.replaceAll("[^\\}\\{/;]+\\{\\}", ""); + + if (linebreakpos >= 0) { + // Some source control tools don't like it when files containing lines longer + // than, say 8000 characters, are checked in. The linebreak option is used in + // that case to split long lines after a specific column. + i = 0; + int linestartpos = 0; + sb = new StringBuffer(css); + while (i < sb.length()) { + char c = sb.charAt(i++); + if (c == '}' && i - linestartpos > linebreakpos) { + sb.insert(i, '\n'); + linestartpos = i; + } + } + + css = sb.toString(); + } + + // Replace multiple semi-colons in a row by a single one + // See SF bug #1980989 + css = css.replaceAll(";;+", ";"); + + // restore preserved comments and strings + for(i = 0, max = preservedTokens.size(); i < max; i++) { + css = css.replace("___YUICSSMIN_PRESERVED_TOKEN_" + i + "___", preservedTokens.get(i).toString()); + } + + // Trim the final string (for any leading or trailing white spaces) + css = css.trim(); + + // Write the output... + out.write(css); + } +} \ No newline at end of file diff --git a/min/lib/Minify/YUI/CssCompressor.php b/min/lib/Minify/YUI/CssCompressor.php new file mode 100644 index 0000000..5ae2185 --- /dev/null +++ b/min/lib/Minify/YUI/CssCompressor.php @@ -0,0 +1,171 @@ ++\\(\\)\\],])@", "$1", $css); + $css = str_replace("___PSEUDOCLASSCOLON___", ":", $css); + + // Remove the spaces after the things that should not have spaces after them. + $css = preg_replace("@([!{}:;>+\\(\\[,])\\s+@", "$1", $css); + + // Add the semicolon where it's missing. + $css = preg_replace("@([^;\\}])}@", "$1;}", $css); + + // Replace 0(px,em,%) with 0. + $css = preg_replace("@([\\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)@", "$1$2", $css); + + // Replace 0 0 0 0; with 0. + $css = str_replace(":0 0 0 0;", ":0;", $css); + $css = str_replace(":0 0 0;", ":0;", $css); + $css = str_replace(":0 0;", ":0;", $css); + + // Replace background-position:0; with background-position:0 0; + $css = str_replace("background-position:0;", "background-position:0 0;", $css); + + // Replace 0.6 to .6, but only when preceded by : or a white-space + $css = preg_replace("@(:|\\s)0+\\.(\\d+)@", "$1.$2", $css); + + // Shorten colors from rgb(51,102,153) to #336699 + // This makes it more likely that it'll get further compressed in the next step. + $css = preg_replace_callback("@rgb\\s*\\(\\s*([0-9,\\s]+)\\s*\\)@", array($this, '_shortenRgbCB'), $css); + + // Shorten colors from #AABBCC to #ABC. Note that we want to make sure + // the color is not preceded by either ", " or =. Indeed, the property + // filter: chroma(color="#FFFFFF"); + // would become + // filter: chroma(color="#FFF"); + // which makes the filter break in IE. + $css = preg_replace_callback("@([^\"'=\\s])(\\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])@", array($this, '_shortenHexCB'), $css); + + // Remove empty rules. + $css = preg_replace("@[^\\}]+\\{;\\}@", "", $css); + + $linebreakpos = isset($this->_options['linebreakpos']) + ? $this->_options['linebreakpos'] + : 0; + + if ($linebreakpos > 0) { + // Some source control tools don't like it when files containing lines longer + // than, say 8000 characters, are checked in. The linebreak option is used in + // that case to split long lines after a specific column. + $i = 0; + $linestartpos = 0; + $sb = $css; + + // make sure strlen returns byte count + $mbIntEnc = null; + if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) { + $mbIntEnc = mb_internal_encoding(); + mb_internal_encoding('8bit'); + } + $sbLength = strlen($css); + while ($i < $sbLength) { + $c = $sb[$i++]; + if ($c === '}' && $i - $linestartpos > $linebreakpos) { + $sb = substr_replace($sb, "\n", $i, 0); + $sbLength++; + $linestartpos = $i; + } + } + $css = $sb; + + // undo potential mb_encoding change + if ($mbIntEnc !== null) { + mb_internal_encoding($mbIntEnc); + } + } + + // Replace the pseudo class for the Box Model Hack + $css = str_replace("___PSEUDOCLASSBMH___", "\"\\\\\"}\\\\\"\"", $css); + + // Replace multiple semi-colons in a row by a single one + // See SF bug #1980989 + $css = preg_replace("@;;+@", ";", $css); + + // prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/ + $css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css); + + // Trim the final string (for any leading or trailing white spaces) + $css = trim($css); + + return $css; + } + + protected function _removeSpacesCB($m) + { + return str_replace(':', '___PSEUDOCLASSCOLON___', $m[0]); + } + + protected function _shortenRgbCB($m) + { + $rgbcolors = explode(',', $m[1]); + $hexcolor = '#'; + for ($i = 0; $i < count($rgbcolors); $i++) { + $val = round($rgbcolors[$i]); + if ($val < 16) { + $hexcolor .= '0'; + } + $hexcolor .= dechex($val); + } + return $hexcolor; + } + + protected function _shortenHexCB($m) + { + // Test for AABBCC pattern + if ((strtolower($m[3])===strtolower($m[4])) && + (strtolower($m[5])===strtolower($m[6])) && + (strtolower($m[7])===strtolower($m[8]))) { + return $m[1] . $m[2] . "#" . $m[3] . $m[5] . $m[7]; + } else { + return $m[0]; + } + } +} \ No newline at end of file diff --git a/min/lib/Minify/YUI/Java/Matcher.php b/min/lib/Minify/YUI/Java/Matcher.php new file mode 100644 index 0000000..c504896 --- /dev/null +++ b/min/lib/Minify/YUI/Java/Matcher.php @@ -0,0 +1,81 @@ +_subject = $subject; + preg_match_all($pattern, $subject, $this->_matches, PREG_OFFSET_CAPTURE); + } + + /** + * + * @return bool + */ + public function find() + { + $this->_match = current($this->_matches); + if ($this->_match) { + next($this->_matches); + return true; + } + return false; + } + + public function group($group = 0) + { + return $this->_match[0][$group]; + } + + public function start() + { + return $this->_match[1]; + } + + public function end() + { + return $this->_match[1] + strlen($this->_match[0][0]); + } + + public function appendReplacement(Minify_YUI_Java_String $string, $replacement) + { + $length = $this->start() - $this->_appendPosition; + $string->append(substr($this->_subject, $this->_appendPosition, $length)); + + $i = 0; + $newReplacement = ''; + $next = ''; + $length = strlen($replacement); + while ($i < $length) { + $curr = $replacement[$i]; + $next = ($i === ($length - 1)) ? '' : $replacement[$i + 1]; + if ($curr === '\\' && $next === '$') { + $newReplacement .= '$'; + $i += 2; + continue; + } + if ($curr === '$' && is_numeric($next) && isset($this->_match[0][(int) $next])) { + $newReplacement .= $this->_match[0][(int) $next]; + $i += 2; + continue; + } + $newReplacement .= $curr; + $i++; + } + + $string->append($newReplacement); + $this->_appendPosition = $this->end(); + } + + public function appendTail(Minify_YUI_Java_String $string) + { + $string->append(substr($this->_subject, $this->_appendPosition)); + } +} \ No newline at end of file diff --git a/min/lib/Minify/YUI/Java/String.php b/min/lib/Minify/YUI/Java/String.php new file mode 100644 index 0000000..63a0c37 --- /dev/null +++ b/min/lib/Minify/YUI/Java/String.php @@ -0,0 +1,64 @@ +content = $content; + } + + public function replace($target, $replacement) + { + return new self(str_replace($target, $replacement, $this->content)); + } + + public function replaceAll($regex, $replacement) + { + $pattern = '/' . str_replace('/', '\/', $regex) . '/'; + return new self(preg_replace($pattern, $replacement, $this->content)); + } + + /** + * Return position (in bytes) of string found or -1 (not FALSE!) + * @param string $needle + * @param int $offset + * @return int + */ + public function indexOf($needle, $offset = 0) { + $pos = strpos($this->content, $needle, $offset); + return ($pos === false) + ? -1 + : $pos; + } + + /** + * Get number of bytes (not characters) in string + * @return int + */ + public function length() + { + return strlen($this->content); + } + + public function toString() + { + return $this->content; + } + + public function append($str) + { + $this->content .= $str; + } +} \ No newline at end of file diff --git a/min_unit_tests/_test_files/css/issue210.css b/min_unit_tests/_test_files/css/issue210.css new file mode 100644 index 0000000..842195b --- /dev/null +++ b/min_unit_tests/_test_files/css/issue210.css @@ -0,0 +1 @@ +.ui-widget { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; } \ No newline at end of file diff --git a/min_unit_tests/_test_files/css/issue210.min.css b/min_unit_tests/_test_files/css/issue210.min.css new file mode 100644 index 0000000..8a1869d --- /dev/null +++ b/min_unit_tests/_test_files/css/issue210.min.css @@ -0,0 +1 @@ +.ui-widget{font-family:Trebuchet MS,Tahoma,Verdana,Arial,sans-serif} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/README b/min_unit_tests/_test_files/yuic/README new file mode 100644 index 0000000..12f9be7 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/README @@ -0,0 +1,6 @@ +To add a test: + +1. Create a "blah.css" or "blah.js" file. +2. Create a "blah.css.min" or "blah.js.min" file, containing the expected minified output. + +That's all! \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/background-position.css b/min_unit_tests/_test_files/yuic/background-position.css new file mode 100644 index 0000000..4cdff82 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/background-position.css @@ -0,0 +1,2 @@ +a {background-position: 0 0 0 0;} +b {BACKGROUND-POSITION: 0 0;} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/background-position.css.min b/min_unit_tests/_test_files/yuic/background-position.css.min new file mode 100644 index 0000000..0895e1a --- /dev/null +++ b/min_unit_tests/_test_files/yuic/background-position.css.min @@ -0,0 +1 @@ +a{background-position:0 0}b{background-position:0 0} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/border-none.css b/min_unit_tests/_test_files/yuic/border-none.css new file mode 100644 index 0000000..29f9cba --- /dev/null +++ b/min_unit_tests/_test_files/yuic/border-none.css @@ -0,0 +1,5 @@ +a { + border: none; +} +b {BACKGROUND:none} +s {border-top: none;} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/border-none.css.min b/min_unit_tests/_test_files/yuic/border-none.css.min new file mode 100644 index 0000000..1ed1b65 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/border-none.css.min @@ -0,0 +1 @@ +a{border:0}b{background:0}s{border-top:0} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/box-model-hack.css b/min_unit_tests/_test_files/yuic/box-model-hack.css new file mode 100644 index 0000000..c00e32f --- /dev/null +++ b/min_unit_tests/_test_files/yuic/box-model-hack.css @@ -0,0 +1,9 @@ +#elem { + width: 100px; + voice-family: "\"}\""; + voice-family:inherit; + width: 200px; +} +html>body #elem { + width: 200px; +} diff --git a/min_unit_tests/_test_files/yuic/box-model-hack.css.min b/min_unit_tests/_test_files/yuic/box-model-hack.css.min new file mode 100644 index 0000000..3340179 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/box-model-hack.css.min @@ -0,0 +1 @@ +#elem{width:100px;voice-family:"\"}\"";voice-family:inherit;width:200px}html>body #elem{width:200px} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/bug2527974.css b/min_unit_tests/_test_files/yuic/bug2527974.css new file mode 100644 index 0000000..b3bc2c8 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/bug2527974.css @@ -0,0 +1,10 @@ +/* this file contains no css, it exists purely to put the revision number into the + combined css before uploading it to SiteManager. The exclaimation at the start + of the comment informs yuicompressor not to strip the comment out */ + +/*! $LastChangedRevision: 81 $ $LastChangedDate: 2009-05-27 17:41:02 +0100 (Wed, 27 May 2009) $ */ + +body { + yo: cats; +} +ul[id$=foo] label:hover {yo: yo;} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/bug2527974.css.min b/min_unit_tests/_test_files/yuic/bug2527974.css.min new file mode 100644 index 0000000..00cc007 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/bug2527974.css.min @@ -0,0 +1 @@ +/*! $LastChangedRevision: 81 $ $LastChangedDate: 2009-05-27 17:41:02 +0100 (Wed, 27 May 2009) $ */body{yo:cats}ul[id$=foo] label:hover{yo:yo} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/bug2527991.css b/min_unit_tests/_test_files/yuic/bug2527991.css new file mode 100644 index 0000000..d4c80ff --- /dev/null +++ b/min_unit_tests/_test_files/yuic/bug2527991.css @@ -0,0 +1,19 @@ +@media screen and/*!YUI-Compresser */(-webkit-min-device-pixel-ratio:0) { + a{ + b: 1; + } +} + + +@media screen and/*! */ /*! */(-webkit-min-device-pixel-ratio:0) { + a{ + b: 1; + } +} + + +@media -webkit-min-device-pixel-ratio:0 { + a{ + b: 1; + } +} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/bug2527991.css.min b/min_unit_tests/_test_files/yuic/bug2527991.css.min new file mode 100644 index 0000000..965755a --- /dev/null +++ b/min_unit_tests/_test_files/yuic/bug2527991.css.min @@ -0,0 +1 @@ +@media screen and/*!YUI-Compresser */(-webkit-min-device-pixel-ratio:0){a{b:1}}@media screen and/*! *//*! */(-webkit-min-device-pixel-ratio:0){a{b:1}}@media -webkit-min-device-pixel-ratio:0{a{b:1}} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/bug2527998.css b/min_unit_tests/_test_files/yuic/bug2527998.css new file mode 100644 index 0000000..9c6c00e --- /dev/null +++ b/min_unit_tests/_test_files/yuic/bug2527998.css @@ -0,0 +1,4 @@ +/*! special */ +body { + +} diff --git a/min_unit_tests/_test_files/yuic/bug2527998.css.min b/min_unit_tests/_test_files/yuic/bug2527998.css.min new file mode 100644 index 0000000..7fabf8a --- /dev/null +++ b/min_unit_tests/_test_files/yuic/bug2527998.css.min @@ -0,0 +1 @@ +/*! special */ \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/bug2528034.css b/min_unit_tests/_test_files/yuic/bug2528034.css new file mode 100644 index 0000000..c315cb1 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/bug2528034.css @@ -0,0 +1,5 @@ +a[href$="/test/"] span:first-child { b:1; } +a[href$="/test/"] span:first-child { } + + + diff --git a/min_unit_tests/_test_files/yuic/bug2528034.css.min b/min_unit_tests/_test_files/yuic/bug2528034.css.min new file mode 100644 index 0000000..1543777 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/bug2528034.css.min @@ -0,0 +1 @@ +a[href$="/test/"] span:first-child{b:1} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/charset-media.css b/min_unit_tests/_test_files/yuic/charset-media.css new file mode 100644 index 0000000..bd02f38 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/charset-media.css @@ -0,0 +1,9 @@ +/* re: 2495387 */ +@charset 'utf-8'; +@media all { +body { +} +body { +background-color: gold; +} +} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/charset-media.css.min b/min_unit_tests/_test_files/yuic/charset-media.css.min new file mode 100644 index 0000000..dcaf49d --- /dev/null +++ b/min_unit_tests/_test_files/yuic/charset-media.css.min @@ -0,0 +1 @@ +@charset 'utf-8';@media all{body{background-color:gold}} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/color.css b/min_unit_tests/_test_files/yuic/color.css new file mode 100644 index 0000000..8d69639 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/color.css @@ -0,0 +1,7 @@ +.color { + me: rgb(123, 123, 123); + impressed: #ffeedd; + filter: chroma(color="#FFFFFF"); + background: none repeat scroll 0 0 rgb(255, 0,0); + alpha: rgba(1, 2, 3, 4); +} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/color.css.min b/min_unit_tests/_test_files/yuic/color.css.min new file mode 100644 index 0000000..c45ebba --- /dev/null +++ b/min_unit_tests/_test_files/yuic/color.css.min @@ -0,0 +1 @@ +.color{me:#7b7b7b;impressed:#fed;filter:chroma(color="#FFFFFF");background:none repeat scroll 0 0 #f00;alpha:rgba(1,2,3,4)} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/comment.css b/min_unit_tests/_test_files/yuic/comment.css new file mode 100644 index 0000000..7073b9e --- /dev/null +++ b/min_unit_tests/_test_files/yuic/comment.css @@ -0,0 +1,3 @@ +html >/**/ body p { + color: blue; +} diff --git a/min_unit_tests/_test_files/yuic/comment.css.min b/min_unit_tests/_test_files/yuic/comment.css.min new file mode 100644 index 0000000..b280371 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/comment.css.min @@ -0,0 +1 @@ +html>/**/body p{color:blue} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/concat-charset.css b/min_unit_tests/_test_files/yuic/concat-charset.css new file mode 100644 index 0000000..87ca565 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/concat-charset.css @@ -0,0 +1,15 @@ +/* This is invalid CSS, but frequently happens as a result of concatenation. */ +@charset "utf-8"; +#foo { + border-width:1px; +} +/* +Note that this is erroneous! +The actual CSS file can only have a single charset. +However, this is the job of the author/application. +The compressor should not get involved. +*/ +@charset "another one"; +#bar { + border-width:10px; +} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/concat-charset.css.min b/min_unit_tests/_test_files/yuic/concat-charset.css.min new file mode 100644 index 0000000..73e8d3b --- /dev/null +++ b/min_unit_tests/_test_files/yuic/concat-charset.css.min @@ -0,0 +1 @@ +@charset "utf-8";#foo{border-width:1px}#bar{border-width:10px} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/decimals.css b/min_unit_tests/_test_files/yuic/decimals.css new file mode 100644 index 0000000..9593979 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/decimals.css @@ -0,0 +1,3 @@ +::selection { + margin: 0.6px 0.333pt 1.2em 8.8cm; +} diff --git a/min_unit_tests/_test_files/yuic/decimals.css.min b/min_unit_tests/_test_files/yuic/decimals.css.min new file mode 100644 index 0000000..4dadedc --- /dev/null +++ b/min_unit_tests/_test_files/yuic/decimals.css.min @@ -0,0 +1 @@ +::selection{margin:.6px .333pt 1.2em 8.8cm} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/dollar-header.css b/min_unit_tests/_test_files/yuic/dollar-header.css new file mode 100644 index 0000000..43999c4 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/dollar-header.css @@ -0,0 +1,7 @@ +/*! +$Header: /temp/dirname/filename.css 3 2/02/08 3:37p JSmith $ +*/ + +foo { + bar: baz +} diff --git a/min_unit_tests/_test_files/yuic/dollar-header.css.min b/min_unit_tests/_test_files/yuic/dollar-header.css.min new file mode 100644 index 0000000..9308100 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/dollar-header.css.min @@ -0,0 +1,3 @@ +/*! +$Header: /temp/dirname/filename.css 3 2/02/08 3:37p JSmith $ +*/foo{bar:baz} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/font-face.css b/min_unit_tests/_test_files/yuic/font-face.css new file mode 100644 index 0000000..4b6956c --- /dev/null +++ b/min_unit_tests/_test_files/yuic/font-face.css @@ -0,0 +1,6 @@ +@font-face { + font-family: 'gzipper'; + src: url(yanone.eot); + src: local('gzipper'), + url(yanone.ttf) format('truetype'); +} diff --git a/min_unit_tests/_test_files/yuic/font-face.css.min b/min_unit_tests/_test_files/yuic/font-face.css.min new file mode 100644 index 0000000..3a1077c --- /dev/null +++ b/min_unit_tests/_test_files/yuic/font-face.css.min @@ -0,0 +1 @@ +@font-face{font-family:'gzipper';src:url(yanone.eot);src:local('gzipper'),url(yanone.ttf) format('truetype')} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/ie5mac.css b/min_unit_tests/_test_files/yuic/ie5mac.css new file mode 100644 index 0000000..e4d5204 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/ie5mac.css @@ -0,0 +1,5 @@ +/* Ignore the next rule in IE mac \*/ +.selector { + color: khaki; +} +/* Stop ignoring in IE mac */ diff --git a/min_unit_tests/_test_files/yuic/ie5mac.css.min b/min_unit_tests/_test_files/yuic/ie5mac.css.min new file mode 100644 index 0000000..f90df41 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/ie5mac.css.min @@ -0,0 +1 @@ +/*\*/.selector{color:khaki}/**/ \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/media-empty-class.css b/min_unit_tests/_test_files/yuic/media-empty-class.css new file mode 100644 index 0000000..d2f22d5 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/media-empty-class.css @@ -0,0 +1,16 @@ +/*! preserved */ +emptiness {} + +@import "another.css"; +/* I'm empty - delete me */ +empty { ;} + +@media print { + .noprint { display: none; } +} + +@media screen { + /* this rule should be removed, not simply minified.*/ + .breakme {} + .printonly { display: none; } +} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/media-empty-class.css.min b/min_unit_tests/_test_files/yuic/media-empty-class.css.min new file mode 100644 index 0000000..0350c7f --- /dev/null +++ b/min_unit_tests/_test_files/yuic/media-empty-class.css.min @@ -0,0 +1 @@ +/*! preserved */@import "another.css";@media print{.noprint{display:none}}@media screen{.printonly{display:none}} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/media-multi.css b/min_unit_tests/_test_files/yuic/media-multi.css new file mode 100644 index 0000000..c589771 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/media-multi.css @@ -0,0 +1,3 @@ +@media only all and (max-width:50em), only all and (max-device-width:800px), only all and (max-width:780px) { + some-css : here +} diff --git a/min_unit_tests/_test_files/yuic/media-multi.css.min b/min_unit_tests/_test_files/yuic/media-multi.css.min new file mode 100644 index 0000000..57b52f7 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/media-multi.css.min @@ -0,0 +1 @@ +@media only all and (max-width:50em),only all and (max-device-width:800px),only all and (max-width:780px){some-css:here} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/media-test.css b/min_unit_tests/_test_files/yuic/media-test.css new file mode 100644 index 0000000..af118ff --- /dev/null +++ b/min_unit_tests/_test_files/yuic/media-test.css @@ -0,0 +1,3 @@ +@media screen and (-webkit-min-device-pixel-ratio:0) { + some-css : here +} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/media-test.css.min b/min_unit_tests/_test_files/yuic/media-test.css.min new file mode 100644 index 0000000..0e7168e --- /dev/null +++ b/min_unit_tests/_test_files/yuic/media-test.css.min @@ -0,0 +1 @@ +@media screen and (-webkit-min-device-pixel-ratio:0){some-css:here} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/opacity-filter.css b/min_unit_tests/_test_files/yuic/opacity-filter.css new file mode 100644 index 0000000..60deca7 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/opacity-filter.css @@ -0,0 +1,14 @@ +/* example from https://developer.mozilla.org/en/CSS/opacity */ +pre { /* make the box translucent (80% opaque) */ + border: solid red; + opacity: 0.8; /* Firefox, Safari(WebKit), Opera */ + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)"; /* IE 8 */ + filter: PROGID:DXImageTransform.Microsoft.Alpha(Opacity=80); /* IE 4-7 */ + zoom: 1; /* set "zoom", "width" or "height" to trigger "hasLayout" in IE 7 and lower */ +} + +/** and again */ +code { + -ms-filter: "PROGID:DXImageTransform.Microsoft.Alpha(Opacity=80)"; /* IE 8 */ + filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); /* IE 4-7 */ +} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/opacity-filter.css.min b/min_unit_tests/_test_files/yuic/opacity-filter.css.min new file mode 100644 index 0000000..99b4fa8 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/opacity-filter.css.min @@ -0,0 +1 @@ +pre{border:solid red;opacity:.8;-ms-filter:"alpha(opacity=80)";filter:alpha(opacity=80);zoom:1}code{-ms-filter:"alpha(opacity=80)";filter:alpha(opacity=80)} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/preserve-new-line.css b/min_unit_tests/_test_files/yuic/preserve-new-line.css new file mode 100644 index 0000000..e1f0c92 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/preserve-new-line.css @@ -0,0 +1,6 @@ +#sel-o { + content: "on\"ce upon \ +a time"; + content: 'once upon \ +a ti\'me'; +} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/preserve-new-line.css.min b/min_unit_tests/_test_files/yuic/preserve-new-line.css.min new file mode 100644 index 0000000..6ac20b6 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/preserve-new-line.css.min @@ -0,0 +1,3 @@ +#sel-o{content:"on\"ce upon \ +a time";content:'once upon \ +a ti\'me'} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/preserve-strings.css b/min_unit_tests/_test_files/yuic/preserve-strings.css new file mode 100644 index 0000000..9151373 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/preserve-strings.css @@ -0,0 +1,7 @@ +/* preserving strings */ +.sele { + content: "\"keep \" me"; + something: '\\\' . . '; + else: 'empty{}'; + content: "/* test */"; /* <---- this is not a comment, should be be kept */ +} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/preserve-strings.css.min b/min_unit_tests/_test_files/yuic/preserve-strings.css.min new file mode 100644 index 0000000..3f1d010 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/preserve-strings.css.min @@ -0,0 +1 @@ +.sele{content:"\"keep \" me";something:'\\\' . . ';else:'empty{}';content:"/* test */"} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/pseudo-first.css b/min_unit_tests/_test_files/yuic/pseudo-first.css new file mode 100644 index 0000000..dbadef4 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/pseudo-first.css @@ -0,0 +1,16 @@ +/* +because of IE6 first-letter and first-line +must be followed by a space +http://reference.sitepoint.com/css/pseudoelement-firstletter +Thanks: P.Sorokin comment at http://www.phpied.com/cssmin-js/ +*/ +p:first-letter{ + buh: hum; +} +p:first-line{ + baa: 1; +} + +p:first-line,a,p:first-letter,b{ + color: red; +} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/pseudo-first.css.min b/min_unit_tests/_test_files/yuic/pseudo-first.css.min new file mode 100644 index 0000000..687117c --- /dev/null +++ b/min_unit_tests/_test_files/yuic/pseudo-first.css.min @@ -0,0 +1 @@ +p:first-letter {buh:hum}p:first-line {baa:1}p:first-line ,a,p:first-letter ,b{color:red} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/pseudo.css b/min_unit_tests/_test_files/yuic/pseudo.css new file mode 100644 index 0000000..126a5b1 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/pseudo.css @@ -0,0 +1,4 @@ +p :link { + ba:zinga;;; + foo: bar;;; +} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/pseudo.css.min b/min_unit_tests/_test_files/yuic/pseudo.css.min new file mode 100644 index 0000000..bb7f8e7 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/pseudo.css.min @@ -0,0 +1 @@ +p :link{ba:zinga;foo:bar} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/special-comments.css b/min_unit_tests/_test_files/yuic/special-comments.css new file mode 100644 index 0000000..4e184ba --- /dev/null +++ b/min_unit_tests/_test_files/yuic/special-comments.css @@ -0,0 +1,13 @@ +/*!************88**** + Preserving comments + as they are + ******************** + Keep the initial ! + *******************/ +#yo { + ma: "ma"; +} +/*! +I said +pre- +serve! */ \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/special-comments.css.min b/min_unit_tests/_test_files/yuic/special-comments.css.min new file mode 100644 index 0000000..92ecbac --- /dev/null +++ b/min_unit_tests/_test_files/yuic/special-comments.css.min @@ -0,0 +1,9 @@ +/*!************88**** + Preserving comments + as they are + ******************** + Keep the initial ! + *******************/#yo{ma:"ma"}/*! +I said +pre- +serve! */ \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/star-underscore-hacks.css b/min_unit_tests/_test_files/yuic/star-underscore-hacks.css new file mode 100644 index 0000000..8b6e517 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/star-underscore-hacks.css @@ -0,0 +1,5 @@ +#elementarr { + width: 1px; + *width: 3pt; + _width: 2em; +} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/star-underscore-hacks.css.min b/min_unit_tests/_test_files/yuic/star-underscore-hacks.css.min new file mode 100644 index 0000000..0a014c3 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/star-underscore-hacks.css.min @@ -0,0 +1 @@ +#elementarr{width:1px;*width:3pt;_width:2em} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/string-in-comment.css b/min_unit_tests/_test_files/yuic/string-in-comment.css new file mode 100644 index 0000000..d94d192 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/string-in-comment.css @@ -0,0 +1,8 @@ +/* te " st */ +a{a:1} +/*!"preserve" me*/ +b{content: "/**/"} +/* quite " quote ' \' \" */ +/* ie mac \*/ +c {c : 3} +/* end hiding */ \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/string-in-comment.css.min b/min_unit_tests/_test_files/yuic/string-in-comment.css.min new file mode 100644 index 0000000..7cdec2d --- /dev/null +++ b/min_unit_tests/_test_files/yuic/string-in-comment.css.min @@ -0,0 +1 @@ +a{a:1}/*!"preserve" me*/b{content:"/**/"}/*\*/c{c:3}/**/ \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/webkit-transform.css b/min_unit_tests/_test_files/yuic/webkit-transform.css new file mode 100644 index 0000000..83a50f2 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/webkit-transform.css @@ -0,0 +1,2 @@ +c {-webkit-transform-origin: 0 0;} +d {-MOZ-TRANSFORM-ORIGIN: 0 0 } \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/webkit-transform.css.min b/min_unit_tests/_test_files/yuic/webkit-transform.css.min new file mode 100644 index 0000000..b640ddf --- /dev/null +++ b/min_unit_tests/_test_files/yuic/webkit-transform.css.min @@ -0,0 +1 @@ +c{-webkit-transform-origin:0 0}d{-moz-transform-origin:0 0} \ No newline at end of file diff --git a/min_unit_tests/_test_files/yuic/zeros.css b/min_unit_tests/_test_files/yuic/zeros.css new file mode 100644 index 0000000..a5a4da2 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/zeros.css @@ -0,0 +1,6 @@ +a { + margin: 0px 0pt 0em 0%; + _padding-top: 0ex; + background-position: 0 0; + padding: 0in 0cm 0mm 0pc +} diff --git a/min_unit_tests/_test_files/yuic/zeros.css.min b/min_unit_tests/_test_files/yuic/zeros.css.min new file mode 100644 index 0000000..14ac7a9 --- /dev/null +++ b/min_unit_tests/_test_files/yuic/zeros.css.min @@ -0,0 +1 @@ +a{margin:0;_padding-top:0;background-position:0 0;padding:0} \ No newline at end of file From 1fed7e4ab8b0570530ec4c0263df52ef43923c06 Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Sun, 9 Jan 2011 23:19:13 +0000 Subject: [PATCH 22/25] move cc-checking to constructor --- min/lib/JSMin.php | 50 ++++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/min/lib/JSMin.php b/min/lib/JSMin.php index 75e8e10..011eca3 100644 --- a/min/lib/JSMin.php +++ b/min/lib/JSMin.php @@ -10,7 +10,7 @@ * modifications to preserve some comments (see below). Also, rather than using * stdin/stdout, JSMin::minify() accepts a string as input and returns another * string as output. - * + * * Comments containing IE conditional compilation are preserved, as are multi-line * comments that begin with "/*!" (for documentation purposes). In the latter case * newlines are inserted around the comment to enhance readability. @@ -60,7 +60,7 @@ class JSMin { const ACTION_KEEP_A = 1; const ACTION_DELETE_A = 2; const ACTION_DELETE_A_B = 3; - + protected $a = "\n"; protected $b = ''; protected $input = ''; @@ -70,36 +70,32 @@ class JSMin { protected $output = ''; /** - * Minify Javascript + * Minify Javascript. * * @param string $js Javascript to be minified * @return string */ public static function minify($js) { - // look out for syntax like "++ +" and "- ++" - $p = '\\+'; - $m = '\\-'; - if (preg_match("/([$p$m])(?:\\1 [$p$m]| (?:$p$p|$m$m))/", $js)) { - // likely pre-minified and would be broken by JSMin - return $js; - } $jsmin = new JSMin($js); return $jsmin->min(); } - /* - * Don't create a JSMin instance, instead use the static function minify, - * which checks for mb_string function overloading and avoids errors - * trying to re-minify the output of Closure Compiler - * - * @private + /** + * @param string $input */ public function __construct($input) { $this->input = $input; + // look out for syntax like "++ +" and "- ++" + $p = '\\+'; + $m = '\\-'; + if (preg_match("/([$p$m])(?:\\1 [$p$m]| (?:$p$p|$m$m))/", $input)) { + // likely pre-minified and would be broken by JSMin + $this->output = $input; + } } - + /** * Perform minification, return result */ @@ -118,7 +114,7 @@ class JSMin { $this->inputLength = strlen($this->input); $this->action(self::ACTION_DELETE_A_B); - + while ($this->a !== null) { // determine next command $command = self::ACTION_KEEP_A; // default @@ -138,7 +134,7 @@ class JSMin { } } elseif (! $this->isAlphaNum($this->a)) { if ($this->b === ' ' - || ($this->b === "\n" + || ($this->b === "\n" && (false === strpos('}])+-"\'', $this->a)))) { $command = self::ACTION_DELETE_A_B; } @@ -152,7 +148,7 @@ class JSMin { } return $this->output; } - + /** * ACTION_KEEP_A = Output A. Copy B to A. Get the next B. * ACTION_DELETE_A = Copy B to A. Get the next B. @@ -214,7 +210,7 @@ class JSMin { // end case ACTION_DELETE_A_B } } - + protected function isRegexpLiteral() { if (false !== strpos("\n{;(,=:[!&|?", $this->a)) { // we aren't dividing @@ -239,7 +235,7 @@ class JSMin { } return false; } - + /** * Get next char. Convert ctrl char to space. */ @@ -263,7 +259,7 @@ class JSMin { } return $c; } - + /** * Get next char. If is ctrl character, translate to a space or newline. */ @@ -272,7 +268,7 @@ class JSMin { $this->lookAhead = $this->get(); return $this->lookAhead; } - + /** * Is $c a letter, digit, underscore, dollar sign, escape, or non-ASCII? */ @@ -280,7 +276,7 @@ class JSMin { { return (preg_match('/^[0-9a-zA-Z_\\$\\\\]$/', $c) || ord($c) > 126); } - + protected function singleLineComment() { $comment = ''; @@ -296,7 +292,7 @@ class JSMin { } } } - + protected function multipleLineComment() { $this->get(); @@ -324,7 +320,7 @@ class JSMin { $comment .= $get; } } - + /** * Get the next character, skipping over comments. * Some comments may be preserved. From 5002cd5fa768d55bcc4b51cf5ba7767751e6fabc Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Thu, 23 Jun 2011 19:21:45 +0000 Subject: [PATCH 23/25] Better fix for Issue 210 --- min/lib/Minify/CSS/Compressor.php | 14 ++++++++------ min_unit_tests/_test_files/css/styles.css | 2 +- min_unit_tests/_test_files/css/styles.min.css | 2 +- .../_test_files/importProcessor/output.css | 2 +- min_unit_tests/_test_files/minify/minified.css | 2 +- min_unit_tests/test_Minify_CSS.php | 2 +- min_unit_tests/test_Minify_JS_ClosureCompiler.php | 2 +- 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/min/lib/Minify/CSS/Compressor.php b/min/lib/Minify/CSS/Compressor.php index 3c96c10..f6fb034 100644 --- a/min/lib/Minify/CSS/Compressor.php +++ b/min/lib/Minify/CSS/Compressor.php @@ -236,14 +236,16 @@ class Minify_CSS_Compressor { */ protected function _fontFamilyCB($m) { - $pieces = preg_split('@(\'[^\']+\'|"[^"]+")@', $m[1], null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); - foreach ($pieces as $i => $piece) { + // Issue 210: must not eliminate WS between words in unquoted families + $pieces = preg_split('/(\'[^\']+\'|"[^"]+")/', $m[1], null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $out = 'font-family:'; + while (null !== ($piece = array_shift($pieces))) { if ($piece[0] !== '"' && $piece[0] !== "'") { - $piece = trim(preg_replace('@\\s+@', ' ', $piece)); - $piece = preg_replace('@\\s*,\\s*@', ',', $piece); - $pieces[$i] = $piece; + $piece = preg_replace('/\\s+/', ' ', $piece); + $piece = preg_replace('/\\s?,\\s?/', ',', $piece); } + $out .= $piece; } - return 'font-family:' . implode('', $pieces) . $m[2]; + return $out . $m[2]; } } diff --git a/min_unit_tests/_test_files/css/styles.css b/min_unit_tests/_test_files/css/styles.css index 0c3a892..ed0bcd4 100644 --- a/min_unit_tests/_test_files/css/styles.css +++ b/min_unit_tests/_test_files/css/styles.css @@ -5,7 +5,7 @@ @import url( /more.css ); body, td, th { - font-family: Verdana , "Bitstream Vera Sans" , sans-serif ; + font-family: Verdana , "Bitstream Vera Sans" , Arial Narrow, sans-serif ; font-size : 12px; } diff --git a/min_unit_tests/_test_files/css/styles.min.css b/min_unit_tests/_test_files/css/styles.min.css index a823b05..a71063d 100644 --- a/min_unit_tests/_test_files/css/styles.min.css +++ b/min_unit_tests/_test_files/css/styles.min.css @@ -1,3 +1,3 @@ -@import url(/more.css);body,td,th{font-family:Verdana,"Bitstream Vera Sans",sans-serif;font-size:12px}.nav{margin-left:20%}#main-nav{background-color:red;border:1px +@import url(/more.css);body,td,th{font-family:Verdana,"Bitstream Vera Sans",Arial Narrow,sans-serif;font-size:12px}.nav{margin-left:20%}#main-nav{background-color:red;border:1px solid #0f7}div#content h1+p{padding-top:0;margin-top:0}@media all and (min-width: 640px){#media-queries-1{background-color:#0f0}}@media screen and (max-width: 2000px){#media-queries-2{background-color:#0f0}} \ No newline at end of file diff --git a/min_unit_tests/_test_files/importProcessor/output.css b/min_unit_tests/_test_files/importProcessor/output.css index 84625e5..a652c99 100644 --- a/min_unit_tests/_test_files/importProcessor/output.css +++ b/min_unit_tests/_test_files/importProcessor/output.css @@ -6,7 +6,7 @@ @import url(/more.css); body, td, th { - font-family: Verdana , "Bitstream Vera Sans" , sans-serif ; + font-family: Verdana , "Bitstream Vera Sans" , Arial Narrow, sans-serif ; font-size : 12px; } diff --git a/min_unit_tests/_test_files/minify/minified.css b/min_unit_tests/_test_files/minify/minified.css index 85d38e9..4798bde 100644 --- a/min_unit_tests/_test_files/minify/minified.css +++ b/min_unit_tests/_test_files/minify/minified.css @@ -1,4 +1,4 @@ -@import url(/more.css);body,td,th{font-family:Verdana,"Bitstream Vera Sans",sans-serif;font-size:12px}.nav{margin-left:20%}#main-nav{background-color:red;border:1px +@import url(/more.css);body,td,th{font-family:Verdana,"Bitstream Vera Sans",Arial Narrow,sans-serif;font-size:12px}.nav{margin-left:20%}#main-nav{background-color:red;border:1px solid #0f7}div#content h1+p{padding-top:0;margin-top:0}@media all and (min-width: 640px){#media-queries-1{background-color:#0f0}}@media screen and (max-width: 2000px){#media-queries-2{background-color:#0f0}} /*! YUI Compressor style comments are preserved */ diff --git a/min_unit_tests/test_Minify_CSS.php b/min_unit_tests/test_Minify_CSS.php index 14f53a4..0e3f630 100644 --- a/min_unit_tests/test_Minify_CSS.php +++ b/min_unit_tests/test_Minify_CSS.php @@ -19,7 +19,7 @@ function test_CSS() $d->close(); foreach ($list as $item) { - + $options = array(); if ($item === 'paths_prepend') { $options = array('prependRelativePath' => '../'); diff --git a/min_unit_tests/test_Minify_JS_ClosureCompiler.php b/min_unit_tests/test_Minify_JS_ClosureCompiler.php index 74df047..bc71324 100644 --- a/min_unit_tests/test_Minify_JS_ClosureCompiler.php +++ b/min_unit_tests/test_Minify_JS_ClosureCompiler.php @@ -16,7 +16,7 @@ function test_Minify_JS_ClosureCompiler() window.undefined = undefined; })(window); "; - $minExpected = "(function(a,b){a.addOne=function(c){return 1+c};a.undefined=b})(window);"; + $minExpected = "(function(a,b){a.addOne=function(a){return 1+a};a.undefined=b})(window);"; $minOutput = Minify_JS_ClosureCompiler::minify($src); if (false !== strpos($minOutput, 'Error(22): Too many compiles')) { echo "!NOTE: Too many recent calls to Closure Compiler API to test.\n"; From 2ccb8042e852fe232bf4e6f0e64f6025f774e031 Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Thu, 23 Jun 2011 19:38:04 +0000 Subject: [PATCH 24/25] Issue 233 --- min/lib/Minify.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/min/lib/Minify.php b/min/lib/Minify.php index 58b9b88..d70e32d 100644 --- a/min/lib/Minify.php +++ b/min/lib/Minify.php @@ -253,7 +253,7 @@ class Minify { if (self::$_options['contentType'] === self::TYPE_CSS && self::$_options['rewriteCssUris']) { reset($controller->sources); - while (list($key, $source) = each($controller->sources)) { + foreach($controller->sources as $key => $source) { if ($source->filepath && !isset($source->minifyOptions['currentDir']) && !isset($source->minifyOptions['prependRelativePath']) From 0fe3b7fc4c0035faed72b8217bb4c5b53aa397d3 Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Thu, 23 Jun 2011 20:28:22 +0000 Subject: [PATCH 25/25] fixed Issue 229 --- min/lib/Minify/HTML.php | 6 ++++++ min_unit_tests/_test_files/html/before.html | 4 ++-- min_unit_tests/_test_files/html/before.min.html | 2 +- min_unit_tests/test_HTTP_Encoder.php | 6 +++--- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/min/lib/Minify/HTML.php b/min/lib/Minify/HTML.php index 9bcf0ad..e9453ff 100644 --- a/min/lib/Minify/HTML.php +++ b/min/lib/Minify/HTML.php @@ -144,6 +144,12 @@ class Minify_HTML { ,array_values($this->_placeholders) ,$this->_html ); + // issue 229: multi-pass to catch scripts that didn't get replaced in textareas + $this->_html = str_replace( + array_keys($this->_placeholders) + ,array_values($this->_placeholders) + ,$this->_html + ); return $this->_html; } diff --git a/min_unit_tests/_test_files/html/before.html b/min_unit_tests/_test_files/html/before.html index c22cc8f..3b06d18 100644 --- a/min_unit_tests/_test_files/html/before.html +++ b/min_unit_tests/_test_files/html/before.html @@ -78,7 +78,7 @@ css hack {

@@ -90,7 +90,7 @@ Design
 		
+1234567890
\ No newline at end of file diff --git a/min_unit_tests/_test_files/html/before.min.html b/min_unit_tests/_test_files/html/before.min.html index 6b1373f..7ec603d 100644 --- a/min_unit_tests/_test_files/html/before.min.html +++ b/min_unit_tests/_test_files/html/before.min.html @@ -35,4 +35,4 @@ class="p2">Download the sample html file and css file

\ No newline at end of file +1234567890
\ No newline at end of file diff --git a/min_unit_tests/test_HTTP_Encoder.php b/min_unit_tests/test_HTTP_Encoder.php index 665bdd2..4c57103 100644 --- a/min_unit_tests/test_HTTP_Encoder.php +++ b/min_unit_tests/test_HTTP_Encoder.php @@ -101,9 +101,9 @@ function test_HTTP_Encoder() $variedLength = countBytes($variedContent); $encodingTests = array( - array('method' => 'deflate', 'inv' => 'gzinflate', 'exp' => 32157) - ,array('method' => 'gzip', 'inv' => '_gzdecode', 'exp' => 32175) - ,array('method' => 'compress', 'inv' => 'gzuncompress', 'exp' => 32211) + array('method' => 'deflate', 'inv' => 'gzinflate', 'exp' => 32268) + ,array('method' => 'gzip', 'inv' => '_gzdecode', 'exp' => 32286) + ,array('method' => 'compress', 'inv' => 'gzuncompress', 'exp' => 32325) ); foreach ($encodingTests as $test) {