images/[0-9a-f]
images/temp
images/thumb
+maintenance/dev/data/
+maintenance/.mweval_history
\ No newline at end of file
* Meno25
* Rotem Liss
* Shinjiman
-* Translatewiki.net Translators http://translatewiki.net/wiki/Special:ListUsers/translator
+* Translatewiki.net Translators https://translatewiki.net/wiki/Special:ListUsers/translator
-2008-11-11
-
For system requirements, installation and upgrade details, see the files
RELEASE-NOTES, INSTALL, and UPGRADE.
http://www.mediawiki.org/
-The code is currently maintained in a Subversion repository at
-svn.wikimedia.org. See http://www.mediawiki.org/wiki/Subversion for details.
+The code is currently maintained in a Git repository at
+gerrit.wikimedia.org. See http://www.mediawiki.org/wiki/Git for details.
Please report bugs and make feature requests in our Bugzilla system:
=== Changes since 1.19 beta 1 ===
* (bug 35014) Including a special page no longer sets the page's title to the
- included page
-* (bug 35019) Edit summaries are no longer transformed in notification e-mails
-* (bug 35152) Help message for e-mail is shown again in user preferences
+ included page.
+* (bug 35019) Edit summaries are no longer transformed in notification e-mails.
+* (bug 35152) Help message for e-mail is shown again in user preferences.
* (bug 34887) $3 and $4 parameters are now substituted correctly in message
- "movepage-moved"
+ "movepage-moved".
* (bug 34841) Edit links are no longer displayed when display old page versions
-* (bug 34889) User name should be normalized on Special:Contributions
-* (bug 35051) If heading has a trailing space after == then its name is not
- preloaded into edit summary on section edit
+* (bug 34889) User name should be normalized on Special:Contributions.
+* (bug 35051) If heading has a trailing space after == then its name is not
+ preloaded into edit summary on section edit.
* (bug 31417) New ID mw-content-text around the actual page text, without categories,
contentSub, ... The same div often also contains the class mw-content-ltr/rtl.
* (bug 35303) Proxy and DNS blacklist blocking works again
core parser functions which operate on strings, such as padleft.
* (bug 18295) Don't expose strip markers when a tag appears inside a link
inside a heading.
+* (bug 34907) Fixed exposure of tokens through load.php that could have facilitated
+ CSRF attacks
+* Special:Watchlist no longer sets links to feed when the user is anonymous
=== Configuration changes in 1.19 ===
* Removed SkinTemplateSetupPageCss hook; use BeforePageDisplay instead.
* (bug 32239) Removed $wgEnableTooltipsAndAccesskeys.
* Removed $wgVectorShowVariantName.
* Removed $wgExtensionAliasesFiles. Use $wgExtensionMessagesFiles.
+* Removed $wgResourceLoaderInlinePrivateModules, now always enabled.
=== New features in 1.19 ===
* (bug 19838) Add ability to get all interwiki prefixes also if the interwiki
200 status code instead of 404 for nonexistent articles.
* (bug 33447) Link to the broken image tracking category from Special:Wantedfiles.
* (bug 27724) Add timestamp to job queue.
-* (bug 30339) Implement SpecialPage for running javascript tests. Disabled by default, due to
- tests potentially being harmful, not to be run on a production wiki.
+* (bug 30339) Implement SpecialPage for running javascript tests. Disabled by default,
+ due to tests potentially being harmful, not to be run on a production wiki.
Enable by setting $wgEnableJavaScriptTest to true.
* Extensions can use the RequestContextCreateSkin hook to override what skin is
loaded in some contexts.
* Special:MovePage now has a dropdown menu for namespaces.
* (bug 34420) Special:Version now shows git HEAD sha1 when available.
* (bug 33952) Refactor mw.toolbar to allow dynamic additions at any time.
-* Now possible to specify separate section title and edit summary when adding a new section to a
- page via the edit API action.
+* Now possible to specify separate section title and edit summary when adding
+ a new section to a page via the edit API action.
=== Bug fixes in 1.19 ===
* $wgUploadNavigationUrl should be used for file redlinks if.
* (bug 34735) Updated compressOld.php documentation to mention the different
usages of -s and -n parameters depending on compression type
* (bug 13896) Rendering of devanagari numbers in automatic '#' number lists
+* (bug 18704) Add an unique CSS class or ID to the tagfilter table row at RecentChanges
=== API changes in 1.20 ===
* (bug 34316) Add ability to retrieve maximum upload size from MediaWiki API.
// Initialise common code.
if ( isset( $_SERVER['MW_COMPILED'] ) ) {
- require ( 'phase3/includes/WebStart.php' );
+ require ( 'core/includes/WebStart.php' );
} else {
require ( dirname( __FILE__ ) . '/includes/WebStart.php' );
}
For information about the MediaWiki database layout, such as a
description of the tables and their contents, please see:
http://www.mediawiki.org/wiki/Manual:Database_layout
- http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/maintenance/tables.sql?view=markup
+ https://gerrit.wikimedia.org/r/gitweb?p=mediawiki/core.git;a=blob_plain;f=maintenance/tables.sql;hb=HEAD
------------------------------------------------------------------------
--- /dev/null
+*
+!README
+!.gitignore
define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
if ( isset( $_SERVER['MW_COMPILED'] ) ) {
- require ( 'phase3/includes/WebStart.php' );
+ require ( 'core/includes/WebStart.php' );
} else {
require ( dirname( __FILE__ ) . '/includes/WebStart.php' );
}
'FormAction' => 'includes/Action.php',
'FormOptions' => 'includes/FormOptions.php',
'FormSpecialPage' => 'includes/SpecialPage.php',
+ 'GitInfo' => 'includes/GitInfo.php',
'HashtableReplacer' => 'includes/StringUtils.php',
'HistoryBlob' => 'includes/HistoryBlob.php',
'HistoryBlobCurStub' => 'includes/HistoryBlob.php',
return $fullForm ? '' : array();
$data = array( Html::rawElement( 'label', array( 'for' => 'tagfilter' ), wfMsgExt( 'tag-filter', 'parseinline' ) ),
- Xml::input( 'tagfilter', 20, $selected ) );
+ Xml::input( 'tagfilter', 20, $selected ), array( 'class' => 'mw-tagfilter-input' ) );
if ( !$fullForm ) {
return $data;
$html = implode( ' ', $data );
$html .= "\n" . Xml::element( 'input', array( 'type' => 'submit', 'value' => wfMsg( 'tag-filter-submit' ) ) );
$html .= "\n" . Html::hidden( 'title', $title->getPrefixedText() );
- $html = Xml::tags( 'form', array( 'action' => $title->getLocalURL(), 'method' => 'get' ), $html );
+ $html = Xml::tags( 'form', array( 'action' => $title->getLocalURL(), 'class' => 'mw-tagfilter-form', 'method' => 'get' ), $html );
return $html;
}
/**
* Randomly hash data while mixing in clock drift data for randomness
*
- * @param $data The data to randomly hash.
+ * @param $data string The data to randomly hash.
* @return String The hashed bytes
* @author Tim Starling
*/
/**
* Return a rolling random state initially build using data from unstable sources
- * @return A new weak random state
+ * @return string A new weak random state
*/
protected function randomState() {
static $state = null;
/**
* Decide on the best acceptable hash algorithm we have available for hash()
+ * @throws MWException
* @return String A hash algorithm
*/
protected function hashAlgo() {
* Generate an acceptably unstable one-way-hash of some text
* making use of the best hash algorithm that we have available.
*
+ * @param $data string
* @return String A raw hash of the data
*/
protected function hash( $data ) {
* Generate an acceptably unstable one-way-hmac of some text
* making use of the best hash algorithm that we have available.
*
+ * @param $data string
+ * @param $key string
* @return String A raw hash of the data
*/
protected function hmac( $data, $key ) {
if ( $iv === false ) {
wfDebug( __METHOD__ . ": mcrypt_create_iv returned false.\n" );
} else {
- $bytes .= $iv;
+ $buffer .= $iv;
wfDebug( __METHOD__ . ": mcrypt_create_iv generated " . strlen( $iv ) . " bytes of randomness.\n" );
}
wfProfileOut( __METHOD__ . '-mcrypt' );
/**
* Return a singleton instance of MWCryptRand
+ * @return MWCryptRand
*/
protected static function singleton() {
if ( is_null( self::$singleton ) ) {
}
}
- if ( $row->rev_sha1 ) {
- $out .= " " . Xml::element('sha1', null, strval( $row->rev_sha1 ) ) . "\n";
- } else {
- $out .= " <sha1/>\n";
- }
-
if ( $row->page_restrictions != '' ) {
$out .= ' ' . Xml::element( 'restrictions', array(),
strval( $row->page_restrictions ) ) . "\n";
"" ) . "\n";
}
+ if ( $row->rev_sha1 && !( $row->rev_deleted & Revision::DELETED_TEXT ) ) {
+ $out .= " " . Xml::element('sha1', null, strval( $row->rev_sha1 ) ) . "\n";
+ } else {
+ $out .= " <sha1/>\n";
+ }
+
wfRunHooks( 'XmlDumpWriterWriteRevision', array( &$this, &$out, $row, $text ) );
$out .= " </revision>\n";
--- /dev/null
+<?php
+/**
+ * A class to help return information about a git repo MediaWiki may be inside
+ * This is used by Special:Version and is also useful for the LocalSettings.php
+ * of anyone working on large branches in git to setup config that show up only
+ * when specific branches are currently checked out.
+ *
+ * @file
+ */
+
+class GitInfo {
+
+ /**
+ * Singleton for the repo at $IP
+ */
+ protected static $repo = null;
+
+ /**
+ * Location of the .git directory
+ */
+ protected $basedir;
+
+ /**
+ * @param $dir The root directory of the repo where the .git dir can be found
+ */
+ public function __construct( $dir ) {
+ $this->basedir = "{$dir}/.git/";
+ }
+
+ /**
+ * Return a singleton for the repo at $IP
+ * @return GitInfo
+ */
+ public static function repo() {
+ global $IP;
+ if ( is_null( self::$repo ) ) {
+ self::$repo = new self( $IP );
+ }
+ return self::$repo;
+ }
+
+ /**
+ * Check if a string looks like a hex encoded SHA1 hash
+ *
+ * @param $str The string to check
+ * @return bool Whether or not the string looks like a SHA1
+ */
+ public static function isSHA1( $str ) {
+ return !!preg_match( '/^[0-9A-Z]{40}$/i', $str );
+ }
+
+ /**
+ * Return the HEAD of the repo (without any opening "ref: ")
+ * @return string The HEAD
+ */
+ public function getHead() {
+ $HEADfile = "{$this->basedir}/HEAD";
+
+ if ( !is_readable( $HEADfile ) ) {
+ return false;
+ }
+
+ $HEAD = file_get_contents( $HEADfile );
+
+ if ( preg_match( "/ref: (.*)/", $HEAD, $m ) ) {
+ return rtrim( $m[1] );
+ } else {
+ return $HEAD;
+ }
+ }
+
+ /**
+ * Return the SHA1 for the current HEAD of the repo
+ * @return string A SHA1 or false
+ */
+ public function getHeadSHA1() {
+ $HEAD = $this->getHead();
+
+ // If detached HEAD may be a SHA1
+ if ( self::isSHA1( $HEAD ) ) {
+ return $HEAD;
+ }
+
+ // If not a SHA1 it may be a ref:
+ $REFfile = "{$this->basedir}{$HEAD}";
+ if ( !is_readable( $REFfile ) ) {
+ return false;
+ }
+
+ $sha1 = rtrim( file_get_contents( $REFfile ) );
+
+ return $sha1;
+ }
+
+ /**
+ * Return the name of the current branch, or HEAD if not found
+ * @return string The branch name, HEAD, or false
+ */
+ public function getCurrentBranch() {
+ $HEAD = $this->getHead();
+ if ( $HEAD && preg_match( "#^refs/heads/(.*)$#", $HEAD, $m ) ) {
+ return $m[1];
+ } else {
+ return $HEAD;
+ }
+ }
+
+ /**
+ * @see self::getHeadSHA1
+ */
+ public static function headSHA1() {
+ return self::repo()->getHeadSHA1();
+ }
+
+ /**
+ * @see self::getCurrentBranch
+ */
+ public static function currentBranch() {
+ return self::repo()->getCurrentBranch();
+ }
+
+}
\ No newline at end of file
/**
* The here's-one-I-made-earlier option: do the submission if
- * posted, or display the form with or without funky valiation
+ * posted, or display the form with or without funky validation
* errors
* @return Bool or Status whether submission was successful.
*/
$this->prepareForm();
$result = $this->tryAuthorizedSubmit();
- if ( $result === true || ( $result instanceof Status && $result->isGood() ) ){
+ if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
return $result;
}
return ChangesList::showCharacterDifference( $old, $new );
}
- public static function checkIPAddress( $ip ) {
+ private static function checkIPAddress( $ip ) {
global $wgRequest;
if ( $ip ) {
if ( !IP::isIPAddress( $ip ) ) {
$count = $wgMemc->get( $key );
// Already pinged?
if( $count ) {
- if( $count > $max ) {
+ if( $count >= $max ) {
wfDebug( __METHOD__ . ": tripped! $key at $count $summary\n" );
if( $wgRateLimitLog ) {
wfSuppressWarnings();
$dbType = strtolower( $dbType );
$class = 'Database' . ucfirst( $dbType );
- if( in_array( $dbType, $canonicalDBTypes ) ) {
+ if( in_array( $dbType, $canonicalDBTypes ) || ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) ) {
return new $class(
isset( $p['host'] ) ? $p['host'] : false,
isset( $p['user'] ) ? $p['user'] : false,
isset( $p['flags'] ) ? $p['flags'] : 0,
isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global'
);
- } elseif ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) {
- return new $class( $p );
} else {
return null;
}
# Replace reserved words with better ones
switch( $name ) {
case 'user':
- return 'mwuser';
+ return $this->realTableName( 'mwuser', $format );
case 'text':
- return 'pagecontent';
+ return $this->realTableName( 'pagecontent', $format );
default:
- return parent::tableName( $name, $format );
+ return $this->realTableName( $name, $format );
}
}
+ /* Don't cheat on installer */
+ function realTableName( $name, $format = 'quoted' ) {
+ return parent::tableName( $name, $format );
+ }
+
/**
* Return the next in a sequence, save the value for retrieval via insertId()
* @return null
if ( !$schema ) {
$schema = $this->getCoreSchema();
}
- $table = $this->tableName( $table, 'raw' );
+ $table = $this->realTableName( $table, 'raw' );
$etable = $this->addQuotes( $table );
$eschema = $this->addQuotes( $schema );
$SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
return $this->backend;
}
+ /**
+ * Get an explanatory message if this repo is read-only
+ *
+ * @return string|bool Returns false if the repo is not read-only
+ */
+ public function getReadOnlyReason() {
+ return $this->backend->getReadOnlyReason();
+ }
+
/**
* Prepare a single zone or list of zones for usage.
* See initDeletedDir() for additional setup needed for the 'deleted' zone.
- *
+ *
* @param $doZones Array Only do a particular zones
* @return Status
*/
*/
final public function createInternal( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
if ( strlen( $params['content'] ) > $this->maxFileSizeInternal() ) {
$status = Status::newFatal( 'backend-fail-maxsize',
$params['dst'], $this->maxFileSizeInternal() );
$status = $this->doCreateInternal( $params );
$this->clearCache( array( $params['dst'] ) );
}
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
final public function storeInternal( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
if ( filesize( $params['src'] ) > $this->maxFileSizeInternal() ) {
$status = Status::newFatal( 'backend-fail-store', $params['dst'] );
} else {
$status = $this->doStoreInternal( $params );
$this->clearCache( array( $params['dst'] ) );
}
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
final public function copyInternal( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$status = $this->doCopyInternal( $params );
$this->clearCache( array( $params['dst'] ) );
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
final public function deleteInternal( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$status = $this->doDeleteInternal( $params );
$this->clearCache( array( $params['src'] ) );
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
final public function moveInternal( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$status = $this->doMoveInternal( $params );
$this->clearCache( array( $params['src'], $params['dst'] ) );
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
final public function concatenate( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$status = Status::newGood();
// Try to lock the source files for the scope of this function
$status->merge( $this->doConcatenate( $params ) );
}
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
final protected function doPrepare( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$status = Status::newGood();
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dir'] );
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status; // invalid storage path
}
}
}
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
final protected function doSecure( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$status = Status::newGood();
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dir'] );
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status; // invalid storage path
}
}
}
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
final protected function doClean( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$status = Status::newGood();
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
if ( $dir === null ) {
$status->fatal( 'backend-fail-invalidpath', $params['dir'] );
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status; // invalid storage path
}
$filesLockEx = array( $params['dir'] );
$scopedLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status );
if ( !$status->isOK() ) {
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status; // abort
}
}
}
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
final public function fileExists( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$stat = $this->getFileStat( $params );
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return ( $stat === null ) ? null : (bool)$stat; // null => failure
}
*/
final public function getFileTimestamp( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$stat = $this->getFileStat( $params );
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $stat ? $stat['mtime'] : false;
}
*/
final public function getFileSize( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$stat = $this->getFileStat( $params );
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $stat ? $stat['size'] : false;
}
*/
final public function getFileStat( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$path = self::normalizeStoragePath( $params['src'] );
if ( $path === null ) {
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return false; // invalid storage path
}
// If we want the latest data, check that this cached
// value was in fact fetched with the latest available data.
if ( !$latest || $this->cache[$path]['stat']['latest'] ) {
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $this->cache[$path]['stat'];
}
}
wfProfileIn( __METHOD__ . '-miss' );
+ wfProfileIn( __METHOD__ . '-miss-' . $this->name );
$stat = $this->doGetFileStat( $params );
+ wfProfileOut( __METHOD__ . '-miss-' . $this->name );
wfProfileOut( __METHOD__ . '-miss' );
if ( is_array( $stat ) ) { // don't cache negatives
$this->trimCache(); // limit memory
$this->cache[$path]['stat'] = $stat;
$this->cache[$path]['stat']['latest'] = $latest;
}
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $stat;
}
*/
public function getFileContents( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$tmpFile = $this->getLocalReference( $params );
if ( !$tmpFile ) {
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return false;
}
wfSuppressWarnings();
$data = file_get_contents( $tmpFile->getPath() );
wfRestoreWarnings();
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $data;
}
*/
final public function getFileSha1Base36( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$path = $params['src'];
if ( isset( $this->cache[$path]['sha1'] ) ) {
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $this->cache[$path]['sha1'];
}
wfProfileIn( __METHOD__ . '-miss' );
+ wfProfileIn( __METHOD__ . '-miss-' . $this->name );
$hash = $this->doGetFileSha1Base36( $params );
+ wfProfileOut( __METHOD__ . '-miss-' . $this->name );
wfProfileOut( __METHOD__ . '-miss' );
if ( $hash ) { // don't cache negatives
$this->trimCache(); // limit memory
$this->cache[$path]['sha1'] = $hash;
}
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $hash;
}
*/
final public function getFileProps( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$fsFile = $this->getLocalReference( $params );
$props = $fsFile ? $fsFile->getProps() : FSFile::placeholderProps();
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $props;
}
*/
public function getLocalReference( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$path = $params['src'];
if ( isset( $this->expensiveCache[$path]['localRef'] ) ) {
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $this->expensiveCache[$path]['localRef'];
}
$this->trimExpensiveCache(); // limit memory
$this->expensiveCache[$path]['localRef'] = $tmpFile;
}
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $tmpFile;
}
*/
final public function streamFile( array $params ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$status = Status::newGood();
$info = $this->getFileStat( $params );
// do nothing; client cache is up to date
} elseif ( $res == StreamFile::READY_STREAM ) {
wfProfileIn( __METHOD__ . '-send' );
+ wfProfileIn( __METHOD__ . '-send-' . $this->name );
$status = $this->doStreamFile( $params );
+ wfProfileOut( __METHOD__ . '-send-' . $this->name );
wfProfileOut( __METHOD__ . '-send' );
} else {
$status->fatal( 'backend-fail-stream', $params['src'] );
}
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
protected function doOperationsInternal( array $ops, array $opts ) {
wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
$status = Status::newGood();
// Build up a list of FileOps...
$scopeLockS = $this->getScopedFileLocks( $filesLockSh, LockManager::LOCK_UW, $status );
$scopeLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status );
if ( !$status->isOK() ) {
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status; // abort
}
$status->merge( $subStatus );
$status->success = $subStatus->success; // not done in merge()
+ wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
return $status;
}
*/
function upload( $srcPath, $comment, $pageText, $flags = 0, $props = false, $timestamp = false, $user = null ) {
global $wgContLang;
+
+ if ( $this->getRepo()->getReadOnlyReason() !== false ) {
+ return $this->readOnlyFatalStatus();
+ }
+
// truncate nicely or the DB will do it for us
- // non-nicely (dangling multi-byte chars, non-truncated
- // version in cache).
+ // non-nicely (dangling multi-byte chars, non-truncated version in cache).
$comment = $wgContLang->truncate( $comment, 255 );
$this->lock(); // begin
$status = $this->publish( $srcPath, $flags );
* archive name, or an empty string if it was a new file.
*/
function publishTo( $srcPath, $dstRel, $flags = 0 ) {
+ if ( $this->getRepo()->getReadOnlyReason() !== false ) {
+ return $this->readOnlyFatalStatus();
+ }
+
$this->lock(); // begin
$archiveName = wfTimestamp( TS_MW ) . '!'. $this->getName();
* @return FileRepoStatus object.
*/
function move( $target ) {
+ if ( $this->getRepo()->getReadOnlyReason() !== false ) {
+ return $this->readOnlyFatalStatus();
+ }
+
wfDebugLog( 'imagemove', "Got request to move {$this->name} to " . $target->getText() );
$this->lock(); // begin
* @return FileRepoStatus object.
*/
function delete( $reason, $suppress = false ) {
+ if ( $this->getRepo()->getReadOnlyReason() !== false ) {
+ return $this->readOnlyFatalStatus();
+ }
+
$this->lock(); // begin
$batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
}
$status = $batch->execute();
- if ( $status->ok ) {
+ if ( $status->isOK() ) {
// Update site_stats
$site_stats = $dbw->tableName( 'site_stats' );
$dbw->query( "UPDATE $site_stats SET ss_images=ss_images-1", __METHOD__ );
* @return FileRepoStatus object.
*/
function deleteOld( $archiveName, $reason, $suppress = false ) {
+ if ( $this->getRepo()->getReadOnlyReason() !== false ) {
+ return $this->readOnlyFatalStatus();
+ }
+
$this->lock(); // begin
$batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
$this->unlock(); // done
- if ( $status->ok ) {
+ if ( $status->isOK() ) {
$this->purgeDescription();
$this->purgeHistory();
}
* @return FileRepoStatus
*/
function restore( $versions = array(), $unsuppress = false ) {
+ if ( $this->getRepo()->getReadOnlyReason() !== false ) {
+ return $this->readOnlyFatalStatus();
+ }
+
+ $this->lock(); // begin
+
$batch = new LocalFileRestoreBatch( $this, $unsuppress );
if ( !$versions ) {
$status = $batch->execute();
- if ( !$status->isGood() ) {
- return $status;
+ if ( $status->isGood() ) {
+ $cleanupStatus = $batch->cleanup();
+ $cleanupStatus->successCount = 0;
+ $cleanupStatus->failCount = 0;
+ $status->merge( $cleanupStatus );
}
- $cleanupStatus = $batch->cleanup();
- $cleanupStatus->successCount = 0;
- $cleanupStatus->failCount = 0;
- $status->merge( $cleanupStatus );
+ $this->unlock(); // done
return $status;
}
$dbw = $this->repo->getMasterDB();
$dbw->rollback( __METHOD__ );
}
+
+ /**
+ * @return Status
+ */
+ protected function readOnlyFatalStatus() {
+ return $this->getRepo()->newFatal( 'filereadonlyerror', $this->getName(),
+ $this->getRepo()->getName(), $this->getRepo()->getReadOnlyReason() );
+ }
} // LocalFile class
# ------------------------------------------------------------------------------
$this->status->merge( $status );
}
- if ( !$this->status->ok ) {
+ if ( !$this->status->isOK() ) {
// Critical file deletion error
// Roll back inserts, release lock and abort
// TODO: delete the defunct filearchive rows if we are using a non-transactional DB
}
$this->db->selectDB( $this->getVar( 'wgDBname' ) );
- if( $this->db->tableExists( 'user', __METHOD__ ) ) {
+ if( $this->db->tableExists( 'archive', __METHOD__ ) ) {
$status->warning( 'config-install-tables-exist' );
$this->enableLB();
return $status;
*/
protected function getCoreUpdateList() {
return array(
+ # rename tables 1.7.3
+ # r15791 Change reserved word table names "user" and "text"
+ array( 'renameTable', 'user', 'mwuser'),
+ array( 'renameTable', 'text', 'pagecontent'),
+
# new sequences
array( 'addSequence', 'logging_log_id_seq' ),
array( 'addSequence', 'page_restrictions_pr_id_seq' ),
protected function renameTable( $old, $new ) {
if ( $this->db->tableExists( $old ) ) {
$this->output( "Renaming table $old to $new\n" );
- $old = $this->db->addQuotes( $old );
+ $old = $this->db->realTableName( $old, "quoted" );
+ $new = $this->db->realTableName( $new, "quoted" );
$this->db->query( "ALTER TABLE $old RENAME TO $new" );
}
}
return false;
}
+ /**
+ * @param $key
+ * @param $timeout int
+ * @return bool
+ */
public function lock( $key, $timeout = 0 ) {
/* stub */
return true;
}
+ /**
+ * @param $key
+ * @return bool
+ */
public function unlock( $key ) {
/* stub */
return true;
$this->stats['get_multi'] = 1;
}
$sock_keys = array();
-
+ $socks = array();
foreach ( $keys as $key ) {
$sock = $this->get_sock( $key );
if ( !is_resource( $sock ) ) {
$sock_keys[$sock][] = $key;
}
+ $gather = array();
// Send out the requests
foreach ( $socks as $sock ) {
$cmd = 'get';
return array();
}
+ $ret = array();
while ( true ) {
$res = fgets( $sock );
$ret[] = $res;
$this->_dead_host( $host );
}
+ /**
+ * @param $host
+ */
function _dead_host( $host ) {
$parts = explode( ':', $host );
$ip = $parts[0];
}
$hv = is_array( $key ) ? intval( $key[0] ) : $this->_hashfunc( $key );
-
if ( $this->_buckets === null ) {
+ $bu = array();
foreach ( $this->_servers as $v ) {
if ( is_array( $v ) ) {
for( $i = 0; $i < $v[1]; $i++ ) {
$this->stats[$cmd] = 1;
}
if ( !$this->_safe_fwrite( $sock, "$cmd $key $amt\r\n" ) ) {
- return $this->_dead_sock( $sock );
+ $this->_dead_sock( $sock );
+ return null;
}
$line = fgets( $sock );
}
}
if ( !$this->_safe_fwrite( $sock, "$cmd $key $flags $exp $len\r\n$val\r\n" ) ) {
- return $this->_dead_sock( $sock );
+ $this->_dead_sock( $sock );
+ return false;
}
$line = trim( fgets( $sock ) );
}
if ( !$this->_connect_sock( $sock, $host ) ) {
- return $this->_dead_host( $host );
+ $this->_dead_host( $host );
+ return null;
}
// Do not buffer writes
return $this->_cache_sock[$host];
}
+ /**
+ * @param $str string
+ */
function _debugprint( $str ) {
print( $str );
}
/**
* Original behaviour
+ * @param $f
+ * @param $buf
+ * @param $len bool
* @return int
*/
function _safe_fwrite( $f, $buf, $len = false ) {
/**
* Flush the read buffer of a stream
+ * @param $f Resource
*/
function _flush_read_buffer( $f ) {
if ( !is_resource( $f ) ) {
$this->mProfileID = $params['profileID'];
}
- // Push an entry for the pre-profile setup time onto the stack
- $initial = $this->getInitialTime();
- if ( $initial !== null ) {
- $this->mWorkStack[] = array( '-total', 0, $initial, 0 );
- $this->mStack[] = array( '-setup', 1, $initial, 0, $this->getTime(), 0 );
- } else {
- $this->profileIn( '-total' );
- }
+ $this->addInitialStack();
}
/**
}
}
+ /**
+ * Add the inital item in the stack.
+ */
+ protected function addInitialStack() {
+ // Push an entry for the pre-profile setup time onto the stack
+ $initial = $this->getInitialTime();
+ if ( $initial !== null ) {
+ $this->mWorkStack[] = array( '-total', 0, $initial, 0 );
+ $this->mStack[] = array( '-setup', 1, $initial, 0, $this->getTime(), 0 );
+ } else {
+ $this->profileIn( '-total' );
+ }
+ }
+
/**
* Called by wfProfieIn()
*
var $zeroEntry = array('cpu'=> 0.0, 'cpu_sq' => 0.0, 'real' => 0.0, 'real_sq' => 0.0, 'count' => 0);
var $errorEntry;
- function __construct( $params ) {
+ protected function addInitialStack() {
global $wgRequestTime, $wgRUstart;
- parent::__construct( $params );
$this->errorEntry = $this->zeroEntry;
$this->errorEntry['count'] = 1;
- if (!empty($wgRequestTime) && !empty($wgRUstart)) {
- # Remove the -total entry from parent::__construct
- $this->mWorkStack = array();
+ if ( !empty( $wgRequestTime ) && !empty( $wgRUstart ) ) {
+ $initialCpu = $this->getCpuTime( $wgRUstart );
+ $this->mWorkStack[] = array( '-total', 0, $wgRequestTime, $initialCpu );
+ $this->mWorkStack[] = array( '-setup', 1, $wgRequestTime, $initialCpu );
- $this->mWorkStack[] = array( '-total', 0, $wgRequestTime,$this->getCpuTime($wgRUstart));
-
- $elapsedcpu = $this->getCpuTime() - $this->getCpuTime($wgRUstart);
- $elapsedreal = microtime(true) - $wgRequestTime;
-
- $entry =& $this->mCollated["-setup"];
- if (!is_array($entry)) {
- $entry = $this->zeroEntry;
- $this->mCollated["-setup"] =& $entry;
- }
- $entry['cpu'] += $elapsedcpu;
- $entry['cpu_sq'] += $elapsedcpu*$elapsedcpu;
- $entry['real'] += $elapsedreal;
- $entry['real_sq'] += $elapsedreal*$elapsedreal;
- $entry['count']++;
+ $this->profileOut( '-setup' );
+ } else {
+ $this->profileIn( '-total' );
}
}
$this->getLanguage()->pipeList( $links )
);
- if( $this->target instanceof User ){
+ $userTitle = self::getTargetUserTitle( $this->target );
+ if( $userTitle ){
# Get relevant extracts from the block and suppression logs, if possible
- $userpage = $this->target->getUserPage();
$out = '';
LogEventsList::showLogExtract(
$out,
'block',
- $userpage,
+ $userTitle,
'',
array(
'lim' => 10,
- 'msgKey' => array( 'blocklog-showlog', $userpage->getText() ),
+ 'msgKey' => array( 'blocklog-showlog', $userTitle->getText() ),
'showIfEmpty' => false
)
);
LogEventsList::showLogExtract(
$out,
'suppress',
- $userpage,
+ $userTitle,
'',
array(
'lim' => 10,
'conds' => array( 'log_action' => array( 'block', 'reblock', 'unblock' ) ),
- 'msgKey' => array( 'blocklog-showsuppresslog', $userpage->getText() ),
+ 'msgKey' => array( 'blocklog-showsuppresslog', $userTitle->getText() ),
'showIfEmpty' => false
)
);
return $text;
}
+ /**
+ * Get a user page target for things like logs.
+ * This handles account and IP range targets.
+ * @param $target User|string
+ * @return Title|null
+ */
+ protected static function getTargetUserTitle( $target ) {
+ if( $target instanceof User ) {
+ return $target->getUserPage();
+ } elseif ( IP::isIPAddress( $target ) ) {
+ return Title::makeTitleSafe( NS_USER, $target );
+ }
+ return null;
+ }
+
/**
* Determine the target of the block, and the type of target
* TODO: should be in Block.php?
$this->gallery->add(
$title,
"$ul<br />\n<i>"
- . htmlspecialchars( $this->getLanguage()->timeanddate( $row->img_timestamp, true ) )
+ . htmlspecialchars( $this->getLanguage()->userTimeAndDate( $row->img_timestamp, $this->getUser() ) )
. "</i><br />\n"
);
}
),
'showbots' => array(
'type' => 'check',
- 'label' => wfMessage( 'showhidebots', wfMsg( 'show' ) ),
+ 'label' => $this->msg( 'showhidebots', $this->msg( 'show' )->plain() )->escaped(),
'name' => 'showbots',
# 'default' => $this->getRequest()->getBool( 'showbots', 0 ),
),
$form = new HTMLForm( $fields, $this->getContext() );
$form->setTitle( $this->getTitle() );
- $form->setSubmitText( wfMsg( 'ilsubmit' ) );
+ $form->setSubmitTextMsg( 'ilsubmit' );
$form->setMethod( 'get' );
- $form->setWrapperLegend( wfMsg( 'newimages-legend' ) );
+ $form->setWrapperLegendMsg( 'newimages-legend' );
return $form;
}
}
public function alterForm( HTMLForm $form ) {
- $form->setSubmitText( wfMessage( "mailmypassword" ) );
+ $form->setSubmitTextMsg( 'mailmypassword' );
}
protected function preText() {
if ( isset( $wgPasswordResetRoutes['domain'] ) && $wgPasswordResetRoutes['domain'] ) {
$i++;
}
- return wfMessage( 'passwordreset-pretext', $i )->parseAsBlock();
+ return $this->msg( 'passwordreset-pretext', $i )->parseAsBlock();
}
/**
$password = $user->randomPassword();
$user->setNewpassword( $password );
$user->saveSettings();
- $passwords[] = wfMessage( 'passwordreset-emailelement', $user->getName(), $password )->plain(); // We'll escape the whole thing later
+ $passwords[] = $this->msg( 'passwordreset-emailelement', $user->getName(), $password )->plain(); // We'll escape the whole thing later
}
$passwordBlock = implode( "\n\n", $passwords );
// Send in the user's language; which should hopefully be the same
$userLanguage = $firstUser->getOption( 'language' );
- $this->email = wfMessage( $msg )->inLanguage( $userLanguage );
+ $this->email = $this->msg( $msg )->inLanguage( $userLanguage );
$this->email->params(
$username,
$passwordBlock,
round( $wgNewPasswordExpiry / 86400 )
);
- $title = wfMessage( 'passwordreset-emailtitle' );
+ $title = $this->msg( 'passwordreset-emailtitle' );
$this->result = $firstUser->sendMail( $title->escaped(), $this->email->escaped() );
$submit = ' ' . Xml::submitbutton( wfMsg( 'allpagessubmit' ) );
$out = Xml::openElement( 'table', array( 'class' => 'mw-recentchanges-table' ) );
- foreach( $extraOpts as $optionRow ) {
+ foreach( $extraOpts as $name => $optionRow ) {
# Add submit button to the last row only
++$count;
- $addSubmit = $count === $extraOptsCount ? $submit : '';
+ $addSubmit = ( $count === $extraOptsCount ) ? $submit : '';
$out .= Xml::openElement( 'tr' );
if( is_array( $optionRow ) ) {
- $out .= Xml::tags( 'td', array( 'class' => 'mw-label' ), $optionRow[0] );
+ $out .= Xml::tags( 'td', array( 'class' => 'mw-label mw-' . $name . '-label' ), $optionRow[0] );
$out .= Xml::tags( 'td', array( 'class' => 'mw-input' ), $optionRow[1] . $addSubmit );
} else {
$out .= Xml::tags( 'td', array( 'class' => 'mw-input', 'colspan' => 2 ), $optionRow . $addSubmit );
global $wgVersion, $IP;
wfProfileIn( __METHOD__ );
- $info = self::getSvnInfo( $IP );
- if ( !$info ) {
+ $gitInfo = self::getGitHeadSha1( $IP );
+ $svnInfo = self::getSvnInfo( $IP );
+ if ( !$svnInfo && !$gitInfo ) {
$version = $wgVersion;
- } elseif( $flags === 'nodb' ) {
+ } elseif ( $gitInfo ) {
+ $shortSha1 = substr( $gitInfo, 0, 7 );
+ $version = "$wgVersion ($shortSha1)";
+ } elseif ( $flags === 'nodb' ) {
$version = "$wgVersion (r{$info['checkout-rev']})";
} else {
$version = $wgVersion . ' ' .
* @return bool|String sha1 of commit HEAD points to
*/
public static function getGitHeadSha1( $dir ) {
- $BASEDIR = "{$dir}/.git/";
- $HEADfile = "{$BASEDIR}/HEAD";
-
- if( !file_exists( $HEADfile ) ) {
- return false;
- }
-
- preg_match( "/ref: (.*)/",
- file_get_contents( $HEADfile ), $m );
-
- $REFfile = "{$BASEDIR}{$m[1]}";
- if( !file_exists( $REFfile ) ) {
- return false;
- }
-
- $sha1 = rtrim( file_get_contents( $REFfile ) );
-
- return $sha1;
+ $repo = new GitInfo( $dir );
+ return $repo->getHeadSHA1();
}
function showEasterEgg() {
$user = $this->getUser();
$output = $this->getOutput();
- // Add feed links
- $wlToken = $user->getOption( 'watchlisttoken' );
- if ( !$wlToken ) {
- $wlToken = MWCryptRand::generateHex( 40 );
- $user->setOption( 'watchlisttoken', $wlToken );
- $user->saveSettings();
- }
-
- $this->addFeedLinks( array( 'action' => 'feedwatchlist', 'allrev' => 'allrev',
- 'wlowner' => $user->getName(), 'wltoken' => $wlToken ) );
-
- $output->setRobotPolicy( 'noindex,nofollow' );
-
# Anons don't get a watchlist
if( $user->isAnon() ) {
$output->setPageTitle( $this->msg( 'watchnologin' ) );
+ $output->setRobotPolicy( 'noindex,nofollow' );
$llink = Linker::linkKnown(
SpecialPage::getTitleFor( 'Userlogin' ),
$this->msg( 'loginreqlink' )->escaped(),
return;
}
+ // Add feed links
+ $wlToken = $user->getOption( 'watchlisttoken' );
+ if ( !$wlToken ) {
+ $wlToken = MWCryptRand::generateHex( 40 );
+ $user->setOption( 'watchlisttoken', $wlToken );
+ $user->saveSettings();
+ }
+
+ $this->addFeedLinks( array( 'action' => 'feedwatchlist', 'allrev' => 'allrev',
+ 'wlowner' => $user->getName(), 'wltoken' => $wlToken ) );
+
$this->setHeaders();
$this->outputHeader();
/**
* Decode an expiry (block, protection, etc) which has come from the DB
- *
+ *
* @FIXME: why are we returnings DBMS-dependent strings???
*
* @param $expiry String: Database expiry String
}
/**
- * It fixes issue with lcfirst for transforming 'I' to 'ı'
+ * It fixes issue with lcfirst for transforming 'I' to 'ı'
*
* @param $string string
*
- * @return string
+ * @return mixed|string
*/
function lcfirst ( $string ) {
if ( substr( $string, 0, 1 ) === 'I' ) {
'directorycreateerror' => 'Could not create directory "$1".',
'filenotfound' => 'Could not find file "$1".',
'fileexistserror' => 'Unable to write to file "$1": File exists.',
+'filereadonlyerror' => 'Unable to the modify the file "$1" because the file repository "$2" is in read-only mode.
+
+The administrator who locked it offered this explanation: "$3".',
'unexpected' => 'Unexpected value: "$1"="$2".',
'formerror' => 'Error: Could not submit form.',
'badarticleerror' => 'This action cannot be performed on this page.',
$dbw->commit( __METHOD__ );
}
- $this->output( 'done!\n', 'msg' );
+ $this->output( "done!\n", 'msg' );
}
}
var $prefetchCountLast = 0;
var $fetchCountLast = 0;
- var $failures = 0;
var $maxFailures = 5;
- var $failedTextRetrievals = 0;
var $maxConsecutiveFailedTextRetrievals = 200;
var $failureTimeout = 5; // Seconds to sleep after db failure
*/
protected $db;
+
+ /**
+ * Drop the database connection $this->db and try to get a new one.
+ *
+ * This function tries to get a /different/ connection if this is
+ * possible. Hence, (if this is possible) it switches to a different
+ * failover upon each call.
+ *
+ * This function resets $this->lb and closes all connections on it.
+ *
+ * @throws MWException
+ */
+ function rotateDb() {
+ // Cleaning up old connections
+ if ( isset( $this->lb ) ) {
+ $this->lb->closeAll();
+ unset( $this->lb );
+ }
+
+ if ( isset( $this->db ) && $this->db->isOpen() ) {
+ throw new MWException( 'DB is set and has not been closed by the Load Balancer' );
+ }
+
+ unset( $this->db );
+
+ // Trying to set up new connection.
+ // We do /not/ retry upon failure, but delegate to encapsulating logic, to avoid
+ // individually retrying at different layers of code.
+
+ // 1. The LoadBalancer.
+ try {
+ $this->lb = wfGetLBFactory()->newMainLB();
+ } catch ( Exception $e ) {
+ throw new MWException( __METHOD__ . " rotating DB failed to obtain new load balancer (" . $e->getMessage() . ")" );
+ }
+
+
+ // 2. The Connection, through the load balancer.
+ try {
+ $this->db = $this->lb->getConnection( DB_SLAVE, 'backup' );
+ } catch ( Exception $e ) {
+ throw new MWException( __METHOD__ . " rotating DB failed to obtain new database (" . $e->getMessage() . ")" );
+ }
+ }
+
+
function initProgress( $history ) {
parent::initProgress();
$this->timeOfCheckpoint = $this->startTime;
$this->initProgress( $this->history );
- $this->db = $this->backupDb();
+ // We are trying to get an initial database connection to avoid that the
+ // first try of this request's first call to getText fails. However, if
+ // obtaining a good DB connection fails it's not a serious issue, as
+ // getText does retry upon failure and can start without having a working
+ // DB connection.
+ try {
+ $this->rotateDb();
+ } catch ( Exception $e ) {
+ // We do not even count this as failure. Just let eventual
+ // watchdogs know.
+ $this->progress( "Getting initial DB connection failed (" .
+ $e->getMessage() . ")" );
+ }
$this->egress = new ExportProgressFilter( $this->sink, $this );
$this->input = $url;
break;
case 'maxtime':
- $this->maxTimeAllowed = intval($val)*60;
+ $this->maxTimeAllowed = intval( $val ) * 60;
break;
case 'checkpointfile':
$this->checkpointFiles[] = $val;
}
function processFileOpt( $val, $param ) {
- $fileURIs = explode(';',$param);
+ $fileURIs = explode( ';', $param );
foreach ( $fileURIs as $URI ) {
switch( $val ) {
case "file":
function finalOptionCheck() {
if ( ( $this->checkpointFiles && ! $this->maxTimeAllowed ) ||
( $this->maxTimeAllowed && !$this->checkpointFiles ) ) {
- throw new MWException("Options checkpointfile and maxtime must be specified together.\n");
+ throw new MWException( "Options checkpointfile and maxtime must be specified together.\n" );
}
- foreach ($this->checkpointFiles as $checkpointFile) {
- $count = substr_count ( $checkpointFile,"%s" );
+ foreach ( $this->checkpointFiles as $checkpointFile ) {
+ $count = substr_count ( $checkpointFile, "%s" );
if ( $count != 2 ) {
- throw new MWException("Option checkpointfile must contain two '%s' for substitution of first and last pageids, count is $count instead, file is $checkpointFile.\n");
+ throw new MWException( "Option checkpointfile must contain two '%s' for substitution of first and last pageids, count is $count instead, file is $checkpointFile.\n" );
}
}
if ( $this->checkpointFiles ) {
$filenameList = (array)$this->egress->getFilenames();
if ( count( $filenameList ) != count( $this->checkpointFiles ) ) {
- throw new MWException("One checkpointfile must be specified for each output option, if maxtime is used.\n");
+ throw new MWException( "One checkpointfile must be specified for each output option, if maxtime is used.\n" );
}
}
}
$offset = 0; // for context extraction on error reporting
$bufferSize = 512 * 1024;
do {
- if ($this->checkIfTimeExceeded()) {
+ if ( $this->checkIfTimeExceeded() ) {
$this->setTimeExceeded();
}
$chunk = fread( $input, $bufferSize );
}
$offset += strlen( $chunk );
} while ( $chunk !== false && !feof( $input ) );
- if ($this->maxTimeAllowed) {
+ if ( $this->maxTimeAllowed ) {
$filenameList = (array)$this->egress->getFilenames();
// we wrote some stuff after last checkpoint that needs renamed
- if (file_exists($filenameList[0])) {
+ if ( file_exists( $filenameList[0] ) ) {
$newFilenames = array();
# we might have just written the header and footer and had no
# pages or revisions written... perhaps they were all deleted
# there's no pageID 0 so we use that. the caller is responsible
# for deciding what to do with a file containing only the
# siteinfo information and the mw tags.
- if (! $this->firstPageWritten) {
- $firstPageID = str_pad(0,9,"0",STR_PAD_LEFT);
- $lastPageID = str_pad(0,9,"0",STR_PAD_LEFT);
+ if ( ! $this->firstPageWritten ) {
+ $firstPageID = str_pad( 0, 9, "0", STR_PAD_LEFT );
+ $lastPageID = str_pad( 0, 9, "0", STR_PAD_LEFT );
}
else {
- $firstPageID = str_pad($this->firstPageWritten,9,"0",STR_PAD_LEFT);
- $lastPageID = str_pad($this->lastPageWritten,9,"0",STR_PAD_LEFT);
+ $firstPageID = str_pad( $this->firstPageWritten, 9, "0", STR_PAD_LEFT );
+ $lastPageID = str_pad( $this->lastPageWritten, 9, "0", STR_PAD_LEFT );
}
for ( $i = 0; $i < count( $filenameList ); $i++ ) {
$checkpointNameFilledIn = sprintf( $this->checkpointFiles[$i], $firstPageID, $lastPageID );
- $fileinfo = pathinfo($filenameList[$i]);
+ $fileinfo = pathinfo( $filenameList[$i] );
$newFilenames[] = $fileinfo['dirname'] . '/' . $checkpointNameFilledIn;
}
$this->egress->closeAndRename( $newFilenames );
return true;
}
+ /**
+ * Tries to get the revision text for a revision id.
+ *
+ * Upon errors, retries (Up to $this->maxFailures tries each call).
+ * If still no good revision get could be found even after this retrying, "" is returned.
+ * If no good revision text could be returned for
+ * $this->maxConsecutiveFailedTextRetrievals consecutive calls to getText, MWException
+ * is thrown.
+ *
+ * @param $id string The revision id to get the text for
+ *
+ * @return string The revision text for $id, or ""
+ * @throws MWException
+ */
function getText( $id ) {
+ $prefetchNotTried = true; // Whether or not we already tried to get the text via prefetch.
+ $text = false; // The candidate for a good text. false if no proper value.
+ $failures = 0; // The number of times, this invocation of getText already failed.
+
+ static $consecutiveFailedTextRetrievals = 0; // The number of times getText failed without
+ // yielding a good text in between.
+
$this->fetchCount++;
- if ( isset( $this->prefetch ) ) {
- $text = $this->prefetch->prefetch( $this->thisPage, $this->thisRev );
- if ( $text !== null ) { // Entry missing from prefetch dump
- $dbr = wfGetDB( DB_SLAVE );
+
+ // To allow to simply return on success and do not have to worry about book keeping,
+ // we assume, this fetch works (possible after some retries). Nevertheless, we koop
+ // the old value, so we can restore it, if problems occur (See after the while loop).
+ $oldConsecutiveFailedTextRetrievals = $consecutiveFailedTextRetrievals;
+ $consecutiveFailedTextRetrievals = 0;
+
+ while ( $failures < $this->maxFailures ) {
+
+ // As soon as we found a good text for the $id, we will return immediately.
+ // Hence, if we make it past the try catch block, we know that we did not
+ // find a good text.
+
+ try {
+ // Step 1: Get some text (or reuse from previous iteratuon if checking
+ // for plausibility failed)
+
+ // Trying to get prefetch, if it has not been tried before
+ if ( $text === false && isset( $this->prefetch ) && $prefetchNotTried ) {
+ $prefetchNotTried = false;
+ $tryIsPrefetch = true;
+ $text = $this->prefetch->prefetch( $this->thisPage, $this->thisRev );
+ if ( $text === null ) {
+ $text = false;
+ }
+ }
+
+ if ( $text === false ) {
+ // Fallback to asking the database
+ $tryIsPrefetch = false;
+ if ( $this->spawn ) {
+ $text = $this->getTextSpawned( $id );
+ } else {
+ $text = $this->getTextDb( $id );
+ }
+ }
+
+ if ( $text === false ) {
+ throw new MWException( "Generic error while obtaining text for id " . $id );
+ }
+
+ // We received a good candidate for the text of $id via some method
+
+ // Step 2: Checking for plausibility and return the text if it is
+ // plausible
$revID = intval( $this->thisRev );
- $revLength = $dbr->selectField( 'revision', 'rev_len', array( 'rev_id' => $revID ) );
- // if length of rev text in file doesn't match length in db, we reload
- // this avoids carrying forward broken data from previous xml dumps
- if( strlen( $text ) == $revLength ) {
- $this->prefetchCount++;
+ if ( ! isset( $this->db ) ) {
+ throw new MWException( "No database available" );
+ }
+ $revLength = $this->db->selectField( 'revision', 'rev_len', array( 'rev_id' => $revID ) );
+ if ( strlen( $text ) == $revLength ) {
+ if ( $tryIsPrefetch ) {
+ $this->prefetchCount++;
+ }
return $text;
}
+
+ $text = false;
+ throw new MWException( "Received text is unplausible for id " . $id );
+
+ } catch ( Exception $e ) {
+ $msg = "getting/checking text " . $id . " failed (" . $e->getMessage() . ")";
+ if ( $failures + 1 < $this->maxFailures ) {
+ $msg .= " (Will retry " . ( $this->maxFailures - $failures - 1 ) . " more times)";
+ }
+ $this->progress( $msg );
}
- }
- return $this->doGetText( $id );
- }
- private function doGetText( $id ) {
- $id = intval( $id );
- $this->failures = 0;
- $ex = new MWException( "Graceful storage failure" );
- while (true) {
- if ( $this->spawn ) {
- if ($this->failures) {
- // we don't know why it failed, could be the child process
- // borked, could be db entry busted, could be db server out to lunch,
- // so cover all bases
+ // Something went wrong; we did not a text that was plausible :(
+ $failures++;
+
+
+ // After backing off for some time, we try to reboot the whole process as
+ // much as possible to not carry over failures from one part to the other
+ // parts
+ sleep( $this->failureTimeout );
+ try {
+ $this->rotateDb();
+ if ( $this->spawn ) {
$this->closeSpawn();
$this->openSpawn();
}
- $text = $this->getTextSpawned( $id );
- } else {
- $text = $this->getTextDbSafe( $id );
- }
- if ( $text === false ) {
- $this->failures++;
- if ( $this->failures > $this->maxFailures) {
- $this->progress( "Failed to retrieve revision text for text id ".
- "$id after $this->maxFailures tries, giving up" );
- // were there so many bad retrievals in a row we want to bail?
- // at some point we have to declare the dump irretrievably broken
- $this->failedTextRetrievals++;
- if ($this->failedTextRetrievals > $this->maxConsecutiveFailedTextRetrievals) {
- throw $ex;
- } else {
- // would be nice to return something better to the caller someday,
- // log what we know about the failure and about the revision
- return "";
- }
- } else {
- $this->progress( "Error $this->failures " .
- "of allowed $this->maxFailures retrieving revision text for text id $id! " .
- "Pausing $this->failureTimeout seconds before retry..." );
- sleep( $this->failureTimeout );
- }
- } else {
- $this->failedTextRetrievals= 0;
- return $text;
+ } catch ( Exception $e ) {
+ $this->progress( "Rebooting getText infrastructure failed (" . $e->getMessage() . ")" .
+ " Trying to continue anyways" );
}
}
- return '';
- }
- /**
- * Fetch a text revision from the database, retrying in case of failure.
- * This may survive some transitory errors by reconnecting, but
- * may not survive a long-term server outage.
- *
- * FIXME: WTF? Why is it using a loop and then returning unconditionally?
- * @param $id int
- * @return bool|string
- */
- private function getTextDbSafe( $id ) {
- while ( true ) {
- try {
- $text = $this->getTextDb( $id );
- } catch ( DBQueryError $ex ) {
- $text = false;
- }
- return $text;
+ // Retirieving a good text for $id failed (at least) maxFailures times.
+ // We abort for this $id.
+
+ // Restoring the consecutive failures, and maybe aborting, if the dump
+ // is too broken.
+ $consecutiveFailedTextRetrievals = $oldConsecutiveFailedTextRetrievals + 1;
+ if ( $consecutiveFailedTextRetrievals > $this->maxConsecutiveFailedTextRetrievals ) {
+ throw new MWException( "Graceful storage failure" );
}
+
+ return "";
}
+
/**
* May throw a database error if, say, the server dies during query.
* @param $id
* @return bool|string
+ * @throws MWException
*/
private function getTextDb( $id ) {
global $wgContLang;
+ if ( ! isset( $this->db ) ) {
+ throw new MWException( __METHOD__ . "No database available" );
+ }
$row = $this->db->selectRow( 'text',
array( 'old_text', 'old_flags' ),
array( 'old_id' => $id ),
$nbytes = intval( $len );
// actual error, not zero-length text
- if ($nbytes < 0 ) return false;
+ if ( $nbytes < 0 ) return false;
$text = "";
$this->buffer = "";
$this->thisRev = "";
} elseif ( $name == 'page' ) {
- if (! $this->firstPageWritten) {
- $this->firstPageWritten = trim($this->thisPage);
+ if ( ! $this->firstPageWritten ) {
+ $this->firstPageWritten = trim( $this->thisPage );
}
- $this->lastPageWritten = trim($this->thisPage);
- if ($this->timeExceeded) {
+ $this->lastPageWritten = trim( $this->thisPage );
+ if ( $this->timeExceeded ) {
$this->egress->writeClosePage( $this->buffer );
// nasty hack, we can't just write the chardata after the
// page tag, it will include leading blanks from the next line
- $this->egress->sink->write("\n");
+ $this->egress->sink->write( "\n" );
$this->buffer = $this->xmlwriterobj->closeStream();
$this->egress->writeCloseStream( $this->buffer );
$filenameList = (array)$this->egress->getFilenames();
$newFilenames = array();
- $firstPageID = str_pad($this->firstPageWritten,9,"0",STR_PAD_LEFT);
- $lastPageID = str_pad($this->lastPageWritten,9,"0",STR_PAD_LEFT);
+ $firstPageID = str_pad( $this->firstPageWritten, 9, "0", STR_PAD_LEFT );
+ $lastPageID = str_pad( $this->lastPageWritten, 9, "0", STR_PAD_LEFT );
for ( $i = 0; $i < count( $filenameList ); $i++ ) {
$checkpointNameFilledIn = sprintf( $this->checkpointFiles[$i], $firstPageID, $lastPageID );
- $fileinfo = pathinfo($filenameList[$i]);
+ $fileinfo = pathinfo( $filenameList[$i] );
$newFilenames[] = $fileinfo['dirname'] . '/' . $checkpointNameFilledIn;
}
$this->egress->closeRenameAndReopen( $newFilenames );
}
// have to skip the newline left over from closepagetag line of
// end of checkpoint files. nasty hack!!
- if ($this->checkpointJustWritten) {
- if ($data[0] == "\n") {
- $data = substr($data,1);
+ if ( $this->checkpointJustWritten ) {
+ if ( $data[0] == "\n" ) {
+ $data = substr( $data, 1 );
}
$this->checkpointJustWritten = false;
}
'customjsprotected',
'ns-specialprotected',
'titleprotected',
+ 'filereadonlyerror'
),
'virus' => array(
'virus-badscanner',
CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp);
CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp);
CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timestamp);
-CREATE INDEX /*i*/page_user_timestamp ON /*_*/revision (rev_page,rev_user,rev_timestamp);
+CREATE INDEX /*i*/page_user_timestamp ON /*_*/revision (rev_page,rev_user,rev_timestamp);
--
-- Holds text of individual page revisions.
-- moved into the actual filestore
--
CREATE TABLE /*_*/uploadstash (
- us_id int unsigned NOT NULL PRIMARY KEY auto_increment,
+ us_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
- -- the user who uploaded the file.
- us_user int unsigned NOT NULL,
+ -- the user who uploaded the file.
+ us_user int unsigned NOT NULL,
- -- file key. this is how applications actually search for the file.
- -- this might go away, or become the primary key.
- us_key varchar(255) NOT NULL,
+ -- file key. this is how applications actually search for the file.
+ -- this might go away, or become the primary key.
+ us_key varchar(255) NOT NULL,
- -- the original path
- us_orig_path varchar(255) NOT NULL,
+ -- the original path
+ us_orig_path varchar(255) NOT NULL,
- -- the temporary path at which the file is actually stored
- us_path varchar(255) NOT NULL,
+ -- the temporary path at which the file is actually stored
+ us_path varchar(255) NOT NULL,
- -- which type of upload the file came from (sometimes)
- us_source_type varchar(50),
+ -- which type of upload the file came from (sometimes)
+ us_source_type varchar(50),
- -- the date/time on which the file was added
- us_timestamp varbinary(14) not null,
+ -- the date/time on which the file was added
+ us_timestamp varbinary(14) NOT NULL,
- us_status varchar(50) not null,
+ us_status varchar(50) NOT NULL,
- -- chunk counter starts at 0, current offset is stored in us_size
- us_chunk_inx int unsigned NULL,
+ -- chunk counter starts at 0, current offset is stored in us_size
+ us_chunk_inx int unsigned NULL,
- -- file properties from File::getPropsFromPath. these may prove unnecessary.
- --
- us_size int unsigned NOT NULL,
- -- this hash comes from File::sha1Base36(), and is 31 characters
- us_sha1 varchar(31) NOT NULL,
- us_mime varchar(255),
- -- Media type as defined by the MEDIATYPE_xxx constants, should duplicate definition in the image table
- us_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
- -- image-specific properties
- us_image_width int unsigned,
- us_image_height int unsigned,
- us_image_bits smallint unsigned
+ -- file properties from File::getPropsFromPath. these may prove unnecessary.
+ --
+ us_size int unsigned NOT NULL,
+ -- this hash comes from File::sha1Base36(), and is 31 characters
+ us_sha1 varchar(31) NOT NULL,
+ us_mime varchar(255),
+ -- Media type as defined by the MEDIATYPE_xxx constants, should duplicate definition in the image table
+ us_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+ -- image-specific properties
+ us_image_width int unsigned,
+ us_image_height int unsigned,
+ us_image_bits smallint unsigned
) /*$wgDBTableOptions*/;
CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp);
CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp);
-CREATE INDEX /*i*/type_action ON /*_*/logging(log_type, log_action, log_timestamp);
+CREATE INDEX /*i*/type_action ON /*_*/logging (log_type, log_action, log_timestamp);
CREATE TABLE /*_*/log_search (
) /*$wgDBTableOptions*/;
CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title, job_params(128));
-CREATE INDEX /*i*/job_timestamp ON /*_*/job(job_timestamp);
+CREATE INDEX /*i*/job_timestamp ON /*_*/job (job_timestamp);
-- Details of updates to cached special pages
chdir( dirname( dirname( __FILE__ ) ) );
if ( isset( $_SERVER['MW_COMPILED'] ) ) {
- require ( 'phase3/includes/WebStart.php' );
+ require ( 'core/includes/WebStart.php' );
} else {
require( dirname( dirname( __FILE__ ) ) . '/includes/WebStart.php' );
}
$wgEnableProfileInfo = $wgProfileToDatabase = false;
if ( isset( $_SERVER['MW_COMPILED'] ) ) {
- require ( 'phase3/includes/WebStart.php' );
+ require ( 'core/includes/WebStart.php' );
} else {
require ( dirname( __FILE__ ) . '/includes/WebStart.php' );
}
text-align: right;
}
td.timep, td.tpc, td.tpr {
- background-color: #fffff0;
+ background-color: #ffff80;
}
td.memoryp, td.mpc, td.mpr {
- background-color: #f0f8ff;
+ background-color: #80f8ff;
}
td.count, td,cpr {
- background-color: #f0fff0;
+ background-color: #80ff80;
}
td.name {
- background-color: #f9f9f9;
+ background-color: #89f9f9;
+ }
+
+ tr:hover {
+ font-weight: bold;
}
</style>
</head>
*/
( function ( $, mw, undefined ) {
-/**
- * The name of the page to watch or unwatch.
- */
-var title = mw.config.get( 'wgRelevantPageName', mw.config.get( 'wgPageName' ) );
-
-/**
- * Update the link text, link href attribute and (if applicable)
- * "loading" class.
- *
- * @param $link {jQuery} Anchor tag of (un)watch link
- * @param action {String} One of 'watch', 'unwatch'.
- * @param state {String} [optional] 'idle' or 'loading'. Default is 'idle'.
- */
-function updateWatchLink( $link, action, state ) {
- // message keys 'watch', 'watching', 'unwatch' or 'unwatching'.
- var msgKey = state === 'loading' ? action + 'ing' : action,
- accesskeyTip = $link.attr( 'title' ).match( mw.util.tooltipAccessKeyRegexp ),
+ /**
+ * The name of the page to watch or unwatch.
+ */
+ var title = mw.config.get( 'wgRelevantPageName', mw.config.get( 'wgPageName' ) );
+
+ /**
+ * Update the link text, link href attribute and (if applicable)
+ * "loading" class.
+ *
+ * @param $link {jQuery} Anchor tag of (un)watch link.
+ * @param action {String} One of 'watch', 'unwatch'.
+ * @param state {String} [optional] 'idle' or 'loading'. Default is 'idle'.
+ */
+ function updateWatchLink( $link, action, state ) {
+ var accesskeyTip, msgKey, $li;
+
+ // message keys 'watch', 'watching', 'unwatch' or 'unwatching'.
+ msgKey = state === 'loading' ? action + 'ing' : action;
+ accesskeyTip = $link.attr( 'title' ).match( mw.util.tooltipAccessKeyRegexp );
$li = $link.closest( 'li' );
- $link
- .text( mw.msg( msgKey ) )
- .attr( 'title', mw.msg( 'tooltip-ca-' + action ) +
- ( accesskeyTip ? ' ' + accesskeyTip[0] : '' )
- )
- .attr( 'href', mw.util.wikiScript() + '?' + $.param({
- title: title,
- action: action
- })
- );
-
- // Special case for vector icon
- if ( $li.hasClass( 'icon' ) ) {
- if ( state === 'loading' ) {
- $link.addClass( 'loading' );
- } else {
- $link.removeClass( 'loading' );
+ $link
+ .text( mw.msg( msgKey ) )
+ .attr( 'title', mw.msg( 'tooltip-ca-' + action ) +
+ ( accesskeyTip ? ' ' + accesskeyTip[0] : '' )
+ )
+ .attr( 'href', mw.util.wikiScript() + '?' + $.param({
+ title: title,
+ action: action
+ })
+ );
+
+ // Special case for vector icon
+ if ( $li.hasClass( 'icon' ) ) {
+ if ( state === 'loading' ) {
+ $link.addClass( 'loading' );
+ } else {
+ $link.removeClass( 'loading' );
+ }
}
}
-}
-/**
- * @todo This should be moved somewhere more accessible.
- * @param url {String}
- * @return {String} The extracted action, defaults to 'view'.
- */
-function mwUriGetAction( url ) {
- var actionPaths = mw.config.get( 'wgActionPaths' ),
- key, parts, m, action;
-
- // @todo: Does MediaWiki give action path or query param
- // precedence ? If the former, move this to the bottom
- action = mw.util.getParamValue( 'action', url );
- if ( action !== null ) {
- return action;
- }
+ /**
+ * @todo This should be moved somewhere more accessible.
+ * @param url {String}
+ * @return {String} The extracted action, defaults to 'view'.
+ */
+ function mwUriGetAction( url ) {
+ var action, actionPaths, key, i, m, parts;
+
+ actionPaths = mw.config.get( 'wgActionPaths' );
+
+ // @todo: Does MediaWiki give action path or query param
+ // precedence ? If the former, move this to the bottom
+ action = mw.util.getParamValue( 'action', url );
+ if ( action !== null ) {
+ return action;
+ }
+
+ for ( key in actionPaths ) {
+ if ( actionPaths.hasOwnProperty( key ) ) {
+ parts = actionPaths[key].split( '$1' );
+ for ( i = 0; i < parts.length; i += 1 ) {
+ parts[i] = $.escapeRE( parts[i] );
+ }
+ m = new RegExp( parts.join( '(.+)' ) ).exec( url );
+ if ( m && m[1] ) {
+ return key;
+ }
- for ( key in actionPaths ) {
- if ( actionPaths.hasOwnProperty( key ) ) {
- parts = actionPaths[key].split( '$1' );
- for ( i = 0; i < parts.length; i += 1 ) {
- parts[i] = $.escapeRE( parts[i] );
- }
- m = new RegExp( parts.join( '(.+)' ) ).exec( url );
- if ( m && m[1] ) {
- return key;
}
-
}
+
+ return 'view';
}
- return 'view';
-}
+ $( document ).ready( function () {
+ var $links = $( '.mw-watchlink a, a.mw-watchlink, ' +
+ '#ca-watch a, #ca-unwatch a, #mw-unwatch-link1, ' +
+ '#mw-unwatch-link2, #mw-watch-link2, #mw-watch-link1' );
-$( document ).ready( function() {
- var $links = $( '.mw-watchlink a, a.mw-watchlink, ' +
- '#ca-watch a, #ca-unwatch a, #mw-unwatch-link1, ' +
- '#mw-unwatch-link2, #mw-watch-link2, #mw-watch-link1' );
+ // Allowing people to add inline animated links is a little scary
+ $links = $links.filter( ':not( #bodyContent *, #content * )' );
- // Allowing people to add inline animated links is a little scary
- $links = $links.filter( ':not( #bodyContent *, #content * )' );
+ $links.click( function ( e ) {
+ var action, api, $link;
- $links.click( function( e ) {
- var $link, api,
action = mwUriGetAction( this.href );
- if ( action !== 'watch' && action !== 'unwatch' ) {
- // Could not extract target action from link url,
- // let native browsing handle it further
- return true;
- }
- e.preventDefault();
- e.stopPropagation();
-
- $link = $( this );
-
- updateWatchLink( $link, action, 'loading' );
-
- api = new mw.Api();
- api[action](
- title,
- // Success
- function( watchResponse ) {
- var otherAction = action === 'watch' ? 'unwatch' : 'watch',
- $li = $link.closest( 'li' );
+ if ( action !== 'watch' && action !== 'unwatch' ) {
+ // Could not extract target action from link url,
+ // let native browsing handle it further
+ return true;
+ }
+ e.preventDefault();
+ e.stopPropagation();
- mw.util.jsMessage( watchResponse.message, 'ajaxwatch' );
+ $link = $( this );
- // Set link to opposite
- updateWatchLink( $link, otherAction );
+ updateWatchLink( $link, action, 'loading' );
- // Most common ID style
- if ( $li.prop( 'id' ) === 'ca-' + otherAction || $li.prop( 'id' ) === 'ca-' + action ) {
- $li.prop( 'id', 'ca-' + otherAction );
- }
-
- // Bug 12395 - update the watch checkbox on edit pages when the
- // page is watched or unwatched via the tab.
- if ( watchResponse.watched !== undefined ) {
- $( '#wpWatchthis' ).prop( 'checked', true );
- } else {
- $( '#wpWatchthis' ).removeProp( 'checked' );
- }
- },
- // Error
- function(){
-
- // Reset link to non-loading mode
- updateWatchLink( $link, action );
-
- // Format error message
- var cleanTitle = title.replace( /_/g, ' ' );
- var link = mw.html.element(
- 'a', {
- 'href': mw.util.wikiGetlink( title ),
- 'title': cleanTitle
- }, cleanTitle
- );
- var html = mw.msg( 'watcherrortext', link );
-
- // Report to user about the error
- mw.util.jsMessage( html, 'ajaxwatch' );
+ api = new mw.Api();
+ api[action](
+ title,
+ // Success
+ function ( watchResponse ) {
+ var $li, otherAction;
- }
- );
- });
+ otherAction = action === 'watch' ? 'unwatch' : 'watch';
+ $li = $link.closest( 'li' );
-});
+ mw.util.jsMessage( watchResponse.message, 'ajaxwatch' );
+
+ // Set link to opposite
+ updateWatchLink( $link, otherAction );
+
+ // Most common ID style
+ if ( $li.prop( 'id' ) === 'ca-' + otherAction || $li.prop( 'id' ) === 'ca-' + action ) {
+ $li.prop( 'id', 'ca-' + otherAction );
+ }
+
+ // Bug 12395 - update the watch checkbox on edit pages when the
+ // page is watched or unwatched via the tab.
+ if ( watchResponse.watched !== undefined ) {
+ $( '#wpWatchthis' ).prop( 'checked', true );
+ } else {
+ $( '#wpWatchthis' ).removeProp( 'checked' );
+ }
+ },
+ // Error
+ function () {
+ var cleanTitle, html, link;
+
+ // Reset link to non-loading mode
+ updateWatchLink( $link, action );
+
+ // Format error message
+ cleanTitle = title.replace( /_/g, ' ' );
+ link = mw.html.element(
+ 'a', {
+ href: mw.util.wikiGetlink( title ),
+ title: cleanTitle
+ }, cleanTitle
+ );
+ html = mw.msg( 'watcherrortext', link );
+
+ // Report to user about the error
+ mw.util.jsMessage( html, 'ajaxwatch' );
+
+ }
+ );
+ });
+ });
-})( jQuery, mediaWiki );
+}( jQuery, mediaWiki ) );
*/
( function( $, mw ) {
+ /**
+ * @context {mw.Api}
+ */
+ function doWatchInternal( page, success, err, addParams ) {
+ var params = {
+ action: 'watch',
+ title: String( page ),
+ token: mw.user.tokens.get( 'watchToken' ),
+ uselang: mw.config.get( 'wgUserLanguage' )
+ };
+ function ok( data ) {
+ success( data.watch );
+ }
+ if ( addParams ) {
+ $.extend( params, addParams );
+ }
+ return this.post( params, { ok: ok, err: err } );
+ }
+
$.extend( mw.Api.prototype, {
/**
* Convinience method for 'action=watch'.
*
* @param page {String|mw.Title} Full page name or instance of mw.Title
- * @param success {Function} callback to which the watch object will be passed
- * watch object contains 'title' (full page name), 'watched' (boolean) and
+ * @param success {Function} Callback to which the watch object will be passed.
+ * Watch object contains properties 'title' (full pagename), 'watched' (boolean) and
* 'message' (parsed HTML of the 'addedwatchtext' message).
- * @param _unwatch {Boolean} Internally used to re-use this logic for unwatch(),
- * do not use outside this module.
- * @param err {Function} callback if error (optional)
+ * @param err {Function} Error callback (optional)
* @return {jqXHR}
*/
- watch: function( page, success, err, _unwatch ) {
- var params, ok;
- params = {
- action: 'watch',
- title: String( page ),
- token: mw.user.tokens.get( 'watchToken' ),
- uselang: mw.config.get( 'wgUserLanguage' )
- };
- if ( _unwatch ) {
- params.unwatch = 1;
- }
- ok = function( data ) {
- success( data.watch );
- };
- return this.post( params, { ok: ok, err: err } );
+ watch: function ( page, success, err ) {
+ return doWatchInternal.call( this, page, success, err );
},
/**
* Convinience method for 'action=watch&unwatch=1'.
*
* @param page {String|mw.Title} Full page name or instance of mw.Title
- * @param success {Function} callback to which the watch object will be passed
- * watch object contains 'title' (full page name), 'unwatched' (boolean) and
+ * @param success {Function} Callback to which the watch object will be passed.
+ * Watch object contains properties 'title' (full pagename), 'watched' (boolean) and
* 'message' (parsed HTML of the 'removedwatchtext' message).
- * @param err {Function} callback if error (optional)
+ * @param err {Function} Error callback (optional)
* @return {jqXHR}
*/
- unwatch: function( page, success, err ) {
- return this.watch( page, success, err, true );
+ unwatch: function ( page, success, err ) {
+ return doWatchInternal.call( this, page, success, err, { unwatch: 1 } );
}
} );
$nav = $this->data['content_navigation'];
if ( $wgVectorUseIconWatch ) {
- $mode = $this->getSkin()->getTitle()->userIsWatching() ? 'unwatch' : 'watch';
+ $mode = $this->getSkin()->getRelevantTitle()->userIsWatching() ? 'unwatch' : 'watch';
if ( isset( $nav['actions'][$mode] ) ) {
$nav['views'][$mode] = $nav['actions'][$mode];
$nav['views'][$mode]['class'] = rtrim( 'icon ' . $nav['views'][$mode]['class'], ' ' );
<?php
+/**
+ * @group Database
+ */
class RecentChangeTest extends MediaWikiTestCase {
protected $title;
protected $target;
'convertDoubleWidth() with the full alphabet and digits'
);
}
-
+
/** @dataProvider provideFormattableTimes */
function testFormatTimePeriod( $seconds, $format, $expected, $desc ) {
$this->assertEquals( $expected, $this->lang->formatTimePeriod( $seconds, $format ), $desc );
}
-
+
function provideFormattableTimes() {
return array(
array( 9.45, array(), '9.5s', 'formatTimePeriod() rounding (<10s)' ),
array( 176460.55, array(), '2d 1h 1m 1s', 'formatTimePeriod() rounding, recursion, (>48h)' ),
array( 176460.55, array( 'noabbrevs' => true ), '2 days 1 hour 1 minute 1 second', 'formatTimePeriod() rounding, recursion, (>48h)' ),
);
-
+
}
function testTruncate() {
"sprintfDate('$format', '$ts'): $msg"
);
- date_default_timezone_set( $oldTZ );
+ date_default_timezone_set( $oldTZ );
}
function provideSprintfDateSamples() {
== Details==
-Automated Selenium test scripts written for MediaWiki Installer is available at svn.wikimedia.org/svnroot/mediawiki/trunk/phase3/tests/selenium/installer.
+Automated Selenium test scripts written for MediaWiki Installer is available at https://gerrit.wikimedia.org/r/gitweb?p=mediawiki/core.git;a=tree;f=tests/selenium/installer;hb=HEAD.
Detailed test cases available at http://www.mediawiki.org/wiki/New_installer/Test_plan.
Version : MediaWiki 1.18alpha
*/
define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
if ( isset( $_SERVER['MW_COMPILED'] ) ) {
- require( 'phase3/includes/WebStart.php' );
+ require( 'core/includes/WebStart.php' );
} else {
require( dirname( __FILE__ ) . '/includes/WebStart.php' );
}