Index: branches/REL1_17/phase3/maintenance/update.php |
— | — | @@ -35,7 +35,7 @@ |
36 | 36 | return 2 /* Maintenance::DB_ADMIN */; |
37 | 37 | } |
38 | 38 | |
39 | | - private function compatChecks() { |
| 39 | + function compatChecks() { |
40 | 40 | $test = new PhpXmlBugTester(); |
41 | 41 | if ( !$test->ok ) { |
42 | 42 | $this->error( |
Property changes on: branches/REL1_17/phase3/maintenance/update.php |
___________________________________________________________________ |
Modified: svn:mergeinfo |
43 | 43 | Merged /trunk/phase3/maintenance/update.php:r83979,83988-83989,83997-83998,84118,84228,84271,84343,84353,84392,84430 |
Index: branches/REL1_17/phase3/maintenance/minify.php |
— | — | @@ -17,9 +17,12 @@ |
18 | 18 | "Directory for output. If this is not specified, and neither is --outfile, then the\n" . |
19 | 19 | "output files will be sent to the same directories as the input files.", |
20 | 20 | false, true ); |
21 | | - $this->addOption( 'minify-vertical-space', |
22 | | - "Boolean value for minifying the vertical space for javascript.", |
| 21 | + $this->addOption( 'js-statements-on-own-line', |
| 22 | + "Boolean value for putting statements on their own line when minifying JavaScript.", |
23 | 23 | false, true ); |
| 24 | + $this->addOption( 'js-max-line-length', |
| 25 | + "Maximum line length for JavaScript minification.", |
| 26 | + false, true ); |
24 | 27 | $this->mDescription = "Minify a file or set of files.\n\n" . |
25 | 28 | "If --outfile is not specified, then the output file names will have a .min extension\n" . |
26 | 29 | "added, e.g. jquery.js -> jquery.min.js."; |
— | — | @@ -81,7 +84,7 @@ |
82 | 85 | } |
83 | 86 | |
84 | 87 | public function minify( $inPath, $outPath ) { |
85 | | - global $wgResourceLoaderMinifyJSVerticalSpace; |
| 88 | + global $wgResourceLoaderMinifierStatementsOnOwnLine, $wgResourceLoaderMinifierMaxLineLength; |
86 | 89 | |
87 | 90 | $extension = $this->getExtension( $inPath ); |
88 | 91 | $this->output( basename( $inPath ) . ' -> ' . basename( $outPath ) . '...' ); |
— | — | @@ -99,7 +102,10 @@ |
100 | 103 | |
101 | 104 | switch ( $extension ) { |
102 | 105 | case 'js': |
103 | | - $outText = JavaScriptDistiller::stripWhiteSpace( $inText, $this->getOption( 'minify-vertical-space', $wgResourceLoaderMinifyJSVerticalSpace ) ); |
| 106 | + $outText = JavaScriptMinifier::minify( $inText, |
| 107 | + $this->getOption( 'js-statements-on-own-line', $wgResourceLoaderMinifierStatementsOnOwnLine ), |
| 108 | + $this->getOption( 'js-max-line-length', $wgResourceLoaderMinifierMaxLineLength ) |
| 109 | + ); |
104 | 110 | break; |
105 | 111 | case 'css': |
106 | 112 | $outText = CSSMin::minify( $inText ); |
Index: branches/REL1_17/phase3/maintenance/tests/parser/parserTests.txt |
— | — | @@ -6658,7 +6658,7 @@ |
6659 | 6659 | image:foobar.jpg |
6660 | 6660 | </gallery> |
6661 | 6661 | !! result |
6662 | | -<ul class="gallery" style="max-width: 220px;_width: 220px;"> |
| 6662 | +<ul class="gallery" style="max-width: 202px;_width: 202px;"> |
6663 | 6663 | <li class='gallerycaption'>Foo <a href="https://www.mediawiki.org/wiki/Main_Page">Main Page</a></li> |
6664 | 6664 | <li class="gallerybox" style="width: 95px"><div style="width: 95px"> |
6665 | 6665 | <div style="height: 70px;">Nonexistant.jpg</div> |
Property changes on: branches/REL1_17/phase3/maintenance/tests/parser/parserTests.txt |
___________________________________________________________________ |
Added: svn:mergeinfo |
6666 | 6666 | Merged /trunk/phase3/tests/parser/parserTests.txt:r83965 |
6667 | 6667 | Merged /trunk/phase3/maintenance/tests/parser/parserTests.txt:r83979,83988-83989,83997-83998,84118,84228,84271,84343,84353,84392,84430 |
6668 | 6668 | Merged /branches/phpunit-restructure/maintenance/tests/parser/parserTests.txt:r72257-72560 |
Index: branches/REL1_17/phase3/HISTORY |
— | — | @@ -221,9 +221,6 @@ |
222 | 222 | * Note on non-existing user and user talk pages if user does not exist |
223 | 223 | * New hook ShowMissingArticle so extensions can modify the output for |
224 | 224 | non-existent pages. |
225 | | -* Added $wgUseAJAXCategories allow enabling AJAX based categories system. |
226 | | - This works on all namespaces. Enabled namespaces can be reduces using |
227 | | - $wgAJAXCategoriesNamespaces. |
228 | 225 | * Admins could disable some variants using $wgDisabledVariants now. ONLY apply |
229 | 226 | on wikis enabled LanguageConverter. |
230 | 227 | * (bug 16310) Credits page now lists IP addresses rather than saying the number |
Index: branches/REL1_17/phase3/includes/upload/UploadBase.php |
— | — | @@ -415,7 +415,7 @@ |
416 | 416 | |
417 | 417 | $overwriteError = $this->checkOverwrite( $user ); |
418 | 418 | if ( $overwriteError !== true ) { |
419 | | - return array( array( $overwriteError ) ); |
| 419 | + return array( $overwriteError ); |
420 | 420 | } |
421 | 421 | |
422 | 422 | return true; |
— | — | @@ -1016,14 +1016,14 @@ |
1017 | 1017 | * Check if there's an overwrite conflict and, if so, if restrictions |
1018 | 1018 | * forbid this user from performing the upload. |
1019 | 1019 | * |
1020 | | - * @return mixed true on success, error string on failure |
| 1020 | + * @return mixed true on success, array on failure |
1021 | 1021 | */ |
1022 | 1022 | private function checkOverwrite( $user ) { |
1023 | 1023 | // First check whether the local file can be overwritten |
1024 | 1024 | $file = $this->getLocalFile(); |
1025 | 1025 | if( $file->exists() ) { |
1026 | 1026 | if( !self::userCanReUpload( $user, $file ) ) { |
1027 | | - return 'fileexists-forbidden'; |
| 1027 | + return array( 'fileexists-forbidden', $file->getName() ); |
1028 | 1028 | } else { |
1029 | 1029 | return true; |
1030 | 1030 | } |
— | — | @@ -1034,7 +1034,7 @@ |
1035 | 1035 | */ |
1036 | 1036 | $file = wfFindFile( $this->getTitle() ); |
1037 | 1037 | if ( $file && !$user->isAllowed( 'reupload-shared' ) ) { |
1038 | | - return 'fileexists-shared-forbidden'; |
| 1038 | + return array( 'fileexists-shared-forbidden', $file->getName() ); |
1039 | 1039 | } |
1040 | 1040 | |
1041 | 1041 | return true; |
Property changes on: branches/REL1_17/phase3/includes/upload/UploadBase.php |
___________________________________________________________________ |
Modified: svn:mergeinfo |
1042 | 1042 | Merged /trunk/phase3/includes/upload/UploadBase.php:r83979,83988-83989,83997-83998,84118,84228,84271,84343,84353,84392,84430 |
Index: branches/REL1_17/phase3/includes/OutputPage.php |
— | — | @@ -2422,10 +2422,12 @@ |
2423 | 2423 | } |
2424 | 2424 | continue; |
2425 | 2425 | } |
2426 | | - // Special handling for user and site groups; because users might change their stuff |
2427 | | - // on-wiki like site or user pages, or user preferences; we need to find the highest |
| 2426 | + // Special handling for the user group; because users might change their stuff |
| 2427 | + // on-wiki like user pages, or user preferences; we need to find the highest |
2428 | 2428 | // timestamp of these user-changable modules so we can ensure cache misses on change |
2429 | | - if ( $group === 'user' || $group === 'site' ) { |
| 2429 | + // This should NOT be done for the site group (bug 27564) because anons get that too |
| 2430 | + // and we shouldn't be putting timestamps in Squid-cached HTML |
| 2431 | + if ( $group === 'user' ) { |
2430 | 2432 | // Get the maximum timestamp |
2431 | 2433 | $timestamp = 1; |
2432 | 2434 | foreach ( $modules as $module ) { |
Property changes on: branches/REL1_17/phase3/includes/OutputPage.php |
___________________________________________________________________ |
Modified: svn:mergeinfo |
2433 | 2435 | Merged /trunk/phase3/includes/OutputPage.php:r83891,83897,83902-83903,83979,83988-83989,83997-83998,84118,84228,84271,84343,84353,84392,84430 |
Index: branches/REL1_17/phase3/includes/api/ApiQueryBase.php |
— | — | @@ -246,14 +246,29 @@ |
247 | 247 | * Execute a SELECT query based on the values in the internal arrays |
248 | 248 | * @param $method string Function the query should be attributed to. |
249 | 249 | * You should usually use __METHOD__ here |
| 250 | + * @param $extraQuery array Query data to add but not store in the object |
| 251 | + * Format is array( 'tables' => ..., 'fields' => ..., 'where' => ..., 'options' => ..., 'join_conds' => ... ) |
250 | 252 | * @return ResultWrapper |
251 | 253 | */ |
252 | | - protected function select( $method ) { |
| 254 | + protected function select( $method, $extraQuery = array() ) { |
| 255 | + $tables = $this->tables; |
| 256 | + $fields = $this->fields; |
| 257 | + $where = $this->where; |
| 258 | + $options = $this->options; |
| 259 | + $join_conds = $this->join_conds; |
| 260 | + |
| 261 | + // Merge $this->tables with $extraQuery['tables'], $this->fields with $extraQuery['fields'], etc. |
| 262 | + foreach ( array( 'tables', 'fields', 'where', 'options', 'join_conds' ) as $var ) { |
| 263 | + if ( isset( $extraQuery[$var] ) ) { |
| 264 | + $$var = array_merge( $$var, (array)$extraQuery[$var] ); |
| 265 | + } |
| 266 | + } |
| 267 | + |
253 | 268 | // getDB has its own profileDBIn/Out calls |
254 | 269 | $db = $this->getDB(); |
255 | 270 | |
256 | 271 | $this->profileDBIn(); |
257 | | - $res = $db->select( $this->tables, $this->fields, $this->where, $method, $this->options, $this->join_conds ); |
| 272 | + $res = $db->select( $tables, $fields, $where, $method, $options, $join_conds ); |
258 | 273 | $this->profileDBOut(); |
259 | 274 | |
260 | 275 | return $res; |
Index: branches/REL1_17/phase3/includes/api/ApiQueryCategoryMembers.php |
— | — | @@ -83,7 +83,8 @@ |
84 | 84 | $this->addTables( array( 'page', 'categorylinks' ) ); // must be in this order for 'USE INDEX' |
85 | 85 | |
86 | 86 | $this->addWhereFld( 'cl_to', $categoryTitle->getDBkey() ); |
87 | | - $this->addWhereFld( 'cl_type', $params['type'] ); |
| 87 | + $queryTypes = $params['type']; |
| 88 | + $contWhere = false; |
88 | 89 | |
89 | 90 | // Scanning large datasets for rare categories sucks, and I already told |
90 | 91 | // how to have efficient subcategory access :-) ~~~~ (oh well, domas) |
— | — | @@ -113,20 +114,22 @@ |
114 | 115 | 'by the previous query', '_badcontinue' |
115 | 116 | ); |
116 | 117 | } |
117 | | - $escType = $this->getDB()->addQuotes( $cont[0] ); |
| 118 | + |
| 119 | + // Remove the types to skip from $queryTypes |
| 120 | + $contTypeIndex = array_search( $cont[0], $queryTypes ); |
| 121 | + $queryTypes = array_slice( $queryTypes, $contTypeIndex ); |
| 122 | + |
| 123 | + // Add a WHERE clause for sortkey and from |
118 | 124 | $from = intval( $cont[1] ); |
119 | 125 | $escSortkey = $this->getDB()->addQuotes( $cont[2] ); |
120 | 126 | $op = $dir == 'newer' ? '>' : '<'; |
121 | | - $this->addWhere( "cl_type $op $escType OR " . |
122 | | - "(cl_type = $escType AND " . |
123 | | - "(cl_sortkey $op $escSortkey OR " . |
| 127 | + // $contWhere is used further down |
| 128 | + $contWhere = "cl_sortkey $op $escSortkey OR " . |
124 | 129 | "(cl_sortkey = $escSortkey AND " . |
125 | | - "cl_from $op= $from)))" |
126 | | - ); |
| 130 | + "cl_from $op= $from)"; |
127 | 131 | |
128 | 132 | } else { |
129 | | - // The below produces ORDER BY cl_type, cl_sortkey, cl_from, possibly with DESC added to each of them |
130 | | - $this->addWhereRange( 'cl_type', $dir, null, null ); |
| 133 | + // The below produces ORDER BY cl_sortkey, cl_from, possibly with DESC added to each of them |
131 | 134 | $this->addWhereRange( 'cl_sortkey', |
132 | 135 | $dir, |
133 | 136 | $params['startsortkey'], |
— | — | @@ -141,9 +144,29 @@ |
142 | 145 | $limit = $params['limit']; |
143 | 146 | $this->addOption( 'LIMIT', $limit + 1 ); |
144 | 147 | |
| 148 | + // Run a separate SELECT query for each value of cl_type. |
| 149 | + // This is needed because cl_type is an enum, and MySQL has |
| 150 | + // inconsistencies between ORDER BY cl_type and |
| 151 | + // WHERE cl_type >= 'foo' making proper paging impossible |
| 152 | + // and unindexed. |
| 153 | + $rows = array(); |
| 154 | + $first = true; |
| 155 | + foreach ( $queryTypes as $type ) { |
| 156 | + $extraConds = array( 'cl_type' => $type ); |
| 157 | + if ( $first && $contWhere ) { |
| 158 | + // Continuation condition. Only added to the |
| 159 | + // first query, otherwise we'll skip things |
| 160 | + $extraConds[] = $contWhere; |
| 161 | + } |
| 162 | + $res = $this->select( __METHOD__, array( 'where' => $extraConds ) ); |
| 163 | + $rows = array_merge( $rows, iterator_to_array( $res ) ); |
| 164 | + if ( count( $rows ) >= $limit + 1 ) { |
| 165 | + break; |
| 166 | + } |
| 167 | + $first = false; |
| 168 | + } |
145 | 169 | $count = 0; |
146 | | - $res = $this->select( __METHOD__ ); |
147 | | - foreach ( $res as $row ) { |
| 170 | + foreach ( $rows as $row ) { |
148 | 171 | if ( ++ $count > $limit ) { |
149 | 172 | // We've reached the one extra which shows that there are additional pages to be had. Stop here... |
150 | 173 | // TODO: Security issue - if the user has no right to view next title, it will still be shown |
Index: branches/REL1_17/phase3/includes/resourceloader/ResourceLoader.php |
— | — | @@ -29,6 +29,7 @@ |
30 | 30 | class ResourceLoader { |
31 | 31 | |
32 | 32 | /* Protected Static Members */ |
| 33 | + protected static $filterCacheVersion = 2; |
33 | 34 | |
34 | 35 | /** Array: List of module name/ResourceLoaderModule object pairs */ |
35 | 36 | protected $modules = array(); |
— | — | @@ -108,7 +109,7 @@ |
109 | 110 | * Runs JavaScript or CSS data through a filter, caching the filtered result for future calls. |
110 | 111 | * |
111 | 112 | * Available filters are: |
112 | | - * - minify-js \see JavaScriptDistiller::stripWhiteSpace |
| 113 | + * - minify-js \see JavaScriptMinifier::minify |
113 | 114 | * - minify-css \see CSSMin::minify |
114 | 115 | * |
115 | 116 | * If $data is empty, only contains whitespace or the filter was unknown, |
— | — | @@ -119,8 +120,7 @@ |
120 | 121 | * @return String: Filtered data, or a comment containing an error message |
121 | 122 | */ |
122 | 123 | protected function filter( $filter, $data ) { |
123 | | - global $wgResourceLoaderMinifyJSVerticalSpace; |
124 | | - |
| 124 | + global $wgResourceLoaderMinifierStatementsOnOwnLine, $wgResourceLoaderMinifierMaxLineLength; |
125 | 125 | wfProfileIn( __METHOD__ ); |
126 | 126 | |
127 | 127 | // For empty/whitespace-only data or for unknown filters, don't perform |
— | — | @@ -146,8 +146,9 @@ |
147 | 147 | try { |
148 | 148 | switch ( $filter ) { |
149 | 149 | case 'minify-js': |
150 | | - $result = JavaScriptDistiller::stripWhiteSpace( |
151 | | - $data, $wgResourceLoaderMinifyJSVerticalSpace |
| 150 | + $result = JavaScriptMinifier::minify( $data, |
| 151 | + $wgResourceLoaderMinifierStatementsOnOwnLine, |
| 152 | + $wgResourceLoaderMinifierMaxLineLength |
152 | 153 | ); |
153 | 154 | break; |
154 | 155 | case 'minify-css': |
— | — | @@ -462,7 +463,9 @@ |
463 | 464 | // Scripts |
464 | 465 | $scripts = ''; |
465 | 466 | if ( $context->shouldIncludeScripts() ) { |
466 | | - $scripts .= $module->getScript( $context ) . "\n"; |
| 467 | + // bug 27054: Append semicolon to prevent weird bugs |
| 468 | + // caused by files not terminating their statements right |
| 469 | + $scripts .= $module->getScript( $context ) . ";\n"; |
467 | 470 | } |
468 | 471 | |
469 | 472 | // Styles |
Property changes on: branches/REL1_17/phase3/includes/resourceloader/ResourceLoader.php |
___________________________________________________________________ |
Modified: svn:mergeinfo |
470 | 473 | Merged /trunk/phase3/includes/resourceloader/ResourceLoader.php:r83885,83891,83897,83902-83903,83979,83988-83989,83997-83998,84118,84228,84271,84343,84353,84392,84430 |
Index: branches/REL1_17/phase3/includes/AutoLoader.php |
— | — | @@ -137,6 +137,7 @@ |
138 | 138 | 'Interwiki' => 'includes/Interwiki.php', |
139 | 139 | 'IP' => 'includes/IP.php', |
140 | 140 | 'JavaScriptDistiller' => 'includes/libs/JavaScriptDistiller.php', |
| 141 | + 'JavaScriptMinifier' => 'includes/libs/JavaScriptMinifier.php', |
141 | 142 | 'LCStore_DB' => 'includes/LocalisationCache.php', |
142 | 143 | 'LCStore_CDB' => 'includes/LocalisationCache.php', |
143 | 144 | 'LCStore_Null' => 'includes/LocalisationCache.php', |
Property changes on: branches/REL1_17/phase3/includes/AutoLoader.php |
___________________________________________________________________ |
Modified: svn:mergeinfo |
144 | 145 | Merged /trunk/phase3/includes/AutoLoader.php:r83885,83891,83897,83902-83903,83979,83988-83989,83997-83998,84118,84228,84271,84343,84353,84392,84430 |
Index: branches/REL1_17/phase3/includes/job/JobQueue.php |
— | — | @@ -217,12 +217,12 @@ |
218 | 218 | * @param $jobs array of Job objects |
219 | 219 | */ |
220 | 220 | static function batchInsert( $jobs ) { |
221 | | - if( !count( $jobs ) ) { |
| 221 | + if ( !count( $jobs ) ) { |
222 | 222 | return; |
223 | 223 | } |
224 | 224 | $dbw = wfGetDB( DB_MASTER ); |
225 | 225 | $rows = array(); |
226 | | - foreach( $jobs as $job ) { |
| 226 | + foreach ( $jobs as $job ) { |
227 | 227 | $rows[] = $job->insertFields(); |
228 | 228 | if ( count( $rows ) >= 50 ) { |
229 | 229 | # Do a small transaction to avoid slave lag |
— | — | @@ -232,13 +232,42 @@ |
233 | 233 | $rows = array(); |
234 | 234 | } |
235 | 235 | } |
236 | | - if ( $rows ) { |
| 236 | + if ( $rows ) { // last chunk |
237 | 237 | $dbw->begin(); |
238 | 238 | $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' ); |
239 | 239 | $dbw->commit(); |
240 | 240 | } |
| 241 | + wfIncrStats( 'job-insert', count( $jobs ) ); |
241 | 242 | } |
242 | 243 | |
| 244 | + /** |
| 245 | + * Insert a group of jobs into the queue. |
| 246 | + * |
| 247 | + * Same as batchInsert() but does not commit and can thus |
| 248 | + * be rolled-back as part of a larger transaction. However, |
| 249 | + * large batches of jobs can cause slave lag. |
| 250 | + * |
| 251 | + * @param $jobs array of Job objects |
| 252 | + */ |
| 253 | + static function safeBatchInsert( $jobs ) { |
| 254 | + if ( !count( $jobs ) ) { |
| 255 | + return; |
| 256 | + } |
| 257 | + $dbw = wfGetDB( DB_MASTER ); |
| 258 | + $rows = array(); |
| 259 | + foreach ( $jobs as $job ) { |
| 260 | + $rows[] = $job->insertFields(); |
| 261 | + if ( count( $rows ) >= 500 ) { |
| 262 | + $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' ); |
| 263 | + $rows = array(); |
| 264 | + } |
| 265 | + } |
| 266 | + if ( $rows ) { // last chunk |
| 267 | + $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' ); |
| 268 | + } |
| 269 | + wfIncrStats( 'job-insert', count( $jobs ) ); |
| 270 | + } |
| 271 | + |
243 | 272 | /*------------------------------------------------------------------------- |
244 | 273 | * Non-static functions |
245 | 274 | *------------------------------------------------------------------------*/ |
Index: branches/REL1_17/phase3/includes/libs/JavaScriptMinifier.php |
— | — | @@ -0,0 +1,579 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * JavaScript Minifier |
| 5 | + * |
| 6 | + * This class is meant to safely minify javascript code, while leaving syntactically correct |
| 7 | + * programs intact. Other libraries, such as JSMin require a certain coding style to work |
| 8 | + * correctly. OTOH, libraries like jsminplus, that do parse the code correctly are rather |
| 9 | + * slow, because they construct a complete parse tree before outputting the code minified. |
| 10 | + * So this class is meant to allow arbitrary (but syntactically correct) input, while being |
| 11 | + * fast enough to be used for on-the-fly minifying. |
| 12 | + * |
| 13 | + * Author: Paul Copperman <paul.copperman@gmail.com> |
| 14 | + * License: choose any of Apache, MIT, GPL, LGPL |
| 15 | + */ |
| 16 | + |
| 17 | +class JavaScriptMinifier { |
| 18 | + |
| 19 | + /* Class constants */ |
| 20 | + /* Parsing states. |
| 21 | + * The state machine is only necessary to decide whether to parse a slash as division |
| 22 | + * operator or as regexp literal. |
| 23 | + * States are named after the next expected item. We only distinguish states when the |
| 24 | + * distinction is relevant for our purpose. |
| 25 | + */ |
| 26 | + const STATEMENT = 0; |
| 27 | + const CONDITION = 1; |
| 28 | + const PROPERTY_ASSIGNMENT = 2; |
| 29 | + const EXPRESSION = 3; |
| 30 | + const EXPRESSION_NO_NL = 4; // only relevant for semicolon insertion |
| 31 | + const EXPRESSION_OP = 5; |
| 32 | + const EXPRESSION_FUNC = 6; |
| 33 | + const EXPRESSION_TERNARY = 7; // used to determine the role of a colon |
| 34 | + const EXPRESSION_TERNARY_OP = 8; |
| 35 | + const EXPRESSION_TERNARY_FUNC = 9; |
| 36 | + const PAREN_EXPRESSION = 10; // expression which is not on the top level |
| 37 | + const PAREN_EXPRESSION_OP = 11; |
| 38 | + const PAREN_EXPRESSION_FUNC = 12; |
| 39 | + const PROPERTY_EXPRESSION = 13; // expression which is within an object literal |
| 40 | + const PROPERTY_EXPRESSION_OP = 14; |
| 41 | + const PROPERTY_EXPRESSION_FUNC = 15; |
| 42 | + |
| 43 | + /* Token types */ |
| 44 | + const TYPE_UN_OP = 1; // unary operators |
| 45 | + const TYPE_INCR_OP = 2; // ++ and -- |
| 46 | + const TYPE_BIN_OP = 3; // binary operators |
| 47 | + const TYPE_ADD_OP = 4; // + and - which can be either unary or binary ops |
| 48 | + const TYPE_HOOK = 5; // ? |
| 49 | + const TYPE_COLON = 6; // : |
| 50 | + const TYPE_COMMA = 7; // , |
| 51 | + const TYPE_SEMICOLON = 8; // ; |
| 52 | + const TYPE_BRACE_OPEN = 9; // { |
| 53 | + const TYPE_BRACE_CLOSE = 10; // } |
| 54 | + const TYPE_PAREN_OPEN = 11; // ( and [ |
| 55 | + const TYPE_PAREN_CLOSE = 12; // ) and ] |
| 56 | + const TYPE_RETURN = 13; // keywords: break, continue, return, throw |
| 57 | + const TYPE_IF = 14; // keywords: catch, for, with, switch, while, if |
| 58 | + const TYPE_DO = 15; // keywords: case, var, finally, else, do, try |
| 59 | + const TYPE_FUNC = 16; // keywords: function |
| 60 | + const TYPE_LITERAL = 17; // all literals, identifiers and unrecognised tokens |
| 61 | + |
| 62 | + // Sanity limit to avoid excessive memory usage |
| 63 | + const STACK_LIMIT = 1000; |
| 64 | + |
| 65 | + /* Static functions */ |
| 66 | + |
| 67 | + /** |
| 68 | + * Returns minified JavaScript code. |
| 69 | + * |
| 70 | + * NOTE: $maxLineLength isn't a strict maximum. Longer lines will be produced when |
| 71 | + * literals (e.g. quoted strings) longer than $maxLineLength are encountered |
| 72 | + * or when required to guard against semicolon insertion. |
| 73 | + * |
| 74 | + * @param $s String JavaScript code to minify |
| 75 | + * @param $statementsOnOwnLine Bool Whether to put each statement on its own line |
| 76 | + * @param $maxLineLength Int Maximum length of a single line, or -1 for no maximum. |
| 77 | + * @return String Minified code |
| 78 | + */ |
| 79 | + public static function minify( $s, $statementsOnOwnLine = false, $maxLineLength = 1000 ) { |
| 80 | + // First we declare a few tables that contain our parsing rules |
| 81 | + |
| 82 | + // $opChars : characters, which can be combined without whitespace in between them |
| 83 | + $opChars = array( |
| 84 | + '!' => true, |
| 85 | + '"' => true, |
| 86 | + '%' => true, |
| 87 | + '&' => true, |
| 88 | + "'" => true, |
| 89 | + '(' => true, |
| 90 | + ')' => true, |
| 91 | + '*' => true, |
| 92 | + '+' => true, |
| 93 | + ',' => true, |
| 94 | + '-' => true, |
| 95 | + '.' => true, |
| 96 | + '/' => true, |
| 97 | + ':' => true, |
| 98 | + ';' => true, |
| 99 | + '<' => true, |
| 100 | + '=' => true, |
| 101 | + '>' => true, |
| 102 | + '?' => true, |
| 103 | + '[' => true, |
| 104 | + ']' => true, |
| 105 | + '^' => true, |
| 106 | + '{' => true, |
| 107 | + '|' => true, |
| 108 | + '}' => true, |
| 109 | + '~' => true |
| 110 | + ); |
| 111 | + |
| 112 | + // $tokenTypes : maps keywords and operators to their corresponding token type |
| 113 | + $tokenTypes = array( |
| 114 | + '!' => self::TYPE_UN_OP, |
| 115 | + '~' => self::TYPE_UN_OP, |
| 116 | + 'delete' => self::TYPE_UN_OP, |
| 117 | + 'new' => self::TYPE_UN_OP, |
| 118 | + 'typeof' => self::TYPE_UN_OP, |
| 119 | + 'void' => self::TYPE_UN_OP, |
| 120 | + '++' => self::TYPE_INCR_OP, |
| 121 | + '--' => self::TYPE_INCR_OP, |
| 122 | + '!=' => self::TYPE_BIN_OP, |
| 123 | + '!==' => self::TYPE_BIN_OP, |
| 124 | + '%' => self::TYPE_BIN_OP, |
| 125 | + '%=' => self::TYPE_BIN_OP, |
| 126 | + '&' => self::TYPE_BIN_OP, |
| 127 | + '&&' => self::TYPE_BIN_OP, |
| 128 | + '&=' => self::TYPE_BIN_OP, |
| 129 | + '*' => self::TYPE_BIN_OP, |
| 130 | + '*=' => self::TYPE_BIN_OP, |
| 131 | + '+=' => self::TYPE_BIN_OP, |
| 132 | + '-=' => self::TYPE_BIN_OP, |
| 133 | + '.' => self::TYPE_BIN_OP, |
| 134 | + '/' => self::TYPE_BIN_OP, |
| 135 | + '/=' => self::TYPE_BIN_OP, |
| 136 | + '<' => self::TYPE_BIN_OP, |
| 137 | + '<<' => self::TYPE_BIN_OP, |
| 138 | + '<<=' => self::TYPE_BIN_OP, |
| 139 | + '<=' => self::TYPE_BIN_OP, |
| 140 | + '=' => self::TYPE_BIN_OP, |
| 141 | + '==' => self::TYPE_BIN_OP, |
| 142 | + '===' => self::TYPE_BIN_OP, |
| 143 | + '>' => self::TYPE_BIN_OP, |
| 144 | + '>=' => self::TYPE_BIN_OP, |
| 145 | + '>>' => self::TYPE_BIN_OP, |
| 146 | + '>>=' => self::TYPE_BIN_OP, |
| 147 | + '>>>' => self::TYPE_BIN_OP, |
| 148 | + '>>>=' => self::TYPE_BIN_OP, |
| 149 | + '^' => self::TYPE_BIN_OP, |
| 150 | + '^=' => self::TYPE_BIN_OP, |
| 151 | + '|' => self::TYPE_BIN_OP, |
| 152 | + '|=' => self::TYPE_BIN_OP, |
| 153 | + '||' => self::TYPE_BIN_OP, |
| 154 | + 'in' => self::TYPE_BIN_OP, |
| 155 | + 'instanceof' => self::TYPE_BIN_OP, |
| 156 | + '+' => self::TYPE_ADD_OP, |
| 157 | + '-' => self::TYPE_ADD_OP, |
| 158 | + '?' => self::TYPE_HOOK, |
| 159 | + ':' => self::TYPE_COLON, |
| 160 | + ',' => self::TYPE_COMMA, |
| 161 | + ';' => self::TYPE_SEMICOLON, |
| 162 | + '{' => self::TYPE_BRACE_OPEN, |
| 163 | + '}' => self::TYPE_BRACE_CLOSE, |
| 164 | + '(' => self::TYPE_PAREN_OPEN, |
| 165 | + '[' => self::TYPE_PAREN_OPEN, |
| 166 | + ')' => self::TYPE_PAREN_CLOSE, |
| 167 | + ']' => self::TYPE_PAREN_CLOSE, |
| 168 | + 'break' => self::TYPE_RETURN, |
| 169 | + 'continue' => self::TYPE_RETURN, |
| 170 | + 'return' => self::TYPE_RETURN, |
| 171 | + 'throw' => self::TYPE_RETURN, |
| 172 | + 'catch' => self::TYPE_IF, |
| 173 | + 'for' => self::TYPE_IF, |
| 174 | + 'if' => self::TYPE_IF, |
| 175 | + 'switch' => self::TYPE_IF, |
| 176 | + 'while' => self::TYPE_IF, |
| 177 | + 'with' => self::TYPE_IF, |
| 178 | + 'case' => self::TYPE_DO, |
| 179 | + 'do' => self::TYPE_DO, |
| 180 | + 'else' => self::TYPE_DO, |
| 181 | + 'finally' => self::TYPE_DO, |
| 182 | + 'try' => self::TYPE_DO, |
| 183 | + 'var' => self::TYPE_DO, |
| 184 | + 'function' => self::TYPE_FUNC |
| 185 | + ); |
| 186 | + |
| 187 | + // $goto : This is the main table for our state machine. For every state/token pair |
| 188 | + // the following state is defined. When no rule exists for a given pair, |
| 189 | + // the state is left unchanged. |
| 190 | + $goto = array( |
| 191 | + self::STATEMENT => array( |
| 192 | + self::TYPE_UN_OP => self::EXPRESSION, |
| 193 | + self::TYPE_INCR_OP => self::EXPRESSION, |
| 194 | + self::TYPE_ADD_OP => self::EXPRESSION, |
| 195 | + self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION, |
| 196 | + self::TYPE_RETURN => self::EXPRESSION_NO_NL, |
| 197 | + self::TYPE_IF => self::CONDITION, |
| 198 | + self::TYPE_FUNC => self::CONDITION, |
| 199 | + self::TYPE_LITERAL => self::EXPRESSION_OP |
| 200 | + ), |
| 201 | + self::CONDITION => array( |
| 202 | + self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION |
| 203 | + ), |
| 204 | + self::PROPERTY_ASSIGNMENT => array( |
| 205 | + self::TYPE_COLON => self::PROPERTY_EXPRESSION, |
| 206 | + self::TYPE_BRACE_OPEN => self::STATEMENT |
| 207 | + ), |
| 208 | + self::EXPRESSION => array( |
| 209 | + self::TYPE_SEMICOLON => self::STATEMENT, |
| 210 | + self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT, |
| 211 | + self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION, |
| 212 | + self::TYPE_FUNC => self::EXPRESSION_FUNC, |
| 213 | + self::TYPE_LITERAL => self::EXPRESSION_OP |
| 214 | + ), |
| 215 | + self::EXPRESSION_NO_NL => array( |
| 216 | + self::TYPE_SEMICOLON => self::STATEMENT, |
| 217 | + self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT, |
| 218 | + self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION, |
| 219 | + self::TYPE_FUNC => self::EXPRESSION_FUNC, |
| 220 | + self::TYPE_LITERAL => self::EXPRESSION_OP |
| 221 | + ), |
| 222 | + self::EXPRESSION_OP => array( |
| 223 | + self::TYPE_BIN_OP => self::EXPRESSION, |
| 224 | + self::TYPE_ADD_OP => self::EXPRESSION, |
| 225 | + self::TYPE_HOOK => self::EXPRESSION_TERNARY, |
| 226 | + self::TYPE_COLON => self::STATEMENT, |
| 227 | + self::TYPE_COMMA => self::EXPRESSION, |
| 228 | + self::TYPE_SEMICOLON => self::STATEMENT, |
| 229 | + self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION |
| 230 | + ), |
| 231 | + self::EXPRESSION_FUNC => array( |
| 232 | + self::TYPE_BRACE_OPEN => self::STATEMENT |
| 233 | + ), |
| 234 | + self::EXPRESSION_TERNARY => array( |
| 235 | + self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT, |
| 236 | + self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION, |
| 237 | + self::TYPE_FUNC => self::EXPRESSION_TERNARY_FUNC, |
| 238 | + self::TYPE_LITERAL => self::EXPRESSION_TERNARY_OP |
| 239 | + ), |
| 240 | + self::EXPRESSION_TERNARY_OP => array( |
| 241 | + self::TYPE_BIN_OP => self::EXPRESSION_TERNARY, |
| 242 | + self::TYPE_ADD_OP => self::EXPRESSION_TERNARY, |
| 243 | + self::TYPE_HOOK => self::EXPRESSION_TERNARY, |
| 244 | + self::TYPE_COMMA => self::EXPRESSION_TERNARY, |
| 245 | + self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION |
| 246 | + ), |
| 247 | + self::EXPRESSION_TERNARY_FUNC => array( |
| 248 | + self::TYPE_BRACE_OPEN => self::STATEMENT |
| 249 | + ), |
| 250 | + self::PAREN_EXPRESSION => array( |
| 251 | + self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT, |
| 252 | + self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION, |
| 253 | + self::TYPE_FUNC => self::PAREN_EXPRESSION_FUNC, |
| 254 | + self::TYPE_LITERAL => self::PAREN_EXPRESSION_OP |
| 255 | + ), |
| 256 | + self::PAREN_EXPRESSION_OP => array( |
| 257 | + self::TYPE_BIN_OP => self::PAREN_EXPRESSION, |
| 258 | + self::TYPE_ADD_OP => self::PAREN_EXPRESSION, |
| 259 | + self::TYPE_HOOK => self::PAREN_EXPRESSION, |
| 260 | + self::TYPE_COLON => self::PAREN_EXPRESSION, |
| 261 | + self::TYPE_COMMA => self::PAREN_EXPRESSION, |
| 262 | + self::TYPE_SEMICOLON => self::PAREN_EXPRESSION, |
| 263 | + self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION |
| 264 | + ), |
| 265 | + self::PAREN_EXPRESSION_FUNC => array( |
| 266 | + self::TYPE_BRACE_OPEN => self::STATEMENT |
| 267 | + ), |
| 268 | + self::PROPERTY_EXPRESSION => array( |
| 269 | + self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT, |
| 270 | + self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION, |
| 271 | + self::TYPE_FUNC => self::PROPERTY_EXPRESSION_FUNC, |
| 272 | + self::TYPE_LITERAL => self::PROPERTY_EXPRESSION_OP |
| 273 | + ), |
| 274 | + self::PROPERTY_EXPRESSION_OP => array( |
| 275 | + self::TYPE_BIN_OP => self::PROPERTY_EXPRESSION, |
| 276 | + self::TYPE_ADD_OP => self::PROPERTY_EXPRESSION, |
| 277 | + self::TYPE_HOOK => self::PROPERTY_EXPRESSION, |
| 278 | + self::TYPE_COMMA => self::PROPERTY_ASSIGNMENT, |
| 279 | + self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION |
| 280 | + ), |
| 281 | + self::PROPERTY_EXPRESSION_FUNC => array( |
| 282 | + self::TYPE_BRACE_OPEN => self::STATEMENT |
| 283 | + ) |
| 284 | + ); |
| 285 | + |
| 286 | + // $push : This table contains the rules for when to push a state onto the stack. |
| 287 | + // The pushed state is the state to return to when the corresponding |
| 288 | + // closing token is found |
| 289 | + $push = array( |
| 290 | + self::STATEMENT => array( |
| 291 | + self::TYPE_BRACE_OPEN => self::STATEMENT, |
| 292 | + self::TYPE_PAREN_OPEN => self::EXPRESSION_OP |
| 293 | + ), |
| 294 | + self::CONDITION => array( |
| 295 | + self::TYPE_PAREN_OPEN => self::STATEMENT |
| 296 | + ), |
| 297 | + self::PROPERTY_ASSIGNMENT => array( |
| 298 | + self::TYPE_BRACE_OPEN => self::PROPERTY_ASSIGNMENT |
| 299 | + ), |
| 300 | + self::EXPRESSION => array( |
| 301 | + self::TYPE_BRACE_OPEN => self::EXPRESSION_OP, |
| 302 | + self::TYPE_PAREN_OPEN => self::EXPRESSION_OP |
| 303 | + ), |
| 304 | + self::EXPRESSION_NO_NL => array( |
| 305 | + self::TYPE_BRACE_OPEN => self::EXPRESSION_OP, |
| 306 | + self::TYPE_PAREN_OPEN => self::EXPRESSION_OP |
| 307 | + ), |
| 308 | + self::EXPRESSION_OP => array( |
| 309 | + self::TYPE_HOOK => self::EXPRESSION, |
| 310 | + self::TYPE_PAREN_OPEN => self::EXPRESSION_OP |
| 311 | + ), |
| 312 | + self::EXPRESSION_FUNC => array( |
| 313 | + self::TYPE_BRACE_OPEN => self::EXPRESSION_OP |
| 314 | + ), |
| 315 | + self::EXPRESSION_TERNARY => array( |
| 316 | + self::TYPE_BRACE_OPEN => self::EXPRESSION_TERNARY_OP, |
| 317 | + self::TYPE_PAREN_OPEN => self::EXPRESSION_TERNARY_OP |
| 318 | + ), |
| 319 | + self::EXPRESSION_TERNARY_OP => array( |
| 320 | + self::TYPE_HOOK => self::EXPRESSION_TERNARY, |
| 321 | + self::TYPE_PAREN_OPEN => self::EXPRESSION_TERNARY_OP |
| 322 | + ), |
| 323 | + self::EXPRESSION_TERNARY_FUNC => array( |
| 324 | + self::TYPE_BRACE_OPEN => self::EXPRESSION_TERNARY_OP |
| 325 | + ), |
| 326 | + self::PAREN_EXPRESSION => array( |
| 327 | + self::TYPE_BRACE_OPEN => self::PAREN_EXPRESSION_OP, |
| 328 | + self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION_OP |
| 329 | + ), |
| 330 | + self::PAREN_EXPRESSION_OP => array( |
| 331 | + self::TYPE_PAREN_OPEN => self::PAREN_EXPRESSION_OP |
| 332 | + ), |
| 333 | + self::PAREN_EXPRESSION_FUNC => array( |
| 334 | + self::TYPE_BRACE_OPEN => self::PAREN_EXPRESSION_OP |
| 335 | + ), |
| 336 | + self::PROPERTY_EXPRESSION => array( |
| 337 | + self::TYPE_BRACE_OPEN => self::PROPERTY_EXPRESSION_OP, |
| 338 | + self::TYPE_PAREN_OPEN => self::PROPERTY_EXPRESSION_OP |
| 339 | + ), |
| 340 | + self::PROPERTY_EXPRESSION_OP => array( |
| 341 | + self::TYPE_PAREN_OPEN => self::PROPERTY_EXPRESSION_OP |
| 342 | + ), |
| 343 | + self::PROPERTY_EXPRESSION_FUNC => array( |
| 344 | + self::TYPE_BRACE_OPEN => self::PROPERTY_EXPRESSION_OP |
| 345 | + ) |
| 346 | + ); |
| 347 | + |
| 348 | + // $pop : Rules for when to pop a state from the stack |
| 349 | + $pop = array( |
| 350 | + self::STATEMENT => array( self::TYPE_BRACE_CLOSE => true ), |
| 351 | + self::PROPERTY_ASSIGNMENT => array( self::TYPE_BRACE_CLOSE => true ), |
| 352 | + self::EXPRESSION => array( self::TYPE_BRACE_CLOSE => true ), |
| 353 | + self::EXPRESSION_NO_NL => array( self::TYPE_BRACE_CLOSE => true ), |
| 354 | + self::EXPRESSION_OP => array( self::TYPE_BRACE_CLOSE => true ), |
| 355 | + self::EXPRESSION_TERNARY_OP => array( self::TYPE_COLON => true ), |
| 356 | + self::PAREN_EXPRESSION => array( self::TYPE_PAREN_CLOSE => true ), |
| 357 | + self::PAREN_EXPRESSION_OP => array( self::TYPE_PAREN_CLOSE => true ), |
| 358 | + self::PROPERTY_EXPRESSION => array( self::TYPE_BRACE_CLOSE => true ), |
| 359 | + self::PROPERTY_EXPRESSION_OP => array( self::TYPE_BRACE_CLOSE => true ) |
| 360 | + ); |
| 361 | + |
| 362 | + // $semicolon : Rules for when a semicolon insertion is appropriate |
| 363 | + $semicolon = array( |
| 364 | + self::EXPRESSION_NO_NL => array( |
| 365 | + self::TYPE_UN_OP => true, |
| 366 | + self::TYPE_INCR_OP => true, |
| 367 | + self::TYPE_ADD_OP => true, |
| 368 | + self::TYPE_BRACE_OPEN => true, |
| 369 | + self::TYPE_PAREN_OPEN => true, |
| 370 | + self::TYPE_RETURN => true, |
| 371 | + self::TYPE_IF => true, |
| 372 | + self::TYPE_DO => true, |
| 373 | + self::TYPE_FUNC => true, |
| 374 | + self::TYPE_LITERAL => true |
| 375 | + ), |
| 376 | + self::EXPRESSION_OP => array( |
| 377 | + self::TYPE_UN_OP => true, |
| 378 | + self::TYPE_INCR_OP => true, |
| 379 | + self::TYPE_BRACE_OPEN => true, |
| 380 | + self::TYPE_RETURN => true, |
| 381 | + self::TYPE_IF => true, |
| 382 | + self::TYPE_DO => true, |
| 383 | + self::TYPE_FUNC => true, |
| 384 | + self::TYPE_LITERAL => true |
| 385 | + ) |
| 386 | + ); |
| 387 | + |
| 388 | + // Rules for when newlines should be inserted if |
| 389 | + // $statementsOnOwnLine is enabled. |
| 390 | + // $newlineBefore is checked before switching state, |
| 391 | + // $newlineAfter is checked after |
| 392 | + $newlineBefore = array( |
| 393 | + self::STATEMENT => array( |
| 394 | + self::TYPE_BRACE_CLOSE => true, |
| 395 | + ), |
| 396 | + ); |
| 397 | + $newlineAfter = array( |
| 398 | + self::STATEMENT => array( |
| 399 | + self::TYPE_BRACE_OPEN => true, |
| 400 | + self::TYPE_PAREN_CLOSE => true, |
| 401 | + self::TYPE_SEMICOLON => true, |
| 402 | + ), |
| 403 | + ); |
| 404 | + |
| 405 | + // $divStates : Contains all states that can be followed by a division operator |
| 406 | + $divStates = array( |
| 407 | + self::EXPRESSION_OP => true, |
| 408 | + self::EXPRESSION_TERNARY_OP => true, |
| 409 | + self::PAREN_EXPRESSION_OP => true, |
| 410 | + self::PROPERTY_EXPRESSION_OP => true |
| 411 | + ); |
| 412 | + |
| 413 | + // Here's where the minifying takes place: Loop through the input, looking for tokens |
| 414 | + // and output them to $out, taking actions to the above defined rules when appropriate. |
| 415 | + $out = ''; |
| 416 | + $pos = 0; |
| 417 | + $length = strlen( $s ); |
| 418 | + $lineLength = 0; |
| 419 | + $newlineFound = true; |
| 420 | + $state = self::STATEMENT; |
| 421 | + $stack = array(); |
| 422 | + $last = ';'; // Pretend that we have seen a semicolon yet |
| 423 | + while( $pos < $length ) { |
| 424 | + // First, skip over any whitespace and multiline comments, recording whether we |
| 425 | + // found any newline character |
| 426 | + $skip = strspn( $s, " \t\n\r\xb\xc", $pos ); |
| 427 | + if( !$skip ) { |
| 428 | + $ch = $s[$pos]; |
| 429 | + if( $ch === '/' && substr( $s, $pos, 2 ) === '/*' ) { |
| 430 | + // Multiline comment. Search for the end token or EOT. |
| 431 | + $end = strpos( $s, '*/', $pos + 2 ); |
| 432 | + $skip = $end === false ? $length - $pos : $end - $pos + 2; |
| 433 | + } |
| 434 | + } |
| 435 | + if( $skip ) { |
| 436 | + // The semicolon insertion mechanism needs to know whether there was a newline |
| 437 | + // between two tokens, so record it now. |
| 438 | + if( !$newlineFound && strcspn( $s, "\r\n", $pos, $skip ) !== $skip ) { |
| 439 | + $newlineFound = true; |
| 440 | + } |
| 441 | + $pos += $skip; |
| 442 | + continue; |
| 443 | + } |
| 444 | + // Handle C++-style comments and html comments, which are treated as single line |
| 445 | + // comments by the browser, regardless of whether the end tag is on the same line. |
| 446 | + // Handle --> the same way, but only if it's at the beginning of the line |
| 447 | + if( ( $ch === '/' && substr( $s, $pos, 2 ) === '//' ) |
| 448 | + || ( $ch === '<' && substr( $s, $pos, 4 ) === '<!--' ) |
| 449 | + || ( $ch === '-' && $newlineFound && substr( $s, $pos, 3 ) === '-->' ) |
| 450 | + ) { |
| 451 | + $pos += strcspn( $s, "\r\n", $pos ); |
| 452 | + continue; |
| 453 | + } |
| 454 | + |
| 455 | + // Find out which kind of token we're handling. $end will point past the end of it. |
| 456 | + $end = $pos + 1; |
| 457 | + // Handle string literals |
| 458 | + if( $ch === "'" || $ch === '"' ) { |
| 459 | + // Search to the end of the string literal, skipping over backslash escapes |
| 460 | + $search = $ch . '\\'; |
| 461 | + do{ |
| 462 | + $end += strcspn( $s, $search, $end ) + 2; |
| 463 | + } while( $end - 2 < $length && $s[$end - 2] === '\\' ); |
| 464 | + $end--; |
| 465 | + // We have to distinguish between regexp literals and division operators |
| 466 | + // A division operator is only possible in certain states |
| 467 | + } elseif( $ch === '/' && !isset( $divStates[$state] ) ) { |
| 468 | + // Regexp literal, search to the end, skipping over backslash escapes and |
| 469 | + // character classes |
| 470 | + for( ; ; ) { |
| 471 | + do{ |
| 472 | + $end += strcspn( $s, '/[\\', $end ) + 2; |
| 473 | + } while( $end - 2 < $length && $s[$end - 2] === '\\' ); |
| 474 | + $end--; |
| 475 | + if( $end - 1 >= $length || $s[$end - 1] === '/' ) { |
| 476 | + break; |
| 477 | + } |
| 478 | + do{ |
| 479 | + $end += strcspn( $s, ']\\', $end ) + 2; |
| 480 | + } while( $end - 2 < $length && $s[$end - 2] === '\\' ); |
| 481 | + $end--; |
| 482 | + }; |
| 483 | + // Search past the regexp modifiers (gi) |
| 484 | + while( $end < $length && ctype_alpha( $s[$end] ) ) { |
| 485 | + $end++; |
| 486 | + } |
| 487 | + } elseif( |
| 488 | + ctype_digit( $ch ) |
| 489 | + || ( $ch === '.' && $pos + 1 < $length && ctype_digit( $s[$pos + 1] ) ) |
| 490 | + ) { |
| 491 | + // Numeric literal. Search for the end of it, but don't care about [+-]exponent |
| 492 | + // at the end, as the results of "numeric [+-] numeric" and "numeric" are |
| 493 | + // identical to our state machine. |
| 494 | + $end += strspn( $s, '0123456789ABCDEFabcdefXx.', $end ); |
| 495 | + while( $s[$end - 1] === '.' ) { |
| 496 | + // Special case: When a numeric ends with a dot, we have to check the |
| 497 | + // literal for proper syntax |
| 498 | + $decimal = strspn( $s, '0123456789', $pos, $end - $pos - 1 ); |
| 499 | + if( $decimal === $end - $pos - 1 ) { |
| 500 | + break; |
| 501 | + } else { |
| 502 | + $end--; |
| 503 | + } |
| 504 | + } |
| 505 | + } elseif( isset( $opChars[$ch] ) ) { |
| 506 | + // Punctuation character. Search for the longest matching operator. |
| 507 | + while( |
| 508 | + $end < $length |
| 509 | + && isset( $tokenTypes[substr( $s, $pos, $end - $pos + 1 )] ) |
| 510 | + ) { |
| 511 | + $end++; |
| 512 | + } |
| 513 | + } else { |
| 514 | + // Identifier or reserved word. Search for the end by excluding whitespace and |
| 515 | + // punctuation. |
| 516 | + $end += strcspn( $s, " \t\n.;,=<>+-{}()[]?:*/%'\"!&|^~\xb\xc\r", $end ); |
| 517 | + } |
| 518 | + |
| 519 | + // Now get the token type from our type array |
| 520 | + $token = substr( $s, $pos, $end - $pos ); // so $end - $pos == strlen( $token ) |
| 521 | + $type = isset( $tokenTypes[$token] ) ? $tokenTypes[$token] : self::TYPE_LITERAL; |
| 522 | + |
| 523 | + if( $newlineFound && isset( $semicolon[$state][$type] ) ) { |
| 524 | + // This token triggers the semicolon insertion mechanism of javascript. While we |
| 525 | + // could add the ; token here ourselves, keeping the newline has a few advantages. |
| 526 | + $out .= "\n"; |
| 527 | + $state = self::STATEMENT; |
| 528 | + $lineLength = 0; |
| 529 | + } elseif( $maxLineLength > 0 && $lineLength + $end - $pos > $maxLineLength && |
| 530 | + !isset( $semicolon[$state][$type] ) && $type !== self::TYPE_INCR_OP ) |
| 531 | + { |
| 532 | + // This line would get too long if we added $token, so add a newline first. |
| 533 | + // Only do this if it won't trigger semicolon insertion and if it won't |
| 534 | + // put a postfix increment operator on its own line, which is illegal in js. |
| 535 | + $out .= "\n"; |
| 536 | + $lineLength = 0; |
| 537 | + // Check, whether we have to separate the token from the last one with whitespace |
| 538 | + } elseif( !isset( $opChars[$last] ) && !isset( $opChars[$ch] ) ) { |
| 539 | + $out .= ' '; |
| 540 | + $lineLength++; |
| 541 | + // Don't accidentally create ++, -- or // tokens |
| 542 | + } elseif( $last === $ch && ( $ch === '+' || $ch === '-' || $ch === '/' ) ) { |
| 543 | + $out .= ' '; |
| 544 | + $lineLength++; |
| 545 | + } |
| 546 | + |
| 547 | + $out .= $token; |
| 548 | + $lineLength += $end - $pos; // += strlen( $token ) |
| 549 | + $last = $s[$end - 1]; |
| 550 | + $pos = $end; |
| 551 | + $newlineFound = false; |
| 552 | + |
| 553 | + // Output a newline after the token if required |
| 554 | + // This is checked before AND after switching state |
| 555 | + $newlineAdded = false; |
| 556 | + if ( $statementsOnOwnLine && !$newlineAdded && isset( $newlineBefore[$state][$type] ) ) { |
| 557 | + $out .= "\n"; |
| 558 | + $lineLength = 0; |
| 559 | + $newlineAdded = true; |
| 560 | + } |
| 561 | + |
| 562 | + // Now that we have output our token, transition into the new state. |
| 563 | + if( isset( $push[$state][$type] ) && count( $stack ) < self::STACK_LIMIT ) { |
| 564 | + $stack[] = $push[$state][$type]; |
| 565 | + } |
| 566 | + if( $stack && isset( $pop[$state][$type] ) ) { |
| 567 | + $state = array_pop( $stack ); |
| 568 | + } elseif( isset( $goto[$state][$type] ) ) { |
| 569 | + $state = $goto[$state][$type]; |
| 570 | + } |
| 571 | + |
| 572 | + // Check for newline insertion again |
| 573 | + if ( $statementsOnOwnLine && !$newlineAdded && isset( $newlineAfter[$state][$type] ) ) { |
| 574 | + $out .= "\n"; |
| 575 | + $lineLength = 0; |
| 576 | + } |
| 577 | + } |
| 578 | + return $out; |
| 579 | + } |
| 580 | +} |
Property changes on: branches/REL1_17/phase3/includes/libs/JavaScriptMinifier.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 581 | + native |
Index: branches/REL1_17/phase3/includes/libs/CSSMin.php |
— | — | @@ -144,6 +144,9 @@ |
145 | 145 | $query = $match['query'][0]; |
146 | 146 | $url = "{$remote}/{$match['file'][0]}"; |
147 | 147 | $file = "{$local}/{$match['file'][0]}"; |
| 148 | + // bug 27052 - Guard against double slashes, because foo//../bar |
| 149 | + // apparently resolves to foo/bar on (some?) clients |
| 150 | + $url = preg_replace( '#([^:])//+#', '\1/', $url ); |
148 | 151 | $replacement = false; |
149 | 152 | if ( $local !== false && file_exists( $file ) ) { |
150 | 153 | // Add version parameter as a time-stamp in ISO 8601 format, |
Property changes on: branches/REL1_17/phase3/includes/libs |
___________________________________________________________________ |
Added: svn:mergeinfo |
151 | 154 | Merged /branches/sqlite/includes/libs:r58211-58321 |
152 | 155 | Merged /trunk/phase3/includes/libs:r82474,82845,82847-82848,83934,83979,83988-83989,83997-83998,84118,84228,84271,84343,84353,84392,84430 |
153 | 156 | Merged /branches/new-installer/phase3/includes/libs:r43664-66004 |
154 | 157 | Merged /branches/wmf-deployment/includes/libs:r53381 |
155 | 158 | Merged /branches/REL1_15/phase3/includes/libs:r51646 |
Index: branches/REL1_17/phase3/includes/DefaultSettings.php |
— | — | @@ -2434,12 +2434,19 @@ |
2435 | 2435 | $wgResourceLoaderUseESI = false; |
2436 | 2436 | |
2437 | 2437 | /** |
2438 | | - * Enable removal of some of the vertical whitespace (like \r and \n) from |
2439 | | - * JavaScript code when minifying. |
| 2438 | + * Put each statement on its own line when minifying JavaScript. This makes |
| 2439 | + * debugging in non-debug mode a bit easier. |
2440 | 2440 | */ |
2441 | | -$wgResourceLoaderMinifyJSVerticalSpace = false; |
| 2441 | +$wgResourceLoaderMinifierStatementsOnOwnLine = false; |
2442 | 2442 | |
2443 | 2443 | /** |
| 2444 | + * Maximum line length when minifying JavaScript. This is not a hard maximum: |
| 2445 | + * the minifier will try not to produce lines longer than this, but may be |
| 2446 | + * forced to do so in certain cases. |
| 2447 | + */ |
| 2448 | +$wgResourceLoaderMinifierMaxLineLength = 1000; |
| 2449 | + |
| 2450 | +/** |
2444 | 2451 | * Whether to include the mediawiki.legacy JS library (old wikibits.js), and its |
2445 | 2452 | * dependencies |
2446 | 2453 | */ |
Property changes on: branches/REL1_17/phase3/includes/DefaultSettings.php |
___________________________________________________________________ |
Modified: svn:mergeinfo |
2447 | 2454 | Merged /trunk/phase3/includes/DefaultSettings.php:r83891,83897,83902-83903,83979,83988-83989,83997-83998,84118,84228,84271,84343,84353,84392,84430 |
Index: branches/REL1_17/phase3/includes/specials/SpecialListfiles.php |
— | — | @@ -235,9 +235,19 @@ |
236 | 236 | if ( !is_null( $this->mUserName ) ) { |
237 | 237 | # Append the username to the query string |
238 | 238 | foreach ( $queries as &$query ) { |
239 | | - $query['username'] = $this->mUserName; |
| 239 | + $query['user'] = $this->mUserName; |
240 | 240 | } |
241 | 241 | } |
242 | 242 | return $queries; |
243 | 243 | } |
| 244 | + |
| 245 | + function getDefaultQuery() { |
| 246 | + $queries = parent::getDefaultQuery(); |
| 247 | + if ( !isset( $queries['user'] ) |
| 248 | + && !is_null( $this->mUserName ) ) |
| 249 | + { |
| 250 | + $queries['user'] = $this->mUserName; |
| 251 | + } |
| 252 | + return $queries; |
| 253 | + } |
244 | 254 | } |
Index: branches/REL1_17/phase3/includes/specials/SpecialUpload.php |
— | — | @@ -448,8 +448,8 @@ |
449 | 449 | $permErrors = $this->mUpload->verifyPermissions( $wgUser ); |
450 | 450 | if( $permErrors !== true ) { |
451 | 451 | $code = array_shift( $permErrors[0] ); |
452 | | - $this->showRecoverableUploadError( wfMsgExt( $code, |
453 | | - 'parseinline', $permErrors[0] ) ); |
| 452 | + $this->showRecoverableUploadError( wfMsgExt( $code[0], |
| 453 | + 'parseinline', $code[1] ) ); |
454 | 454 | return; |
455 | 455 | } |
456 | 456 | |
Property changes on: branches/REL1_17/phase3/includes/specials/SpecialUpload.php |
___________________________________________________________________ |
Modified: svn:mergeinfo |
457 | 457 | Merged /trunk/phase3/includes/specials/SpecialUpload.php:r83979,83988-83989,83997-83998,84118,84228,84271,84343,84353,84392,84430 |
Index: branches/REL1_17/phase3/includes/ImageGallery.php |
— | — | @@ -35,6 +35,13 @@ |
36 | 36 | private $mAttribs = array(); |
37 | 37 | |
38 | 38 | /** |
| 39 | + * Fixed margins |
| 40 | + */ |
| 41 | + const THUMB_PADDING = 30; |
| 42 | + const GB_PADDING = 5; |
| 43 | + const GB_BORDERS = 6; |
| 44 | + |
| 45 | + /** |
39 | 46 | * Create a new image gallery object. |
40 | 47 | */ |
41 | 48 | function __construct( ) { |
— | — | @@ -226,7 +233,7 @@ |
227 | 234 | $sk = $this->getSkin(); |
228 | 235 | |
229 | 236 | if ( $this->mPerRow > 0 ) { |
230 | | - $maxwidth = $this->mPerRow * ( $this->mWidths + 50 ); |
| 237 | + $maxwidth = $this->mPerRow * ( $this->mWidths + self::THUMB_PADDING + self::GB_PADDING + self::GB_BORDERS ); |
231 | 238 | $oldStyle = isset( $this->mAttribs['style'] ) ? $this->mAttribs['style'] : ""; |
232 | 239 | $this->mAttribs['style'] = "max-width: {$maxwidth}px;_width: {$maxwidth}px;" . $oldStyle; |
233 | 240 | } |
— | — | @@ -258,11 +265,11 @@ |
259 | 266 | |
260 | 267 | if( !$img ) { |
261 | 268 | # We're dealing with a non-image, spit out the name and be done with it. |
262 | | - $thumbhtml = "\n\t\t\t".'<div style="height: '.(30 + $this->mHeights).'px;">' |
| 269 | + $thumbhtml = "\n\t\t\t".'<div style="height: '.(self::THUMB_PADDING + $this->mHeights).'px;">' |
263 | 270 | . htmlspecialchars( $nt->getText() ) . '</div>'; |
264 | 271 | } elseif( $this->mHideBadImages && wfIsBadImage( $nt->getDBkey(), $this->getContextTitle() ) ) { |
265 | 272 | # The image is blacklisted, just show it as a text link. |
266 | | - $thumbhtml = "\n\t\t\t".'<div style="height: '.(30 + $this->mHeights).'px;">' . |
| 273 | + $thumbhtml = "\n\t\t\t".'<div style="height: '.(self::THUMB_PADDING + $this->mHeights).'px;">' . |
267 | 274 | $sk->link( |
268 | 275 | $nt, |
269 | 276 | htmlspecialchars( $nt->getText() ), |
— | — | @@ -273,13 +280,13 @@ |
274 | 281 | '</div>'; |
275 | 282 | } elseif( !( $thumb = $img->transform( $params ) ) ) { |
276 | 283 | # Error generating thumbnail. |
277 | | - $thumbhtml = "\n\t\t\t".'<div style="height: '.(30 + $this->mHeights).'px;">' |
| 284 | + $thumbhtml = "\n\t\t\t".'<div style="height: '.(self::THUMB_PADDING + $this->mHeights).'px;">' |
278 | 285 | . htmlspecialchars( $img->getLastError() ) . '</div>'; |
279 | 286 | } else { |
280 | 287 | //We get layout problems with the margin, if the image is smaller |
281 | 288 | //than the line-height, so we less margin in these cases. |
282 | 289 | $minThumbHeight = $thumb->height > 17 ? $thumb->height : 17; |
283 | | - $vpad = floor(( 30 + $this->mHeights - $minThumbHeight ) /2); |
| 290 | + $vpad = floor(( self::THUMB_PADDING + $this->mHeights - $minThumbHeight ) /2); |
284 | 291 | |
285 | 292 | |
286 | 293 | $imageParameters = array( |
— | — | @@ -293,7 +300,7 @@ |
294 | 301 | |
295 | 302 | # Set both fixed width and min-height. |
296 | 303 | $thumbhtml = "\n\t\t\t". |
297 | | - '<div class="thumb" style="width: ' .($this->mWidths+30).'px;">' |
| 304 | + '<div class="thumb" style="width: ' .($this->mWidths + self::THUMB_PADDING).'px;">' |
298 | 305 | # Auto-margin centering for block-level elements. Needed now that we have video |
299 | 306 | # handlers since they may emit block-level elements as opposed to simple <img> tags. |
300 | 307 | # ref http://css-discuss.incutio.com/?page=CenteringBlockElement |
— | — | @@ -339,8 +346,8 @@ |
340 | 347 | # Weird double wrapping in div needed due to FF2 bug |
341 | 348 | # Can be safely removed if FF2 falls completely out of existance |
342 | 349 | $s .= |
343 | | - "\n\t\t" . '<li class="gallerybox" style="width: ' . ( $this->mWidths + 35 ) . 'px">' |
344 | | - . '<div style="width: ' . ( $this->mWidths + 35 ) . 'px">' |
| 350 | + "\n\t\t" . '<li class="gallerybox" style="width: ' . ( $this->mWidths + self::THUMB_PADDING + self::GB_PADDING ) . 'px">' |
| 351 | + . '<div style="width: ' . ( $this->mWidths + self::THUMB_PADDING + self::GB_PADDING ) . 'px">' |
345 | 352 | . $thumbhtml |
346 | 353 | . "\n\t\t\t" . '<div class="gallerytext">' . "\n" |
347 | 354 | . $textlink . $text . $nb |
Property changes on: branches/REL1_17/phase3/includes/ImageGallery.php |
___________________________________________________________________ |
Added: svn:mergeinfo |
348 | 355 | Merged /branches/sqlite/includes/ImageGallery.php:r58211-58321 |
349 | 356 | Merged /trunk/phase3/includes/ImageGallery.php:r82474,82845,82847-82848,83965,83979,83988-83989,83997-83998,84118,84228,84271,84343,84353,84392,84430 |
350 | 357 | Merged /branches/new-installer/phase3/includes/ImageGallery.php:r43664-66004 |
351 | 358 | Merged /branches/wmf-deployment/includes/ImageGallery.php:r53381 |
352 | 359 | Merged /branches/REL1_15/phase3/includes/ImageGallery.php:r51646 |
Index: branches/REL1_17/phase3/RELEASE-NOTES |
— | — | @@ -515,8 +515,10 @@ |
516 | 516 | * (bug 27560) Search queries no longer fail in walloon language |
517 | 517 | * (bug 27700) The upload protection can now also be set for files that do not |
518 | 518 | exist. |
| 519 | +* (bug 28034) uploading file to local wiki when file exists on shared repository |
| 520 | + (commons) gives spurious info in the warning message |
| 521 | +* Usernames get lost when selecting different sorts on Special:listfiles |
519 | 522 | |
520 | | -=== API changes in 1.17 === |
521 | 523 | * BREAKING CHANGE: action=patrol now requires POST |
522 | 524 | * BREAKING CHANGE: patrol token is no longer the same as edit token |
523 | 525 | * BREAKING CHANGE: Session keys returned by ApiUpload are now strings instead of integers |
— | — | @@ -651,8 +653,8 @@ |
652 | 654 | * (bug 27633) Add characters to linkTrail for Potuguese (pt and pt-br) |
653 | 655 | |
654 | 656 | * (bug 23156) Commafy and search normalization updated for Belarusian |
655 | | - (Tara�kievica). |
656 | | -* (bug 23283) Native name for Old English -> �nglisc. |
| 657 | + (Taraškievica). |
| 658 | +* (bug 23283) Native name for Old English -> Ænglisc. |
657 | 659 | * (bug 23364) Native name for Azerbaijani -> Az?rbaycanca. |
658 | 660 | * (bug 24593) Native name for Sorani now uses only Arabic script. |
659 | 661 | * (bug 24628) Generic translations for NS_USER/NS_USER_TALK for Esperanto. |
Property changes on: branches/REL1_17/phase3/RELEASE-NOTES |
___________________________________________________________________ |
Modified: svn:mergeinfo |
660 | 662 | Merged /trunk/phase3/RELEASE-NOTES:r83979,83988-83989,83997-83998,84118,84228,84271,84343,84353,84392,84430 |
Property changes on: branches/REL1_17/phase3 |
___________________________________________________________________ |
Modified: svn:mergeinfo |
661 | 663 | Merged /trunk/phase3:r83885,83891,83897,83902-83903,83979,83988-83989,83997-83998,84118,84228,84271,84343,84353,84392,84430 |