#
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
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 ==
}
/**
- * 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()
+ );
}
}
/** @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 );
*/
public function setInternalPassword( $str ) {
$this->setToken();
+ $this->setOption( 'watchlisttoken', false );
$passwordFactory = self::getPasswordFactory();
$this->mPassword = $passwordFactory->newFromPlaintext( $str );
* @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;
}
"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 <var>$1type</var>. Wildcard actions like <kbd>action/*</kbd> allows to specify any string for the asterisk.",
+ "apihelp-query+logevents-param-action": "Filter log actions to only this action. Overrides <var>$1type</var>. In the list of possible values, values with the asterisk wildcard such as <kbd>action/*</kbd> 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.",
namespace MediaWiki\Logger;
use DateTimeZone;
+use Exception;
use MWDebug;
use MWExceptionHandler;
use Psr\Log\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 );
return $item->format( 'c' );
}
- if ( $item instanceof \Exception ) {
+ if ( $item instanceof Exception ) {
return '[Exception ' . get_class( $item ) . '( ' .
$item->getFile() . ':' . $item->getLine() . ') ' .
$item->getMessage() . ']';
/**
* 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 <bd808@wikimedia.org>
}
+ /**
+ * {@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.
*
* @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;
}
}
*/
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' );
}
/**
}
/**
+ * 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:
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.
/**
- * 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 = <<<TXT
+ if ( $level === null ) {
+ // Called as a shutdown handler, get data from error_get_last()
+ if ( static::$handledFatalCallback ) {
+ // Already called once (probably as an error handler callback
+ // under HHVM) so don't log again.
+ return false;
+ }
+
+ $lastError = error_get_last();
+ if ( $lastError !== null ) {
+ $level = $lastError['type'];
+ $message = $lastError['message'];
+ $file = $lastError['file'];
+ $line = $lastError['line'];
+ } else {
+ $level = 0;
+ $message = '';
+ }
+ }
+
+ if ( !in_array( $level, self::$fatalErrorTypes ) ) {
+ // Only interested in fatal errors, others should have been
+ // handled by MWExceptionHandler::handleError
+ return false;
+ }
+
+ $msg = "[{exception_id}] PHP Fatal Error: {$message}";
+
+ // Look at message to see if this is a class not found failure
+ // HHVM: Class undefined: foo
+ // PHP5: Class 'foo' not found
+ if ( preg_match( "/Class (undefined: \w+|'\w+' not found)/", $msg ) ) {
+ // @codingStandardsIgnoreStart Generic.Files.LineLength.TooLong
+ $msg = <<<TXT
{$msg}
MediaWiki or an installed extension requires this class but it is not embedded directly in MediaWiki's git repository and must be installed separately by the end user.
Please see <a href="https://www.mediawiki.org/wiki/Download_from_Git#Fetch_external_libraries">mediawiki.org</a> 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;
}
/**
*
* @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'] ) ) {
}
$level = $level + 1;
- $text .= "#{$level} {main}";
+ $text .= "{$pad}#{$level} {main}";
return $text;
}
* @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 ) {
}, $frame['args'] );
}
return $frame;
- }, $e->getTrace() );
+ }, $trace );
}
/**
public static function getLogContext( Exception $e ) {
return array(
'exception' => $e,
+ 'exception_id' => static::getLogId( $e ),
);
}
*/
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 );
# 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 )
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 );
if ( $subtype == 'DJVU' ) {
wfDebug( __METHOD__ . ": found first subpage\n" );
- return $this->getPageInfo( $file, $length );
+ return $this->getPageInfo( $file );
}
$this->skipChunk( $file, $length - 4 );
} else {
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" );
* Let users reset tokens like the watchlist token.
*
* @ingroup SpecialPage
+ * @deprecated 1.26
*/
class SpecialResetTokens extends FormSpecialPage {
private $tokensList;
"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** 不能接受的使用者名",
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|
('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,''),
'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',
$editform = $( '#editform' );
$textbox = $editform.find( '#wpTextbox1' );
$summary = $editform.find( '#wpSummary' );
+ $spinner = $( '.mw-spinner-preview' );
$errorBox = $( '.errorbox' );
section = $editform.find( '[name="wpSection"]' ).val();
// 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)
mw.hook( 'wikipage.editform' ).fire( $editform );
} );
request.always( function () {
- $spinner.remove();
+ $spinner.hide();
$copyElements.animate( {
opacity: 1
}, 'fast' );
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="21.059" height="21.06"><path fill="#575757" d="M10.529 0c-5.814 0-10.529 4.714-10.529 10.529s4.715 10.53 10.529 10.53c5.816 0 10.529-4.715 10.529-10.53s-4.712-10.529-10.529-10.529zm-.002 16.767c-.861 0-1.498-.688-1.498-1.516 0-.862.637-1.534 1.498-1.534.828 0 1.5.672 1.5 1.534 0 .827-.672 1.516-1.5 1.516zm2.137-6.512c-.723.568-1 .931-1 1.739v.5h-2.205v-.603c0-1.517.449-2.136 1.154-2.688.707-.552 1.139-.845 1.139-1.637 0-.672-.414-1.051-1.24-1.051-.707 0-1.328.189-1.982.638l-1.051-1.807c.861-.604 1.93-1.034 3.342-1.034 1.912 0 3.516 1.051 3.516 3.066-.001 1.43-.794 2.188-1.673 2.877z"/></svg>
\ No newline at end of file
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;
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="21.059" height="21.06"><path fill="#575757" d="M10.529 0c-5.814 0-10.529 4.714-10.529 10.529s4.715 10.53 10.529 10.53c5.816 0 10.529-4.715 10.529-10.53s-4.712-10.529-10.529-10.529zm-.002 16.767c-.861 0-1.498-.688-1.498-1.516 0-.862.637-1.534 1.498-1.534.828 0 1.5.672 1.5 1.534 0 .827-.672 1.516-1.5 1.516zm2.137-6.512c-.723.568-1 .931-1 1.739v.5h-2.205v-.603c0-1.517.449-2.136 1.154-2.688.707-.552 1.139-.845 1.139-1.637 0-.672-.414-1.051-1.24-1.051-.707 0-1.328.189-1.982.638l-1.051-1.807c.861-.604 1.93-1.034 3.342-1.034 1.912 0 3.516 1.051 3.516 3.066-.001 1.43-.794 2.188-1.673 2.877z"/></svg>
\ No newline at end of file
--- /dev/null
+/* 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);
+}
*
* @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
// text, so we'd need to $.globalEval, which then messes up line numbers.
crossDomain: true,
cache: true
- } ).always( callback );
+ } );
}
/**
return;
}
- addScript( arr[ i ], function () {
+ addScript( arr[ i ] ).always( function () {
nestedAddScript( arr, callback, i + 1 );
} );
};
}
}
- // 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 ) );
)
);
$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 );
}
/**
)
);
$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 );
}
}