r68478 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r68477‎ | r68478 | r68479 >
Date:19:06, 23 June 2010
Author:catrope
Status:deferred
Tags:
Comment:
resourceloader: Add library for minifying CSS (and rewriting relative URLs, probably gotta tweak those parameters) and cache minfied/janused stuff in $wgMemc
Modified paths:
  • /branches/resourceloader/phase3/includes/AutoLoader.php (modified) (history)
  • /branches/resourceloader/phase3/includes/Minify_CSS.php (added) (history)
  • /branches/resourceloader/phase3/includes/ResourceLoader.php (modified) (history)
  • /branches/resourceloader/phase3/load.php (modified) (history)

Diff [purge]

Index: branches/resourceloader/phase3/includes/Minify_CSS.php
@@ -0,0 +1,699 @@
 2+<?php
 3+/**
 4+ * Minify_CSS class and its dependencies, copied from Minfiy r416 <http://code.google.com/p/minify/source/browse/>
 5+ *
 6+ * Minify is distributed with the following license:
 7+ * Copyright (c) 2008 Ryan Grove <ryan@wonko.com>
 8+ * Copyright (c) 2008 Steve Clay <steve@mrclay.org>
 9+ * All rights reserved.
 10+ *
 11+ * Redistribution and use in source and binary forms, with or without
 12+ * modification, are permitted provided that the following conditions are met:
 13+ *
 14+ * * Redistributions of source code must retain the above copyright notice,
 15+ * this list of conditions and the following disclaimer.
 16+ * * Redistributions in binary form must reproduce the above copyright notice,
 17+ * this list of conditions and the following disclaimer in the documentation
 18+ * and/or other materials provided with the distribution.
 19+ * * Neither the name of this project nor the names of its contributors may be
 20+ * used to endorse or promote products derived from this software without
 21+ * specific prior written permission.
 22+ *
 23+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 24+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 25+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 26+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 27+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 28+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 29+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 30+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 31+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 32+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 33+ */
 34+
 35+/**
 36+ * Minify CSS
 37+ *
 38+ * This class uses Minify_CSS_Compressor and Minify_CSS_UriRewriter to
 39+ * minify CSS and rewrite relative URIs.
 40+ *
 41+ * @author Stephen Clay <steve@mrclay.org>
 42+ * @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
 43+ */
 44+class Minify_CSS {
 45+
 46+ /**
 47+ * Minify a CSS string
 48+ *
 49+ * @param string $css
 50+ *
 51+ * @param array $options available options:
 52+ *
 53+ * 'preserveComments': (default true) multi-line comments that begin
 54+ * with "/*!" will be preserved with newlines before and after to
 55+ * enhance readability.
 56+ *
 57+ * 'prependRelativePath': (default null) if given, this string will be
 58+ * prepended to all relative URIs in import/url declarations
 59+ *
 60+ * 'currentDir': (default null) if given, this is assumed to be the
 61+ * directory of the current CSS file. Using this, minify will rewrite
 62+ * all relative URIs in import/url declarations to correctly point to
 63+ * the desired files. For this to work, the files *must* exist and be
 64+ * visible by the PHP process.
 65+ *
 66+ * 'symlinks': (default = array()) If the CSS file is stored in
 67+ * a symlink-ed directory, provide an array of link paths to
 68+ * target paths, where the link paths are within the document root. Because
 69+ * paths need to be normalized for this to work, use "//" to substitute
 70+ * the doc root in the link paths (the array keys). E.g.:
 71+ * <code>
 72+ * array('//symlink' => '/real/target/path') // unix
 73+ * array('//static' => 'D:\\staticStorage') // Windows
 74+ * </code>
 75+ *
 76+ * @return string
 77+ */
 78+ public static function minify($css, $options = array())
 79+ {
 80+ if (isset($options['preserveComments'])
 81+ && !$options['preserveComments']) {
 82+ $css = Minify_CSS_Compressor::process($css, $options);
 83+ } else {
 84+ $css = Minify_CommentPreserver::process(
 85+ $css
 86+ ,array('Minify_CSS_Compressor', 'process')
 87+ ,array($options)
 88+ );
 89+ }
 90+ if (! isset($options['currentDir']) && ! isset($options['prependRelativePath'])) {
 91+ return $css;
 92+ }
 93+ if (isset($options['currentDir'])) {
 94+ return Minify_CSS_UriRewriter::rewrite(
 95+ $css
 96+ ,$options['currentDir']
 97+ ,isset($options['docRoot']) ? $options['docRoot'] : $_SERVER['DOCUMENT_ROOT']
 98+ ,isset($options['symlinks']) ? $options['symlinks'] : array()
 99+ );
 100+ } else {
 101+ return Minify_CSS_UriRewriter::prepend(
 102+ $css
 103+ ,$options['prependRelativePath']
 104+ );
 105+ }
 106+ }
 107+}
 108+
 109+/**
 110+ * Compress CSS
 111+ *
 112+ * This is a heavy regex-based removal of whitespace, unnecessary
 113+ * comments and tokens, and some CSS value minimization, where practical.
 114+ * Many steps have been taken to avoid breaking comment-based hacks,
 115+ * including the ie5/mac filter (and its inversion), but expect tricky
 116+ * hacks involving comment tokens in 'content' value strings to break
 117+ * minimization badly. A test suite is available.
 118+ *
 119+ * @author Stephen Clay <steve@mrclay.org>
 120+ * @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
 121+ */
 122+class Minify_CSS_Compressor {
 123+
 124+ /**
 125+ * Minify a CSS string
 126+ *
 127+ * @param string $css
 128+ *
 129+ * @param array $options (currently ignored)
 130+ *
 131+ * @return string
 132+ */
 133+ public static function process($css, $options = array())
 134+ {
 135+ $obj = new Minify_CSS_Compressor($options);
 136+ return $obj->_process($css);
 137+ }
 138+
 139+ /**
 140+ * @var array options
 141+ */
 142+ protected $_options = null;
 143+
 144+ /**
 145+ * @var bool Are we "in" a hack?
 146+ *
 147+ * I.e. are some browsers targetted until the next comment?
 148+ */
 149+ protected $_inHack = false;
 150+
 151+
 152+ /**
 153+ * Constructor
 154+ *
 155+ * @param array $options (currently ignored)
 156+ *
 157+ * @return null
 158+ */
 159+ private function __construct($options) {
 160+ $this->_options = $options;
 161+ }
 162+
 163+ /**
 164+ * Minify a CSS string
 165+ *
 166+ * @param string $css
 167+ *
 168+ * @return string
 169+ */
 170+ protected function _process($css)
 171+ {
 172+ $css = str_replace("\r\n", "\n", $css);
 173+
 174+ // preserve empty comment after '>'
 175+ // http://www.webdevout.net/css-hacks#in_css-selectors
 176+ $css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css);
 177+
 178+ // preserve empty comment between property and value
 179+ // http://css-discuss.incutio.com/?page=BoxModelHack
 180+ $css = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $css);
 181+ $css = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $css);
 182+
 183+ // apply callback to all valid comments (and strip out surrounding ws
 184+ $css = preg_replace_callback('@\\s*/\\*([\\s\\S]*?)\\*/\\s*@'
 185+ ,array($this, '_commentCB'), $css);
 186+
 187+ // remove ws around { } and last semicolon in declaration block
 188+ $css = preg_replace('/\\s*{\\s*/', '{', $css);
 189+ $css = preg_replace('/;?\\s*}\\s*/', '}', $css);
 190+
 191+ // remove ws surrounding semicolons
 192+ $css = preg_replace('/\\s*;\\s*/', ';', $css);
 193+
 194+ // remove ws around urls
 195+ $css = preg_replace('/
 196+ url\\( # url(
 197+ \\s*
 198+ ([^\\)]+?) # 1 = the URL (really just a bunch of non right parenthesis)
 199+ \\s*
 200+ \\) # )
 201+ /x', 'url($1)', $css);
 202+
 203+ // remove ws between rules and colons
 204+ $css = preg_replace('/
 205+ \\s*
 206+ ([{;]) # 1 = beginning of block or rule separator
 207+ \\s*
 208+ ([\\*_]?[\\w\\-]+) # 2 = property (and maybe IE filter)
 209+ \\s*
 210+ :
 211+ \\s*
 212+ (\\b|[#\'"]) # 3 = first character of a value
 213+ /x', '$1$2:$3', $css);
 214+
 215+ // remove ws in selectors
 216+ $css = preg_replace_callback('/
 217+ (?: # non-capture
 218+ \\s*
 219+ [^~>+,\\s]+ # selector part
 220+ \\s*
 221+ [,>+~] # combinators
 222+ )+
 223+ \\s*
 224+ [^~>+,\\s]+ # selector part
 225+ { # open declaration block
 226+ /x'
 227+ ,array($this, '_selectorsCB'), $css);
 228+
 229+ // minimize hex colors
 230+ $css = preg_replace('/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i'
 231+ , '$1#$2$3$4$5', $css);
 232+
 233+ // remove spaces between font families
 234+ $css = preg_replace_callback('/font-family:([^;}]+)([;}])/'
 235+ ,array($this, '_fontFamilyCB'), $css);
 236+
 237+ $css = preg_replace('/@import\\s+url/', '@import url', $css);
 238+
 239+ // replace any ws involving newlines with a single newline
 240+ $css = preg_replace('/[ \\t]*\\n+\\s*/', "\n", $css);
 241+
 242+ // separate common descendent selectors w/ newlines (to limit line lengths)
 243+ $css = preg_replace('/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/', "$1\n$2{", $css);
 244+
 245+ // Use newline after 1st numeric value (to limit line lengths).
 246+ $css = preg_replace('/
 247+ ((?:padding|margin|border|outline):\\d+(?:px|em)?) # 1 = prop : 1st numeric value
 248+ \\s+
 249+ /x'
 250+ ,"$1\n", $css);
 251+
 252+ // prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
 253+ $css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);
 254+
 255+ return trim($css);
 256+ }
 257+
 258+ /**
 259+ * Replace what looks like a set of selectors
 260+ *
 261+ * @param array $m regex matches
 262+ *
 263+ * @return string
 264+ */
 265+ protected function _selectorsCB($m)
 266+ {
 267+ // remove ws around the combinators
 268+ return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]);
 269+ }
 270+
 271+ /**
 272+ * Process a comment and return a replacement
 273+ *
 274+ * @param array $m regex matches
 275+ *
 276+ * @return string
 277+ */
 278+ protected function _commentCB($m)
 279+ {
 280+ $hasSurroundingWs = (trim($m[0]) !== $m[1]);
 281+ $m = $m[1];
 282+ // $m is the comment content w/o the surrounding tokens,
 283+ // but the return value will replace the entire comment.
 284+ if ($m === 'keep') {
 285+ return '/**/';
 286+ }
 287+ if ($m === '" "') {
 288+ // component of http://tantek.com/CSS/Examples/midpass.html
 289+ return '/*" "*/';
 290+ }
 291+ if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) {
 292+ // component of http://tantek.com/CSS/Examples/midpass.html
 293+ return '/*";}}/* */';
 294+ }
 295+ if ($this->_inHack) {
 296+ // inversion: feeding only to one browser
 297+ if (preg_match('@
 298+ ^/ # comment started like /*/
 299+ \\s*
 300+ (\\S[\\s\\S]+?) # has at least some non-ws content
 301+ \\s*
 302+ /\\* # ends like /*/ or /**/
 303+ @x', $m, $n)) {
 304+ // end hack mode after this comment, but preserve the hack and comment content
 305+ $this->_inHack = false;
 306+ return "/*/{$n[1]}/**/";
 307+ }
 308+ }
 309+ if (substr($m, -1) === '\\') { // comment ends like \*/
 310+ // begin hack mode and preserve hack
 311+ $this->_inHack = true;
 312+ return '/*\\*/';
 313+ }
 314+ if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */
 315+ // begin hack mode and preserve hack
 316+ $this->_inHack = true;
 317+ return '/*/*/';
 318+ }
 319+ if ($this->_inHack) {
 320+ // a regular comment ends hack mode but should be preserved
 321+ $this->_inHack = false;
 322+ return '/**/';
 323+ }
 324+ // Issue 107: if there's any surrounding whitespace, it may be important, so
 325+ // replace the comment with a single space
 326+ return $hasSurroundingWs // remove all other comments
 327+ ? ' '
 328+ : '';
 329+ }
 330+
 331+ /**
 332+ * Process a font-family listing and return a replacement
 333+ *
 334+ * @param array $m regex matches
 335+ *
 336+ * @return string
 337+ */
 338+ protected function _fontFamilyCB($m)
 339+ {
 340+ $m[1] = preg_replace('/
 341+ \\s*
 342+ (
 343+ "[^"]+" # 1 = family in double qutoes
 344+ |\'[^\']+\' # or 1 = family in single quotes
 345+ |[\\w\\-]+ # or 1 = unquoted family
 346+ )
 347+ \\s*
 348+ /x', '$1', $m[1]);
 349+ return 'font-family:' . $m[1] . $m[2];
 350+ }
 351+}
 352+
 353+/**
 354+ * Rewrite file-relative URIs as root-relative in CSS files
 355+ *
 356+ * @author Stephen Clay <steve@mrclay.org>
 357+ */
 358+class Minify_CSS_UriRewriter {
 359+
 360+ /**
 361+ * Defines which class to call as part of callbacks, change this
 362+ * if you extend Minify_CSS_UriRewriter
 363+ * @var string
 364+ */
 365+ protected static $className = 'Minify_CSS_UriRewriter';
 366+
 367+ /**
 368+ * rewrite() and rewriteRelative() append debugging information here
 369+ * @var string
 370+ */
 371+ public static $debugText = '';
 372+
 373+ /**
 374+ * Rewrite file relative URIs as root relative in CSS files
 375+ *
 376+ * @param string $css
 377+ *
 378+ * @param string $currentDir The directory of the current CSS file.
 379+ *
 380+ * @param string $docRoot The document root of the web site in which
 381+ * the CSS file resides (default = $_SERVER['DOCUMENT_ROOT']).
 382+ *
 383+ * @param array $symlinks (default = array()) If the CSS file is stored in
 384+ * a symlink-ed directory, provide an array of link paths to
 385+ * target paths, where the link paths are within the document root. Because
 386+ * paths need to be normalized for this to work, use "//" to substitute
 387+ * the doc root in the link paths (the array keys). E.g.:
 388+ * <code>
 389+ * array('//symlink' => '/real/target/path') // unix
 390+ * array('//static' => 'D:\\staticStorage') // Windows
 391+ * </code>
 392+ *
 393+ * @return string
 394+ */
 395+ public static function rewrite($css, $currentDir, $docRoot = null, $symlinks = array())
 396+ {
 397+ self::$_docRoot = self::_realpath(
 398+ $docRoot ? $docRoot : $_SERVER['DOCUMENT_ROOT']
 399+ );
 400+ self::$_currentDir = self::_realpath($currentDir);
 401+ self::$_symlinks = array();
 402+
 403+ // normalize symlinks
 404+ foreach ($symlinks as $link => $target) {
 405+ $link = ($link === '//')
 406+ ? self::$_docRoot
 407+ : str_replace('//', self::$_docRoot . '/', $link);
 408+ $link = strtr($link, '/', DIRECTORY_SEPARATOR);
 409+ self::$_symlinks[$link] = self::_realpath($target);
 410+ }
 411+
 412+ self::$debugText .= "docRoot : " . self::$_docRoot . "\n"
 413+ . "currentDir : " . self::$_currentDir . "\n";
 414+ if (self::$_symlinks) {
 415+ self::$debugText .= "symlinks : " . var_export(self::$_symlinks, 1) . "\n";
 416+ }
 417+ self::$debugText .= "\n";
 418+
 419+ $css = self::_trimUrls($css);
 420+
 421+ // rewrite
 422+ $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
 423+ ,array(self::$className, '_processUriCB'), $css);
 424+ $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
 425+ ,array(self::$className, '_processUriCB'), $css);
 426+
 427+ return $css;
 428+ }
 429+
 430+ /**
 431+ * Prepend a path to relative URIs in CSS files
 432+ *
 433+ * @param string $css
 434+ *
 435+ * @param string $path The path to prepend.
 436+ *
 437+ * @return string
 438+ */
 439+ public static function prepend($css, $path)
 440+ {
 441+ self::$_prependPath = $path;
 442+
 443+ $css = self::_trimUrls($css);
 444+
 445+ // append
 446+ $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
 447+ ,array(self::$className, '_processUriCB'), $css);
 448+ $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
 449+ ,array(self::$className, '_processUriCB'), $css);
 450+
 451+ self::$_prependPath = null;
 452+ return $css;
 453+ }
 454+
 455+
 456+ /**
 457+ * @var string directory of this stylesheet
 458+ */
 459+ private static $_currentDir = '';
 460+
 461+ /**
 462+ * @var string DOC_ROOT
 463+ */
 464+ private static $_docRoot = '';
 465+
 466+ /**
 467+ * @var array directory replacements to map symlink targets back to their
 468+ * source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'
 469+ */
 470+ private static $_symlinks = array();
 471+
 472+ /**
 473+ * @var string path to prepend
 474+ */
 475+ private static $_prependPath = null;
 476+
 477+ private static function _trimUrls($css)
 478+ {
 479+ return preg_replace('/
 480+ url\\( # url(
 481+ \\s*
 482+ ([^\\)]+?) # 1 = URI (assuming does not contain ")")
 483+ \\s*
 484+ \\) # )
 485+ /x', 'url($1)', $css);
 486+ }
 487+
 488+ private static function _processUriCB($m)
 489+ {
 490+ // $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
 491+ $isImport = ($m[0][0] === '@');
 492+ // determine URI and the quote character (if any)
 493+ if ($isImport) {
 494+ $quoteChar = $m[1];
 495+ $uri = $m[2];
 496+ } else {
 497+ // $m[1] is either quoted or not
 498+ $quoteChar = ($m[1][0] === "'" || $m[1][0] === '"')
 499+ ? $m[1][0]
 500+ : '';
 501+ $uri = ($quoteChar === '')
 502+ ? $m[1]
 503+ : substr($m[1], 1, strlen($m[1]) - 2);
 504+ }
 505+ // analyze URI
 506+ if ('/' !== $uri[0] // root-relative
 507+ && false === strpos($uri, '//') // protocol (non-data)
 508+ && 0 !== strpos($uri, 'data:') // data protocol
 509+ ) {
 510+ // URI is file-relative: rewrite depending on options
 511+ $uri = (self::$_prependPath !== null)
 512+ ? (self::$_prependPath . $uri)
 513+ : self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks);
 514+ }
 515+ return $isImport
 516+ ? "@import {$quoteChar}{$uri}{$quoteChar}"
 517+ : "url({$quoteChar}{$uri}{$quoteChar})";
 518+ }
 519+
 520+ /**
 521+ * Rewrite a file relative URI as root relative
 522+ *
 523+ * <code>
 524+ * Minify_CSS_UriRewriter::rewriteRelative(
 525+ * '../img/hello.gif'
 526+ * , '/home/user/www/css' // path of CSS file
 527+ * , '/home/user/www' // doc root
 528+ * );
 529+ * // returns '/img/hello.gif'
 530+ *
 531+ * // example where static files are stored in a symlinked directory
 532+ * Minify_CSS_UriRewriter::rewriteRelative(
 533+ * 'hello.gif'
 534+ * , '/var/staticFiles/theme'
 535+ * , '/home/user/www'
 536+ * , array('/home/user/www/static' => '/var/staticFiles')
 537+ * );
 538+ * // returns '/static/theme/hello.gif'
 539+ * </code>
 540+ *
 541+ * @param string $uri file relative URI
 542+ *
 543+ * @param string $realCurrentDir realpath of the current file's directory.
 544+ *
 545+ * @param string $realDocRoot realpath of the site document root.
 546+ *
 547+ * @param array $symlinks (default = array()) If the file is stored in
 548+ * a symlink-ed directory, provide an array of link paths to
 549+ * real target paths, where the link paths "appear" to be within the document
 550+ * root. E.g.:
 551+ * <code>
 552+ * array('/home/foo/www/not/real/path' => '/real/target/path') // unix
 553+ * array('C:\\htdocs\\not\\real' => 'D:\\real\\target\\path') // Windows
 554+ * </code>
 555+ *
 556+ * @return string
 557+ */
 558+ public static function rewriteRelative($uri, $realCurrentDir, $realDocRoot, $symlinks = array())
 559+ {
 560+ // prepend path with current dir separator (OS-independent)
 561+ $path = strtr($realCurrentDir, '/', DIRECTORY_SEPARATOR)
 562+ . DIRECTORY_SEPARATOR . strtr($uri, '/', DIRECTORY_SEPARATOR);
 563+
 564+ self::$debugText .= "file-relative URI : {$uri}\n"
 565+ . "path prepended : {$path}\n";
 566+
 567+ // "unresolve" a symlink back to doc root
 568+ foreach ($symlinks as $link => $target) {
 569+ if (0 === strpos($path, $target)) {
 570+ // replace $target with $link
 571+ $path = $link . substr($path, strlen($target));
 572+
 573+ self::$debugText .= "symlink unresolved : {$path}\n";
 574+
 575+ break;
 576+ }
 577+ }
 578+ // strip doc root
 579+ $path = substr($path, strlen($realDocRoot));
 580+
 581+ self::$debugText .= "docroot stripped : {$path}\n";
 582+
 583+ // fix to root-relative URI
 584+
 585+
 586+ $uri = strtr($path, '/\\', '//');
 587+
 588+ // remove /./ and /../ where possible
 589+ $uri = str_replace('/./', '/', $uri);
 590+ // inspired by patch from Oleg Cherniy
 591+ do {
 592+ $uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);
 593+ } while ($changed);
 594+
 595+ self::$debugText .= "traversals removed : {$uri}\n\n";
 596+
 597+ return $uri;
 598+ }
 599+
 600+ /**
 601+ * Get realpath with any trailing slash removed. If realpath() fails,
 602+ * just remove the trailing slash.
 603+ *
 604+ * @param string $path
 605+ *
 606+ * @return mixed path with no trailing slash
 607+ */
 608+ protected static function _realpath($path)
 609+ {
 610+ $realPath = realpath($path);
 611+ if ($realPath !== false) {
 612+ $path = $realPath;
 613+ }
 614+ return rtrim($path, '/\\');
 615+ }
 616+}
 617+
 618+/**
 619+ * Process a string in pieces preserving C-style comments that begin with "/*!"
 620+ *
 621+ * @author Stephen Clay <steve@mrclay.org>
 622+ */
 623+class Minify_CommentPreserver {
 624+
 625+ /**
 626+ * String to be prepended to each preserved comment
 627+ *
 628+ * @var string
 629+ */
 630+ public static $prepend = "\n";
 631+
 632+ /**
 633+ * String to be appended to each preserved comment
 634+ *
 635+ * @var string
 636+ */
 637+ public static $append = "\n";
 638+
 639+ /**
 640+ * Process a string outside of C-style comments that begin with "/*!"
 641+ *
 642+ * On each non-empty string outside these comments, the given processor
 643+ * function will be called. The first "!" will be removed from the
 644+ * preserved comments, and the comments will be surrounded by
 645+ * Minify_CommentPreserver::$preprend and Minify_CommentPreserver::$append.
 646+ *
 647+ * @param string $content
 648+ * @param callback $processor function
 649+ * @param array $args array of extra arguments to pass to the processor
 650+ * function (default = array())
 651+ * @return string
 652+ */
 653+ public static function process($content, $processor, $args = array())
 654+ {
 655+ $ret = '';
 656+ while (true) {
 657+ list($beforeComment, $comment, $afterComment) = self::_nextComment($content);
 658+ if ('' !== $beforeComment) {
 659+ $callArgs = $args;
 660+ array_unshift($callArgs, $beforeComment);
 661+ $ret .= call_user_func_array($processor, $callArgs);
 662+ }
 663+ if (false === $comment) {
 664+ break;
 665+ }
 666+ $ret .= $comment;
 667+ $content = $afterComment;
 668+ }
 669+ return $ret;
 670+ }
 671+
 672+ /**
 673+ * Extract comments that YUI Compressor preserves.
 674+ *
 675+ * @param string $in input
 676+ *
 677+ * @return array 3 elements are returned. If a YUI comment is found, the
 678+ * 2nd element is the comment and the 1st and 2nd are the surrounding
 679+ * strings. If no comment is found, the entire string is returned as the
 680+ * 1st element and the other two are false.
 681+ */
 682+ private static function _nextComment($in)
 683+ {
 684+ if (
 685+ false === ($start = strpos($in, '/*!'))
 686+ || false === ($end = strpos($in, '*/', $start + 3))
 687+ ) {
 688+ return array($in, false, false);
 689+ }
 690+ $ret = array(
 691+ substr($in, 0, $start)
 692+ ,self::$prepend . '/*' . substr($in, $start + 3, $end - $start - 1) . self::$append
 693+ );
 694+ $endChars = (strlen($in) - $end - 2);
 695+ $ret[] = (0 === $endChars)
 696+ ? ''
 697+ : substr($in, -$endChars);
 698+ return $ret;
 699+ }
 700+}
