Merge "Construct clean canonical URLs for wiki pages, ignoring request URL"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 22 Jun 2015 18:18:40 +0000 (18:18 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 22 Jun 2015 18:18:40 +0000 (18:18 +0000)
29 files changed:
RELEASE-NOTES-1.26
includes/DefaultSettings.php
includes/media/Bitmap.php
includes/media/ExifBitmap.php
includes/media/Jpeg.php
includes/media/tinyrgb.icc [new file with mode: 0644]
languages/i18n/en.json
languages/i18n/qqq.json
languages/messages/MessagesEn.php
maintenance/dictionary/mediawiki.dic
maintenance/jsduck/categories.json
resources/Resources.php
resources/src/jquery/jquery.accessKeyLabel.js
resources/src/jquery/jquery.highlightText.js
resources/src/jquery/jquery.mwExtension.js
resources/src/jquery/jquery.tablesorter.js
resources/src/mediawiki.page/mediawiki.page.watch.ajax.js
resources/src/mediawiki/mediawiki.RegExp.js [new file with mode: 0644]
resources/src/mediawiki/mediawiki.htmlform.js
resources/src/mediawiki/mediawiki.inspect.js
resources/src/mediawiki/mediawiki.util.js
tests/parser/preprocess/All_system_messages.expected
tests/parser/preprocess/All_system_messages.txt
tests/phpunit/data/media/srgb.jpg [new file with mode: 0644]
tests/phpunit/data/media/tinyrgb.icc [new file with mode: 0644]
tests/phpunit/data/media/tinyrgb.jpg [new file with mode: 0644]
tests/phpunit/includes/media/ExifBitmapTest.php
tests/qunit/QUnitTestResources.php
tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js [new file with mode: 0644]

index 896d1d5..852023b 100644 (file)
@@ -96,6 +96,7 @@ changes to languages because of Bugzilla reports.
 * The Block class constructor now takes an associative array of parameters
   instead of many optional positional arguments. Calling the constructor the old
   way will issue a deprecation warning.
+* The jquery.mwExtension module was deprecated.
 
 
 == Compatibility ==
index 39f739d..0aba961 100644 (file)
@@ -970,6 +970,14 @@ $wgJpegTran = '/usr/bin/jpegtran';
  */
 $wgExiv2Command = '/usr/bin/exiv2';
 
+
+/**
+ * Path to exiftool binary. Used for lossless ICC profile swapping.
+ *
+ * @since 1.26
+ */
+$wgExiftool = '/usr/bin/exiftool';
+
 /**
  * Scalable Vector Graphics (SVG) may be uploaded as images.
  * Since SVG support is not yet standard in browsers, it is
@@ -1322,6 +1330,14 @@ $wgUploadThumbnailRenderHttpCustomHost = false;
  */
 $wgUploadThumbnailRenderHttpCustomDomain = false;
 
+/**
+ * When this variable is true and JPGs use the sRGB ICC profile, swaps it for the more lightweight
+ * (and free) TinyRGB profile when generating thumbnails.
+ *
+ * @since 1.26
+ */
+$wgUseTinyRGBForJPGThumbnails = false;
+
 /**
  * Default parameters for the "<gallery>" tag
  */
index 09d3807..5af7fbe 100644 (file)
@@ -70,7 +70,8 @@ class BitmapHandler extends TransformationalImageHandler {
        protected function transformImageMagick( $image, $params ) {
                # use ImageMagick
                global $wgSharpenReductionThreshold, $wgSharpenParameter, $wgMaxAnimatedGifArea,
-                       $wgImageMagickTempDir, $wgImageMagickConvertCommand;
+                       $wgImageMagickTempDir, $wgImageMagickConvertCommand, $wgResourceBasePath,
+                       $wgUseTinyRGBForJPGThumbnails;
 
                $quality = array();
                $sharpen = array();
index bf6f7fc..5ba5c68 100644 (file)
@@ -30,6 +30,7 @@
 class ExifBitmapHandler extends BitmapHandler {
        const BROKEN_FILE = '-1'; // error extracting metadata
        const OLD_BROKEN_FILE = '0'; // outdated error extracting metadata.
+       const SRGB_ICC_PROFILE_NAME = 'IEC 61966-2.1 Default RGB colour space - sRGB';
 
        function convertMetadataVersion( $metadata, $version = 1 ) {
                // basically flattens arrays.
@@ -243,4 +244,73 @@ class ExifBitmapHandler extends BitmapHandler {
 
                return 0;
        }
+
+       protected function transformImageMagick( $image, $params ) {
+               global $wgUseTinyRGBForJPGThumbnails;
+
+               $ret = parent::transformImageMagick( $image, $params );
+
+               if ( $ret ) {
+                       return $ret;
+               }
+
+               if ( $params['mimeType'] === 'image/jpeg' && $wgUseTinyRGBForJPGThumbnails ) {
+                       // T100976 If the profile embedded in the JPG is sRGB, swap it for the smaller
+                       // (and free) TinyRGB
+
+                       $this->swapICCProfile(
+                               $params['dstPath'],
+                               self::SRGB_ICC_PROFILE_NAME,
+                               realpath( __DIR__ ) . '/tinyrgb.icc'
+                       );
+               }
+
+               return false;
+       }
+
+       /**
+        * Swaps an embedded ICC profile for another, if found. Depends on exiftool, no-op if not installed.
+        * @param string $filepath File to be manipulated (will be overwritten)
+        * @param string $oldProfileString Exact name of color profile to look for (the one that will be replaced)
+        * @param string $profileFilepath ICC profile file to apply to the file
+        * @since 1.26
+        * @return bool
+        */
+       public function swapICCProfile( $filepath, $oldProfileString, $profileFilepath ) {
+               global $wgExiftool;
+
+               if ( !$wgExiftool || !is_executable( $wgExiftool ) ) {
+                       return false;
+               }
+
+               $cmd = wfEscapeShellArg( $wgExiftool,
+                       '-DeviceModelDesc',
+                       '-S',
+                       '-T',
+                       $filepath
+               );
+
+               $output = wfShellExecWithStderr( $cmd, $retval );
+
+               if ( $retval !== 0 || strcasecmp( trim( $output ), $oldProfileString ) !== 0 ) {
+                       // We can't establish that this file has the expected ICC profile, don't process it
+                       return false;
+               }
+
+               $cmd = wfEscapeShellArg( $wgExiftool,
+                       '-overwrite_original',
+                       '-icc_profile<=' . $profileFilepath,
+                       $filepath
+               );
+
+               $output = wfShellExecWithStderr( $cmd, $retval );
+
+               if ( $retval !== 0 ) {
+                       $this->logErrorForExternalProcess( $retval, $output, $cmd );
+
+                       return false;
+               }
+
+               return true;
+       }
 }
index 5463922..040ff96 100644 (file)
@@ -137,7 +137,7 @@ class JpegHandler extends ExifBitmapHandler {
 
                $rotation = ( $params['rotation'] + $this->getRotation( $file ) ) % 360;
 
-               if ( $wgJpegTran && is_file( $wgJpegTran ) ) {
+               if ( $wgJpegTran && is_executable( $wgJpegTran ) ) {
                        $cmd = wfEscapeShellArg( $wgJpegTran ) .
                                " -rotate " . wfEscapeShellArg( $rotation ) .
                                " -outfile " . wfEscapeShellArg( $params['dstPath'] ) .
diff --git a/includes/media/tinyrgb.icc b/includes/media/tinyrgb.icc
new file mode 100644 (file)
index 0000000..eab973f
Binary files /dev/null and b/includes/media/tinyrgb.icc differ
index 7bcf987..542c387 100644 (file)
        "nmembers": "$1 {{PLURAL:$1|member|members}}",
        "nmemberschanged": "$1 → $2 {{PLURAL:$2|member|members}}",
        "nrevisions": "$1 {{PLURAL:$1|revision|revisions}}",
-       "nviews": "$1 {{PLURAL:$1|view|views}}",
        "nimagelinks": "Used on $1 {{PLURAL:$1|page|pages}}",
        "ntransclusions": "used on $1 {{PLURAL:$1|page|pages}}",
        "specialpage-empty": "There are no results for this report.",
index dff3dfb..486c144 100644 (file)
        "nmembers": "Appears in brackets after each category listed on the special page [[Special:WantedCategories]].\n\nParameters:\n* $1 - the number of members of the category\nSee also:\n* {{msg-mw|Nmemberschanged}}",
        "nmemberschanged": "Appears in brackets after each category listed on the special page [[Special:WantedCategories]] if the number of pages in the category has changed since the list was last refreshed.\n\nParameters:\n* $1 - the original number of members of the category\n* $2 - the current one\nSee also:\n* {{msg-mw|Nmembers}}",
        "nrevisions": "Used as link text in [[Special:FewestRevisions]].\n\nThe link points to the page history (action=history).\n\nParameters:\n* $1 - number of revisions",
-       "nviews": "This message is used on [[Special:PopularPages]] to say how many times each page has been viewed.\n\nPreceded by the page title, like: Page title ($1 views)\n\nParameters:\n* $1 - the number of views",
        "nimagelinks": "Used on [[Special:MostLinkedFiles]] to indicate how often a specific file is used.\n\nParameters:\n* $1 - number of pages\nSee also:\n* {{msg-mw|Ntransclusions}}",
        "ntransclusions": "Used on [[Special:MostTranscludedPages]] to indicate how often a template is in use.\n\nParameters:\n* $1 - number of pages\nSee also:\n* {{msg-mw|Nimagelinks}}",
        "specialpage-empty": "Used on a special page when there is no data. For example on [[Special:Unusedimages]] when all images are used.",
index 23b702c..8abb491 100644 (file)
@@ -451,7 +451,6 @@ $specialPageAliases = array(
        'PageLanguage'              => array( 'PageLanguage' ),
        'PasswordReset'             => array( 'PasswordReset' ),
        'PermanentLink'             => array( 'PermanentLink', 'PermaLink' ),
-       'Popularpages'              => array( 'PopularPages' ),
        'Preferences'               => array( 'Preferences' ),
        'Prefixindex'               => array( 'PrefixIndex' ),
        'Protectedpages'            => array( 'ProtectedPages' ),
index dd27c8c..f19c64f 100644 (file)
@@ -1434,6 +1434,7 @@ excludepage
 excludeuser
 executables
 exempt
+exiftool
 existingwiki
 exists
 exiv
@@ -3158,7 +3159,6 @@ pnmtopng
 pointsize
 poolcounter
 popts
-popularpages
 portlet
 portlets
 posplus
index 7e73e64..96c05a1 100644 (file)
@@ -23,6 +23,7 @@
                                "classes": [
                                        "mw.Title",
                                        "mw.Uri",
+                                       "mw.RegExp",
                                        "mw.messagePoster.*",
                                        "mw.notification",
                                        "mw.Notification_",
index c280770..d75c8a1 100644 (file)
@@ -148,7 +148,7 @@ return array(
                'scripts' => 'resources/src/jquery/jquery.accessKeyLabel.js',
                'dependencies' => array(
                        'jquery.client',
-                       'jquery.mwExtension',
+                       'mediawiki.RegExp',
                ),
                'messages' => array( 'brackets', 'word-separator' ),
                'targets' => array( 'mobile', 'desktop' ),
@@ -247,7 +247,7 @@ return array(
        ),
        'jquery.highlightText' => array(
                'scripts' => 'resources/src/jquery/jquery.highlightText.js',
-               'dependencies' => 'jquery.mwExtension',
+               'dependencies' => 'mediawiki.RegExp',
                'targets' => array( 'desktop', 'mobile' ),
        ),
        'jquery.hoverIntent' => array(
@@ -309,7 +309,7 @@ return array(
                'styles' => 'resources/src/jquery/jquery.tablesorter.css',
                'messages' => array( 'sort-descending', 'sort-ascending' ),
                'dependencies' => array(
-                       'jquery.mwExtension',
+                       'mediawiki.RegExp',
                        'mediawiki.language.months',
                ),
        ),
@@ -954,7 +954,7 @@ return array(
        'mediawiki.htmlform' => array(
                'scripts' => 'resources/src/mediawiki/mediawiki.htmlform.js',
                'dependencies' => array(
-                       'jquery.mwExtension',
+                       'mediawiki.RegExp',
                        'jquery.byteLimit',
                ),
                'messages' => array(
@@ -978,6 +978,7 @@ return array(
                'scripts' => 'resources/src/mediawiki/mediawiki.inspect.js',
                'dependencies' => array(
                        'jquery.byteLength',
+                       'mediawiki.RegExp',
                        'json',
                ),
                'targets' => array( 'desktop', 'mobile' ),
@@ -1020,6 +1021,10 @@ return array(
                'scripts' => 'resources/src/mediawiki/mediawiki.notify.js',
                'targets' => array( 'desktop', 'mobile' ),
        ),
+       'mediawiki.RegExp' => array(
+               'scripts' => 'resources/src/mediawiki/mediawiki.RegExp.js',
+               'targets' => array( 'desktop', 'mobile' ),
+       ),
        'mediawiki.pager.tablePager' => array(
                'styles' => 'resources/src/mediawiki/mediawiki.pager.tablePager.less',
                'position' => 'top',
@@ -1085,7 +1090,7 @@ return array(
                'scripts' => 'resources/src/mediawiki/mediawiki.util.js',
                'dependencies' => array(
                        'jquery.accessKeyLabel',
-                       'jquery.mwExtension',
+                       'mediawiki.RegExp',
                        'mediawiki.notify',
                ),
                'position' => 'top', // For $wgPreloadJavaScriptMwUtil
@@ -1377,7 +1382,7 @@ return array(
                        'mediawiki.page.startup',
                        'mediawiki.util',
                        'jquery.accessKeyLabel',
-                       'jquery.mwExtension',
+                       'mediawiki.RegExp',
                ),
                'messages' => array(
                        'watch',
index 867c25e..1ac34a5 100644 (file)
@@ -112,7 +112,7 @@ function getAccessKeyLabel( element ) {
  */
 function updateTooltipOnElement( element, titleElement ) {
        var array = ( mw.msg( 'word-separator' ) + mw.msg( 'brackets' ) ).split( '$1' ),
-               regexp = new RegExp( $.map( array, $.escapeRE ).join( '.*?' ) + '$' ),
+               regexp = new RegExp( $.map( array, mw.RegExp.escape ).join( '.*?' ) + '$' ),
                oldTitle = titleElement.title,
                rawTitle = oldTitle.replace( regexp, '' ),
                newTitle = rawTitle,
index 1338218..8deb669 100644 (file)
@@ -3,7 +3,7 @@
  * TODO: Add a function for restoring the previous text.
  * TODO: Accept mappings for converting shortcuts like WP: to Wikipedia:.
  */
-( function ( $ ) {
+( function ( $, mw ) {
 
        $.highlightText = {
 
@@ -29,7 +29,7 @@
                                // non latin characters can make regex think a new word has begun: do not use \b
                                // http://stackoverflow.com/questions/3787072/regex-wordwrap-with-utf8-characters-in-js
                                // look for an occurrence of our pattern and store the starting position
-                               match = node.data.match( new RegExp( '(^|\\s)' + $.escapeRE( pat ), 'i' ) );
+                               match = node.data.match( new RegExp( '(^|\\s)' + mw.RegExp.escape( pat ), 'i' ) );
                                if ( match ) {
                                        pos = match.index + match[1].length; // include length of any matched spaces
                                        // create the span wrapper for the matched text
@@ -70,4 +70,4 @@
                } );
        };
 
-}( jQuery ) );
+}( jQuery, mediaWiki ) );
index e6e33ad..5484212 100644 (file)
@@ -1,9 +1,11 @@
 /*
  * JavaScript backwards-compatibility alternatives and other convenience functions
+ *
+ * @deprecated since 1.26 Dated collection of miscellaneous utilities. Methods are
+ *  either trivially inline, obsolete, or have a better place elsewhere.
  */
-( function ( $ ) {
-
-       $.extend( {
+( function ( $, mw ) {
+       $.each( {
                trimLeft: function ( str ) {
                        return str === null ? '' : str.toString().replace( /^\s+/, '' );
                },
@@ -14,9 +16,6 @@
                ucFirst: function ( str ) {
                        return str.charAt( 0 ).toUpperCase() + str.slice( 1 );
                },
-               escapeRE: function ( str ) {
-                       return str.replace( /([\\{}()|.?*+\-\^$\[\]])/g, '\\$1' );
-               },
                isDomElement: function ( el ) {
                        return !!el && !!el.nodeType;
                },
@@ -28,7 +27,7 @@
                                return true;
                        }
                        // the for-loop could potentially contain prototypes
-                       // to avoid that we check it's length first
+                       // to avoid that we check its length first
                        if ( v.length === 0 ) {
                                return true;
                        }
                        }
                        return true;
                }
+       }, function ( key, value ) {
+               mw.log.deprecate( $, key, value );
        } );
 
-}( jQuery ) );
+       mw.log.deprecate( $, 'escapeRE', function ( str ) {
+               return str.replace( /([\\{}()|.?*+\-\^$\[\]])/g, '\\$1' );
+       }, 'Use mediawiki.RegExp instead.' );
+
+} )( jQuery, mediaWiki );
index 3278ad5..c8e3cb3 100644 (file)
                        // Construct regex for number identification
                        for ( i = 0; i < ascii.length; i++ ) {
                                ts.transformTable[localised[i]] = ascii[i];
-                               digits.push( $.escapeRE( localised[i] ) );
+                               digits.push( mw.RegExp.escape( localised[i] ) );
                        }
                }
                digitClass = '[' + digits.join( '', digits ) + ']';
                for ( i = 0; i < 12; i++ ) {
                        name = mw.language.months.names[i].toLowerCase();
                        ts.monthNames[name] = i + 1;
-                       regex.push( $.escapeRE( name ) );
+                       regex.push( mw.RegExp.escape( name ) );
                        name = mw.language.months.genitive[i].toLowerCase();
                        ts.monthNames[name] = i + 1;
-                       regex.push( $.escapeRE( name ) );
+                       regex.push( mw.RegExp.escape( name ) );
                        name = mw.language.months.abbrev[i].toLowerCase().replace( '.', '' );
                        ts.monthNames[name] = i + 1;
-                       regex.push( $.escapeRE( name ) );
+                       regex.push( mw.RegExp.escape( name ) );
                }
 
                // Build piped string
index d252f0e..50f280a 100644 (file)
@@ -86,7 +86,7 @@
                        if ( actionPaths.hasOwnProperty( key ) ) {
                                parts = actionPaths[key].split( '$1' );
                                for ( i = 0; i < parts.length; i++ ) {
-                                       parts[i] = $.escapeRE( parts[i] );
+                                       parts[i] = mw.RegExp.escape( parts[i] );
                                }
                                m = new RegExp( parts.join( '(.+)' ) ).exec( url );
                                if ( m && m[1] ) {
diff --git a/resources/src/mediawiki/mediawiki.RegExp.js b/resources/src/mediawiki/mediawiki.RegExp.js
new file mode 100644 (file)
index 0000000..1da4ab4
--- /dev/null
@@ -0,0 +1,22 @@
+( function ( mw ) {
+       /**
+        * @class mw.RegExp
+        */
+       mw.RegExp = {
+               /**
+                * Escape string for safe inclusion in regular expression
+                *
+                * The following characters are escaped:
+                *
+                *     \ { } ( ) | . ? * + - ^ $ [ ]
+                *
+                * @since 1.26
+                * @static
+                * @param {string} str String to escape
+                * @return {string} Escaped string
+                */
+               escape: function ( str ) {
+                       return str.replace( /([\\{}()|.?*+\-\^$\[\]])/g, '\\$1' );
+               }
+       };
+}( mediaWiki ) );
index 4a4a97e..7fdaa6e 100644 (file)
                        $ul = $( this ).prev( 'ul.mw-htmlform-cloner-ul' );
 
                        html = $ul.data( 'template' ).replace(
-                               new RegExp( $.escapeRE( $ul.data( 'uniqueId' ) ), 'g' ),
+                               new RegExp( mw.RegExp.escape( $ul.data( 'uniqueId' ) ), 'g' ),
                                'clone' + ( ++cloneCounter )
                        );
 
index 22d3cbb..31cd6c4 100644 (file)
                 */
                grep: function ( pattern ) {
                        if ( typeof pattern.test !== 'function' ) {
-                               // Based on Y.Escape.regex from YUI v3.15.0
-                               pattern = new RegExp( pattern.replace( /[\-$\^*()+\[\]{}|\\,.?\s]/g, '\\$&' ), 'g' );
+                               pattern = new RegExp( mw.RegExp.escape( pattern ), 'g' );
                        }
 
                        return $.grep( inspect.getLoadedModules(), function ( moduleName ) {
index 6723e5f..13bf455 100644 (file)
                                url = location.href;
                        }
                        // Get last match, stop at hash
-                       var     re = new RegExp( '^[^#]*[&?]' + $.escapeRE( param ) + '=([^&#]*)' ),
+                       var     re = new RegExp( '^[^#]*[&?]' + mw.RegExp.escape( param ) + '=([^&#]*)' ),
                                m = re.exec( url );
                        if ( m ) {
                                // Beware that decodeURIComponent is not required to understand '+'
index 2ee805e..3665e3c 100644 (file)
@@ -3193,13 +3193,6 @@ About
 &lt;/td&gt;&lt;td&gt;
 <template lineStart="1"><title>int:Nstab-wp</title></template>
 &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;
-[http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Nviews&amp;action=edit nviews]&lt;br&gt;
-[[MediaWiki_talk:Nviews|Talk]]
-&lt;/td&gt;&lt;td&gt;
-$1 views
-&lt;/td&gt;&lt;td&gt;
-<template lineStart="1"><title>int:Nviews</title></template>
-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;
 [http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Ok&amp;action=edit ok]&lt;br&gt;
 [[MediaWiki_talk:Ok|Talk]]
 &lt;/td&gt;&lt;td&gt;
@@ -3319,13 +3312,6 @@ Personal tools
 &lt;/td&gt;&lt;td&gt;
 <template lineStart="1"><title>int:Personaltools</title></template>
 &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;
-[http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Popularpages&amp;action=edit popularpages]&lt;br&gt;
-[[MediaWiki_talk:Popularpages|Talk]]
-&lt;/td&gt;&lt;td&gt;
-Popular pages
-&lt;/td&gt;&lt;td&gt;
-<template lineStart="1"><title>int:Popularpages</title></template>
-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;
 [http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Portal&amp;action=edit portal]&lt;br&gt;
 [[MediaWiki_talk:Portal|Talk]]
 &lt;/td&gt;&lt;td&gt;
index 4a30f56..c619df7 100644 (file)
@@ -3193,13 +3193,6 @@ About
 </td><td>
 {{int:Nstab-wp}}
 </td></tr><tr><td>
-[http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Nviews&action=edit nviews]<br>
-[[MediaWiki_talk:Nviews|Talk]]
-</td><td>
-$1 views
-</td><td>
-{{int:Nviews}}
-</td></tr><tr><td>
 [http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Ok&action=edit ok]<br>
 [[MediaWiki_talk:Ok|Talk]]
 </td><td>
@@ -3319,13 +3312,6 @@ Personal tools
 </td><td>
 {{int:Personaltools}}
 </td></tr><tr><td>
-[http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Popularpages&action=edit popularpages]<br>
-[[MediaWiki_talk:Popularpages|Talk]]
-</td><td>
-Popular pages
-</td><td>
-{{int:Popularpages}}
-</td></tr><tr><td>
 [http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Portal&action=edit portal]<br>
 [[MediaWiki_talk:Portal|Talk]]
 </td><td>
diff --git a/tests/phpunit/data/media/srgb.jpg b/tests/phpunit/data/media/srgb.jpg
new file mode 100644 (file)
index 0000000..b965dc4
Binary files /dev/null and b/tests/phpunit/data/media/srgb.jpg differ
diff --git a/tests/phpunit/data/media/tinyrgb.icc b/tests/phpunit/data/media/tinyrgb.icc
new file mode 100644 (file)
index 0000000..eab973f
Binary files /dev/null and b/tests/phpunit/data/media/tinyrgb.icc differ
diff --git a/tests/phpunit/data/media/tinyrgb.jpg b/tests/phpunit/data/media/tinyrgb.jpg
new file mode 100644 (file)
index 0000000..12a8e09
Binary files /dev/null and b/tests/phpunit/data/media/tinyrgb.jpg differ
index 41330f4..adbc977 100644 (file)
@@ -3,7 +3,7 @@
 /**
  * @group Media
  */
-class ExifBitmapTest extends MediaWikiTestCase {
+class ExifBitmapTest extends MediaWikiMediaTestCase {
 
        /**
         * @var ExifBitmapHandler
@@ -143,4 +143,41 @@ class ExifBitmapTest extends MediaWikiTestCase {
                $res = $this->handler->convertMetadataVersion( $metadata, 1 );
                $this->assertEquals( $expected, $res );
        }
+
+       /**
+        * @dataProvider provideSwappingICCProfile
+        * @covers BitmapHandler::swapICCProfile
+        */
+       public function testSwappingICCProfile( $sourceFilename, $controlFilename, $newProfileFilename, $oldProfileName ) {
+               global $wgExiftool;
+
+               if ( !$wgExiftool || !is_file( $wgExiftool ) ) {
+                       $this->markTestSkipped( "Exiftool not installed, cannot test ICC profile swapping" );
+               }
+
+               $this->setMwGlobals( 'wgUseTinyRGBForJPGThumbnails', true );
+
+               $sourceFilepath = $this->filePath . $sourceFilename;
+               $controlFilepath = $this->filePath . $controlFilename;
+               $profileFilepath = $this->filePath . $newProfileFilename;
+               $filepath = $this->getNewTempFile();
+
+               copy( $sourceFilepath, $filepath );
+
+               $file = $this->dataFile( $sourceFilename, 'image/jpeg' );
+               $this->handler->swapICCProfile( $filepath, $oldProfileName, $profileFilepath );
+
+               $this->assertEquals( sha1( file_get_contents( $filepath ) ), sha1( file_get_contents( $controlFilepath ) ) );
+       }
+
+       public function provideSwappingICCProfile() {
+               return array(
+                       // File with sRGB should end up with TinyRGB
+                       array( 'srgb.jpg', 'tinyrgb.jpg', 'tinyrgb.icc', 'IEC 61966-2.1 Default RGB colour space - sRGB' ),
+                       // File with TinyRGB should be left unchanged
+                       array( 'tinyrgb.jpg', 'tinyrgb.jpg', 'tinyrgb.icc', 'IEC 61966-2.1 Default RGB colour space - sRGB' ),
+                       // File with no profile should be left unchanged
+                       array( 'test.jpg', 'test.jpg', 'tinyrgb.icc', 'IEC 61966-2.1 Default RGB colour space - sRGB' )
+               );
+       }
 }
index 9093797..c6f3a23 100644 (file)
@@ -66,6 +66,7 @@ return array(
                        'tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js',
+                       'tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js',
@@ -108,6 +109,7 @@ return array(
                        'mediawiki.api.watch',
                        'mediawiki.jqueryMsg',
                        'mediawiki.messagePoster',
+                       'mediawiki.RegExp',
                        'mediawiki.Title',
                        'mediawiki.toc',
                        'mediawiki.Uri',
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js
new file mode 100644 (file)
index 0000000..2388497
--- /dev/null
@@ -0,0 +1,38 @@
+( function ( mw, $ ) {
+       QUnit.module( 'mediawiki.RegExp' );
+
+       QUnit.test( 'escape', 16, function ( assert ) {
+               var specials, normal;
+
+               specials = [
+                       '\\',
+                       '{',
+                       '}',
+                       '(',
+                       ')',
+                       '[',
+                       ']',
+                       '|',
+                       '.',
+                       '?',
+                       '*',
+                       '+',
+                       '-',
+                       '^',
+                       '$'
+               ];
+
+               normal = [
+                       'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
+                       'abcdefghijklmnopqrstuvwxyz',
+                       '0123456789'
+               ].join( '' );
+
+               $.each( specials, function ( i, str ) {
+                       assert.propEqual( str.match( new RegExp( mw.RegExp.escape( str ) ) ), [ str ], 'Match ' + str );
+               } );
+
+               assert.equal( mw.RegExp.escape( normal ), normal, 'Alphanumerals are left alone' );
+       } );
+
+}( mediaWiki, jQuery ) );