$wgRCFeeds configuration array. $wgRCFeeds makes both the format and
destination of recent change notifications customizable, and allows for
multiple destinations to be specified.
+* (bug 53862) portal-url, currentevents-url and helppage have been removed from the
+ default Sidebar.
=== New features in 1.22 ===
* (bug 44525) mediawiki.jqueryMsg can now parse (whitelisted) HTML elements and attributes.
* Add deferrable update support for callback/closure
* Add TitleMove hook before page renames
* Revision deletion backend code is moved out of SpecialRevisiondelete
+* Add a variable (wgRedactedFunctionArguments) to redact the values sent as certain function
+ parameters from exception stack traces.
+* Added {{REVISIONSIZE}} variable to get the current size of a revision.
=== Bug fixes in 1.22 ===
* Disable Special:PasswordReset when $wgEnableEmail is false. Previously one
* mw.util.tooltipAccessKeyRegexp: The regex now matches "option-" as well.
Support for Mac "option" was added in 1.16, but the regex was never updated.
* (bug 46768) Usernames of blocking users now display correctly, even if numeric.
-* (bug 39590) {{PAGESIZE}} for the current page and self-transclusions now
- show the most up to date result always instead of being a revision behind.
+* (bug 39590) Self-transclusions now show the most up to date result always
+ after save instead of being a revision behind.
* A bias in wfRandomString() toward digits 1-7 has been corrected. Generated
strings will now start with digits 0 and 8-f as often as they should.
* (bug 45371) Removed Parser_LinkHooks and CoreLinkFunctions classes.
*/
$wgShowExceptionDetails = false;
+/**
+ * Array of functions which need parameters redacted from stack traces shown to
+ * clients and logged. Keys are in the format '[class::]function', and the
+ * values should be either an integer or an array of integers. These are the
+ * indexes of the parameters which need to be kept secret.
+ * @since 1.22
+ */
+$wgRedactedFunctionArguments = array(
+ 'AuthPlugin::setPassword' => 1,
+ 'AuthPlugin::authenticate' => 1,
+ 'AuthPlugin::addUser' => 1,
+
+ 'DatabaseBase::__construct' => 2,
+ 'DatabaseBase::open' => 2,
+
+ 'SpecialChangeEmail::attemptChange' => 1,
+ 'SpecialChangePassword::attemptReset' => 0,
+
+ 'User::setPassword' => 0,
+ 'User::setInternalPassword' => 0,
+ 'User::checkPassword' => 0,
+ 'User::setNewpassword' => 0,
+ 'User::comparePasswords' => array( 0, 1 ),
+ 'User::checkTemporaryPassword' => 0,
+ 'User::setToken' => 0,
+ 'User::crypt' => 0,
+ 'User::oldCrypt' => 0,
+ 'User::getPasswordValidity' => 0,
+ 'User::isValidPassword' => 0,
+);
+
/**
* If true, show a backtrace for database errors
*/
if ( $wgShowExceptionDetails ) {
return '<p>' . nl2br( htmlspecialchars( $this->getMessage() ) ) .
- '</p><p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) ) .
+ '</p><p>Backtrace:</p><p>' . nl2br( htmlspecialchars( MWExceptionHandler::formatRedactedTrace( $this ) ) ) .
"</p>\n";
} else {
return "<div class=\"errorbox\">" .
if ( $wgShowExceptionDetails ) {
return $this->getMessage() .
- "\nBacktrace:\n" . $this->getTraceAsString() . "\n";
+ "\nBacktrace:\n" . MWExceptionHandler::formatRedactedTrace( $this ) . "\n";
} else {
return "Set \$wgShowExceptionDetails = true; " .
"in LocalSettings.php to show detailed debugging information.\n";
$log = $this->getLogMessage();
if ( $log ) {
if ( $wgLogExceptionBacktrace ) {
- wfDebugLog( 'exception', $log . "\n" . $this->getTraceAsString() . "\n" );
+ wfDebugLog( 'exception', $log . "\n" . MWExceptionHandler::formatRedactedTrace( $this ) . "\n" );
} else {
wfDebugLog( 'exception', $log );
}
$message = "MediaWiki internal error.\n\n";
if ( $wgShowExceptionDetails ) {
- $message .= 'Original exception: ' . $e->__toString() . "\n\n" .
+ $message .= 'Original exception: ' . self::formatRedactedTrace( $e ) . "\n\n" .
'Exception caught inside exception handler: ' . $e2->__toString();
} else {
$message .= "Exception caught inside exception handler.\n\n" .
}
}
} else {
- $message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\"\n" .
- $e->__toString() . "\n";
+ $message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\"";
if ( $wgShowExceptionDetails ) {
- $message .= "\n" . $e->getTraceAsString() . "\n";
+ $message .= "\nexception '" . get_class( $e ) . "' in " . $e->getFile() . ":" . $e->getLine() . "\nStack trace:\n" . self::formatRedactedTrace( $e ) . "\n";
}
if ( $cmdLine ) {
// Exit value should be nonzero for the benefit of shell jobs
exit( 1 );
}
+
+ /**
+ * Get the stack trace from the exception as a string, redacting certain function arguments in the process
+ * @param Exception $e The exception
+ * @return string The stack trace as a string
+ */
+ public static function formatRedactedTrace( Exception $e ) {
+ global $wgRedactedFunctionArguments;
+ $finalExceptionText = '';
+
+ foreach ( $e->getTrace() as $i => $call ) {
+ $checkFor = array();
+ if ( isset( $call['class'] ) ) {
+ $checkFor[] = $call['class'] . '::' . $call['function'];
+ foreach ( class_parents( $call['class'] ) as $parent ) {
+ $checkFor[] = $parent . '::' . $call['function'];
+ }
+ } else {
+ $checkFor[] = $call['function'];
+ }
+
+ foreach ( $checkFor as $check ) {
+ if ( isset( $wgRedactedFunctionArguments[$check] ) ) {
+ foreach ( (array)$wgRedactedFunctionArguments[$check] as $argNo ) {
+ $call['args'][$argNo] = 'REDACTED';
+ }
+ }
+ }
+
+ $finalExceptionText .= "#{$i} {$call['file']}({$call['line']}): ";
+ if ( isset( $call['class'] ) ) {
+ $finalExceptionText .= $call['class'] . $call['type'] . $call['function'];
+ } else {
+ $finalExceptionText .= $call['function'];
+ }
+ $args = array();
+ foreach ( $call['args'] as $arg ) {
+ if ( is_object( $arg ) ) {
+ $args[] = 'Object(' . get_class( $arg ) . ')';
+ } elseif( is_array( $arg ) ) {
+ $args[] = 'Array';
+ } else {
+ $args[] = var_export( $arg, true );
+ }
+ }
+ $finalExceptionText .= '(' . implode( ', ', $args ) . ")\n";
+ }
+ return $finalExceptionText . '#' . ( $i + 1 ) . ' {main}';
+ }
}
'revisionyear',
'revisiontimestamp',
'revisionuser',
+ 'revisionsize',
'subpagename',
'subpagenamee',
'talkspace',
*/
static function isUtf8( $value, $disableMbstring = false ) {
$value = (string)$value;
- if ( preg_match( "/[\x80-\xff]/S", $value ) === 0 ) {
- // String contains only ASCII characters, has to be valid
- return true;
- }
// If the mbstring extension is loaded, use it. However, before PHP 5.4, values above
// U+10FFFF are incorrectly allowed, so we have to check for them separately.
( $newPHP || preg_match( "/\xf4[\x90-\xbf]|[\xf5-\xff]/S", $value ) === 0 );
}
+ if ( preg_match( "/[\x80-\xff]/S", $value ) === 0 ) {
+ // String contains only ASCII characters, has to be valid
+ return true;
+ }
+
// PCRE implements repetition using recursion; to avoid a stack overflow (and segfault)
// for large input, we check for invalid sequences (<= 5 bytes) rather than valid
// sequences, which can be as long as the input string is. Multiple short regexes are
* @param array $what what updates to perform
*/
public function doUpdates( $what = array( 'core', 'extensions', 'stats' ) ) {
- global $wgVersion, $wgLocalisationCacheConf;
+ global $wgVersion;
$this->db->begin( __METHOD__ );
$what = array_flip( $what );
$this->checkStats();
}
- if ( isset( $what['purge'] ) ) {
- $this->purgeCache();
-
- if ( $wgLocalisationCacheConf['manualRecache'] ) {
- $this->rebuildLocalisationCache();
- }
- }
-
$this->setAppliedUpdates( $wgVersion, $this->updates );
if ( $this->fileHandle ) {
$page = $title->getPrefixedText();
$length = 0;
- if ( $title->equals( $parser->getTitle() )
- && $parser->mInputSize !== false
- ) {
- # We are on current page (and not in PST), so
- # take length of input to parser.
- $length = $parser->mInputSize;
- } elseif ( isset( $cache[$page] ) ) {
+ if ( isset( $cache[$page] ) ) {
$length = $cache[$page];
} elseif ( $parser->incrementExpensiveFunctionCount() ) {
$rev = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
var $mRevisionId; # ID to display in {{REVISIONID}} tags
var $mRevisionTimestamp; # The timestamp of the specified revision ID
var $mRevisionUser; # User to display in {{REVISIONUSER}} tag
+ var $mRevisionSize; # Size to display in {{REVISIONSIZE}} variable
var $mRevIdForTs; # The revision ID which was used to fetch the timestamp
var $mInputSize = false; # For {{PAGESIZE}} on current page.
$this->mLinkHolders = new LinkHolderArray( $this );
$this->mLinkID = 0;
$this->mRevisionObject = $this->mRevisionTimestamp =
- $this->mRevisionId = $this->mRevisionUser = null;
+ $this->mRevisionId = $this->mRevisionUser = $this->mRevisionSize = null;
$this->mVarCache = array();
$this->mUser = null;
$this->mLangLinkLanguages = array();
$oldRevisionObject = $this->mRevisionObject;
$oldRevisionTimestamp = $this->mRevisionTimestamp;
$oldRevisionUser = $this->mRevisionUser;
+ $oldRevisionSize = $this->mRevisionSize;
if ( $revid !== null ) {
$this->mRevisionId = $revid;
$this->mRevisionObject = null;
$this->mRevisionTimestamp = null;
$this->mRevisionUser = null;
+ $this->mRevisionSize = null;
}
wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
$this->mRevisionObject = $oldRevisionObject;
$this->mRevisionTimestamp = $oldRevisionTimestamp;
$this->mRevisionUser = $oldRevisionUser;
+ $this->mRevisionSize = $oldRevisionSize;
$this->mInputSize = false;
wfProfileOut( $fname );
wfProfileOut( __METHOD__ );
wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-revision...\n" );
$value = $this->getRevisionUser();
break;
+ case 'revisionsize':
+ # Let the edit saving system know we should parse the page
+ # *after* a revision ID has been assigned. This is for null edits.
+ $this->mOutput->setFlag( 'vary-revision' );
+ wfDebug( __METHOD__ . ": {{REVISIONSIZE}} used, setting vary-revision...\n" );
+ $value = $this->getRevisionSize();
+ break;
case 'namespace':
$value = str_replace( '_', ' ', $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
break;
return $this->mRevisionUser;
}
+ /**
+ * Get the size of the revision
+ *
+ * @return int|null revision size
+ */
+ function getRevisionSize() {
+ if ( is_null( $this->mRevisionSize ) ) {
+ $revObject = $this->getRevisionObject();
+
+ # if this variable is subst: the revision id will be blank,
+ # so just use the parser input size, because the own substituation
+ # will change the size.
+ if ( $revObject ) {
+ $this->mRevisionSize = $revObject->getSize();
+ } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
+ $this->mRevisionSize = $this->mInputSize;
+ }
+ }
+ return $this->mRevisionSize;
+ }
+
/**
* Mutator for $mDefaultSort
*
$xml = UtfNormal::cleanUp( $xml );
// 1 << 19 == XML_PARSE_HUGE, needed so newer versions of libxml2 don't barf when the XML is >256 levels deep
$result = $dom->loadXML( $xml, 1 << 19 );
- if ( !$result ) {
- wfProfileOut( __METHOD__ . '-loadXML' );
- if ( $cacheable ) {
- wfProfileOut( __METHOD__ . '-cacheable' );
- }
- wfProfileOut( __METHOD__ );
- throw new MWException( __METHOD__ . ' generated invalid XML' );
- }
}
- $obj = new PPNode_DOM( $dom->documentElement );
+ if ( $result ) {
+ $obj = new PPNode_DOM( $dom->documentElement );
+ }
wfProfileOut( __METHOD__ . '-loadXML' );
+
if ( $cacheable ) {
wfProfileOut( __METHOD__ . '-cacheable' );
}
+
wfProfileOut( __METHOD__ );
+
+ if ( !$result ) {
+ throw new MWException( __METHOD__ . ' generated invalid XML' );
+ }
return $obj;
}
* @return String: highlighted text snippet, null (and not '') if not supported
*/
function getTextSnippet( $terms ) {
- global $wgUser, $wgAdvancedSearchHighlighting;
+ global $wgAdvancedSearchHighlighting;
$this->initText();
// TODO: make highliter take a content object. Make ContentHandler a factory for SearchHighliter.
- list( $contextlines, $contextchars ) = SearchEngine::userHighlightPrefs( $wgUser );
+ list( $contextlines, $contextchars ) = SearchEngine::userHighlightPrefs();
$h = new SearchHighlighter();
if ( $wgAdvancedSearchHighlighting ) {
return $h->highlightText( $this->mText, $terms, $contextlines, $contextchars );
'revisionyear' => array( 1, 'REVISIONYEAR' ),
'revisiontimestamp' => array( 1, 'REVISIONTIMESTAMP' ),
'revisionuser' => array( 1, 'REVISIONUSER' ),
+ 'revisionsize' => array( 1, 'REVISIONSIZE' ),
'plural' => array( 0, 'PLURAL:' ),
'fullurl' => array( 0, 'FULLURL:' ),
'fullurle' => array( 0, 'FULLURLE:' ),
rc_this_oldid INT DEFAULT 0,
rc_last_oldid INT DEFAULT 0,
rc_type tinyint DEFAULT 0,
- rc_moved_to_ns BIT DEFAULT 0,
- rc_moved_to_title NVARCHAR(255) DEFAULT '',
rc_patrolled BIT DEFAULT 0,
rc_ip NCHAR(40) DEFAULT '',
rc_old_len INT DEFAULT 0,
rc_this_oldid NUMBER DEFAULT 0 NOT NULL,
rc_last_oldid NUMBER DEFAULT 0 NOT NULL,
rc_type CHAR(1) DEFAULT '0' NOT NULL,
- rc_moved_to_ns NUMBER DEFAULT 0 NOT NULL,
- rc_moved_to_title VARCHAR2(255),
rc_patrolled CHAR(1) DEFAULT '0' NOT NULL,
rc_ip VARCHAR2(15),
rc_old_len NUMBER,
rc_this_oldid INTEGER NOT NULL,
rc_last_oldid INTEGER NOT NULL,
rc_type SMALLINT NOT NULL DEFAULT 0,
- rc_moved_to_ns SMALLINT,
- rc_moved_to_title TEXT,
rc_patrolled SMALLINT NOT NULL DEFAULT 0,
rc_ip CIDR,
rc_old_len INTEGER,
--- /dev/null
+<?php
+require_once __DIR__ . '/Maintenance.php';
+
+/**
+ * Fixes all rows affected by https://bugzilla.wikimedia.org/show_bug.cgi?id=37714
+ */
+class TidyUpBug37714 extends Maintenance {
+ public function execute() {
+ // Search for all log entries which are about changing the visability of other log entries.
+ $result = wfGetDB( DB_SLAVE )->select(
+ 'logging',
+ array( 'log_id', 'log_params' ),
+ array(
+ 'log_type' => array( 'suppress', 'delete' ),
+ 'log_action' => 'event',
+ 'log_namespace' => NS_SPECIAL,
+ 'log_title' => SpecialPage::getTitleFor( 'Log' )->getText()
+ ),
+ __METHOD__
+ );
+
+ foreach ( $result as $row ) {
+ $paramLines = explode( "\n", $row->log_params );
+ $ids = explode( ',', $paramLines[0] ); // Array dereferencing is PHP >= 5.4 :(
+ $result = wfGetDB( DB_SLAVE )->select( // Work out what log entries were changed here.
+ 'logging',
+ 'log_type',
+ array( 'log_id' => $ids ),
+ __METHOD__,
+ 'DISTINCT'
+ );
+ if ( $result->numRows() === 1 ) {
+ // If there's only one type, the target title can be set to include it.
+ $logTitle = SpecialPage::getTitleFor( 'Log', $result->current()->log_type )->getText();
+ $this->output( 'Set log_title to "' . $logTitle . '" for log entry ' . $row->log_id . ".\n" );
+ wfGetDB( DB_MASTER )->update(
+ 'logging',
+ array( 'log_title' => $logTitle ),
+ array( 'log_id' => $row->log_id ),
+ __METHOD__
+ );
+ wfWaitForSlaves();
+ }
+ }
+ }
+}
+
+$maintClass = 'TidyUpBug37714';
+require_once RUN_MAINTENANCE_IF_MAIN;
$updates[] = 'noschema';
}
$updates[] = 'stats';
-
- if ( !$this->hasOption( 'nopurge' ) ) {
- $updates[] = 'purge';
- }
}
$updater = DatabaseUpdater::newForDb( $db, $shared, $this );
$redirectedLocation = false;
if ( !$isTemp ) {
// Check for file redirect
- if ( $isOld ) {
- // Since redirects are associated with pages, not versions of files,
- // we look for the most current version to see if its a redirect.
- $possibleRedirFile = RepoGroup::singleton()->getLocalRepo()->findFile( $img->getName() );
- } else {
- $possibleRedirFile = RepoGroup::singleton()->getLocalRepo()->findFile( $fileName );
- }
- if ( $possibleRedirFile && !is_null( $possibleRedirFile->getRedirected() ) ) {
- $redirTarget = $possibleRedirFile->getName();
+ // Since redirects are associated with pages, not versions of files,
+ // we look for the most current version to see if its a redirect.
+ $possRedirFile = RepoGroup::singleton()->getLocalRepo()->findFile( $img->getName() );
+ if ( $possRedirFile && !is_null( $possRedirFile->getRedirected() ) ) {
+ $redirTarget = $possRedirFile->getName();
$targetFile = wfLocalFile( Title::makeTitleSafe( NS_FILE, $redirTarget ) );
if ( $targetFile->exists() ) {
$newThumbName = $targetFile->thumbName( $params );
if ( $isOld ) {
- $newThumbUrl = $targetFile->getArchiveThumbUrl( $bits[0] . '!' . $targetFile->getName(), $newThumbName );
+ $newThumbUrl = $targetFile->getArchiveThumbUrl(
+ $bits[0] . '!' . $targetFile->getName(), $newThumbName );
} else {
$newThumbUrl = $targetFile->getThumbUrl( $newThumbName );
}