Index: branches/resourceloader/phase3/includes/ResourceLoader.php
@@ -150,10 +150,13 @@
151151 $script = file_get_contents( $mod['script'] );
152152 }
153153 if ( isset( $mod['style'] ) && file_exists( $mod['style'] ) ) {
154 - $style = file_get_contents( $mod['style'] );
 154+ $css = file_get_contents( $mod['style'] );
155155 if ( $this->useCSSJanus ) {
156 - $css = $this->cssJanus( $style );
 156+ $css = $this->cssJanus( $css );
157157 }
 158+ if ( $this->useCSSMin ) {
 159+ $css = $this->cssMin( $css, $mod['style'] );
 160+ }
158161 $style = Xml::escapeJsString( $css );
159162 }
160163
@@ -186,17 +189,40 @@
187190 }
188191
189192 public function jsMin( $js ) {
190 - return JSMin::minify( $js );
 193+ global $wgMemc;
 194+ $key = wfMemcKey( 'resourceloader', 'jsmin', md5( $js ) );
 195+ $cached = $wgMemc->get( $key );
 196+ if ( $cached !== false ) {
 197+ return $cached;
 198+ }
 199+ $retval = JSMin::minify( $js );
 200+ $wgMemc->set( $key, $retval );
 201+ return $retval;
191202 }
192203
193 - public function cssMin( $css ) {
194 - // TODO: Implement
195 - return $css;
 204+ public function cssMin( $css, $file ) {
 205+ global $wgMemc;
 206+ $key = wfMemcKey( 'resourceloader', 'cssmin', md5( $css ) );
 207+ $cached = $wgMemc->get( $key );
 208+ if( $cached !== false ) {
 209+ return $cached;
 210+ }
 211+ // TODO: Test how well this path rewriting stuff works with various setups
 212+ $retval = Minify_CSS::minify( $css, array( 'currentDir' => dirname( $file ), 'docRoot' => '.' ) );
 213+ $wgMemc->set( $key, $retval );
 214+ return $retval;
196215 }
197216
198217 public function cssJanus( $css ) {
199 - // TODO: Implement
200 - return $css;
 218+ global $wgMemc;
 219+ $key = wfMemcKey( 'resourceloader', 'cssjanus', md5( $css ) );
 220+ $cached = $wgMemc->get( $key );
 221+ if ( $cached !== false ) {
 222+ return $cached;
 223+ }
 224+ $retval = $css; // TODO: Actually flip
 225+ $wgMemc->set( $key, $retval );
 226+ return $retval;
201227 }
202228 }
203229
@@ -208,6 +234,7 @@
209235 * @return array An array of incomplete JSON objects (i.e. without the {} ) containing messages keys and their values. Array keys are module names.
210236 */
211237 public static function get( $lang, $modules ) {
 238+ // TODO: Invalidate blob when module touched
212239 if ( !count( $modules ) ) {
213240 return array();
214241 }
@@ -226,9 +253,14 @@
227254 return $blobs;
228255 }
229256
 257+ /**
 258+ * Set the message blob for a given module in a given language
 259+ * @param $lang string Language code
 260+ * @param $module string Module name
 261+ * @param $blob string Incomplete JSON object, see get()
 262+ */
230263 public static function set( $lang, $module, $blob ) {
231264 $dbw = wfGetDb( DB_MASTER );
232 - // TODO: Timestamp stuff to handle concurrency
233265 $dbw->replace( 'msg_resource', array( array( 'mr_lang', 'mr_resource' ) ),
234266 array( array(
235267 'mr_lang' => $lang,
Index: branches/resourceloader/phase3/includes/AutoLoader.php
@@ -156,6 +156,7 @@
157157 'Message' => 'includes/Message.php',
158158 'MessageCache' => 'includes/MessageCache.php',
159159 'MimeMagic' => 'includes/MimeMagic.php',
 160+ 'Minify_CSS' => 'includes/Minify_CSS.php',
160161 'MWException' => 'includes/Exception.php',
161162 'MWMemcached' => 'includes/memcached-client.php',
162163 'MWNamespace' => 'includes/Namespace.php',
Index: branches/resourceloader/phase3/load.php
@@ -57,6 +57,7 @@
5858 }
5959
6060 // TODO: Cache-Control header
 61+// TODO: gziphandler
6162 header( 'Content-Type', 'text/javascript' );
6263 echo $loader->getOutput();
6364

Status & tagging log