Merge "Common interface for ImportStreamSource and ImportStringSource."
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 10 Feb 2015 10:54:43 +0000 (10:54 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 10 Feb 2015 10:54:43 +0000 (10:54 +0000)
32 files changed:
.jscsrc
autoload.php
composer.json
docs/extension.schema.json
img_auth.php
includes/Message.php
includes/Status.php
includes/filerepo/FileRepo.php
includes/filerepo/FileRepoStatus.php
includes/jobqueue/JobQueueRedis.php
includes/libs/MessageSpecifier.php [new file with mode: 0644]
includes/libs/StatusValue.php [new file with mode: 0644]
includes/registration/ExtensionProcessor.php
maintenance/Maintenance.php
maintenance/convertExtensionToRegistration.php
package.json
profileinfo.php
resources/Resources.php
resources/src/jquery/jquery.mwExtension.js
resources/src/jquery/jquery.placeholder.js
resources/src/jquery/jquery.textSelection.js
resources/src/mediawiki.action/mediawiki.action.view.postEdit.js
resources/src/mediawiki.special/mediawiki.special.pageLanguage.js
resources/src/mediawiki/mediawiki.cookie.js
resources/src/mediawiki/mediawiki.js
resources/src/mediawiki/mediawiki.startUp.js [new file with mode: 0644]
tests/phpunit/includes/StatusTest.php
tests/phpunit/includes/registration/ExtensionProcessorTest.php
tests/qunit/suites/resources/jquery/jquery.accessKeyLabel.test.js
tests/qunit/suites/resources/jquery/jquery.placeholder.test.js
tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js
thumb.php

diff --git a/.jscsrc b/.jscsrc
index 2ebd40e..34b2435 100644 (file)
--- a/.jscsrc
+++ b/.jscsrc
@@ -3,6 +3,10 @@
 
        "disallowKeywordsOnNewLine": null,
        "disallowQuotedKeysInObjects": null,
+       "disallowImplicitTypeConversion": null,
+       "requireLineBreakAfterVariableAssignment": null,
+       "requireSpaceAfterLineComment": null,
+       "requireSpacesInsideParentheses": null,
        "requireSpacesInsideArrayBrackets": null,
        "validateIndentation": null
 }
index c11a5bc..d78b0ff 100644 (file)
@@ -746,6 +746,7 @@ $wgAutoloadLocalClasses = array(
        'MessageBlobStore' => __DIR__ . '/includes/MessageBlobStore.php',
        'MessageCache' => __DIR__ . '/includes/cache/MessageCache.php',
        'MessageContent' => __DIR__ . '/includes/content/MessageContent.php',
+       'MessageSpecifier' => __DIR__ . '/includes/libs/MessageSpecifier.php',
        'MigrateUserGroup' => __DIR__ . '/maintenance/migrateUserGroup.php',
        'MimeMagic' => __DIR__ . '/includes/MimeMagic.php',
        'MinifyScript' => __DIR__ . '/maintenance/minify.php',
@@ -1152,6 +1153,7 @@ $wgAutoloadLocalClasses = array(
        'StatCounter' => __DIR__ . '/includes/StatCounter.php',
        'StatsOutput' => __DIR__ . '/maintenance/language/StatOutputs.php',
        'Status' => __DIR__ . '/includes/Status.php',
+       'StatusValue' => __DIR__ . '/includes/libs/StatusValue.php',
        'StorageTypeStats' => __DIR__ . '/maintenance/storage/storageTypeStats.php',
        'StoreFileOp' => __DIR__ . '/includes/filebackend/FileOp.php',
        'StreamFile' => __DIR__ . '/includes/StreamFile.php',
index bae3b55..94bec94 100644 (file)
@@ -27,7 +27,7 @@
        },
        "require-dev": {
                "justinrainbow/json-schema": "~1.3",
-               "phpunit/phpunit": "*"
+               "phpunit/phpunit": "~4.5"
        },
        "suggest": {
                "ext-fileinfo": "*",
index 4583559..33029bd 100644 (file)
                                "Unlicense"
                        ]
                },
