http://www.mediawiki.org/wiki/Special:Code/MediaWiki/84613
Revision: 84613
Author: catrope
Date: 2011-03-23 17:42:35 +0000 (Wed, 23 Mar 2011)
Log Message:
-----------
1.17wmf1: MFT r81692, r82468, r83814, r83885, r83891, r83897, r83902, r83903,
r83988, r83989, r83997, r83998, r84392
Modified Paths:
--------------
branches/wmf/1.17wmf1/includes/AutoLoader.php
branches/wmf/1.17wmf1/includes/DefaultSettings.php
branches/wmf/1.17wmf1/includes/OutputPage.php
branches/wmf/1.17wmf1/includes/api/ApiQueryBase.php
branches/wmf/1.17wmf1/includes/api/ApiQueryCategoryMembers.php
branches/wmf/1.17wmf1/includes/libs/CSSMin.php
branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoader.php
branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderModule.php
branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderSiteModule.php
branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderUserModule.php
branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderWikiModule.php
branches/wmf/1.17wmf1/maintenance/minify.php
Added Paths:
-----------
branches/wmf/1.17wmf1/includes/libs/JavaScriptMinifier.php
Property Changed:
----------------
branches/wmf/1.17wmf1/includes/AutoLoader.php
branches/wmf/1.17wmf1/includes/DefaultSettings.php
branches/wmf/1.17wmf1/includes/OutputPage.php
branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoader.php
Modified: branches/wmf/1.17wmf1/includes/AutoLoader.php
===================================================================
--- branches/wmf/1.17wmf1/includes/AutoLoader.php 2011-03-23 17:39:56 UTC
(rev 84612)
+++ branches/wmf/1.17wmf1/includes/AutoLoader.php 2011-03-23 17:42:35 UTC
(rev 84613)
@@ -137,6 +137,7 @@
'Interwiki' => 'includes/Interwiki.php',
'IP' => 'includes/IP.php',
'JavaScriptDistiller' => 'includes/libs/JavaScriptDistiller.php',
+ 'JavaScriptMinifier' => 'includes/libs/JavaScriptMinifier.php',
'LCStore_DB' => 'includes/LocalisationCache.php',
'LCStore_CDB' => 'includes/LocalisationCache.php',
'LCStore_Null' => 'includes/LocalisationCache.php',
Property changes on: branches/wmf/1.17wmf1/includes/AutoLoader.php
___________________________________________________________________
Modified: svn:mergeinfo
- /branches/REL1_15/phase3/includes/AutoLoader.php:51646
/branches/new-installer/phase3/includes/AutoLoader.php:43664-66004
/branches/sqlite/includes/AutoLoader.php:58211-58321
/branches/uploadwizard/phase3/includes/AutoLoader.php:73550-75905
/branches/wmf/1.16wmf4/includes/AutoLoader.php:67177,69199,76243,77266
/branches/wmf-deployment/includes/AutoLoader.php:53381,60970
/trunk/phase3/includes/AutoLoader.php:77981-77982,77994,78097,78118-78119,78137,78141,78165,78192,78204,78209,78230,78246,78250-78251,78264,78276,78404,78424,79828,79830,79848,79853,79915,79950-79951,79954,79957,79964,79989-79990,80006-80007,80013,80016,80080,80083,80109,80113,80124,80128,80223,80238,80432,80443,80475,80554,80575,80590,80614-80616,80620,80656,80666,80687,80999,81006,81011,81101,81105,81138,81141,81146,81149-81150,81166,81171,81186-81187,81197,81209-81211,81215,81238,81246,81262,81264
+ /branches/REL1_15/phase3/includes/AutoLoader.php:51646
/branches/new-installer/phase3/includes/AutoLoader.php:43664-66004
/branches/sqlite/includes/AutoLoader.php:58211-58321
/branches/uploadwizard/phase3/includes/AutoLoader.php:73550-75905
/branches/wmf/1.16wmf4/includes/AutoLoader.php:67177,69199,76243,77266
/branches/wmf-deployment/includes/AutoLoader.php:53381,60970
/trunk/phase3/includes/AutoLoader.php:77981-77982,77994,78097,78118-78119,78137,78141,78165,78192,78204,78209,78230,78246,78250-78251,78264,78276,78404,78424,79828,79830,79848,79853,79915,79950-79951,79954,79957,79964,79989-79990,80006-80007,80013,80016,80080,80083,80109,80113,80124,80128,80223,80238,80432,80443,80475,80554,80575,80590,80614-80616,80620,80656,80666,80687,80999,81006,81011,81101,81105,81138,81141,81146,81149-81150,81166,81171,81186-81187,81197,81209-81211,81215,81238,81246,81262,81264,81692,82468,83814,83885,83891,83897,83902-83903,83988-83989,83997-83998,84392
Modified: branches/wmf/1.17wmf1/includes/DefaultSettings.php
===================================================================
--- branches/wmf/1.17wmf1/includes/DefaultSettings.php 2011-03-23 17:39:56 UTC
(rev 84612)
+++ branches/wmf/1.17wmf1/includes/DefaultSettings.php 2011-03-23 17:42:35 UTC
(rev 84613)
@@ -2434,11 +2434,18 @@
$wgResourceLoaderUseESI = false;
/**
- * Enable removal of some of the vertical whitespace (like \r and \n) from
- * JavaScript code when minifying.
+ * Put each statement on its own line when minifying JavaScript. This makes
+ * debugging in non-debug mode a bit easier.
*/
-$wgResourceLoaderMinifyJSVerticalSpace = false;
+$wgResourceLoaderMinifierStatementsOnOwnLine = false;
+/**
+ * Maximum line length when minifying JavaScript. This is not a hard maximum:
+ * the minifier will try not to produce lines longer than this, but may be
+ * forced to do so in certain cases.
+ */
+$wgResourceLoaderMinifierMaxLineLength = 1000;
+
/** @} */ # End of resource loader settings }
Property changes on: branches/wmf/1.17wmf1/includes/DefaultSettings.php
___________________________________________________________________
Modified: svn:mergeinfo
- /branches/REL1_15/phase3/includes/DefaultSettings.php:51646
/branches/REL1_17/phase3/includes/DefaultSettings.php:81654
/branches/new-installer/phase3/includes/DefaultSettings.php:43664-66004
/branches/sqlite/includes/DefaultSettings.php:58211-58321
/branches/wmf/1.16wmf4/includes/DefaultSettings.php:67177,69199,76243,77266
/branches/wmf-deployment/includes/DefaultSettings.php:53381,60970
/trunk/phase3/includes/DefaultSettings.php:78395,78990,79166,79324,79746,79844,79860,79968,80109,80113,80137-80138,80205,80210,80222-80223,80231,80272,80381,80432-80433,80436,80443,80475,80554,80575,80590,80614-80616,80620,80656,80666,80837,80841,81311,81313,81349,81352,81376,81389,81397,81399,81412,81430,81488,81496,81548,81554,81561,81589,81600,81611-81612,81615,81620,81622,81640,81648,81650-81652,81657,81674,81689,82022,82193,82696,82836,82853,82858,83061,83067,83114,83270,83284,83374,83390,83392,83402-83403,83410-83411,83420,83461,83463,83512,83583,83610,83617,83716,83764,83787,83813,83866
+ /branches/REL1_15/phase3/includes/DefaultSettings.php:51646
/branches/REL1_17/phase3/includes/DefaultSettings.php:81654
/branches/new-installer/phase3/includes/DefaultSettings.php:43664-66004
/branches/sqlite/includes/DefaultSettings.php:58211-58321
/branches/wmf/1.16wmf4/includes/DefaultSettings.php:67177,69199,76243,77266
/branches/wmf-deployment/includes/DefaultSettings.php:53381,60970
/trunk/phase3/includes/DefaultSettings.php:78395,78990,79166,79324,79746,79844,79860,79968,80109,80113,80137-80138,80205,80210,80222-80223,80231,80272,80381,80432-80433,80436,80443,80475,80554,80575,80590,80614-80616,80620,80656,80666,80837,80841,81311,81313,81349,81352,81376,81389,81397,81399,81412,81430,81488,81496,81548,81554,81561,81589,81600,81611-81612,81615,81620,81622,81640,81648,81650-81652,81657,81674,81689,81692,82022,82193,82468,82696,82836,82853,82858,83061,83067,83114,83270,83284,83374,83390,83392,83402-83403,83410-83411,83420,83461,83463,83512,83583,83610,83617,83716,83764,83787,83813-83814,83866,83885,83891,83897,83902-83903,83988-83989,83997-83998,84392
Modified: branches/wmf/1.17wmf1/includes/OutputPage.php
===================================================================
--- branches/wmf/1.17wmf1/includes/OutputPage.php 2011-03-23 17:39:56 UTC
(rev 84612)
+++ branches/wmf/1.17wmf1/includes/OutputPage.php 2011-03-23 17:42:35 UTC
(rev 84613)
@@ -2386,10 +2386,25 @@
if ( ( $group === 'user' || $group === 'private' ) &&
$wgUser->isLoggedIn() ) {
$query['user'] = $wgUser->getName();
}
+
+ // Create a fake request based on the one we are about
to make so modules return
+ // correct timestamp and emptiness data
+ $context = new ResourceLoaderContext( $resourceLoader,
new FauxRequest( $query ) );
+ // Drop modules that know they're empty
+ foreach ( $modules as $key => $module ) {
+ if ( $module->isKnownEmpty( $context ) ) {
+ unset( $modules[$key] );
+ }
+ }
+ // If there are no modules left, skip this group
+ if ( $modules === array() ) {
+ continue;
+ }
+
$query['modules'] = implode( '|', array_keys( $modules
) );
+
// Support inlining of private modules if configured as
such
if ( $group === 'private' &&
$wgResourceLoaderInlinePrivateModules ) {
- $context = new ResourceLoaderContext(
$resourceLoader, new FauxRequest( $query ) );
if ( $only == 'styles' ) {
$links .= Html::inlineStyle(
$resourceLoader->makeModuleResponse( $context, $modules )
@@ -2403,13 +2418,12 @@
}
continue;
}
- // Special handling for user and site groups; because
users might change their stuff
- // on-wiki like site or user pages, or user
preferences; we need to find the highest
+ // Special handling for the user group; because users
might change their stuff
+ // on-wiki like user pages, or user preferences; we
need to find the highest
// timestamp of these user-changable modules so we can
ensure cache misses on change
- if ( $group === 'user' || $group === 'site' ) {
- // Create a fake request based on the one we
are about to make so modules return
- // correct times
- $context = new ResourceLoaderContext(
$resourceLoader, new FauxRequest( $query ) );
+ // This should NOT be done for the site group (bug
27564) because anons get that too
+ // and we shouldn't be putting timestamps in
Squid-cached HTML
+ if ( $group === 'user' ) {
// Get the maximum timestamp
$timestamp = 1;
foreach ( $modules as $module ) {
Property changes on: branches/wmf/1.17wmf1/includes/OutputPage.php
___________________________________________________________________
Modified: svn:mergeinfo
- /branches/REL1_15/phase3/includes/OutputPage.php:51646
/branches/REL1_17/phase3/includes/OutputPage.php:81654
/branches/resourceloader/phase3/includes/OutputPage.php:68366-69676,69678-70682,70684-71999,72001-72255,72257-72305,72307-72342
/branches/wmf/1.16wmf4/includes/OutputPage.php:67177,69199,76243,77266
/branches/wmf-deployment/includes/OutputPage.php:53381,57468,60970
/trunk/phase3/includes/OutputPage.php:78011-78012,78014-78016,78018,78043,78046,78071,78081,78083,78099,78117,78161,78170,78172,78199,78248,78393,78506-78507,78510-78511,78536,78539,78544,78565,78574,78660,78679,78774,78808,78886-78887,78893,78897,78909,78926,78943,79013,79018-79019,79034,79072,79115,79122,79324,79561,79732,79839,80003,80005,80697,80755,80771,80773-80774,80779,80816,80833,80841,81430,81488,81496,81554,81561,81589,81600,81611,81620,81622,81640,81648,81650-81652,82219,82404,82408-82409,82453,82456-82458,82460,82465,82474,82478,82482,82486,82513,82518,82530
+ /branches/REL1_15/phase3/includes/OutputPage.php:51646
/branches/REL1_17/phase3/includes/OutputPage.php:81654
/branches/resourceloader/phase3/includes/OutputPage.php:68366-69676,69678-70682,70684-71999,72001-72255,72257-72305,72307-72342
/branches/wmf/1.16wmf4/includes/OutputPage.php:67177,69199,76243,77266
/branches/wmf-deployment/includes/OutputPage.php:53381,57468,60970
/trunk/phase3/includes/OutputPage.php:78011-78012,78014-78016,78018,78043,78046,78071,78081,78083,78099,78117,78161,78170,78172,78199,78248,78393,78506-78507,78510-78511,78536,78539,78544,78565,78574,78660,78679,78774,78808,78886-78887,78893,78897,78909,78926,78943,79013,79018-79019,79034,79072,79115,79122,79324,79561,79732,79839,80003,80005,80697,80755,80771,80773-80774,80779,80816,80833,80841,81430,81488,81496,81554,81561,81589,81600,81611,81620,81622,81640,81648,81650-81652,81692,82219,82404,82408-82409,82453,82456-82458,82460,82465,82468,82474,82478,82482,82486,82513,82518,82530,83814,83885,83891,83897,83902-83903,83988-83989,83997-83998,84392
Modified: branches/wmf/1.17wmf1/includes/api/ApiQueryBase.php
===================================================================
--- branches/wmf/1.17wmf1/includes/api/ApiQueryBase.php 2011-03-23 17:39:56 UTC
(rev 84612)
+++ branches/wmf/1.17wmf1/includes/api/ApiQueryBase.php 2011-03-23 17:42:35 UTC
(rev 84613)
@@ -246,14 +246,21 @@
* Execute a SELECT query based on the values in the internal arrays
* @param $method string Function the query should be attributed to.
* You should usually use __METHOD__ here
+ * @param $extraQuery array Query data to add but not store in the
object
+ * Format is array( 'tables' => ..., 'fields' => ..., 'where' => ...,
'options' => ..., 'join_conds' => ... )
* @return ResultWrapper
*/
- protected function select( $method ) {
+ protected function select( $method, $extraQuery = array() ) {
+ // Merge $this->tables with $extraQuery['tables'],
$this->fields with $extraQuery['fields'], etc.
+ foreach ( array( 'tables', 'fields', 'where', 'options',
'join_conds' ) as $var ) {
+ $$var = array_merge( $this->{$var}, isset(
$extraQuery[$var] ) ? (array)$extraQuery[$var] : array() );
+ }
+
// getDB has its own profileDBIn/Out calls
$db = $this->getDB();
$this->profileDBIn();
- $res = $db->select( $this->tables, $this->fields, $this->where,
$method, $this->options, $this->join_conds );
+ $res = $db->select( $tables, $fields, $where, $method,
$options, $join_conds );
$this->profileDBOut();
return $res;
Modified: branches/wmf/1.17wmf1/includes/api/ApiQueryCategoryMembers.php
===================================================================
--- branches/wmf/1.17wmf1/includes/api/ApiQueryCategoryMembers.php
2011-03-23 17:39:56 UTC (rev 84612)
+++ branches/wmf/1.17wmf1/includes/api/ApiQueryCategoryMembers.php
2011-03-23 17:42:35 UTC (rev 84613)
@@ -70,22 +70,21 @@
$fld_type = isset( $prop['type'] );
if ( is_null( $resultPageSet ) ) {
- $this->addFields( array( 'cl_from', 'page_namespace',
'page_title' ) );
+ $this->addFields( array( 'cl_from', 'cl_sortkey',
'cl_type', 'page_namespace', 'page_title' ) );
$this->addFieldsIf( 'page_id', $fld_ids );
$this->addFieldsIf( 'cl_sortkey_prefix',
$fld_sortkeyprefix );
- $this->addFieldsIf( 'cl_sortkey', $fld_sortkey );
} else {
$this->addFields( $resultPageSet->getPageTableFields()
); // will include page_ id, ns, title
- $this->addFields( array( 'cl_from', 'cl_sortkey' ) );
+ $this->addFields( array( 'cl_from', 'cl_sortkey',
'cl_type' ) );
}
$this->addFieldsIf( 'cl_timestamp', $fld_timestamp ||
$params['sort'] == 'timestamp' );
- $this->addFieldsIf( 'cl_type', $fld_type );
$this->addTables( array( 'page', 'categorylinks' ) ); // must
be in this order for 'USE INDEX'
$this->addWhereFld( 'cl_to', $categoryTitle->getDBkey() );
- $this->addWhereFld( 'cl_type', $params['type'] );
+ $queryTypes = $params['type'];
+ $contWhere = false;
// Scanning large datasets for rare categories sucks, and I
already told
// how to have efficient subcategory access :-) ~~~~ (oh well,
domas)
@@ -107,33 +106,80 @@
$this->addOption( 'USE INDEX', 'cl_timestamp' );
} else {
- // The below produces ORDER BY cl_type, cl_sortkey,
cl_from, possibly with DESC added to each of them
- $this->addWhereRange( 'cl_type', $dir, null, null );
- $this->addWhereRange( 'cl_sortkey',
- $dir,
- $params['startsortkey'],
- $params['endsortkey'] );
- $this->addWhereRange( 'cl_from', $dir, null, null );
+ if ( $params['continue'] ) {
+ // type|from|sortkey
+ $cont = explode( '|', $params['continue'], 3 );
+ if ( count( $cont ) != 3 ) {
+ $this->dieUsage( 'Invalid continue
param. You should pass the original value returned '.
+ 'by the previous query',
'_badcontinue'
+ );
+ }
+
+ // Remove the types to skip from $queryTypes
+ $contTypeIndex = array_search( $cont[0],
$queryTypes );
+ $queryTypes = array_slice( $queryTypes,
$contTypeIndex );
+
+ // Add a WHERE clause for sortkey and from
+ $from = intval( $cont[1] );
+ $escSortkey = $this->getDB()->addQuotes(
$cont[2] );
+ $op = $dir == 'newer' ? '>' : '<';
+ // $contWhere is used further down
+ $contWhere = "cl_sortkey $op $escSortkey OR " .
+ "(cl_sortkey = $escSortkey AND " .
+ "cl_from $op= $from)";
+
+ } else {
+ // The below produces ORDER BY cl_sortkey,
cl_from, possibly with DESC added to each of them
+ $this->addWhereRange( 'cl_sortkey',
+ $dir,
+ $params['startsortkey'],
+ $params['endsortkey'] );
+ $this->addWhereRange( 'cl_from', $dir, null,
null );
+ }
$this->addOption( 'USE INDEX', 'cl_sortkey' );
}
- $this->setContinuation( $params['continue'], $params['dir'] );
-
$this->addWhere( 'cl_from=page_id' );
$limit = $params['limit'];
$this->addOption( 'LIMIT', $limit + 1 );
+ // Run a separate SELECT query for each value of cl_type.
+ // This is needed because cl_type is an enum, and MySQL has
+ // inconsistencies between ORDER BY cl_type and
+ // WHERE cl_type >= 'foo' making proper paging impossible
+ // and unindexed.
+ $rows = array();
+ $first = true;
+ foreach ( $queryTypes as $type ) {
+ $extraConds = array( 'cl_type' => $type );
+ if ( $first && $contWhere ) {
+ // Continuation condition. Only added to the
+ // first query, otherwise we'll skip things
+ $extraConds[] = $contWhere;
+ }
+ $res = $this->select( __METHOD__, array( 'where' =>
$extraConds ) );
+ $rows = array_merge( $rows, iterator_to_array( $res ) );
+ if ( count( $rows ) >= $limit + 1 ) {
+ break;
+ }
+ $first = false;
+ }
$count = 0;
- $res = $this->select( __METHOD__ );
- foreach ( $res as $row ) {
+ foreach ( $rows as $row ) {
if ( ++ $count > $limit ) {
// We've reached the one extra which shows that
there are additional pages to be had. Stop here...
// TODO: Security issue - if the user has no
right to view next title, it will still be shown
if ( $params['sort'] == 'timestamp' ) {
$this->setContinueEnumParameter(
'start', wfTimestamp( TS_ISO_8601, $row->cl_timestamp ) );
} else {
- $this->setContinueEnumParameter(
'continue', $row->cl_from );
+ // Continue format is type|from|sortkey
+ // The order is a bit weird but it's
convenient to put the sortkey at the end
+ // because we don't have to worry about
pipes in the sortkey that way
+ // (and type and from can't contain
pipes anyway)
+ $this->setContinueEnumParameter(
'continue',
+
"{$row->cl_type}|{$row->cl_from}|{$row->cl_sortkey}"
+ );
}
break;
}
@@ -173,7 +219,9 @@
if ( $params['sort'] == 'timestamp' ) {
$this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601,
$row->cl_timestamp ) );
} else {
-
$this->setContinueEnumParameter( 'continue', $row->cl_from );
+
$this->setContinueEnumParameter( 'continue',
+
"{$row->cl_type}|{$row->cl_from}|{$row->cl_sortkey}"
+ );
}
break;
}
@@ -188,21 +236,6 @@
}
}
- /**
- * Add DB WHERE clause to continue previous query based on 'continue'
parameter
- */
- private function setContinuation( $continue, $dir ) {
- if ( is_null( $continue ) ) {
- return; // This is not a continuation request
- }
-
- $encFrom = $this->getDB()->addQuotes( intval( $continue ) );
-
- $op = ( $dir == 'desc' ? '<=' : '>=' );
-
- $this->addWhere( "cl_from $op $encFrom" );
- }
-
public function getAllowedParams() {
return array(
'title' => array(
@@ -296,7 +329,8 @@
$desc['namespace'] = array(
$desc['namespace'],
'NOTE: Due to $wgMiserMode, using this may
result in fewer than "limit" results',
- 'returned before continuing; in extreme cases,
zero results may be returned',
+ 'returned before continuing; in extreme cases,
zero results may be returned.',
+ 'Note that you can use cmtype=subcat or
cmtype=file instead of cmnamespace=14 or 6.',
);
}
return $desc;
Modified: branches/wmf/1.17wmf1/includes/libs/CSSMin.php
===================================================================
--- branches/wmf/1.17wmf1/includes/libs/CSSMin.php 2011-03-23 17:39:56 UTC
(rev 84612)
+++ branches/wmf/1.17wmf1/includes/libs/CSSMin.php 2011-03-23 17:42:35 UTC
(rev 84613)
@@ -144,6 +144,9 @@
$query = $match['query'][0];
$url = "{$remote}/{$match['file'][0]}";
$file = "{$local}/{$match['file'][0]}";
+ // bug 27052 - Guard against double slashes, because
foo//../bar
+ // apparently resolves to foo/bar on (some?) clients
+ $url = preg_replace( '#([^:])//+#', '\1/', $url );
$replacement = false;
if ( $local !== false && file_exists( $file ) ) {
// Add version parameter as a time-stamp in ISO
8601 format,
Copied: branches/wmf/1.17wmf1/includes/libs/JavaScriptMinifier.php (from rev
83885, trunk/phase3/includes/libs/JavaScriptMinifier.php)
===================================================================
--- branches/wmf/1.17wmf1/includes/libs/JavaScriptMinifier.php
(rev 0)
+++ branches/wmf/1.17wmf1/includes/libs/JavaScriptMinifier.php 2011-03-23
17:42:35 UTC (rev 84613)
@@ -0,0 +1,578 @@
+<?php
+/**
+ * JavaScript Minifier
+ *
+ * This class is meant to safely minify javascript code, while leaving
syntactically correct
+ * programs intact. Other libraries, such as JSMin require a certain coding
style to work
+ * correctly. OTOH, libraries like jsminplus, that do parse the code correctly
are rather
+ * slow, because they construct a complete parse tree before outputting the
code minified.
+ * So this class is meant to allow arbitrary (but syntactically correct)
input, while being
+ * fast enough to be used for on-the-fly minifying.
+ *
+ * Author: Paul Copperman <[email protected]>
+ * License: choose any of Apache, MIT, GPL, LGPL
+ */
+
+class JavaScriptMinifier {
+
+ /* Class constants */
+ /* Parsing states.
+ * The state machine is only necessary to decide whether to parse a
slash as division
+ * operator or as regexp literal.
+ * States are named after the next expected item. We only distinguish
states when the
+ * distinction is relevant for our purpose.
+ */
+ const STATEMENT = 0;
+ const CONDITION = 1;
+ const PROPERTY_ASSIGNMENT = 2;
+ const EXPRESSION = 3;
+ const EXPRESSION_NO_NL = 4; // only relevant for semicolon
insertion
+ const EXPRESSION_OP = 5;
+ const EXPRESSION_FUNC = 6;
+ const EXPRESSION_TERNARY = 7; // used to determine the role of a
colon
+ const EXPRESSION_TERNARY_OP = 8;
+ const EXPRESSION_TERNARY_FUNC = 9;
+ const PAREN_EXPRESSION = 10; // expression which is not on the
top level
+ const PAREN_EXPRESSION_OP = 11;
+ const PAREN_EXPRESSION_FUNC = 12;
+ const PROPERTY_EXPRESSION = 13; // expression which is within an
object literal
+ const PROPERTY_EXPRESSION_OP = 14;
+ const PROPERTY_EXPRESSION_FUNC = 15;
+
+ /* Token types */
+ const TYPE_UN_OP = 1; // unary operators
+ const TYPE_INCR_OP = 2; // ++ and --
+ const TYPE_BIN_OP = 3; // binary operators
+ const TYPE_ADD_OP = 4; // + and - which can be either unary or
binary ops
+ const TYPE_HOOK = 5; // ?
+ const TYPE_COLON = 6; // :
+ const TYPE_COMMA = 7; // ,
+ const TYPE_SEMICOLON = 8; // ;
+ const TYPE_BRACE_OPEN = 9; // {
+ const TYPE_BRACE_CLOSE = 10; // }
+ const TYPE_PAREN_OPEN = 11; // ( and [
+ const TYPE_PAREN_CLOSE = 12; // ) and ]
+ const TYPE_RETURN = 13; // keywords: break, continue, return, throw
+ const TYPE_IF = 14; // keywords: catch, for, with, switch,
while, if
+ const TYPE_DO = 15; // keywords: case, var, finally, else, do,
try
+ const TYPE_FUNC = 16; // keywords: function
+ const TYPE_LITERAL = 17; // all literals, identifiers and
unrecognised tokens
+
+ // Sanity limit to avoid excessive memory usage
+ const STACK_LIMIT = 1000;
+
+ /* Static functions */
+
+ /**
+ * Returns minified JavaScript code.
+ *
+ * NOTE: $maxLineLength isn't a strict maximum. Longer lines will be
produced when
+ * literals (e.g. quoted strings) longer than $maxLineLength are
encountered
+ * or when required to guard against semicolon insertion.
+ *
+ * @param $s String JavaScript code to minify
+ * @param $statementsOnOwnLine Bool Whether to put each statement on
its own line
+ * @param $maxLineLength Int Maximum length of a single line, or -1 for
no maximum.
+ * @return String Minified code
+ */
+ public static function minify( $s, $statementsOnOwnLine = false,
$maxLineLength = 1000 ) {
+ // First we declare a few tables that contain our parsing rules
+
+ // $opChars : characters, which can be combined without
whitespace in between them
+ $opChars = array(
+ '!' => true,
+ '"' => true,
+ '%' => true,
+ '&' => true,
+ "'" => true,
+ '(' => true,
+ ')' => true,
+ '*' => true,
+ '+' => true,
+ ',' => true,
+ '-' => true,
+ '.' => true,
+ '/' => true,
+ ':' => true,
+ ';' => true,
+ '<' => true,
+ '=' => true,
+ '>' => true,
+ '?' => true,
+ '[' => true,
+ ']' => true,
+ '^' => true,
+ '{' => true,
+ '|' => true,
+ '}' => true,
+ '~' => true
+ );
+
+ // $tokenTypes : maps keywords and operators to their
corresponding token type
+ $tokenTypes = array(
+ '!' => self::TYPE_UN_OP,
+ '~' => self::TYPE_UN_OP,
+ 'delete' => self::TYPE_UN_OP,
+ 'new' => self::TYPE_UN_OP,
+ 'typeof' => self::TYPE_UN_OP,
+ 'void' => self::TYPE_UN_OP,
+ '++' => self::TYPE_INCR_OP,
+ '--' => self::TYPE_INCR_OP,
+ '!=' => self::TYPE_BIN_OP,
+ '!==' => self::TYPE_BIN_OP,
+ '%' => self::TYPE_BIN_OP,
+ '%=' => self::TYPE_BIN_OP,
+ '&' => self::TYPE_BIN_OP,
+ '&&' => self::TYPE_BIN_OP,
+ '&=' => self::TYPE_BIN_OP,
+ '*' => self::TYPE_BIN_OP,
+ '*=' => self::TYPE_BIN_OP,
+ '+=' => self::TYPE_BIN_OP,
+ '-=' => self::TYPE_BIN_OP,
+ '.' => self::TYPE_BIN_OP,
+ '/' => self::TYPE_BIN_OP,
+ '/=' => self::TYPE_BIN_OP,
+ '<' => self::TYPE_BIN_OP,
+ '<<' => self::TYPE_BIN_OP,
+ '<<=' => self::TYPE_BIN_OP,
+ '<=' => self::TYPE_BIN_OP,
+ '=' => self::TYPE_BIN_OP,
+ '==' => self::TYPE_BIN_OP,
+ '===' => self::TYPE_BIN_OP,
+ '>' => self::TYPE_BIN_OP,
+ '>=' => self::TYPE_BIN_OP,
+ '>>' => self::TYPE_BIN_OP,
+ '>>=' => self::TYPE_BIN_OP,
+ '>>>' => self::TYPE_BIN_OP,
+ '>>>=' => self::TYPE_BIN_OP,
+ '^' => self::TYPE_BIN_OP,
+ '^=' => self::TYPE_BIN_OP,
+ '|' => self::TYPE_BIN_OP,
+ '|=' => self::TYPE_BIN_OP,
+ '||' => self::TYPE_BIN_OP,
+ 'in' => self::TYPE_BIN_OP,
+ 'instanceof' => self::TYPE_BIN_OP,
+ '+' => self::TYPE_ADD_OP,
+ '-' => self::TYPE_ADD_OP,
+ '?' => self::TYPE_HOOK,
+ ':' => self::TYPE_COLON,
+ ',' => self::TYPE_COMMA,
+ ';' => self::TYPE_SEMICOLON,
+ '{' => self::TYPE_BRACE_OPEN,
+ '}' => self::TYPE_BRACE_CLOSE,
+ '(' => self::TYPE_PAREN_OPEN,
+ '[' => self::TYPE_PAREN_OPEN,
+ ')' => self::TYPE_PAREN_CLOSE,
+ ']' => self::TYPE_PAREN_CLOSE,
+ 'break' => self::TYPE_RETURN,
+ 'continue' => self::TYPE_RETURN,
+ 'return' => self::TYPE_RETURN,
+ 'throw' => self::TYPE_RETURN,
+ 'catch' => self::TYPE_IF,
+ 'for' => self::TYPE_IF,
+ 'if' => self::TYPE_IF,
+ 'switch' => self::TYPE_IF,
+ 'while' => self::TYPE_IF,
+ 'with' => self::TYPE_IF,
+ 'case' => self::TYPE_DO,
+ 'do' => self::TYPE_DO,
+ 'else' => self::TYPE_DO,
+ 'finally' => self::TYPE_DO,
+ 'try' => self::TYPE_DO,
+ 'var' => self::TYPE_DO,
+ 'function' => self::TYPE_FUNC
+ );
+
+ // $goto : This is the main table for our state machine. For
every state/token pair
+ // the following state is defined. When no rule exists
for a given pair,
+ // the state is left unchanged.
+ $goto = array(
+ self::STATEMENT => array(
+ self::TYPE_UN_OP => self::EXPRESSION,
+ self::TYPE_INCR_OP => self::EXPRESSION,
+ self::TYPE_ADD_OP => self::EXPRESSION,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
+ self::TYPE_RETURN => self::EXPRESSION_NO_NL,
+ self::TYPE_IF => self::CONDITION,
+ self::TYPE_FUNC => self::CONDITION,
+ self::TYPE_LITERAL => self::EXPRESSION_OP
+ ),
+ self::CONDITION => array(
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION
+ ),
+ self::PROPERTY_ASSIGNMENT => array(
+ self::TYPE_COLON =>
self::PROPERTY_EXPRESSION,
+ self::TYPE_BRACE_OPEN => self::STATEMENT
+ ),
+ self::EXPRESSION => array(
+ self::TYPE_SEMICOLON => self::STATEMENT,
+ self::TYPE_BRACE_OPEN =>
self::PROPERTY_ASSIGNMENT,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
+ self::TYPE_FUNC => self::EXPRESSION_FUNC,
+ self::TYPE_LITERAL => self::EXPRESSION_OP
+ ),
+ self::EXPRESSION_NO_NL => array(
+ self::TYPE_SEMICOLON => self::STATEMENT,
+ self::TYPE_BRACE_OPEN =>
self::PROPERTY_ASSIGNMENT,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
+ self::TYPE_FUNC => self::EXPRESSION_FUNC,
+ self::TYPE_LITERAL => self::EXPRESSION_OP
+ ),
+ self::EXPRESSION_OP => array(
+ self::TYPE_BIN_OP => self::EXPRESSION,
+ self::TYPE_ADD_OP => self::EXPRESSION,
+ self::TYPE_HOOK =>
self::EXPRESSION_TERNARY,
+ self::TYPE_COLON => self::STATEMENT,
+ self::TYPE_COMMA => self::EXPRESSION,
+ self::TYPE_SEMICOLON => self::STATEMENT,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION
+ ),
+ self::EXPRESSION_FUNC => array(
+ self::TYPE_BRACE_OPEN => self::STATEMENT
+ ),
+ self::EXPRESSION_TERNARY => array(
+ self::TYPE_BRACE_OPEN =>
self::PROPERTY_ASSIGNMENT,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
+ self::TYPE_FUNC =>
self::EXPRESSION_TERNARY_FUNC,
+ self::TYPE_LITERAL =>
self::EXPRESSION_TERNARY_OP
+ ),
+ self::EXPRESSION_TERNARY_OP => array(
+ self::TYPE_BIN_OP =>
self::EXPRESSION_TERNARY,
+ self::TYPE_ADD_OP =>
self::EXPRESSION_TERNARY,
+ self::TYPE_HOOK =>
self::EXPRESSION_TERNARY,
+ self::TYPE_COMMA =>
self::EXPRESSION_TERNARY,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION
+ ),
+ self::EXPRESSION_TERNARY_FUNC => array(
+ self::TYPE_BRACE_OPEN => self::STATEMENT
+ ),
+ self::PAREN_EXPRESSION => array(
+ self::TYPE_BRACE_OPEN =>
self::PROPERTY_ASSIGNMENT,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
+ self::TYPE_FUNC =>
self::PAREN_EXPRESSION_FUNC,
+ self::TYPE_LITERAL =>
self::PAREN_EXPRESSION_OP
+ ),
+ self::PAREN_EXPRESSION_OP => array(
+ self::TYPE_BIN_OP => self::PAREN_EXPRESSION,
+ self::TYPE_ADD_OP => self::PAREN_EXPRESSION,
+ self::TYPE_HOOK => self::PAREN_EXPRESSION,
+ self::TYPE_COLON => self::PAREN_EXPRESSION,
+ self::TYPE_COMMA => self::PAREN_EXPRESSION,
+ self::TYPE_SEMICOLON => self::PAREN_EXPRESSION,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION
+ ),
+ self::PAREN_EXPRESSION_FUNC => array(
+ self::TYPE_BRACE_OPEN => self::STATEMENT
+ ),
+ self::PROPERTY_EXPRESSION => array(
+ self::TYPE_BRACE_OPEN =>
self::PROPERTY_ASSIGNMENT,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION,
+ self::TYPE_FUNC =>
self::PROPERTY_EXPRESSION_FUNC,
+ self::TYPE_LITERAL =>
self::PROPERTY_EXPRESSION_OP
+ ),
+ self::PROPERTY_EXPRESSION_OP => array(
+ self::TYPE_BIN_OP =>
self::PROPERTY_EXPRESSION,
+ self::TYPE_ADD_OP =>
self::PROPERTY_EXPRESSION,
+ self::TYPE_HOOK =>
self::PROPERTY_EXPRESSION,
+ self::TYPE_COMMA =>
self::PROPERTY_ASSIGNMENT,
+ self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION
+ ),
+ self::PROPERTY_EXPRESSION_FUNC => array(
+ self::TYPE_BRACE_OPEN => self::STATEMENT
+ )
+ );
+
+ // $push : This table contains the rules for when to push a
state onto the stack.
+ // The pushed state is the state to return to when the
corresponding
+ // closing token is found
+ $push = array(
+ self::STATEMENT => array(
+ self::TYPE_BRACE_OPEN => self::STATEMENT,
+ self::TYPE_PAREN_OPEN => self::EXPRESSION_OP
+ ),
+ self::CONDITION => array(
+ self::TYPE_PAREN_OPEN => self::STATEMENT
+ ),
+ self::PROPERTY_ASSIGNMENT => array(
+ self::TYPE_BRACE_OPEN =>
self::PROPERTY_ASSIGNMENT
+ ),
+ self::EXPRESSION => array(
+ self::TYPE_BRACE_OPEN => self::EXPRESSION_OP,
+ self::TYPE_PAREN_OPEN => self::EXPRESSION_OP
+ ),
+ self::EXPRESSION_NO_NL => array(
+ self::TYPE_BRACE_OPEN => self::EXPRESSION_OP,
+ self::TYPE_PAREN_OPEN => self::EXPRESSION_OP
+ ),
+ self::EXPRESSION_OP => array(
+ self::TYPE_HOOK => self::EXPRESSION,
+ self::TYPE_PAREN_OPEN => self::EXPRESSION_OP
+ ),
+ self::EXPRESSION_FUNC => array(
+ self::TYPE_BRACE_OPEN => self::EXPRESSION_OP
+ ),
+ self::EXPRESSION_TERNARY => array(
+ self::TYPE_BRACE_OPEN =>
self::EXPRESSION_TERNARY_OP,
+ self::TYPE_PAREN_OPEN =>
self::EXPRESSION_TERNARY_OP
+ ),
+ self::EXPRESSION_TERNARY_OP => array(
+ self::TYPE_HOOK =>
self::EXPRESSION_TERNARY,
+ self::TYPE_PAREN_OPEN =>
self::EXPRESSION_TERNARY_OP
+ ),
+ self::EXPRESSION_TERNARY_FUNC => array(
+ self::TYPE_BRACE_OPEN =>
self::EXPRESSION_TERNARY_OP
+ ),
+ self::PAREN_EXPRESSION => array(
+ self::TYPE_BRACE_OPEN =>
self::PAREN_EXPRESSION_OP,
+ self::TYPE_PAREN_OPEN =>
self::PAREN_EXPRESSION_OP
+ ),
+ self::PAREN_EXPRESSION_OP => array(
+ self::TYPE_PAREN_OPEN =>
self::PAREN_EXPRESSION_OP
+ ),
+ self::PAREN_EXPRESSION_FUNC => array(
+ self::TYPE_BRACE_OPEN =>
self::PAREN_EXPRESSION_OP
+ ),
+ self::PROPERTY_EXPRESSION => array(
+ self::TYPE_BRACE_OPEN =>
self::PROPERTY_EXPRESSION_OP,
+ self::TYPE_PAREN_OPEN =>
self::PROPERTY_EXPRESSION_OP
+ ),
+ self::PROPERTY_EXPRESSION_OP => array(
+ self::TYPE_PAREN_OPEN =>
self::PROPERTY_EXPRESSION_OP
+ ),
+ self::PROPERTY_EXPRESSION_FUNC => array(
+ self::TYPE_BRACE_OPEN =>
self::PROPERTY_EXPRESSION_OP
+ )
+ );
+
+ // $pop : Rules for when to pop a state from the stack
+ $pop = array(
+ self::STATEMENT => array(
self::TYPE_BRACE_CLOSE => true ),
+ self::PROPERTY_ASSIGNMENT => array(
self::TYPE_BRACE_CLOSE => true ),
+ self::EXPRESSION => array(
self::TYPE_BRACE_CLOSE => true ),
+ self::EXPRESSION_NO_NL => array(
self::TYPE_BRACE_CLOSE => true ),
+ self::EXPRESSION_OP => array(
self::TYPE_BRACE_CLOSE => true ),
+ self::EXPRESSION_TERNARY_OP => array( self::TYPE_COLON
=> true ),
+ self::PAREN_EXPRESSION => array(
self::TYPE_PAREN_CLOSE => true ),
+ self::PAREN_EXPRESSION_OP => array(
self::TYPE_PAREN_CLOSE => true ),
+ self::PROPERTY_EXPRESSION => array(
self::TYPE_BRACE_CLOSE => true ),
+ self::PROPERTY_EXPRESSION_OP => array(
self::TYPE_BRACE_CLOSE => true )
+ );
+
+ // $semicolon : Rules for when a semicolon insertion is
appropriate
+ $semicolon = array(
+ self::EXPRESSION_NO_NL => array(
+ self::TYPE_UN_OP => true,
+ self::TYPE_INCR_OP => true,
+ self::TYPE_ADD_OP => true,
+ self::TYPE_BRACE_OPEN => true,
+ self::TYPE_PAREN_OPEN => true,
+ self::TYPE_RETURN => true,
+ self::TYPE_IF => true,
+ self::TYPE_DO => true,
+ self::TYPE_FUNC => true,
+ self::TYPE_LITERAL => true
+ ),
+ self::EXPRESSION_OP => array(
+ self::TYPE_UN_OP => true,
+ self::TYPE_INCR_OP => true,
+ self::TYPE_BRACE_OPEN => true,
+ self::TYPE_RETURN => true,
+ self::TYPE_IF => true,
+ self::TYPE_DO => true,
+ self::TYPE_FUNC => true,
+ self::TYPE_LITERAL => true
+ )
+ );
+
+ // Rules for when newlines should be inserted if
+ // $statementsOnOwnLine is enabled.
+ // $newlineBefore is checked before switching state,
+ // $newlineAfter is checked after
+ $newlineBefore = array(
+ self::STATEMENT => array(
+ self::TYPE_BRACE_CLOSE => true,
+ ),
+ );
+ $newlineAfter = array(
+ self::STATEMENT => array(
+ self::TYPE_BRACE_OPEN => true,
+ self::TYPE_PAREN_CLOSE => true,
+ self::TYPE_SEMICOLON => true,
+ ),
+ );
+
+ // $divStates : Contains all states that can be followed by a
division operator
+ $divStates = array(
+ self::EXPRESSION_OP => true,
+ self::EXPRESSION_TERNARY_OP => true,
+ self::PAREN_EXPRESSION_OP => true,
+ self::PROPERTY_EXPRESSION_OP => true
+ );
+
+ // Here's where the minifying takes place: Loop through the
input, looking for tokens
+ // and output them to $out, taking actions to the above defined
rules when appropriate.
+ $out = '';
+ $pos = 0;
+ $length = strlen( $s );
+ $lineLength = 0;
+ $newlineFound = true;
+ $state = self::STATEMENT;
+ $stack = array();
+ $last = ';'; // Pretend that we have seen a semicolon yet
+ while( $pos < $length ) {
+ // First, skip over any whitespace and multiline
comments, recording whether we
+ // found any newline character
+ $skip = strspn( $s, " \t\n\r\xb\xc", $pos );
+ if( !$skip ) {
+ $ch = $s[$pos];
+ if( $ch === '/' && substr( $s, $pos, 2 ) ===
'/*' ) {
+ // Multiline comment. Search for the
end token or EOT.
+ $end = strpos( $s, '*/', $pos + 2 );
+ $skip = $end === false ? $length - $pos
: $end - $pos + 2;
+ }
+ }
+ if( $skip ) {
+ // The semicolon insertion mechanism needs to
know whether there was a newline
+ // between two tokens, so record it now.
+ if( !$newlineFound && strcspn( $s, "\r\n",
$pos, $skip ) !== $skip ) {
+ $newlineFound = true;
+ }
+ $pos += $skip;
+ continue;
+ }
+ // Handle C++-style comments and html comments, which
are treated as single line
+ // comments by the browser, regardless of whether the
end tag is on the same line.
+ // Handle --> the same way, but only if it's at the
beginning of the line
+ if( ( $ch === '/' && substr( $s, $pos, 2 ) === '//' )
+ || ( $ch === '<' && substr( $s, $pos, 4 ) ===
'<!--' )
+ || ( $ch === '-' && $newlineFound && substr(
$s, $pos, 3 ) === '-->' )
+ ) {
+ $pos += strcspn( $s, "\r\n", $pos );
+ continue;
+ }
+
+ // Find out which kind of token we're handling. $end
will point past the end of it.
+ $end = $pos + 1;
+ // Handle string literals
+ if( $ch === "'" || $ch === '"' ) {
+ // Search to the end of the string literal,
skipping over backslash escapes
+ $search = $ch . '\\';
+ do{
+ $end += strcspn( $s, $search, $end ) +
2;
+ } while( $end - 2 < $length && $s[$end - 2] ===
'\\' );
+ $end--;
+ // We have to distinguish between regexp literals and
division operators
+ // A division operator is only possible in certain
states
+ } elseif( $ch === '/' && !isset( $divStates[$state] ) )
{
+ // Regexp literal, search to the end, skipping
over backslash escapes and
+ // character classes
+ for( ; ; ) {
+ do{
+ $end += strcspn( $s, '/[\\',
$end ) + 2;
+ } while( $end - 2 < $length && $s[$end
- 2] === '\\' );
+ $end--;
+ if( $end - 1 >= $length || $s[$end - 1]
=== '/' ) {
+ break;
+ }
+ do{
+ $end += strcspn( $s, ']\\',
$end ) + 2;
+ } while( $end - 2 < $length && $s[$end
- 2] === '\\' );
+ $end--;
+ };
+ // Search past the regexp modifiers (gi)
+ while( $end < $length && ctype_alpha( $s[$end]
) ) {
+ $end++;
+ }
+ } elseif(
+ ctype_digit( $ch )
+ || ( $ch === '.' && $pos + 1 < $length &&
ctype_digit( $s[$pos + 1] ) )
+ ) {
+ // Numeric literal. Search for the end of it,
but don't care about [+-]exponent
+ // at the end, as the results of "numeric [+-]
numeric" and "numeric" are
+ // identical to our state machine.
+ $end += strspn( $s,
'0123456789ABCDEFabcdefXx.', $end );
+ while( $s[$end - 1] === '.' ) {
+ // Special case: When a numeric ends
with a dot, we have to check the
+ // literal for proper syntax
+ $decimal = strspn( $s, '0123456789',
$pos, $end - $pos - 1 );
+ if( $decimal === $end - $pos - 1 ) {
+ break;
+ } else {
+ $end--;
+ }
+ }
+ } elseif( isset( $opChars[$ch] ) ) {
+ // Punctuation character. Search for the
longest matching operator.
+ while(
+ $end < $length
+ && isset( $tokenTypes[substr( $s, $pos,
$end - $pos + 1 )] )
+ ) {
+ $end++;
+ }
+ } else {
+ // Identifier or reserved word. Search for the
end by excluding whitespace and
+ // punctuation.
+ $end += strcspn( $s, "
\t\n.;,=<>+-{}()[]?:*/%'\"!&|^~\xb\xc\r", $end );
+ }
+
+ // Now get the token type from our type array
+ $token = substr( $s, $pos, $end - $pos ); // so $end -
$pos == strlen( $token )
+ $type = isset( $tokenTypes[$token] ) ?
$tokenTypes[$token] : self::TYPE_LITERAL;
+
+ if( $newlineFound && isset( $semicolon[$state][$type] )
) {
+ // This token triggers the semicolon insertion
mechanism of javascript. While we
+ // could add the ; token here ourselves,
keeping the newline has a few advantages.
+ $out .= "\n";
+ $state = self::STATEMENT;
+ $lineLength = 0;
+ } elseif( $maxLineLength > 0 && $lineLength + $end -
$pos > $maxLineLength &&
+ !isset( $semicolon[$state][$type] ) )
+ {
+ // This line would get too long if we added
$token, so add a newline first.
+ // Only do this if it won't trigger semicolon
insertion though.
+ $out .= "\n";
+ $lineLength = 0;
+ // Check, whether we have to separate the token from
the last one with whitespace
+ } elseif( !isset( $opChars[$last] ) && !isset(
$opChars[$ch] ) ) {
+ $out .= ' ';
+ $lineLength++;
+ // Don't accidentally create ++, -- or // tokens
+ } elseif( $last === $ch && ( $ch === '+' || $ch === '-'
|| $ch === '/' ) ) {
+ $out .= ' ';
+ $lineLength++;
+ }
+
+ $out .= $token;
+ $lineLength += $end - $pos; // += strlen( $token )
+ $last = $s[$end - 1];
+ $pos = $end;
+ $newlineFound = false;
+
+ // Output a newline after the token if required
+ // This is checked before AND after switching state
+ $newlineAdded = false;
+ if ( $statementsOnOwnLine && !$newlineAdded && isset(
$newlineBefore[$state][$type] ) ) {
+ $out .= "\n";
+ $lineLength = 0;
+ $newlineAdded = true;
+ }
+
+ // Now that we have output our token, transition into
the new state.
+ if( isset( $push[$state][$type] ) && count( $stack ) <
self::STACK_LIMIT ) {
+ $stack[] = $push[$state][$type];
+ }
+ if( $stack && isset( $pop[$state][$type] ) ) {
+ $state = array_pop( $stack );
+ } elseif( isset( $goto[$state][$type] ) ) {
+ $state = $goto[$state][$type];
+ }
+
+ // Check for newline insertion again
+ if ( $statementsOnOwnLine && !$newlineAdded && isset(
$newlineAfter[$state][$type] ) ) {
+ $out .= "\n";
+ $lineLength = 0;
+ }
+ }
+ return $out;
+ }
+}
Modified: branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoader.php
===================================================================
--- branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoader.php
2011-03-23 17:39:56 UTC (rev 84612)
+++ branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoader.php
2011-03-23 17:42:35 UTC (rev 84613)
@@ -29,7 +29,7 @@
class ResourceLoader {
/* Protected Static Members */
- protected static $filterCacheVersion = 1;
+ protected static $filterCacheVersion = 2;
/** Array: List of module name/ResourceLoaderModule object pairs */
protected $modules = array();
@@ -110,7 +110,7 @@
* Runs JavaScript or CSS data through a filter, caching the filtered
result for future calls.
*
* Available filters are:
- * - minify-js \see JavaScriptDistiller::stripWhiteSpace
+ * - minify-js \see JavaScriptMinifier::minify
* - minify-css \see CSSMin::minify
*
* If $data is empty, only contains whitespace or the filter was
unknown,
@@ -121,8 +121,7 @@
* @return String: Filtered data, or a comment containing an error
message
*/
protected function filter( $filter, $data ) {
- global $wgResourceLoaderMinifyJSVerticalSpace;
-
+ global $wgResourceLoaderMinifierStatementsOnOwnLine,
$wgResourceLoaderMinifierMaxLineLength;
wfProfileIn( __METHOD__ );
// For empty/whitespace-only data or for unknown filters, don't
perform
@@ -148,8 +147,9 @@
try {
switch ( $filter ) {
case 'minify-js':
- $result =
JavaScriptDistiller::stripWhiteSpace(
- $data,
$wgResourceLoaderMinifyJSVerticalSpace
+ $result = JavaScriptMinifier::minify(
$data,
+
$wgResourceLoaderMinifierStatementsOnOwnLine,
+
$wgResourceLoaderMinifierMaxLineLength
);
$result .= "\n\n/* cache key: $key
*/\n";
break;
@@ -467,7 +467,9 @@
// Scripts
$scripts = '';
if ( $context->shouldIncludeScripts() ) {
- $scripts .= $module->getScript(
$context ) . "\n";
+ // bug 27054: Append semicolon to
prevent weird bugs
+ // caused by files not terminating
their statements right
+ $scripts .= $module->getScript(
$context ) . ";\n";
}
// Styles
Property changes on:
branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoader.php
___________________________________________________________________
Modified: svn:mergeinfo
- /branches/REL1_15/phase3/includes/ResourceLoader.php:51646
/branches/new-installer/phase3/includes/ResourceLoader.php:43664-66004
/branches/resourceloader/phase3/includes/ResourceLoader.php:68366-69676,69678-71999,72001-72255,72257-72305,72307-72342
/branches/sqlite/includes/ResourceLoader.php:58211-58321
/branches/wmf/1.16wmf4/includes/resourceloader/ResourceLoader.php:67177,69199,76243,77266
/branches/wmf-deployment/includes/ResourceLoader.php:53381
/branches/wmf-deployment/includes/resourceloader/ResourceLoader.php:60970
/trunk/phase3/includes/resourceloader/ResourceLoader.php:78011,78014-78016,78078,78099,78117,78161,78170,78172,78199,78248,78285,78393,78506-78507,78510-78511,78536,78539,78544,78565,78574,78660,78679,78774,78808,78886-78887,78924,78926,78943,79013,79018-79019,79034,79072,79115,79122,79246,79358,79480,79693,79732,79839,79862,79891,79900,80109,80113,80223,80475,80554,80575-80576,80583,80620,80656,80666,80842,80900,80913,80918-80920,80973-80974,80979,80993,81689,81729,81778,81890-81894,81896-81898,81900,82042,82354,82378
+ /branches/REL1_15/phase3/includes/ResourceLoader.php:51646
/branches/new-installer/phase3/includes/ResourceLoader.php:43664-66004
/branches/resourceloader/phase3/includes/ResourceLoader.php:68366-69676,69678-71999,72001-72255,72257-72305,72307-72342
/branches/sqlite/includes/ResourceLoader.php:58211-58321
/branches/wmf/1.16wmf4/includes/resourceloader/ResourceLoader.php:67177,69199,76243,77266
/branches/wmf-deployment/includes/ResourceLoader.php:53381
/branches/wmf-deployment/includes/resourceloader/ResourceLoader.php:60970
/trunk/phase3/includes/resourceloader/ResourceLoader.php:78011,78014-78016,78078,78099,78117,78161,78170,78172,78199,78248,78285,78393,78506-78507,78510-78511,78536,78539,78544,78565,78574,78660,78679,78774,78808,78886-78887,78924,78926,78943,79013,79018-79019,79034,79072,79115,79122,79246,79358,79480,79693,79732,79839,79862,79891,79900,80109,80113,80223,80475,80554,80575-80576,80583,80620,80656,80666,80842,80900,80913,80918-80920,80973-80974,80979,80993,81689,81692,81729,81778,81890-81894,81896-81898,81900,82042,82354,82378,82468,83814,83885,83891,83897,83902-83903,83988-83989,83997-83998,84392
Modified: branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderModule.php
===================================================================
--- branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderModule.php
2011-03-23 17:39:56 UTC (rev 84612)
+++ branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderModule.php
2011-03-23 17:42:35 UTC (rev 84613)
@@ -228,4 +228,17 @@
// 0 would mean now
return 1;
}
+
+ /**
+ * Check whether this module is known to be empty. If a child class
+ * has an easy and cheap way to determine that this module is
+ * definitely going to be empty, it should override this method to
+ * return true in that case. Callers may optimize the request for this
+ * module away if this function returns true.
+ * @param $context ResourceLoaderContext: Context object
+ * @return Boolean
+ */
+ public function isKnownEmpty( ResourceLoaderContext $context ) {
+ return false;
+ }
}
Modified:
branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderSiteModule.php
===================================================================
--- branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderSiteModule.php
2011-03-23 17:39:56 UTC (rev 84612)
+++ branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderSiteModule.php
2011-03-23 17:42:35 UTC (rev 84613)
@@ -36,15 +36,14 @@
global $wgHandheldStyle;
$pages = array(
- 'Common.js' => array( 'ns' => NS_MEDIAWIKI, 'type' =>
'script' ),
- 'Common.css' => array( 'ns' => NS_MEDIAWIKI, 'type' =>
'style' ),
- ucfirst( $context->getSkin() ) . '.js' => array( 'ns'
=> NS_MEDIAWIKI, 'type' => 'script' ),
- ucfirst( $context->getSkin() ) . '.css' => array( 'ns'
=> NS_MEDIAWIKI, 'type' => 'style' ),
- 'Print.css' => array( 'ns' => NS_MEDIAWIKI, 'type' =>
'style', 'media' => 'print' ),
+ 'MediaWiki:Common.js' => array( 'type' => 'script' ),
+ 'MediaWiki:Common.css' => array( 'type' => 'style' ),
+ 'MediaWiki:' . ucfirst( $context->getSkin() ) . '.js'
=> array( 'type' => 'script' ),
+ 'MediaWiki:' . ucfirst( $context->getSkin() ) . '.css'
=> array( 'type' => 'style' ),
+ 'MediaWiki:Print.css' => array( 'type' => 'style',
'media' => 'print' ),
);
if ( $wgHandheldStyle ) {
- $pages['Handheld.css'] = array(
- 'ns' => NS_MEDIAWIKI,
+ $pages['MediaWiki:Handheld.css'] = array(
'type' => 'style',
'media' => 'handheld' );
}
Modified:
branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderUserModule.php
===================================================================
--- branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderUserModule.php
2011-03-23 17:39:56 UTC (rev 84612)
+++ branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderUserModule.php
2011-03-23 17:42:35 UTC (rev 84613)
@@ -31,12 +31,12 @@
if ( $context->getUser() ) {
$username = $context->getUser();
return array(
- "$username/common.js" => array( 'ns' =>
NS_USER, 'type' => 'script' ),
- "$username/" . $context->getSkin() . '.js' =>
- array( 'ns' => NS_USER, 'type' =>
'script' ),
- "$username/common.css" => array( 'ns' =>
NS_USER, 'type' => 'style' ),
- "$username/" . $context->getSkin() . '.css' =>
- array( 'ns' => NS_USER, 'type' =>
'style' ),
+ "User:$username/common.js" => array( 'type' =>
'script' ),
+ "User:$username/" . $context->getSkin() . '.js'
=>
+ array( 'type' => 'script' ),
+ "User:$username/common.css" => array( 'type' =>
'style' ),
+ "User:$username/" . $context->getSkin() .
'.css' =>
+ array( 'type' => 'style' ),
);
}
return array();
Modified:
branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderWikiModule.php
===================================================================
--- branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderWikiModule.php
2011-03-23 17:39:56 UTC (rev 84612)
+++ branches/wmf/1.17wmf1/includes/resourceloader/ResourceLoaderWikiModule.php
2011-03-23 17:42:35 UTC (rev 84613)
@@ -33,8 +33,8 @@
/* Protected Members */
- // In-object cache for modified time
- protected $modifiedTime = array();
+ // In-object cache for title mtimes
+ protected $titleMtimes = array();
/* Abstract Protected Methods */
@@ -42,12 +42,12 @@
/* Protected Methods */
- protected function getContent( $page, $ns ) {
- if ( $ns === NS_MEDIAWIKI ) {
- return wfEmptyMsg( $page ) ? '' : wfMsgExt( $page,
'content' );
+ protected function getContent( $title ) {
+ if ( $title->getNamespace() === NS_MEDIAWIKI ) {
+ $dbkey = $title->getDBkey();
+ return wfEmptyMsg( $dbkey ) ? '' : wfMsgExt( $dbkey,
'content' );
}
- $title = Title::newFromText( $page, $ns );
- if ( !$title || !$title->isCssJsSubpage() ) {
+ if ( !$title->isCssJsSubpage() ) {
return null;
}
$revision = Revision::newFromTitle( $title );
@@ -61,15 +61,21 @@
public function getScript( ResourceLoaderContext $context ) {
$scripts = '';
- foreach ( $this->getPages( $context ) as $page => $options ) {
+ foreach ( $this->getPages( $context ) as $titleText => $options
) {
if ( $options['type'] !== 'script' ) {
continue;
}
- $script = $this->getContent( $page, $options['ns'] );
- if ( $script ) {
- $ns = MWNamespace::getCanonicalName(
$options['ns'] );
- $scripts .= "/* $ns:$page */\n$script\n";
+ $title = Title::newFromText( $titleText );
+ if ( !$title ) {
+ continue;
}
+ $script = $this->getContent( $title );
+ if ( strval( $script ) !== '' ) {
+ if ( strpos( $titleText, '*/' ) === false ) {
+ $scripts .= "/* $titleText */\n";
+ }
+ $scripts .= $script . "\n";
+ }
}
return $scripts;
}
@@ -78,13 +84,17 @@
global $wgScriptPath;
$styles = array();
- foreach ( $this->getPages( $context ) as $page => $options ) {
+ foreach ( $this->getPages( $context ) as $titleText => $options
) {
if ( $options['type'] !== 'style' ) {
continue;
}
+ $title = Title::newFromText( $titleText );
+ if ( !$title ) {
+ continue;
+ }
$media = isset( $options['media'] ) ? $options['media']
: 'all';
- $style = $this->getContent( $page, $options['ns'] );
- if ( !$style ) {
+ $style = $this->getContent( $title );
+ if ( strval( $style ) === '' ) {
continue;
}
if ( $this->getFlip( $context ) ) {
@@ -94,36 +104,58 @@
if ( !isset( $styles[$media] ) ) {
$styles[$media] = '';
}
- $ns = MWNamespace::getCanonicalName( $options['ns'] );
- $styles[$media] .= "/* $ns:$page */\n$style\n";
+ if ( strpos( $titleText, '*/' ) === false ) {
+ $styles[$media] .= "/* $titleText */\n";
+ }
+ $styles[$media] .= $style . "\n";
}
return $styles;
}
public function getModifiedTime( ResourceLoaderContext $context ) {
+ $modifiedTime = 1; // wfTimestamp() interprets 0 as "now"
+ $mtimes = $this->getTitleMtimes( $context );
+ if ( count( $mtimes ) ) {
+ $modifiedTime = max( $modifiedTime, max( $mtimes ) );
+ }
+ return $modifiedTime;
+ }
+
+ public function isKnownEmpty( ResourceLoaderContext $context ) {
+ return count( $this->getTitleMtimes( $context ) ) == 0;
+ }
+
+ /**
+ * Get the modification times of all titles that would be loaded for
+ * a given context.
+ * @param $context ResourceLoaderContext: Context object
+ * @return array( prefixed DB key => UNIX timestamp ), nonexistent
titles are dropped
+ */
+ protected function getTitleMtimes( ResourceLoaderContext $context ) {
$hash = $context->getHash();
- if ( isset( $this->modifiedTime[$hash] ) ) {
- return $this->modifiedTime[$hash];
+ if ( isset( $this->titleMtimes[$hash] ) ) {
+ return $this->titleMtimes[$hash];
}
-
- $titles = array();
- foreach ( $this->getPages( $context ) as $page => $options ) {
- $titles[$options['ns']][str_replace( ' ', '_', $page)]
= true;
+
+ $this->titleMtimes[$hash] = array();
+ $batch = new LinkBatch;
+ foreach ( $this->getPages( $context ) as $titleText => $options
) {
+ $batch->addObj( Title::newFromText( $titleText ) );
}
-
- $modifiedTime = 1; // wfTimestamp() interprets 0 as "now"
-
- if ( $titles ) {
+
+ if ( !$batch->isEmpty() ) {
$dbr = wfGetDB( DB_SLAVE );
- $latest = $dbr->selectField( 'page',
'MAX(page_touched)',
- $dbr->makeWhereFrom2d( $titles,
'page_namespace', 'page_title' ),
- __METHOD__ );
-
- if ( $latest ) {
- $modifiedTime = wfTimestamp( TS_UNIX, $latest );
+ $res = $dbr->select( 'page',
+ array( 'page_namespace', 'page_title',
'page_touched' ),
+ $batch->constructSet( 'page', $dbr ),
+ __METHOD__
+ );
+ foreach ( $res as $row ) {
+ $title = Title::makeTitle(
$row->page_namespace, $row->page_title );
+
$this->titleMtimes[$hash][$title->getPrefixedDBkey()] =
+ wfTimestamp( TS_UNIX,
$row->page_touched );
}
}
-
- return $this->modifiedTime[$hash] = $modifiedTime;
+ return $this->titleMtimes[$hash];
}
}
Modified: branches/wmf/1.17wmf1/maintenance/minify.php
===================================================================
--- branches/wmf/1.17wmf1/maintenance/minify.php 2011-03-23 17:39:56 UTC
(rev 84612)
+++ branches/wmf/1.17wmf1/maintenance/minify.php 2011-03-23 17:42:35 UTC
(rev 84613)
@@ -17,9 +17,12 @@
"Directory for output. If this is not specified, and
neither is --outfile, then the\n" .
"output files will be sent to the same directories as
the input files.",
false, true );
- $this->addOption( 'minify-vertical-space',
- "Boolean value for minifying the vertical space for
javascript.",
+ $this->addOption( 'js-statements-on-own-line',
+ "Boolean value for putting statements on their own line
when minifying JavaScript.",
false, true );
+ $this->addOption( 'js-max-line-length',
+ "Maximum line length for JavaScript minification.",
+ false, true );
$this->mDescription = "Minify a file or set of files.\n\n" .
"If --outfile is not specified, then the output file
names will have a .min extension\n" .
"added, e.g. jquery.js -> jquery.min.js.";
@@ -81,7 +84,7 @@
}
public function minify( $inPath, $outPath ) {
- global $wgResourceLoaderMinifyJSVerticalSpace;
+ global $wgResourceLoaderMinifierStatementsOnOwnLine,
$wgResourceLoaderMinifierMaxLineLength;
$extension = $this->getExtension( $inPath );
$this->output( basename( $inPath ) . ' -> ' . basename(
$outPath ) . '...' );
@@ -99,7 +102,10 @@
switch ( $extension ) {
case 'js':
- $outText =
JavaScriptDistiller::stripWhiteSpace( $inText, $this->getOption(
'minify-vertical-space', $wgResourceLoaderMinifyJSVerticalSpace ) );
+ $outText = JavaScriptMinifier::minify( $inText,
+ $this->getOption(
'js-statements-on-own-line', $wgResourceLoaderMinifierStatementsOnOwnLine ),
+ $this->getOption( 'js-max-line-length',
$wgResourceLoaderMinifierMaxLineLength )
+ );
break;
case 'css':
$outText = CSSMin::minify( $inText );
_______________________________________________
MediaWiki-CVS mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs