From: jenkins-bot Date: Tue, 8 Sep 2015 14:55:56 +0000 (+0000) Subject: Merge "resourceloader: Don't create empty objects for every module" X-Git-Tag: 1.31.0-rc.0~10095 X-Git-Url: https://git.heureux-cyclage.org/?a=commitdiff_plain;h=2ac96b61739f91519069066c31f8a6bd9b73f6cd;hp=70c8dfe1c5dd393e6bdd75da16766211876e2d70;p=lhc%2Fweb%2Fwiklou.git Merge "resourceloader: Don't create empty objects for every module" --- diff --git a/.travis.yml b/.travis.yml index 206ead34aa..8ba46b5455 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,19 +8,17 @@ # language: php -php: - - hhvm - - 5.3 - -env: - - dbtype=mysql - - dbtype=postgres - -# TODO: Travis CI's hhvm does not support PostgreSQL at the moment. matrix: - exclude: - - php: hhvm - env: dbtype=postgres + fast_finish: true + include: + - env: dbtype=mysql + php: 5.3 + - env: dbtype=postgres + php: 5.3 + - env: dbtype=mysql + php: hhvm + - env: dbtype=mysql + php: 7 services: - mysql diff --git a/RELEASE-NOTES-1.26 b/RELEASE-NOTES-1.26 index ff7f884fdd..a532171d6e 100644 --- a/RELEASE-NOTES-1.26 +++ b/RELEASE-NOTES-1.26 @@ -170,6 +170,9 @@ changes to languages because of Phabricator reports. a lengthy deprecation period. * The ScopedPHPTimeout class was removed. * Removed maintenance script fixSlaveDesync.php. +* Watchlist tokens, SpecialResetTokens, and User::getTokenFromOption() + are deprecated. Applications using those can work via the OAuth + extension instead. New tokens types should not be added. == Compatibility == diff --git a/includes/Hooks.php b/includes/Hooks.php index 036d65c71e..a414562436 100644 --- a/includes/Hooks.php +++ b/includes/Hooks.php @@ -231,22 +231,25 @@ class Hooks { } /** - * Handle PHP errors issued inside a hook. Catch errors that have to do with - * a function expecting a reference, and let all others pass through. - * - * This REALLY should be protected... but it's public for compatibility + * Handle PHP errors issued inside a hook. Catch errors that have to do + * with a function expecting a reference, and pass all others through to + * MWExceptionHandler::handleError() for default processing. * * @since 1.18 * * @param int $errno Error number (unused) * @param string $errstr Error message * @throws MWHookException If the error has to do with the function signature - * @return bool Always returns false + * @return bool */ public static function hookErrorHandler( $errno, $errstr ) { if ( strpos( $errstr, 'expected to be a reference, value given' ) !== false ) { throw new MWHookException( $errstr, $errno ); } - return false; + + // Delegate unhandled errors to the default MW handler + return call_user_func_array( + 'MWExceptionHandler::handleError', func_get_args() + ); } } diff --git a/includes/OutputPage.php b/includes/OutputPage.php index 073762a035..1d1b9fb73a 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -142,9 +142,6 @@ class OutputPage extends ContextSource { /** @var string Inline CSS styles. Use addInlineStyle() sparingly */ protected $mInlineStyles = ''; - /** @todo Unused? */ - private $mLinkColours; - /** * @var string Used by skin template. * Example: $tpl->set( 'displaytitle', $out->mPageLinkTitle ); diff --git a/includes/User.php b/includes/User.php index da8ff7925d..605dab6834 100644 --- a/includes/User.php +++ b/includes/User.php @@ -2438,6 +2438,7 @@ class User implements IDBAccessObject { */ public function setInternalPassword( $str ) { $this->setToken(); + $this->setOption( 'watchlisttoken', false ); $passwordFactory = self::getPasswordFactory(); $this->mPassword = $passwordFactory->newFromPlaintext( $str ); @@ -2715,20 +2716,24 @@ class User implements IDBAccessObject { * @return string|bool User's current value for the option, or false if this option is disabled. * @see resetTokenFromOption() * @see getOption() + * @deprecated 1.26 Applications should use the OAuth extension */ public function getTokenFromOption( $oname ) { global $wgHiddenPrefs; - if ( in_array( $oname, $wgHiddenPrefs ) ) { + + $id = $this->getId(); + if ( !$id || in_array( $oname, $wgHiddenPrefs ) ) { return false; } $token = $this->getOption( $oname ); if ( !$token ) { - $token = $this->resetTokenFromOption( $oname ); - if ( !wfReadOnly() ) { - $this->saveSettings(); - } + // Default to a value based on the user token to avoid space + // wasted on storing tokens for all users. When this option + // is set manually by the user, only then is it stored. + $token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() ); } + return $token; } diff --git a/includes/api/i18n/en.json b/includes/api/i18n/en.json index 425e062251..39d44d2344 100644 --- a/includes/api/i18n/en.json +++ b/includes/api/i18n/en.json @@ -869,7 +869,7 @@ "apihelp-query+logevents-paramvalue-prop-details": "Lists additional details about the log event.", "apihelp-query+logevents-paramvalue-prop-tags": "Lists tags for the log event.", "apihelp-query+logevents-param-type": "Filter log entries to only this type.", - "apihelp-query+logevents-param-action": "Filter log actions to only this action. Overrides $1type. Wildcard actions like action/* allows to specify any string for the asterisk.", + "apihelp-query+logevents-param-action": "Filter log actions to only this action. Overrides $1type. In the list of possible values, values with the asterisk wildcard such as action/* can have different strings after the slash (/).", "apihelp-query+logevents-param-start": "The timestamp to start enumerating from.", "apihelp-query+logevents-param-end": "The timestamp to end enumerating.", "apihelp-query+logevents-param-user": "Filter entries to those made by the given user.", diff --git a/includes/debug/logger/LegacyLogger.php b/includes/debug/logger/LegacyLogger.php index b6439b8222..0f4c648406 100644 --- a/includes/debug/logger/LegacyLogger.php +++ b/includes/debug/logger/LegacyLogger.php @@ -21,6 +21,7 @@ namespace MediaWiki\Logger; use DateTimeZone; +use Exception; use MWDebug; use MWExceptionHandler; use Psr\Log\AbstractLogger; @@ -217,13 +218,22 @@ class LegacyLogger extends AbstractLogger { } // Append stacktrace of exception if available - if ( $wgLogExceptionBacktrace && - isset( $context['exception'] ) && - $context['exception'] instanceof Exception - ) { - $text .= MWExceptionHandler::getRedactedTraceAsString( - $context['exception'] - ) . "\n"; + if ( $wgLogExceptionBacktrace && isset( $context['exception'] ) ) { + $e = $context['exception']; + $backtrace = false; + + if ( $e instanceof Exception ) { + $backtrace = MWExceptionHandler::getRedactedTrace( $e ); + + } elseif ( is_array( $e ) && isset( $e['trace'] ) ) { + // Exception has already been unpacked as structured data + $backtrace = $e['trace']; + } + + if ( $backtrace ) { + $text .= MWExceptionHandler::prettyPrintTrace( $backtrace ) . + "\n"; + } } return self::interpolate( $text, $context ); @@ -358,7 +368,7 @@ class LegacyLogger extends AbstractLogger { return $item->format( 'c' ); } - if ( $item instanceof \Exception ) { + if ( $item instanceof Exception ) { return '[Exception ' . get_class( $item ) . '( ' . $item->getFile() . ':' . $item->getLine() . ') ' . $item->getMessage() . ']'; diff --git a/includes/debug/logger/monolog/LineFormatter.php b/includes/debug/logger/monolog/LineFormatter.php index e593d63bc4..2ba7a53cb5 100644 --- a/includes/debug/logger/monolog/LineFormatter.php +++ b/includes/debug/logger/monolog/LineFormatter.php @@ -27,10 +27,14 @@ use MWExceptionHandler; /** * Formats incoming records into a one-line string. * + * An 'exeception' in the log record's context will be treated specially. + * It will be output for an '%exception%' placeholder in the format and + * excluded from '%context%' output if the '%exception%' placeholder is + * present. + * * Exceptions that are logged with this formatter will optional have their - * stack traces appended. If that is done, - * MWExceptionHandler::getRedactedTraceAsString() will be used to redact the - * trace information. + * stack traces appended. If that is done, MWExceptionHandler::redactedTrace() + * will be used to redact the trace information. * * @since 1.26 * @author Bryan Davis @@ -57,6 +61,40 @@ class LineFormatter extends MonologLineFormatter { } + /** + * {@inheritdoc} + */ + public function format( array $record ) { + // Drop the 'private' flag from the context + unset( $record['context']['private'] ); + + // Handle exceptions specially: pretty format and remove from context + // Will be output for a '%exception%' placeholder in format + $prettyException = ''; + if ( isset( $record['context']['exception'] ) && + strpos( $this->format, '%exception%' ) !== false + ) { + $e = $record['context']['exception']; + unset( $record['context']['exception'] ); + + if ( $e instanceof Exception ) { + $prettyException = $this->normalizeException( $e ); + } elseif ( is_array( $e ) ) { + $prettyException = $this->normalizeExceptionArray( $e ); + } else { + $prettyException = $this->stringify( $e ); + } + } + + $output = parent::format( $record ); + + if ( strpos( $output, '%exception%' ) !== false ) { + $output = str_replace( '%exception%', $prettyException, $output ); + } + return $output; + } + + /** * Convert an Exception to a string. * @@ -64,24 +102,76 @@ class LineFormatter extends MonologLineFormatter { * @return string */ protected function normalizeException( Exception $e ) { - $str = '[Exception ' . get_class( $e ) . '] (' . - $e->getFile() . ':' . $e->getLine() . ') ' . - $e->getMessage(); + return $this->normalizeExceptionArray( $this->exceptionAsArray( $e ) ); + } + + + /** + * Convert an exception to an array of structured data. + * + * @param Exception $e + * @return array + */ + protected function exceptionAsArray( Exception $e ) { + $out = array( + 'class' => get_class( $e ), + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'trace' => MWExceptionHandler::redactTrace( $e->getTrace() ), + ); $prev = $e->getPrevious(); - while ( $prev ) { - $str .= ', [Exception ' . get_class( $prev ) . '] (' . - $prev->getFile() . ':' . $prev->getLine() . ') ' . - $prev->getMessage(); - $prev = $prev->getPrevious(); + if ( $prev ) { + $out['previous'] = $this->exceptionAsArray( $prev ); } - if ( $this->includeStacktraces ) { - $str .= "\n[stacktrace]\n" . - MWExceptionHandler::getRedactedTraceAsString( $e ) . - "\n"; + return $out; + } + + + /** + * Convert an array of Exception data to a string. + * + * @param array $e + * @return string + */ + protected function normalizeExceptionArray( array $e ) { + $defaults = array( + 'class' => 'Unknown', + 'file' => 'unknown', + 'line' => null, + 'message' => 'unknown', + 'trace' => array(), + ); + $e = array_merge( $defaults, $e ); + + $str = "\n[Exception {$e['class']}] (" . + "{$e['file']}:{$e['line']}) {$e['message']}"; + + if ( $this->includeStacktraces && $e['trace'] ) { + $str .= "\n" . + MWExceptionHandler::prettyPrintTrace( $e['trace'], ' ' ); } + if ( isset( $e['previous'] ) ) { + $prev = $e['previous']; + while ( $prev ) { + $prev = array_merge( $defaults, $prev ); + $str .= "\nCaused by: [Exception {$prev['class']}] (" . + "{$prev['file']}:{$prev['line']}) {$prev['message']}"; + + if ( $this->includeStacktraces && $prev['trace'] ) { + $str .= "\n" . + MWExceptionHandler::prettyPrintTrace( + $prev['trace'], ' ' + ); + } + + $prev = isset( $prev['previous'] ) ? $prev['previous'] : null; + } + } return $str; } } diff --git a/includes/exception/MWExceptionHandler.php b/includes/exception/MWExceptionHandler.php index def653fb30..d4a240ff20 100644 --- a/includes/exception/MWExceptionHandler.php +++ b/includes/exception/MWExceptionHandler.php @@ -26,24 +26,32 @@ use MediaWiki\Logger\LoggerFactory; */ class MWExceptionHandler { + /** + * @var string $reservedMemory + */ protected static $reservedMemory; + /** + * @var array $fatalErrorTypes + */ protected static $fatalErrorTypes = array( E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, /* HHVM's FATAL_ERROR level */ 16777217, ); + /** + * @var bool $handledFatalCallback + */ + protected static $handledFatalCallback = false; /** * Install handlers with PHP. */ public static function installHandler() { - set_exception_handler( array( 'MWExceptionHandler', 'handleException' ) ); - set_error_handler( array( 'MWExceptionHandler', 'handleError' ) ); + set_exception_handler( 'MWExceptionHandler::handleException' ); + set_error_handler( 'MWExceptionHandler::handleError' ); // Reserve 16k of memory so we can report OOM fatals self::$reservedMemory = str_repeat( ' ', 16384 ); - register_shutdown_function( - array( 'MWExceptionHandler', 'handleFatalError' ) - ); + register_shutdown_function( 'MWExceptionHandler::handleFatalError' ); } /** @@ -176,24 +184,36 @@ class MWExceptionHandler { } /** + * Handler for set_error_handler() callback notifications. + * + * Receive a callback from the interpreter for a raised error, create an + * ErrorException, and log the exception to the 'error' logging + * channel(s). If the raised error is a fatal error type (only under HHVM) + * delegate to handleFatalError() instead. + * * @since 1.25 + * * @param int $level Error level raised * @param string $message * @param string $file * @param int $line + * + * @see logError() */ - public static function handleError( $level, $message, $file = null, $line = null ) { - // Map error constant to error name (reverse-engineer PHP error reporting) - $channel = 'error'; + public static function handleError( + $level, $message, $file = null, $line = null + ) { + if ( in_array( $level, self::$fatalErrorTypes ) ) { + return call_user_func_array( + 'MWExceptionHandler::handleFatalError', func_get_args() + ); + } + + // Map error constant to error name (reverse-engineer PHP error + // reporting) switch ( $level ) { - case E_ERROR: - case E_CORE_ERROR: - case E_COMPILE_ERROR: - case E_USER_ERROR: case E_RECOVERABLE_ERROR: - case E_PARSE: $levelName = 'Error'; - $channel = 'fatal'; break; case E_WARNING: case E_CORE_WARNING: @@ -212,17 +232,13 @@ class MWExceptionHandler { case E_USER_DEPRECATED: $levelName = 'Deprecated'; break; - case /* HHVM's FATAL_ERROR */ 16777217: - $levelName = 'Fatal'; - $channel = 'fatal'; - break; default: $levelName = 'Unknown error'; break; } $e = new ErrorException( "PHP $levelName: $message", 0, $level, $file, $line ); - self::logError( $e, $channel ); + self::logError( $e, 'error' ); // This handler is for logging only. Return false will instruct PHP // to continue regular handling. @@ -231,42 +247,101 @@ class MWExceptionHandler { /** - * Look for a fatal error as the cause of the request termination and log - * as an exception. + * Dual purpose callback used as both a set_error_handler() callback and + * a registered shutdown function. Receive a callback from the interpreter + * for a raised error or system shutdown, check for a fatal error, and log + * to the 'fatal' logging channel. * * Special handling is included for missing class errors as they may * indicate that the user needs to install 3rd-party libraries via * Composer or other means. * * @since 1.25 + * + * @param int $level Error level raised + * @param string $message Error message + * @param string $file File that error was raised in + * @param int $line Line number error was raised at + * @param array $context Active symbol table point of error + * @param array $trace Backtrace at point of error (undocumented HHVM + * feature) + * @return bool Always returns false */ - public static function handleFatalError() { + public static function handleFatalError( + $level = null, $message = null, $file = null, $line = null, + $context = null, $trace = null + ) { + // Free reserved memory so that we have space to process OOM + // errors self::$reservedMemory = null; - $lastError = error_get_last(); - if ( $lastError && - isset( $lastError['type'] ) && - in_array( $lastError['type'], self::$fatalErrorTypes ) - ) { - $msg = "Fatal Error: {$lastError['message']}"; - // HHVM: Class undefined: foo - // PHP5: Class 'foo' not found - if ( preg_match( "/Class (undefined: \w+|'\w+' not found)/", - $lastError['message'] - ) ) { - // @codingStandardsIgnoreStart Generic.Files.LineLength.TooLong - $msg = <<mediawiki.org for help on installing the required components. TXT; - // @codingStandardsIgnoreEnd - } - $e = new ErrorException( $msg, 0, $lastError['type'] ); - self::logError( $e, 'fatal' ); + // @codingStandardsIgnoreEnd } + + // We can't just create an exception and log it as it is likely that + // the interpreter has unwound the stack already. If that is true the + // stacktrace we would get would be functionally empty. If however we + // have been called as an error handler callback *and* HHVM is in use + // we will have been provided with a useful stacktrace that we can + // log. + $trace = $trace ?: debug_backtrace(); + $logger = LoggerFactory::getInstance( 'fatal' ); + $logger->error( $msg, array( + 'exception' => array( + 'class' => 'ErrorException', + 'message' => "PHP Fatal Error: {$message}", + 'code' => $level, + 'file' => $file, + 'line' => $line, + 'trace' => static::redactTrace( $trace ), + ), + 'exception_id' => wfRandomString( 8 ), + ) ); + + // Remember call so we don't double process via HHVM's fatal + // notifications and the shutdown hook behavior + static::$handledFatalCallback = true; + return false; } /** @@ -277,32 +352,32 @@ TXT; * * @param Exception $e * @return string + * @see prettyPrintTrace() */ public static function getRedactedTraceAsString( Exception $e ) { - return self::prettyPrintRedactedTrace( - self::getRedactedTrace( $e ) - ); + return self::prettyPrintTrace( self::getRedactedTrace( $e ) ); } /** - * Generate a string representation of a structured stack trace generated - * by getRedactedTrace(). + * Generate a string representation of a stacktrace. * * @param array $trace + * @param string $pad Constant padding to add to each line of trace * @return string * @since 1.26 */ - public static function prettyPrintRedactedTrace( array $trace ) { + public static function prettyPrintTrace( array $trace, $pad = '' ) { $text = ''; foreach ( $trace as $level => $frame ) { if ( isset( $frame['file'] ) && isset( $frame['line'] ) ) { - $text .= "#{$level} {$frame['file']}({$frame['line']}): "; + $text .= "{$pad}#{$level} {$frame['file']}({$frame['line']}): "; } else { - // 'file' and 'line' are unset for calls via call_user_func (bug 55634) - // This matches behaviour of Exception::getTraceAsString to instead - // display "[internal function]". - $text .= "#{$level} [internal function]: "; + // 'file' and 'line' are unset for calls via call_user_func + // (bug 55634) This matches behaviour of + // Exception::getTraceAsString to instead display "[internal + // function]". + $text .= "{$pad}#{$level} [internal function]: "; } if ( isset( $frame['class'] ) ) { @@ -319,7 +394,7 @@ TXT; } $level = $level + 1; - $text .= "#{$level} {main}"; + $text .= "{$pad}#{$level} {main}"; return $text; } @@ -336,6 +411,20 @@ TXT; * @return array */ public static function getRedactedTrace( Exception $e ) { + return static::redactTrace( $e->getTrace() ); + } + + /** + * Redact a stacktrace generated by Exception::getTrace(), + * debug_backtrace() or similar means. Replaces each element in each + * frame's argument array with the name of its class (if the element is an + * object) or its type (if the element is a PHP primitive). + * + * @since 1.26 + * @param array $trace Stacktrace + * @return array Stacktrace with arugment values converted to data types + */ + public static function redactTrace( array $trace ) { return array_map( function ( $frame ) { if ( isset( $frame['args'] ) ) { $frame['args'] = array_map( function ( $arg ) { @@ -343,7 +432,7 @@ TXT; }, $frame['args'] ); } return $frame; - }, $e->getTrace() ); + }, $trace ); } /** @@ -409,6 +498,7 @@ TXT; public static function getLogContext( Exception $e ) { return array( 'exception' => $e, + 'exception_id' => static::getLogId( $e ), ); } @@ -548,7 +638,8 @@ TXT; */ protected static function logError( ErrorException $e, $channel ) { // The set_error_handler callback is independent from error_reporting. - // Filter out unwanted errors manually (e.g. when MediaWiki\suppressWarnings is active). + // Filter out unwanted errors manually (e.g. when + // MediaWiki\suppressWarnings is active). $suppressed = ( error_reporting() & $e->getSeverity() ) === 0; if ( !$suppressed ) { $logger = LoggerFactory::getInstance( $channel ); diff --git a/includes/htmlform/HTMLForm.php b/includes/htmlform/HTMLForm.php index 38729a5ec3..08fa0a91ef 100644 --- a/includes/htmlform/HTMLForm.php +++ b/includes/htmlform/HTMLForm.php @@ -898,6 +898,7 @@ class HTMLForm extends ContextSource { # For good measure (it is the default) $this->getOutput()->preventClickjacking(); $this->getOutput()->addModules( 'mediawiki.htmlform' ); + $this->getOutput()->addModuleStyles( 'mediawiki.htmlform.styles' ); $html = '' . $this->getErrors( $submitResult ) diff --git a/includes/media/DjVuImage.php b/includes/media/DjVuImage.php index dbbe991b18..dac76fad17 100644 --- a/includes/media/DjVuImage.php +++ b/includes/media/DjVuImage.php @@ -150,7 +150,7 @@ class DjVuImage { wfDebug( __METHOD__ . ": not a DjVu file\n" ); } elseif ( $subtype == 'DJVU' ) { // Single-page document - $info = $this->getPageInfo( $file, $formLength ); + $info = $this->getPageInfo( $file ); } elseif ( $subtype == 'DJVM' ) { // Multi-page document $info = $this->getMultiPageInfo( $file, $formLength ); @@ -202,7 +202,7 @@ class DjVuImage { if ( $subtype == 'DJVU' ) { wfDebug( __METHOD__ . ": found first subpage\n" ); - return $this->getPageInfo( $file, $length ); + return $this->getPageInfo( $file ); } $this->skipChunk( $file, $length - 4 ); } else { @@ -216,7 +216,7 @@ class DjVuImage { return false; } - private function getPageInfo( $file, $formLength ) { + private function getPageInfo( $file ) { list( $chunk, $length ) = $this->readChunk( $file ); if ( $chunk != 'INFO' ) { wfDebug( __METHOD__ . ": expected INFO chunk, got '$chunk'\n" ); diff --git a/includes/specials/SpecialResetTokens.php b/includes/specials/SpecialResetTokens.php index 27a3a699ef..cba5a44930 100644 --- a/includes/specials/SpecialResetTokens.php +++ b/includes/specials/SpecialResetTokens.php @@ -25,6 +25,7 @@ * Let users reset tokens like the watchlist token. * * @ingroup SpecialPage + * @deprecated 1.26 */ class SpecialResetTokens extends FormSpecialPage { private $tokensList; diff --git a/languages/i18n/zh-tw.json b/languages/i18n/zh-tw.json index 81e0605003..8e5704e785 100644 --- a/languages/i18n/zh-tw.json +++ b/languages/i18n/zh-tw.json @@ -414,7 +414,6 @@ "sp-contributions-blocklog": "封鎖記錄", "sp-contributions-userrights": "使用者權限管理", "sp-contributions-username": "IP位址或使用者名稱:", - "whatlinkshere-title": "鏈接到$1的頁面", "blockip": "封鎖使用者", "ipadressorusername": "IP地址或使用者名:", "ipbreason-dropdown": "*一般的封鎖理由\n** 屢次增加不實資料\n** 刪除頁面內容\n** 外部連結廣告\n** 在頁面中增加無意義文字\n** 無禮的行為、攻擊/騷擾別人\n** 濫用多個帳號\n** 不能接受的使用者名", diff --git a/maintenance/interwiki.list b/maintenance/interwiki.list index 91c60c1c26..4899143422 100644 --- a/maintenance/interwiki.list +++ b/maintenance/interwiki.list @@ -46,7 +46,6 @@ s23wiki|http://s23.org/wiki/$1|0|http://s23.org/w/api.php seattlewireless|http://seattlewireless.net/$1|0| senseislibrary|http://senseis.xmp.net/?$1|0| shoutwiki|http://www.shoutwiki.com/wiki/$1|0|http://www.shoutwiki.com/w/api.php -sourceforge|http://sourceforge.net/$1|0| sourcewatch|http://www.sourcewatch.org/index.php?title=$1|0|http://www.sourcewatch.org/api.php squeak|http://wiki.squeak.org/squeak/$1|0| tejo|http://www.tejo.org/vikio/$1|0| diff --git a/maintenance/interwiki.sql b/maintenance/interwiki.sql index 0628773e8f..12352e7c56 100644 --- a/maintenance/interwiki.sql +++ b/maintenance/interwiki.sql @@ -48,7 +48,6 @@ REPLACE INTO /*$wgDBprefix*/interwiki (iw_prefix,iw_url,iw_local,iw_api) VALUES ('seattlewireless','http://seattlewireless.net/$1',0,''), ('senseislibrary','http://senseis.xmp.net/?$1',0,''), ('shoutwiki','http://www.shoutwiki.com/wiki/$1',0,'http://www.shoutwiki.com/w/api.php'), -('sourceforge','http://sourceforge.net/$1',0,''), ('sourcewatch','http://www.sourcewatch.org/index.php?title=$1',0,'http://www.sourcewatch.org/api.php'), ('squeak','http://wiki.squeak.org/squeak/$1',0,''), ('tejo','http://www.tejo.org/vikio/$1',0,''), diff --git a/resources/Resources.php b/resources/Resources.php index 5ad349afe7..1df28adcd0 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -1030,6 +1030,10 @@ return array( 'colon-separator', ), ), + 'mediawiki.htmlform.styles' => array( + 'styles' => 'resources/src/mediawiki/mediawiki.htmlform.css', + 'position' => 'top', + ), 'mediawiki.htmlform.ooui.styles' => array( 'styles' => 'resources/src/mediawiki/mediawiki.htmlform.ooui.css', 'position' => 'top', diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.preview.js b/resources/src/mediawiki.action/mediawiki.action.edit.preview.js index 0610643bcf..ab4535b66e 100644 --- a/resources/src/mediawiki.action/mediawiki.action.edit.preview.js +++ b/resources/src/mediawiki.action/mediawiki.action.edit.preview.js @@ -17,6 +17,7 @@ $editform = $( '#editform' ); $textbox = $editform.find( '#wpTextbox1' ); $summary = $editform.find( '#wpSummary' ); + $spinner = $( '.mw-spinner-preview' ); $errorBox = $( '.errorbox' ); section = $editform.find( '[name="wpSection"]' ).val(); @@ -55,14 +56,17 @@ // Not shown during normal preview, to be removed if present $( '.mw-newarticletext' ).remove(); - $spinner = $.createSpinner( { - size: 'large', - type: 'block' - } ); - $wikiPreview.before( $spinner ); - $spinner.css( { - marginTop: $spinner.height() - } ); + if ( $spinner.length === 0 ) { + $spinner = $.createSpinner( { + size: 'large', + type: 'block' + } ) + .addClass( 'mw-spinner-preview' ) + .css( 'margin-top', '1em' ); + $wikiPreview.before( $spinner ); + } else { + $spinner.show(); + } // Can't use fadeTo because it calls show(), and we might want to keep some elements hidden // (e.g. empty #catlinks) @@ -227,7 +231,7 @@ mw.hook( 'wikipage.editform' ).fire( $editform ); } ); request.always( function () { - $spinner.remove(); + $spinner.hide(); $copyElements.animate( { opacity: 1 }, 'fast' ); diff --git a/resources/src/mediawiki.legacy/images/question.png b/resources/src/mediawiki.legacy/images/question.png deleted file mode 100644 index f7405d2626..0000000000 Binary files a/resources/src/mediawiki.legacy/images/question.png and /dev/null differ diff --git a/resources/src/mediawiki.legacy/images/question.svg b/resources/src/mediawiki.legacy/images/question.svg deleted file mode 100644 index 98fbe8dd75..0000000000 --- a/resources/src/mediawiki.legacy/images/question.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/resources/src/mediawiki.legacy/shared.css b/resources/src/mediawiki.legacy/shared.css index 019d339107..f55d2a8e0b 100644 --- a/resources/src/mediawiki.legacy/shared.css +++ b/resources/src/mediawiki.legacy/shared.css @@ -218,56 +218,10 @@ td.mw-label { width: auto; } -.mw-icon-question { - /* SVG support using a transparent gradient to guarantee cross-browser - * compatibility (browsers able to understand gradient syntax support also SVG). - * http://pauginer.tumblr.com/post/36614680636/invisible-gradient-technique */ - background-image: url(images/question.png); - background-image: -webkit-linear-gradient(transparent, transparent), url(images/question.svg); - background-image: linear-gradient(transparent, transparent), url(images/question.svg); - background-repeat: no-repeat; - background-size: 13px 13px; - display: inline-block; - height: 13px; - width: 13px; - margin-left: 4px; -} - -.mw-icon-question:lang(ar), -.mw-icon-question:lang(fa), -.mw-icon-question:lang(ur) { - -webkit-transform: scaleX(-1); - -ms-transform: scaleX(-1); - transform: scaleX(-1); -} - td.mw-submit { white-space: nowrap; } -table.mw-htmlform-nolabel td.mw-label { - width: 1px; -} - -tr.mw-htmlform-vertical-label td.mw-label { - text-align: left !important; -} - -.mw-htmlform-invalid-input td.mw-input input { - border-color: red; -} - -.mw-htmlform-flatlist div.mw-htmlform-flatlist-item { - display: inline; - margin-right: 1em; - white-space: nowrap; -} - -.mw-htmlform-matrix td { - padding-left: 0.5em; - padding-right: 0.5em; -} - input#wpSummary { width: 80%; margin-bottom: 1em; diff --git a/resources/src/mediawiki/images/question.png b/resources/src/mediawiki/images/question.png new file mode 100644 index 0000000000..f7405d2626 Binary files /dev/null and b/resources/src/mediawiki/images/question.png differ diff --git a/resources/src/mediawiki/images/question.svg b/resources/src/mediawiki/images/question.svg new file mode 100644 index 0000000000..98fbe8dd75 --- /dev/null +++ b/resources/src/mediawiki/images/question.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/src/mediawiki/mediawiki.htmlform.css b/resources/src/mediawiki/mediawiki.htmlform.css new file mode 100644 index 0000000000..e41248c1be --- /dev/null +++ b/resources/src/mediawiki/mediawiki.htmlform.css @@ -0,0 +1,51 @@ +/* HTMLForm styles */ + +table.mw-htmlform-nolabel td.mw-label { + width: 1px; +} + +.mw-htmlform-invalid-input td.mw-input input { + border-color: red; +} + +.mw-htmlform-flatlist div.mw-htmlform-flatlist-item { + display: inline; + margin-right: 1em; + white-space: nowrap; +} + +/* HTMLCheckMatrix */ + +.mw-htmlform-matrix td { + padding-left: 0.5em; + padding-right: 0.5em; +} + +tr.mw-htmlform-vertical-label td.mw-label { + text-align: left !important; +} + +.mw-icon-question { + /* SVG support using a transparent gradient to guarantee cross-browser + * compatibility (browsers able to understand gradient syntax support also SVG). + * http://pauginer.tumblr.com/post/36614680636/invisible-gradient-technique */ + background-image: url(images/question.png); + /* @embed */ + background-image: -webkit-linear-gradient(transparent, transparent), url(images/question.svg); + /* @embed */ + background-image: linear-gradient(transparent, transparent), url(images/question.svg); + background-repeat: no-repeat; + background-size: 13px 13px; + display: inline-block; + height: 13px; + width: 13px; + margin-left: 4px; +} + +.mw-icon-question:lang(ar), +.mw-icon-question:lang(fa), +.mw-icon-question:lang(ur) { + -webkit-transform: scaleX(-1); + -ms-transform: scaleX(-1); + transform: scaleX(-1); +} diff --git a/resources/src/mediawiki/mediawiki.js b/resources/src/mediawiki/mediawiki.js index b9abd92e16..5ba97814d4 100644 --- a/resources/src/mediawiki/mediawiki.js +++ b/resources/src/mediawiki/mediawiki.js @@ -1143,10 +1143,10 @@ * * @private * @param {string} src URL to script, will be used as the src attribute in the script tag - * @param {Function} [callback] Callback which will be run when the script is done + * @return {jQuery.Promise} */ - function addScript( src, callback ) { - $.ajax( { + function addScript( src ) { + return $.ajax( { url: src, dataType: 'script', // Force jQuery behaviour to be for crossDomain. Otherwise jQuery would use @@ -1156,7 +1156,7 @@ // text, so we'd need to $.globalEval, which then messes up line numbers. crossDomain: true, cache: true - } ).always( callback ); + } ); } /** @@ -1217,7 +1217,7 @@ return; } - addScript( arr[ i ], function () { + addScript( arr[ i ] ).always( function () { nestedAddScript( arr, callback, i + 1 ); } ); }; @@ -2559,10 +2559,33 @@ } } - // subscribe to error streams + // Subscribe to error streams mw.trackSubscribe( 'resourceloader.exception', log ); mw.trackSubscribe( 'resourceloader.assert', log ); + /** + * Fired when all modules associated with the page have finished loading. + * + * @event resourceloader_loadEnd + * @member mw.hook + */ + $( function () { + var loading = $.grep( mw.loader.getModuleNames(), function ( module ) { + return mw.loader.getState( module ) === 'loading'; + } ); + // In order to use jQuery.when (which stops early if one of the promises got rejected) + // cast any loading failures into successes. We only need a callback, not the module. + loading = $.map( loading, function ( module ) { + return mw.loader.using( module ).then( null, function () { + return $.Deferred().resolve(); + } ); + } ); + $.when.apply( $, loading ).then( function () { + performance.mark( 'mwLoadEnd' ); + mw.hook( 'resourceloader.loadEnd' ).fire(); + } ); + } ); + // Attach to window and globally alias window.mw = window.mediaWiki = mw; }( jQuery ) ); diff --git a/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php b/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php index f12cf5bd80..6ee54d3380 100644 --- a/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php +++ b/tests/phpunit/includes/debug/logger/monolog/LineFormatterTest.php @@ -48,10 +48,10 @@ class LineFormatterTest extends MediaWikiTestCase { ) ); $out = $fixture->normalizeException( $boom ); - $this->assertContains( '[Exception InvalidArgumentException]', $out ); - $this->assertContains( ', [Exception LengthException]', $out ); - $this->assertContains( ', [Exception LogicException]', $out ); - $this->assertNotContains( '[stacktrace]', $out ); + $this->assertContains( "\n[Exception InvalidArgumentException]", $out ); + $this->assertContains( "\nCaused by: [Exception LengthException]", $out ); + $this->assertContains( "\nCaused by: [Exception LogicException]", $out ); + $this->assertNotContains( "\n #0", $out ); } /** @@ -67,9 +67,9 @@ class LineFormatterTest extends MediaWikiTestCase { ) ); $out = $fixture->normalizeException( $boom ); - $this->assertContains( '[Exception InvalidArgumentException', $out ); - $this->assertContains( ', [Exception LengthException]', $out ); - $this->assertContains( ', [Exception LogicException]', $out ); - $this->assertContains( '[stacktrace]', $out ); + $this->assertContains( "\n[Exception InvalidArgumentException]", $out ); + $this->assertContains( "\nCaused by: [Exception LengthException]", $out ); + $this->assertContains( "\nCaused by: [Exception LogicException]", $out ); + $this->assertContains( "\n #0", $out ); } }