+               "ResourceFileModulePaths": {
+                       "type": "object",
+                       "description": "Default paths to use for all ResourceLoader file modules",
+                       "additionalProperties": false,
+                       "properties": {
+                               "localBasePath": {
+                                       "type": "string",
+                                       "description": "Base path to prepend to all local paths, relative to current directory"
+                               },
+                               "remoteExtPath": {
+                                       "type": "string",
+                                       "description": "Base path to prepend to all remote paths, relative to $wgExtensionAssetsPath"
+                               },
+                               "remoteSkinPath": {
+                                       "type": "string",
+                                       "description": "Base path to prepend to all remote paths, relative to $wgStylePath"
+                               }
+                       }
+               },
                "ResourceLoaderModules": {
                        "type": "object",
                        "description": "ResourceLoader modules to register",
index 51470b6..f44cac0 100644 (file)
@@ -201,7 +201,12 @@ function wfForbidden( $msg1, $msg2 ) {
        header( 'Cache-Control: no-cache' );
        header( 'Content-Type: text/html; charset=utf-8' );
        echo <<<ENDS
+<!DOCTYPE html>
 <html>
+<head>
+<meta charset="UTF-8" />
+<title>$msgHdr</title>
+</head>
 <body>
 <h1>$msgHdr</h1>
 <p>$detailMsg</p>
index 93a37cb..49437f4 100644 (file)
  *
  * @since 1.17
  */
-class Message {
+class Message implements MessageSpecifier {
 
        /**
         * In which language to get this message. True, which is the default,
@@ -276,7 +276,7 @@ class Message {
         * Returns the message key.
         *
         * If a list of multiple possible keys was supplied to the constructor, this method may
-        * return any of these keys. After the message ahs been fetched, this method will return
+        * return any of these keys. After the message has been fetched, this method will return
         * the key that was actually used to fetch the message.
         *
         * @since 1.21
index fb267bd..61a0047 100644 (file)
  * so that a lack of error-handling will be explicit.
  */
 class Status {
-       /** @var bool */
-       public $ok = true;
+       /** @var StatusValue */
+       protected $sv;
 
        /** @var mixed */
        public $value;
-
-       /** Counters for batch operations */
-       /** @var int */
+       /** @var array Map of (key => bool) to indicate success of each part of batch operations */
+       public $success = array();
+       /** @var int Counter for batch operations */
        public $successCount = 0;
-
-       /** @var int */
+       /** @var int Counter for batch operations */
        public $failCount = 0;
 
-       /** Array to indicate which items of the batch operations were successful */
-       /** @var array */
-       public $success = array();
-
-       /** @var array */
-       public $errors = array();
-
        /** @var callable */
        public $cleanCallback = false;
 
+       /**
+        * @param StatusValue $sv [optional]
+        */
+       public function __construct( StatusValue $sv = null ) {
+               $this->sv = ( $sv === null ) ? new StatusValue() : $sv;
+               // B/C field aliases
+               $this->value =& $this->sv->value;
+               $this->successCount =& $this->sv->successCount;
+               $this->failCount =& $this->sv->failCount;
+               $this->success =& $this->sv->success;
+       }
+
+       /**
+        * Succinct helper method to wrap a StatusValue
+        *
+        * This is is useful when formatting StatusValue objects:
+        * <code>
+        *     $this->getOutput()->addHtml( Status::wrap( $sv )->getHTML() );
+        * </code>
+        *
+        * @param StatusValue|Status $sv
+        * @return Status
+        */
+       public static function wrap( $sv ) {
+               return $sv instanceof Status ? $sv : new self( $sv );
+       }
+
        /**
         * Factory function for fatal errors
         *
         * @param string|Message $message Message name or object
         * @return Status
         */
-       static function newFatal( $message /*, parameters...*/ ) {
-               $params = func_get_args();
-               $result = new self;
-               call_user_func_array( array( &$result, 'error' ), $params );
-               $result->ok = false;
-               return $result;
+       public static function newFatal( $message /*, parameters...*/ ) {
+               return new self( call_user_func_array(
+                       array( 'StatusValue', 'newFatal' ), func_get_args()
+               ) );
        }
 
        /**
@@ -81,10 +98,11 @@ class Status {
         * @param mixed $value
         * @return Status
         */
-       static function newGood( $value = null ) {
-               $result = new self;
-               $result->value = $value;
-               return $result;
+       public static function newGood( $value = null ) {
+               $sv = new StatusValue();
+               $sv->value = $value;
+
+               return new self( $sv );
        }
 
        /**
@@ -94,8 +112,7 @@ class Status {
         * @param mixed $value
         */
        public function setResult( $ok, $value = null ) {
-               $this->ok = $ok;
-               $this->value = $value;
+               $this->sv->setResult( $ok, $value );
        }
 
        /**
@@ -105,7 +122,7 @@ class Status {
         * @return bool
         */
        public function isGood() {
-               return $this->ok && !$this->errors;
+               return $this->sv->isGood();
        }
 
        /**
@@ -114,7 +131,7 @@ class Status {
         * @return bool
         */
        public function isOK() {
-               return $this->ok;
+               return $this->sv->isOK();
        }
 
        /**
@@ -123,11 +140,7 @@ class Status {
         * @param string|Message $message Message name or object
         */
        public function warning( $message /*, parameters... */ ) {
-               $params = array_slice( func_get_args(), 1 );
-               $this->errors[] = array(
-                       'type' => 'warning',
-                       'message' => $message,
-                       'params' => $params );
+               call_user_func_array( array( $this->sv, 'warning' ), func_get_args() );
        }
 
        /**
@@ -137,11 +150,7 @@ class Status {
         * @param string|Message $message Message name or object
         */
        public function error( $message /*, parameters... */ ) {
-               $params = array_slice( func_get_args(), 1 );
-               $this->errors[] = array(
-                       'type' => 'error',
-                       'message' => $message,
-                       'params' => $params );
+               call_user_func_array( array( $this->sv, 'error' ), func_get_args() );
        }
 
        /**
@@ -151,35 +160,14 @@ class Status {
         * @param string|Message $message Message name or object
         */
        public function fatal( $message /*, parameters... */ ) {
-               $params = array_slice( func_get_args(), 1 );
-               $this->errors[] = array(
-                       'type' => 'error',
-                       'message' => $message,
-                       'params' => $params );
-               $this->ok = false;
-       }
-
-       /**
-        * Don't save the callback when serializing, because Closures can't be
-        * serialized and we're going to clear it in __wakeup anyway.
-        */
-       public function __sleep() {
-               $keys = array_keys( get_object_vars( $this ) );
-               return array_diff( $keys, array( 'cleanCallback' ) );
-       }
-
-       /**
-        * Sanitize the callback parameter on wakeup, to avoid arbitrary execution.
-        */
-       public function __wakeup() {
-               $this->cleanCallback = false;
+               call_user_func_array( array( $this->sv, 'fatal' ), func_get_args() );
        }
 
        /**
         * @param array $params
         * @return array
         */
-       protected function cleanParams( $params ) {
+       protected function cleanParams( array $params ) {
                if ( !$this->cleanCallback ) {
                        return $params;
                }
@@ -199,24 +187,26 @@ class Status {
         * @return string
         */
        public function getWikiText( $shortContext = false, $longContext = false ) {
-               if ( count( $this->errors ) == 0 ) {
-                       if ( $this->ok ) {
-                               $this->fatal( 'internalerror_info',
+               $rawErrors = $this->sv->getErrors();
+               if ( count( $rawErrors ) == 0 ) {
+                       if ( $this->sv->isOK() ) {
+                               $this->sv->fatal( 'internalerror_info',
                                        __METHOD__ . " called for a good result, this is incorrect\n" );
                        } else {
-                               $this->fatal( 'internalerror_info',
+                               $this->sv->fatal( 'internalerror_info',
                                        __METHOD__ . ": Invalid result object: no error text but not OK\n" );
                        }
+                       $rawErrors = $this->sv->getErrors(); // just added a fatal
                }
-               if ( count( $this->errors ) == 1 ) {
-                       $s = $this->getErrorMessage( $this->errors[0] )->plain();
+               if ( count( $rawErrors ) == 1 ) {
+                       $s = $this->getErrorMessage( $rawErrors[0] )->plain();
                        if ( $shortContext ) {
                                $s = wfMessage( $shortContext, $s )->plain();
                        } elseif ( $longContext ) {
                                $s = wfMessage( $longContext, "* $s\n" )->plain();
                        }
                } else {
-                       $errors = $this->getErrorMessageArray( $this->errors );
+                       $errors = $this->getErrorMessageArray( $rawErrors );
                        foreach ( $errors as &$error ) {
                                $error = $error->plain();
                        }
@@ -241,17 +231,19 @@ class Status {
         * @return Message
         */
        public function getMessage( $shortContext = false, $longContext = false ) {
-               if ( count( $this->errors ) == 0 ) {
-                       if ( $this->ok ) {
-                               $this->fatal( 'internalerror_info',
+               $rawErrors = $this->sv->getErrors();
+               if ( count( $rawErrors ) == 0 ) {
+                       if ( $this->sv->isOK() ) {
+                               $this->sv->fatal( 'internalerror_info',
                                        __METHOD__ . " called for a good result, this is incorrect\n" );
                        } else {
-                               $this->fatal( 'internalerror_info',
+                               $this->sv->fatal( 'internalerror_info',
                                        __METHOD__ . ": Invalid result object: no error text but not OK\n" );
                        }
+                       $rawErrors = $this->sv->getErrors(); // just added a fatal
                }
-               if ( count( $this->errors ) == 1 ) {
-                       $s = $this->getErrorMessage( $this->errors[0] );
+               if ( count( $rawErrors ) == 1 ) {
+                       $s = $this->getErrorMessage( $rawErrors[0] );
                        if ( $shortContext ) {
                                $s = wfMessage( $shortContext, $s );
                        } elseif ( $longContext ) {
@@ -260,7 +252,7 @@ class Status {
                                $s = wfMessage( $longContext, $wrapper );
                        }
                } else {
-                       $msgs = $this->getErrorMessageArray( $this->errors );
+                       $msgs = $this->getErrorMessageArray( $rawErrors );
                        $msgCount = count( $msgs );
 
                        if ( $shortContext ) {
@@ -339,13 +331,7 @@ class Status {
         * @param bool $overwriteValue Whether to override the "value" member
         */
        public function merge( $other, $overwriteValue = false ) {
-               $this->errors = array_merge( $this->errors, $other->errors );
-               $this->ok = $this->ok && $other->ok;
-               if ( $overwriteValue ) {
-                       $this->value = $other->value;
-               }
-               $this->successCount += $other->successCount;
-               $this->failCount += $other->failCount;
+               $this->sv->merge( $other->sv, $overwriteValue );
        }
 
        /**
@@ -353,9 +339,10 @@ class Status {
         *
         * @return array A list in which each entry is an array with a message key as its first element.
         *         The remaining array elements are the message parameters.
+        * @deprecated 1.25
         */
        public function getErrorsArray() {
-               return $this->getStatusArray( "error" );
+               return $this->getStatusArray( 'error' );
        }
 
        /**
@@ -363,21 +350,26 @@ class Status {
         *
         * @return array A list in which each entry is an array with a message key as its first element.
         *         The remaining array elements are the message parameters.
+        * @deprecated 1.25
         */
        public function getWarningsArray() {
-               return $this->getStatusArray( "warning" );
+               return $this->getStatusArray( 'warning' );
        }
 
        /**
         * Returns a list of status messages of the given type (or all if false)
+        *
+        * @note: this handles RawMessage poorly
+        *
         * @param string $type
         * @return array
         */
        protected function getStatusArray( $type = false ) {
                $result = array();
-               foreach ( $this->errors as $error ) {
+
+               foreach ( $this->sv->getErrors() as $error ) {
                        if ( $type === false || $error['type'] === $type ) {
-                               if ( $error['message'] instanceof Message ) {
+                               if ( $error['message'] instanceof MessageSpecifier ) {
                                        $result[] = array_merge(
                                                array( $error['message']->getKey() ),
                                                $error['message']->getParams()
@@ -402,13 +394,7 @@ class Status {
         * @return array
         */
        public function getErrorsByType( $type ) {
-               $result = array();
-               foreach ( $this->errors as $error ) {
-                       if ( $error['type'] === $type ) {
-                               $result[] = $error;
-                       }
-               }
-               return $result;
+               return $this->sv->getErrorsByType( $type );
        }
 
        /**
@@ -419,19 +405,7 @@ class Status {
         * @return bool
         */
        public function hasMessage( $message ) {
-               if ( $message instanceof Message ) {
-                       $message = $message->getKey();
-               }
-               foreach ( $this->errors as $error ) {
-                       if ( $error['message'] instanceof Message
-                               && $error['message']->getKey() === $message
-                       ) {
-                               return true;
-                       } elseif ( $error['message'] === $message ) {
-                               return true;
-                       }
-               }
-               return false;
+               return $this->sv->hasMessage( $message );
        }
 
        /**
@@ -446,61 +420,67 @@ class Status {
         * @return bool Return true if the replacement was done, false otherwise.
         */
        public function replaceMessage( $source, $dest ) {
-               $replaced = false;
-               foreach ( $this->errors as $index => $error ) {
-                       if ( $error['message'] === $source ) {
-                               $this->errors[$index]['message'] = $dest;
-                               $replaced = true;
-                       }
-               }
-               return $replaced;
+               return $this->sv->replaceMessage( $source, $dest );
        }
 
        /**
         * @return mixed
         */
        public function getValue() {
-               return $this->value;
+               return $this->sv->getValue();
        }
 
        /**
-        * @return string
+        * Backwards compatibility logic
+        *
+        * @param string $name
         */
-       public function __toString() {
-               $status = $this->isOK() ? "OK" : "Error";
-               if ( count( $this->errors ) ) {
-                       $errorcount = "collected " . ( count( $this->errors ) ) . " error(s) on the way";
-               } else {
-                       $errorcount = "no errors detected";
+       function __get( $name ) {
+               if ( $name === 'ok' ) {
+                       return $this->sv->getOK();
+               } elseif ( $name === 'errors' ) {
+                       return $this->sv->getErrors();
                }
-               if ( isset( $this->value ) ) {
-                       $valstr = gettype( $this->value ) . " value set";
-                       if ( is_object( $this->value ) ) {
-                               $valstr .= "\"" . get_class( $this->value ) . "\" instance";
-                       }
+               throw new Exception( "Cannot get '$name' property." );
+       }
+
+       /**
+        * Backwards compatibility logic
+        *
+        * @param string $name
+        * @param mixed $value
+        */
+       function __set( $name, $value ) {
+               if ( $name === 'ok' ) {
+                       $this->sv->setOK( $value );
+               } elseif ( !property_exists( $this, $name ) ) {
+                       // Caller is using undeclared ad-hoc properties
+                       $this->$name = $value;
                } else {
-                       $valstr = "no value set";
+                       throw new Exception( "Cannot set '$name' property." );
                }
-               $out = sprintf( "<%s, %s, %s>",
-                       $status,
-                       $errorcount,
-                       $valstr
-               );
-               if ( count( $this->errors ) > 0 ) {
-                       $hdr = sprintf( "+-%'-4s-+-%'-25s-+-%'-40s-+\n", "", "", "" );
-                       $i = 1;
-                       $out .= "\n";
-                       $out .= $hdr;
-                       foreach ( $this->getStatusArray() as $stat ) {
-                               $out .= sprintf( "| %4d | %-25.25s | %-40.40s |\n",
-                                       $i,
-                                       $stat[0],
-                                       implode( " ", array_slice( $stat, 1 ) )
-                               );
-                               $i += 1;
-                       }
-                       $out .= $hdr;
-               };
-               return $out;
+       }
+
+       /**
+        * @return string
+        */
+       public function __toString() {
+               return $this->sv->__toString();
+       }
+
+       /**
+        * Don't save the callback when serializing, because Closures can't be
+        * serialized and we're going to clear it in __wakeup anyway.
+        */
+       function __sleep() {
+               $keys = array_keys( get_object_vars( $this ) );
+               return array_diff( $keys, array( 'cleanCallback' ) );
+       }
+
+       /**
+        * Sanitize the callback parameter on wakeup, to avoid arbitrary execution.
+        */
+       function __wakeup() {
+               $this->cleanCallback = false;
        }
 }
index 58245a5..0393416 100644 (file)
@@ -1681,23 +1681,26 @@ class FileRepo {
         * Create a new fatal error
         *
         * @param string $message
-        * @return FileRepoStatus
+        * @return Status
         */
        public function newFatal( $message /*, parameters...*/ ) {
-               $params = func_get_args();
-               array_unshift( $params, $this );
+               $status = call_user_func_array( array( 'Status', 'newFatal' ), func_get_args() );
+               $status->cleanCallback = $this->getErrorCleanupFunction();
 
-               return call_user_func_array( array( 'FileRepoStatus', 'newFatal' ), $params );
+               return $status;
        }
 
        /**
         * Create a new good result
         *
         * @param null|string $value
-        * @return FileRepoStatus
+        * @return Status
         */
        public function newGood( $value = null ) {
-               return FileRepoStatus::newGood( $this, $value );
+               $status = Status::newGood( $this, $value );
+               $status->cleanCallback = $this->getErrorCleanupFunction();
+
+               return $status;
        }
 
        /**
index 56848df..daf26ba 100644 (file)
 /**
  * Generic operation result class for FileRepo-related operations
  * @ingroup FileRepo
+ * @deprecated 1.25
  */
-class FileRepoStatus extends Status {
-       /**
-        * Factory function for fatal errors
-        *
-        * @param FileRepo $repo
-        * @return FileRepoStatus
-        */
-       static function newFatal( $repo /*, parameters...*/ ) {
-               $params = array_slice( func_get_args(), 1 );
-               $result = new self( $repo );
-               call_user_func_array( array( &$result, 'error' ), $params );
-               $result->ok = false;
-
-               return $result;
-       }
-
-       /**
-        * @param FileRepo|bool $repo Default: false
-        * @param mixed $value
-        * @return FileRepoStatus
-        */
-       static function newGood( $repo = false, $value = null ) {
-               $result = new self( $repo );
-               $result->value = $value;
-
-               return $result;
-       }
-
-       /**
-        * @param bool|FileRepo $repo
-        */
-       function __construct( $repo = false ) {
-               if ( $repo ) {
-                       $this->cleanCallback = $repo->getErrorCleanupFunction();
-               }
-       }
-}
+class FileRepoStatus extends Status {}
index abfdc8c..9368fbf 100644 (file)
@@ -24,7 +24,7 @@
 /**
  * Class to handle job queues stored in Redis
  *
- * This is faster, less resource intensive, queue that JobQueueDB.
+ * This is a faster and less resource-intensive job queue than JobQueueDB.
  * All data for a queue using this class is placed into one redis server.
  *
  * There are eight main redis keys used to track jobs:
@@ -49,7 +49,7 @@
  *
  * This class requires Redis 2.6 as it makes use Lua scripts for fast atomic operations.
  * Additionally, it should be noted that redis has different persistence modes, such
- * as rdb snapshots, journaling, and no persistent. Appropriate configuration should be
+ * as rdb snapshots, journaling, and no persistence. Appropriate configuration should be
  * made on the servers based on what queues are using it and what tolerance they have.
  *
  * @ingroup JobQueue
diff --git a/includes/libs/MessageSpecifier.php b/includes/libs/MessageSpecifier.php
new file mode 100644 (file)
index 0000000..b417f29
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+interface MessageSpecifier {
+       /**
+        * Returns the message key
+        *
+        * If a list of multiple possible keys was supplied to the constructor, this method may
+        * return any of these keys. After the message has been fetched, this method will return
+        * the key that was actually used to fetch the message.
+        *
+        * @return string
+        */
+       public function getKey();
+
+       /**
+        * Returns the message parameters
+        *
+        * @return array
+        */
+       public function getParams();
+}
diff --git a/includes/libs/StatusValue.php b/includes/libs/StatusValue.php
new file mode 100644 (file)
index 0000000..3c2dd40
--- /dev/null
@@ -0,0 +1,316 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Generic operation result class
+ * Has warning/error list, boolean status and arbitrary value
+ *
+ * "Good" means the operation was completed with no warnings or errors.
+ *
+ * "OK" means the operation was partially or wholly completed.
+ *
+ * An operation which is not OK should have errors so that the user can be
+ * informed as to what went wrong. Calling the fatal() function sets an error
+ * message and simultaneously switches off the OK flag.
+ *
+ * The recommended pattern for Status objects is to return a StatusValue
+ * unconditionally, i.e. both on success and on failure -- so that the
+ * developer of the calling code is reminded that the function can fail, and
+ * so that a lack of error-handling will be explicit.
+ *
+ * The use of Message objects should be avoided when serializability is needed.
+ *
+ * @since 1.25
+ */
+class StatusValue {
+       /** @var bool */
+       protected $ok = true;
+       /** @var array */
+       protected $errors = array();
+
+       /** @var mixed */
+       public $value;
+       /** @var array Map of (key => bool) to indicate success of each part of batch operations */
+       public $success = array();
+       /** @var int Counter for batch operations */
+       public $successCount = 0;
+       /** @var int Counter for batch operations */
+       public $failCount = 0;
+
+       /**
+        * Factory function for fatal errors
+        *
+        * @param string|MessageSpecifier $message Message key or object
+        * @return Status
+        */
+       public static function newFatal( $message /*, parameters...*/ ) {
+               $params = func_get_args();
+               $result = new static();
+               call_user_func_array( array( &$result, 'fatal' ), $params );
+               return $result;
+       }
+
+       /**
+        * Factory function for good results
+        *
+        * @param mixed $value
+        * @return Status
+        */
+       public static function newGood( $value = null ) {
+               $result = new static();
+               $result->value = $value;
+               return $result;
+       }
+
+       /**
+        * Returns whether the operation completed and didn't have any error or
+        * warnings
+        *
+        * @return bool
+        */
+       public function isGood() {
+               return $this->ok && !$this->errors;
+       }
+
+       /**
+        * Returns whether the operation completed
+        *
+        * @return bool
+        */
+       public function isOK() {
+               return $this->ok;
+       }
+
+       /**
+        * @return mixed
+        */
+       public function getValue() {
+               return $this->value;
+       }
+
+       /**
+        * Get the list of errors
+        *
+        * Each error is a (message:string or MessageSpecifier,params:array) map
+        *
+        * @return array
+        */
+       public function getErrors() {
+               return $this->errors;
+       }
+
+       /**
+        * Change operation status
+        *
+        * @param bool $ok
+        */
+       public function setOK( $ok ) {
+               $this->ok = $ok;
+       }
+
+       /**
+        * Change operation resuklt
+        *
+        * @param bool $ok Whether the operation completed
+        * @param mixed $value
+        */
+       public function setResult( $ok, $value = null ) {
+               $this->ok = $ok;
+               $this->value = $value;
+       }
+
+       /**
+        * Add a new warning
+        *
+        * @param string|MessageSpecifier $message Message key or object
+        */
+       public function warning( $message /*, parameters... */ ) {
+               $this->errors[] = array(
+                       'type' => 'warning',
+                       'message' => $message,
+                       'params' => array_slice( func_get_args(), 1 )
+               );
+       }
+
+       /**
+        * Add an error, do not set fatal flag
+        * This can be used for non-fatal errors
+        *
+        * @param string|MessageSpecifier $message Message key or object
+        */
+       public function error( $message /*, parameters... */ ) {
+               $this->errors[] = array(
+                       'type' => 'error',
+                       'message' => $message,
+                       'params' => array_slice( func_get_args(), 1 )
+               );
+       }
+
+       /**
+        * Add an error and set OK to false, indicating that the operation
+        * as a whole was fatal
+        *
+        * @param string|MessageSpecifier $message Message key or object
+        */
+       public function fatal( $message /*, parameters... */ ) {
+               $this->errors[] = array(
+                       'type' => 'error',
+                       'message' => $message,
+                       'params' => array_slice( func_get_args(), 1 )
+               );
+               $this->ok = false;
+       }
+
+       /**
+        * Merge another status object into this one
+        *
+        * @param Status $other Other Status object
+        * @param bool $overwriteValue Whether to override the "value" member
+        */
+       public function merge( $other, $overwriteValue = false ) {
+               $this->errors = array_merge( $this->errors, $other->errors );
+               $this->ok = $this->ok && $other->ok;
+               if ( $overwriteValue ) {
+                       $this->value = $other->value;
+               }
+               $this->successCount += $other->successCount;
+               $this->failCount += $other->failCount;
+       }
+
+       /**
+        * Returns a list of status messages of the given type
+        *
+        * Each entry is a map of (message:string or MessageSpecifier,params:array))
+        *
+        * @param string $type
+        * @return array
+        */
+       public function getErrorsByType( $type ) {
+               $result = array();
+               foreach ( $this->errors as $error ) {
+                       if ( $error['type'] === $type ) {
+                               $result[] = $error;
+                       }
+               }
+
+               return $result;
+       }
+
+       /**
+        * Returns true if the specified message is present as a warning or error
+        *
+        * @param string|MessageSpecifier $message Message key or object to search for
+        *
+        * @return bool
+        */
+       public function hasMessage( $message ) {
+               if ( $message instanceof MessageSpecifier ) {
+                       $message = $message->getKey();
+               }
+               foreach ( $this->errors as $error ) {
+                       if ( $error['message'] instanceof MessageSpecifier
+                               && $error['message']->getKey() === $message
+                       ) {
+                               return true;
+                       } elseif ( $error['message'] === $message ) {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * If the specified source message exists, replace it with the specified
+        * destination message, but keep the same parameters as in the original error.
+        *
+        * Note, due to the lack of tools for comparing IStatusMessage objects, this
+        * function will not work when using such an object as the search parameter.
+        *
+        * @param IStatusMessage|string $source Message key or object to search for
+        * @param IStatusMessage|string $dest Replacement message key or object
+        * @return bool Return true if the replacement was done, false otherwise.
+        */
+       public function replaceMessage( $source, $dest ) {
+               $replaced = false;
+
+               foreach ( $this->errors as $index => $error ) {
+                       if ( $error['message'] === $source ) {
+                               $this->errors[$index]['message'] = $dest;
+                               $replaced = true;
+                       }
+               }
+
+               return $replaced;
+       }
+
+       /**
+        * @return string
+        */
+       public function __toString() {
+               $status = $this->isOK() ? "OK" : "Error";
+               if ( count( $this->errors ) ) {
+                       $errorcount = "collected " . ( count( $this->errors ) ) . " error(s) on the way";
+               } else {
+                       $errorcount = "no errors detected";
+               }
+               if ( isset( $this->value ) ) {
+                       $valstr = gettype( $this->value ) . " value set";
+                       if ( is_object( $this->value ) ) {
+                               $valstr .= "\"" . get_class( $this->value ) . "\" instance";
+                       }
+               } else {
+                       $valstr = "no value set";
+               }
+               $out = sprintf( "<%s, %s, %s>",
+                       $status,
+                       $errorcount,
+                       $valstr
+               );
+               if ( count( $this->errors ) > 0 ) {
+                       $hdr = sprintf( "+-%'-4s-+-%'-25s-+-%'-40s-+\n", "", "", "" );
+                       $i = 1;
+                       $out .= "\n";
+                       $out .= $hdr;
+                       foreach ( $this->errors as $error ) {
+                               if ( $error['message'] instanceof MessageSpecifier ) {
+                                       $key = $error['message']->getKey();
+                                       $params = $error['message']->getParams();
+                               } elseif ( $error['params'] ) {
+                                       $key = $error['message'];
+                                       $params = $error['params'];
+                               } else {
+                                       $key = $error['message'];
+                                       $params = array();
+                               }
+
+                               $out .= sprintf( "| %4d | %-25.25s | %-40.40s |\n",
+                                       $i,
+                                       $key,
+                                       implode( " ", $params )
+                               );
+                               $i += 1;
+                       }
+                       $out .= $hdr;
+               }
+
+               return $out;
+       }
+}
index 1462840..bf42aba 100644 (file)
@@ -185,11 +185,21 @@ class ExtensionProcessor implements Processor {
        }
 
        protected function extractResourceLoaderModules( $dir, array $info ) {
+               $defaultPaths = isset( $info['ResourceFileModulePaths'] )
+                       ? $info['ResourceFileModulePaths']
+                       : false;
+               if ( isset( $defaultPaths['localBasePath'] ) ) {
+                       $defaultPaths['localBasePath'] = "$dir/{$defaultPaths['localBasePath']}";
+               }
+
                if ( isset( $info['ResourceModules'] ) ) {
                        foreach ( $info['ResourceModules'] as $name => $data ) {
                                if ( isset( $data['localBasePath'] ) ) {
                                        $data['localBasePath'] = "$dir/{$data['localBasePath']}";
                                }
+                               if ( $defaultPaths && !isset( $data['class'] ) ) {
+                                       $data += $defaultPaths;
+                               }
                                $this->globals['wgResourceModules'][$name] = $data;
                        }
                }
index 9b98b20..af14bb3 100644 (file)
@@ -1207,7 +1207,13 @@ abstract class Maintenance {
                }
 
                if ( $isatty && function_exists( 'readline' ) ) {
-                       return readline( $prompt );
+                       $resp = readline( $prompt );
+                       if ( $resp === null ) {
+                               // Workaround for https://github.com/facebook/hhvm/issues/4776
+                               return false;
+                       } else {
+                               return $resp;
+                       }
                } else {
                        if ( $isatty ) {
                                $st = self::readlineEmulation( $prompt );
index a0dee3c..76bc982 100644 (file)
@@ -155,12 +155,36 @@ class ConvertExtensionToRegistration extends Maintenance {
        }
 
        protected function handleResourceModules( $realName, $value ) {
+               $defaults = array();
+               $remote = $this->hasOption( 'skin' ) ? 'remoteSkinPath' : 'remoteExtPath';
                foreach ( $value as $name => $data ) {
                        if ( isset( $data['localBasePath'] ) ) {
                                $data['localBasePath'] = $this->stripPath( $data['localBasePath'], $this->dir );
+                               if ( !$defaults ) {
+                                       $defaults['localBasePath'] = $data['localBasePath'];
+                                       unset( $data['localBasePath'] );
+                                       if ( isset( $data[$remote] ) ) {
+                                               $defaults[$remote] = $data[$remote];
+                                               unset( $data[$remote] );
+                                       }
+                               } else {
+                                       if ( $data['localBasePath'] === $defaults['localBasePath'] ) {
+                                               unset( $data['localBasePath'] );
+                                       }
+                                       if ( isset( $data[$remote] ) && isset( $defaults[$remote] )
+                                               && $data[$remote] === $defaults[$remote]
+                                       ) {
+                                               unset( $data[$remote] );
+                                       }
+                               }
                        }
+
+
                        $this->json[$realName][$name] = $data;
                }
+               if ( $defaults ) {
+                       $this->json['ResourceFileModulePaths'] = $defaults;
+               }
        }
 }
 
index 6bbcf39..4ef12ba 100644 (file)
@@ -5,17 +5,17 @@
     "test": "grunt test"
   },
   "devDependencies": {
-    "grunt": "0.4.2",
+    "grunt": "0.4.5",
     "grunt-banana-checker": "0.2.0",
-    "grunt-contrib-jshint": "0.10.0",
+    "grunt-contrib-jshint": "0.11.0",
     "grunt-contrib-watch": "0.6.1",
-    "grunt-jscs": "0.8.1",
+    "grunt-jscs": "1.5.0",
     "grunt-jsonlint": "1.0.4",
-    "grunt-karma": "0.9.0",
+    "grunt-karma": "0.10.1",
     "karma": "0.12.31",
     "karma-chrome-launcher": "0.1.7",
-    "karma-firefox-launcher": "0.1.3",
+    "karma-firefox-launcher": "0.1.4",
     "karma-qunit": "0.1.4",
-    "qunitjs": "1.15.0"
+    "qunitjs": "1.17.1"
   }
 }
index 4e3fb5a..f172cfb 100644 (file)
@@ -36,7 +36,7 @@ header( 'Content-Type: text/html; charset=utf-8' );
 <!DOCTYPE html>
 <html>
 <head>
-       <meta charset="UTF-8">
+       <meta charset="UTF-8" />
        <title>Profiling data</title>
        <style>
                /* noc.wikimedia.org/base.css */
@@ -422,7 +422,7 @@ if ( isset( $_REQUEST['filter'] ) ) {
        ?>
        </tbody>
 </table>
-<hr>
+<hr />
 <p>Total time: <code><?php printf( '%5.02f', profile_point::$totaltime ); ?></code></p>
 
 <p>Total memory: <code><?php printf( '%5.02f', profile_point::$totalmemory / 1024 ); ?></code></p>
index 835e33f..2896171 100644 (file)
@@ -765,7 +765,10 @@ return array(
        /* MediaWiki */
 
        'mediawiki' => array(
-               'scripts' => 'resources/src/mediawiki/mediawiki.js',
+               'scripts' => array(
+                       'resources/src/mediawiki/mediawiki.js',
+                       'resources/src/mediawiki/mediawiki.startUp.js',
+               ),
                'debugScripts' => 'resources/src/mediawiki/mediawiki.log.js',
                'raw' => true,
                'targets' => array( 'desktop', 'mobile' ),
index dc7aaa4..e6e33ad 100644 (file)
                        return str.charAt( 0 ).toUpperCase() + str.slice( 1 );
                },
                escapeRE: function ( str ) {
-                       return str.replace ( /([\\{}()|.?*+\-\^$\[\]])/g, '\\$1' );
+                       return str.replace( /([\\{}()|.?*+\-\^$\[\]])/g, '\\$1' );
                },
                isDomElement: function ( el ) {
                        return !!el && !!el.nodeType;
                },
                isEmpty: function ( v ) {
                        var key;
-                       if ( v === '' || v === 0 || v === '0' || v === null
-                               || v === false || v === undefined )
-                       {
+                       if (
+                               v === '' || v === 0 || v === '0' || v === null || v === false || v === undefined
+                       {
                                return true;
                        }
                        // the for-loop could potentially contain prototypes
index d458019..d50422e 100644 (file)
@@ -13,7 +13,7 @@
  * @version 2.1.0
  * @license MIT
  */
-(function ($) {
+( function ($) {
 
        var isInputSupported = 'placeholder' in document.createElement('input'),
                isTextareaSupported = 'placeholder' in document.createElement('textarea'),
@@ -49,7 +49,7 @@
 
                        $this
                                .filter((isInputSupported ? 'textarea' : ':input') + '[placeholder]')
-                               .filter(function () {
+                               .filter( function () {
                                        return !$(this).data('placeholder-enabled');
                                })
                                .bind({
                        propHooks.value = hooks;
                }
 
-               $(function () {
+               $( function () {
                        // Look for forms
                        $(document).delegate('form', 'submit.placeholder', function () {
                                // Clear the placeholder values so they don't get submitted
                                var $inputs = $('.placeholder', this).each(clearPlaceholder);
-                               setTimeout(function () {
+                               setTimeout( function () {
                                        $inputs.each(setPlaceholder);
                                }, 10);
                        });
 
                // Clear placeholder values upon page reload
                $(window).bind('beforeunload.placeholder', function () {
-                       $('.placeholder').each(function () {
+                       $('.placeholder').each( function () {
                                this.value = '';
                        });
                });
index bd6518d..e8fc8e4 100644 (file)
                                                        endPos = this.selectionEnd;
                                                        scrollTop = this.scrollTop;
                                                        checkSelectedText();
-                                                       if ( options.selectionStart !== undefined
-                                                                       && endPos - startPos !== options.selectionEnd - options.selectionStart )
-                                                       {
+                                                       if (
+                                                               options.selectionStart !== undefined &&
+                                                               endPos - startPos !== options.selectionEnd - options.selectionStart
+                                                       ) {
                                                                // This means there is a difference in the selection range returned by browser and what we passed.
                                                                // This happens for Chrome in the case of composite characters. Ref bug #30130
                                                                // Set the startPos to the correct position.
index 95ef62c..c008dfd 100644 (file)
@@ -76,4 +76,4 @@
                mw.cookie.set( cookieKey, null );
        }
 
-} ( mediaWiki, jQuery ) );
+}( mediaWiki, jQuery ) );
index ba7f734..7c2269f 100644 (file)
@@ -6,4 +6,4 @@
                        $( '#mw-pl-options-2' ).prop( 'checked', true );
                } );
        } );
-} ( jQuery ) );
+}( jQuery ) );
index 85f4ffa..73e7f6e 100644 (file)
                }
        };
 
-} ( mediaWiki, jQuery ) );
+}( mediaWiki, jQuery ) );
index bed5cb5..98a2f6a 100644 (file)
 
        // Attach to window and globally alias
        window.mw = window.mediaWiki = mw;
-
-       // Auto-register from pre-loaded startup scripts
-       if ( $.isFunction( window.startUp ) ) {
-               window.startUp();
-               window.startUp = undefined;
-       }
-
 }( jQuery ) );
diff --git a/resources/src/mediawiki/mediawiki.startUp.js b/resources/src/mediawiki/mediawiki.startUp.js
new file mode 100644 (file)
index 0000000..8032aa2
--- /dev/null
@@ -0,0 +1,12 @@
+/**
+ * Auto-register from pre-loaded startup scripts
+ * @ignore (this line will make JSDuck happy)
+ */
+( function ( $ ) {
+       'use strict';
+
+       if ( $.isFunction( window.startUp ) ) {
+               window.startUp();
+               window.startUp = undefined;
+       }
+}( jQuery ) );
index 628c59b..44463cf 100644 (file)
@@ -109,7 +109,9 @@ class StatusTest extends MediaWikiLangTestCase {
        public function testIsGood( $ok, $errors, $expected ) {
                $status = new Status();
                $status->ok = $ok;
-               $status->errors = $errors;
+               foreach ( $errors as $error ) {
+                       $status->warning( $error );
+               }
                $this->assertEquals( $expected, $status->isGood() );
        }
 
index 9461a16..d2254df 100644 (file)
@@ -162,6 +162,105 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
                }
        }
 
+       /**
+        * @covers ExtensionProcessor::extractResourceLoaderModules
+        * @dataProvider provideExtractResourceLoaderModules
+        */
+       public function testExtractResourceLoaderModules( $input, $expected ) {
+               $processor = new ExtensionProcessor();
+               $processor->extractInfo( $this->dir, $input + self::$default );
+               $out = $processor->getExtractedInfo();
+               foreach ( $expected as $key => $value ) {
+                       $this->assertEquals( $value, $out['globals'][$key] );
+               }
+       }
+
+       public static function provideExtractResourceLoaderModules() {
+               $dir = __DIR__ . '/FooBar/';
+               return array(
+                       // Generic module with localBasePath/remoteExtPath specified
+                       array(
+                               // Input
+                               array(
+                                       'ResourceModules' => array(
+                                               'test.foo' => array(
+                                                       'styles' => 'foobar.js',
+                                                       'localBasePath' => '',
+                                                       'remoteExtPath' => 'FooBar',
+                                               ),
+                                       ),
+                               ),
+                               // Expected
+                               array(
+                                       'wgResourceModules' => array(
+                                               'test.foo' => array(
+                                                       'styles' => 'foobar.js',
+                                                       'localBasePath' => $dir,
+                                                       'remoteExtPath' => 'FooBar',
+                                               ),
+                                       ),
+                               ),
+                       ),
+                       // ResourceFileModulePaths specified:
+                       array(
+                               // Input
+                               array(
+                                       'ResourceFileModulePaths' => array(
+                                               'localBasePath' => '',
+                                               'remoteExtPath' => 'FooBar',
+                                       ),
+                                       'ResourceModules' => array(
+                                               // No paths
+                                               'test.foo' => array(
+                                                       'styles' => 'foo.js',
+                                               ),
+                                               // Different paths set
+                                               'test.bar' => array(
+                                                       'styles' => 'bar.js',
+                                                       'localBasePath' => 'subdir',
+                                                       'remoteExtPath' => 'FooBar/subdir',
+                                               ),
+                                               // Custom class with no paths set
+                                               'test.class' => array(
+                                                       'class' => 'FooBarModule',
+                                                       'extra' => 'argument',
+                                               ),
+                                               // Custom class with a localBasePath
+                                               'test.class.with.path' => array(
+                                                       'class' => 'FooBarPathModule',
+                                                       'extra' => 'argument',
+                                                       'localBasePath' => '',
+                                               )
+                                       ),
+                               ),
+                               // Expected
+                               array(
+                                       'wgResourceModules' => array(
+                                               'test.foo' => array(
+                                                       'styles' => 'foo.js',
+                                                       'localBasePath' => $dir,
+                                                       'remoteExtPath' => 'FooBar',
+                                               ),
+                                               'test.bar' => array(
+                                                       'styles' => 'bar.js',
+                                                       'localBasePath' => $dir . 'subdir',
+                                                       'remoteExtPath' => 'FooBar/subdir',
+                                               ),
+                                               'test.class' => array(
+                                                       'class' => 'FooBarModule',
+                                                       'extra' => 'argument',
+                                               ),
+                                               'test.class.with.path' => array(
+                                                       'class' => 'FooBarPathModule',
+                                                       'extra' => 'argument',
+                                                       'localBasePath' => $dir,
+                                               )
+                                       ),
+                               ),
+                       ),
+               );
+       }
+
        public static function provideSetToGlobal() {
                return array(
                        array(
index f6ea1b4..a87721b 100644 (file)
@@ -46,7 +46,7 @@
        } );
 
        QUnit.test( 'updateTooltipAccessKeys - current browser', 2, function ( assert ) {
-               var title = $( makeInput ( 'Title', 'a' ) ).updateTooltipAccessKeys().prop( 'title' ),
+               var title = $( makeInput( 'Title', 'a' ) ).updateTooltipAccessKeys().prop( 'title' ),
                        //The new title should be something like "Title [alt-a]", but the exact label will depend on the browser.
                        //The "a" could be capitalized, and the prefix could be anything, e.g. a simple "^" for ctrl-
                        //(no browser is known using such a short prefix, though) or "Alt+Umschalt+" in German Firefox.
index bbea829..78c185f 100644 (file)
@@ -1,10 +1,10 @@
-(function ($) {
+( function ($) {
 
        QUnit.module('jquery.placeholder', QUnit.newMwEnvironment());
 
        QUnit.test('caches results of feature tests', 2, function (assert) {
-               assert.strictEqual(typeof $.fn.placeholder.input, 'boolean', '$.fn.placeholder.input');
-               assert.strictEqual(typeof $.fn.placeholder.textarea, 'boolean', '$.fn.placeholder.textarea');
+               assert.strictEqual( typeof $.fn.placeholder.input, 'boolean', '$.fn.placeholder.input');
+               assert.strictEqual( typeof $.fn.placeholder.textarea, 'boolean', '$.fn.placeholder.textarea');
        });
 
        if ($.fn.placeholder.input && $.fn.placeholder.textarea) {
index ece5116..28d6d92 100644 (file)
@@ -80,7 +80,7 @@
                                only: 'scripts'
                        },
                        dataType: 'script'
-               } ).done(function () {
+               } ).done( function () {
                                mwLanguageCache[langCode].fire( mw.language );
                        } ).fail( function () {
                                mwLanguageCache[langCode].fire( false );
index 88314bc..28f22fd 100644 (file)
--- a/thumb.php
+++ b/thumb.php
@@ -321,6 +321,7 @@ function wfStreamThumb( array $params ) {
 
        // Check for thumbnail generation errors...
        $msg = wfMessage( 'thumbnail_error' );
+       $errorCode = 500;
        if ( !$thumb ) {
                $errorMsg = $errorMsg ?: $msg->rawParams( 'File::transform() returned false' )->escaped();
        } elseif ( $thumb->isError() ) {
@@ -330,10 +331,11 @@ function wfStreamThumb( array $params ) {
        } elseif ( $thumb->fileIsSource() ) {
                $errorMsg = $msg->
                        rawParams( 'Image was not scaled, is the requested width bigger than the source?' )->escaped();
+               $errorCode = 400;
        }
 
        if ( $errorMsg !== false ) {
-               wfThumbError( 500, $errorMsg );
+               wfThumbError( $errorCode, $errorMsg );
        } else {
                // Stream the file if there were no errors
                $thumb->streamFile( $headers );
@@ -545,7 +547,9 @@ function wfThumbError( $status, $msg ) {
 
        header( 'Cache-Control: no-cache' );
        header( 'Content-Type: text/html; charset=utf-8' );
-       if ( $status == 404 ) {
+       if ( $status == 400 ) {
+               header( 'HTTP/1.1 400 Bad request' );
+       } elseif ( $status == 404 ) {
                header( 'HTTP/1.1 404 Not found' );
        } elseif ( $status == 403 ) {
                header( 'HTTP/1.1 403 Forbidden' );