From: Tim Starling Date: Mon, 26 Mar 2012 03:45:23 +0000 (+0000) Subject: Merge "Localisation updates for namespace names for core from http://translatewiki... X-Git-Tag: 1.31.0-rc.0~24131 X-Git-Url: http://git.cyclocoop.org/%7B%24www_url%7Dadmin/compta/pie.php?a=commitdiff_plain;h=5a7b4d3f95db6055da83dc546933d8232520d950;hp=8ea48351a37043db57f3db544a98ab72a62bb868;p=lhc%2Fweb%2Fwiklou.git Merge "Localisation updates for namespace names for core from translatewiki.net." --- diff --git a/.gitignore b/.gitignore index 6688707225..943378aec4 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ cache/*.cdb images/[0-9a-f] images/temp images/thumb +maintenance/dev/data/ +maintenance/.mweval_history \ No newline at end of file diff --git a/CREDITS b/CREDITS index 54402b01f0..d0c4623d1a 100644 --- a/CREDITS +++ b/CREDITS @@ -199,4 +199,4 @@ following names for their contribution to the product. * Meno25 * Rotem Liss * Shinjiman -* Translatewiki.net Translators http://translatewiki.net/wiki/Special:ListUsers/translator +* Translatewiki.net Translators https://translatewiki.net/wiki/Special:ListUsers/translator diff --git a/README b/README index 078f92c991..48cf336c48 100644 --- a/README +++ b/README @@ -1,5 +1,3 @@ -2008-11-11 - For system requirements, installation and upgrade details, see the files RELEASE-NOTES, INSTALL, and UPGRADE. @@ -88,8 +86,8 @@ The official website for MediaWiki is located at: 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: diff --git a/RELEASE-NOTES-1.19 b/RELEASE-NOTES-1.19 index 576cfa8422..9255909ebc 100644 --- a/RELEASE-NOTES-1.19 +++ b/RELEASE-NOTES-1.19 @@ -13,15 +13,15 @@ production. === 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 @@ -31,6 +31,9 @@ production. 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. @@ -45,6 +48,7 @@ production. * (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 @@ -128,8 +132,8 @@ production. 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. @@ -146,8 +150,8 @@ production. * 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. diff --git a/RELEASE-NOTES-1.20 b/RELEASE-NOTES-1.20 index bef93eeb7e..d4533aac3b 100644 --- a/RELEASE-NOTES-1.20 +++ b/RELEASE-NOTES-1.20 @@ -50,6 +50,7 @@ production. * (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. diff --git a/api.php b/api.php index a5a257992d..889c5f10a2 100644 --- a/api.php +++ b/api.php @@ -45,7 +45,7 @@ if ( !function_exists( 'version_compare' ) || version_compare( phpversion(), '5. // 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' ); } diff --git a/docs/database.txt b/docs/database.txt index 70815d48b2..c0a2412ca7 100644 --- a/docs/database.txt +++ b/docs/database.txt @@ -8,7 +8,7 @@ By Tim Starling, January 2006. 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 ------------------------------------------------------------------------ diff --git a/extensions/.gitignore b/extensions/.gitignore new file mode 100644 index 0000000000..f8476208f6 --- /dev/null +++ b/extensions/.gitignore @@ -0,0 +1,3 @@ +* +!README +!.gitignore diff --git a/img_auth.php b/img_auth.php index 3999bf3e80..82afef27bc 100644 --- a/img_auth.php +++ b/img_auth.php @@ -28,7 +28,7 @@ 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' ); } diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 4ab5de868b..e7918730d7 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -95,6 +95,7 @@ $wgAutoloadLocalClasses = array( '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', diff --git a/includes/ChangeTags.php b/includes/ChangeTags.php index 63d3732707..01b93ad494 100644 --- a/includes/ChangeTags.php +++ b/includes/ChangeTags.php @@ -210,7 +210,7 @@ class ChangeTags { 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; @@ -219,7 +219,7 @@ class ChangeTags { $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; } diff --git a/includes/CryptRand.php b/includes/CryptRand.php index 10f379cb2a..e4be1b3788 100644 --- a/includes/CryptRand.php +++ b/includes/CryptRand.php @@ -120,7 +120,7 @@ class MWCryptRand { /** * 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 */ @@ -166,7 +166,7 @@ class MWCryptRand { /** * 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; @@ -184,6 +184,7 @@ class MWCryptRand { /** * Decide on the best acceptable hash algorithm we have available for hash() + * @throws MWException * @return String A hash algorithm */ protected function hashAlgo() { @@ -227,6 +228,7 @@ class MWCryptRand { * 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 ) { @@ -237,6 +239,8 @@ class MWCryptRand { * 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 ) { @@ -282,7 +286,7 @@ class MWCryptRand { 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' ); @@ -409,6 +413,7 @@ class MWCryptRand { /** * Return a singleton instance of MWCryptRand + * @return MWCryptRand */ protected static function singleton() { if ( is_null( self::$singleton ) ) { diff --git a/includes/Export.php b/includes/Export.php index 35a1b5b51e..82aa9465aa 100644 --- a/includes/Export.php +++ b/includes/Export.php @@ -487,12 +487,6 @@ class XmlDumpWriter { } } - if ( $row->rev_sha1 ) { - $out .= " " . Xml::element('sha1', null, strval( $row->rev_sha1 ) ) . "\n"; - } else { - $out .= " \n"; - } - if ( $row->page_restrictions != '' ) { $out .= ' ' . Xml::element( 'restrictions', array(), strval( $row->page_restrictions ) ) . "\n"; @@ -560,6 +554,12 @@ class XmlDumpWriter { "" ) . "\n"; } + if ( $row->rev_sha1 && !( $row->rev_deleted & Revision::DELETED_TEXT ) ) { + $out .= " " . Xml::element('sha1', null, strval( $row->rev_sha1 ) ) . "\n"; + } else { + $out .= " \n"; + } + wfRunHooks( 'XmlDumpWriterWriteRevision', array( &$this, &$out, $row, $text ) ); $out .= " \n"; diff --git a/includes/GitInfo.php b/includes/GitInfo.php new file mode 100644 index 0000000000..bc3f35e3d1 --- /dev/null +++ b/includes/GitInfo.php @@ -0,0 +1,122 @@ +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 diff --git a/includes/HTMLForm.php b/includes/HTMLForm.php index 3b3e1b692d..dccf96766a 100644 --- a/includes/HTMLForm.php +++ b/includes/HTMLForm.php @@ -271,7 +271,7 @@ class HTMLForm extends ContextSource { /** * 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. */ @@ -279,7 +279,7 @@ class HTMLForm extends ContextSource { $this->prepareForm(); $result = $this->tryAuthorizedSubmit(); - if ( $result === true || ( $result instanceof Status && $result->isGood() ) ){ + if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) { return $result; } diff --git a/includes/RecentChange.php b/includes/RecentChange.php index a1097bec15..e57efae3d7 100644 --- a/includes/RecentChange.php +++ b/includes/RecentChange.php @@ -751,7 +751,7 @@ class RecentChange { return ChangesList::showCharacterDifference( $old, $new ); } - public static function checkIPAddress( $ip ) { + private static function checkIPAddress( $ip ) { global $wgRequest; if ( $ip ) { if ( !IP::isIPAddress( $ip ) ) { diff --git a/includes/User.php b/includes/User.php index 90bef9141f..566dcc7407 100644 --- a/includes/User.php +++ b/includes/User.php @@ -1517,7 +1517,7 @@ class User { $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(); diff --git a/includes/db/Database.php b/includes/db/Database.php index 47eb596d6a..5c03617445 100644 --- a/includes/db/Database.php +++ b/includes/db/Database.php @@ -680,7 +680,7 @@ abstract class DatabaseBase implements DatabaseType { $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, @@ -689,8 +689,6 @@ abstract class DatabaseBase implements DatabaseType { 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; } diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php index 6452f54069..e2b38f5203 100644 --- a/includes/db/DatabasePostgres.php +++ b/includes/db/DatabasePostgres.php @@ -708,14 +708,19 @@ class DatabasePostgres extends DatabaseBase { # 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 @@ -990,7 +995,7 @@ class DatabasePostgres extends DatabaseBase { 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 " diff --git a/includes/filerepo/FileRepo.php b/includes/filerepo/FileRepo.php index 0dd5466bc8..db0dbcc8ff 100644 --- a/includes/filerepo/FileRepo.php +++ b/includes/filerepo/FileRepo.php @@ -119,10 +119,19 @@ class FileRepo { 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 */ diff --git a/includes/filerepo/backend/FileBackendStore.php b/includes/filerepo/backend/FileBackendStore.php index e96f257c8f..403fc9c6ba 100644 --- a/includes/filerepo/backend/FileBackendStore.php +++ b/includes/filerepo/backend/FileBackendStore.php @@ -66,6 +66,7 @@ abstract class FileBackendStore extends FileBackend { */ 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() ); @@ -73,6 +74,7 @@ abstract class FileBackendStore extends FileBackend { $status = $this->doCreateInternal( $params ); $this->clearCache( array( $params['dst'] ) ); } + wfProfileOut( __METHOD__ . '-' . $this->name ); wfProfileOut( __METHOD__ ); return $status; } @@ -96,12 +98,14 @@ abstract class FileBackendStore extends FileBackend { */ 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; } @@ -125,8 +129,10 @@ abstract class FileBackendStore extends FileBackend { */ 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; } @@ -149,8 +155,10 @@ abstract class FileBackendStore extends FileBackend { */ 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; } @@ -174,8 +182,10 @@ abstract class FileBackendStore extends FileBackend { */ 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; } @@ -201,6 +211,7 @@ abstract class FileBackendStore extends FileBackend { */ 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 @@ -210,6 +221,7 @@ abstract class FileBackendStore extends FileBackend { $status->merge( $this->doConcatenate( $params ) ); } + wfProfileOut( __METHOD__ . '-' . $this->name ); wfProfileOut( __METHOD__ ); return $status; } @@ -276,11 +288,13 @@ abstract class FileBackendStore extends FileBackend { */ 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 } @@ -295,6 +309,7 @@ abstract class FileBackendStore extends FileBackend { } } + wfProfileOut( __METHOD__ . '-' . $this->name ); wfProfileOut( __METHOD__ ); return $status; } @@ -313,11 +328,13 @@ abstract class FileBackendStore extends FileBackend { */ 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 } @@ -332,6 +349,7 @@ abstract class FileBackendStore extends FileBackend { } } + wfProfileOut( __METHOD__ . '-' . $this->name ); wfProfileOut( __METHOD__ ); return $status; } @@ -350,11 +368,13 @@ abstract class FileBackendStore extends FileBackend { */ 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 } @@ -363,6 +383,7 @@ abstract class FileBackendStore extends FileBackend { $filesLockEx = array( $params['dir'] ); $scopedLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status ); if ( !$status->isOK() ) { + wfProfileOut( __METHOD__ . '-' . $this->name ); wfProfileOut( __METHOD__ ); return $status; // abort } @@ -377,6 +398,7 @@ abstract class FileBackendStore extends FileBackend { } } + wfProfileOut( __METHOD__ . '-' . $this->name ); wfProfileOut( __METHOD__ ); return $status; } @@ -395,7 +417,9 @@ abstract class FileBackendStore extends FileBackend { */ 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 } @@ -406,7 +430,9 @@ abstract class FileBackendStore extends FileBackend { */ 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; } @@ -417,7 +443,9 @@ abstract class FileBackendStore extends FileBackend { */ 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; } @@ -428,8 +456,10 @@ abstract class FileBackendStore extends FileBackend { */ 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 } @@ -438,18 +468,22 @@ abstract class FileBackendStore extends FileBackend { // 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; } @@ -465,14 +499,17 @@ abstract class FileBackendStore extends FileBackend { */ 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; } @@ -483,18 +520,23 @@ abstract class FileBackendStore extends FileBackend { */ 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; } @@ -518,8 +560,10 @@ abstract class FileBackendStore extends FileBackend { */ 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; } @@ -530,8 +574,10 @@ abstract class FileBackendStore extends FileBackend { */ 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']; } @@ -540,6 +586,7 @@ abstract class FileBackendStore extends FileBackend { $this->trimExpensiveCache(); // limit memory $this->expensiveCache[$path]['localRef'] = $tmpFile; } + wfProfileOut( __METHOD__ . '-' . $this->name ); wfProfileOut( __METHOD__ ); return $tmpFile; } @@ -550,6 +597,7 @@ abstract class FileBackendStore extends FileBackend { */ final public function streamFile( array $params ) { wfProfileIn( __METHOD__ ); + wfProfileIn( __METHOD__ . '-' . $this->name ); $status = Status::newGood(); $info = $this->getFileStat( $params ); @@ -564,12 +612,15 @@ abstract class FileBackendStore extends FileBackend { // 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; } @@ -678,6 +729,7 @@ abstract class FileBackendStore extends FileBackend { */ protected function doOperationsInternal( array $ops, array $opts ) { wfProfileIn( __METHOD__ ); + wfProfileIn( __METHOD__ . '-' . $this->name ); $status = Status::newGood(); // Build up a list of FileOps... @@ -699,6 +751,7 @@ abstract class FileBackendStore extends FileBackend { $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 } @@ -714,6 +767,7 @@ abstract class FileBackendStore extends FileBackend { $status->merge( $subStatus ); $status->success = $subStatus->success; // not done in merge() + wfProfileOut( __METHOD__ . '-' . $this->name ); wfProfileOut( __METHOD__ ); return $status; } diff --git a/includes/filerepo/file/LocalFile.php b/includes/filerepo/file/LocalFile.php index cd2617045e..7be554d3df 100644 --- a/includes/filerepo/file/LocalFile.php +++ b/includes/filerepo/file/LocalFile.php @@ -908,9 +908,13 @@ class LocalFile extends File { */ 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 ); @@ -1175,6 +1179,10 @@ class LocalFile extends File { * 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(); @@ -1211,6 +1219,10 @@ class LocalFile extends File { * @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 @@ -1250,6 +1262,10 @@ class LocalFile extends File { * @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 ); @@ -1266,7 +1282,7 @@ class LocalFile extends File { } $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__ ); @@ -1293,6 +1309,10 @@ class LocalFile extends File { * @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 ); @@ -1302,7 +1322,7 @@ class LocalFile extends File { $this->unlock(); // done - if ( $status->ok ) { + if ( $status->isOK() ) { $this->purgeDescription(); $this->purgeHistory(); } @@ -1322,6 +1342,12 @@ class LocalFile extends File { * @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 ) { @@ -1332,14 +1358,14 @@ class LocalFile extends File { $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; } @@ -1444,6 +1470,14 @@ class LocalFile extends File { $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 # ------------------------------------------------------------------------------ @@ -1711,7 +1745,7 @@ class LocalFileDeleteBatch { $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 diff --git a/includes/installer/DatabaseInstaller.php b/includes/installer/DatabaseInstaller.php index 14604c162b..046fa16431 100644 --- a/includes/installer/DatabaseInstaller.php +++ b/includes/installer/DatabaseInstaller.php @@ -158,7 +158,7 @@ abstract class DatabaseInstaller { } $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; diff --git a/includes/installer/PostgresUpdater.php b/includes/installer/PostgresUpdater.php index d4412cb2e2..6b3cb514e8 100644 --- a/includes/installer/PostgresUpdater.php +++ b/includes/installer/PostgresUpdater.php @@ -27,6 +27,11 @@ class PostgresUpdater extends DatabaseUpdater { */ 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' ), @@ -406,7 +411,8 @@ END; 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" ); } } diff --git a/includes/objectcache/MemcachedClient.php b/includes/objectcache/MemcachedClient.php index f8208f61da..4f49f7ddfb 100644 --- a/includes/objectcache/MemcachedClient.php +++ b/includes/objectcache/MemcachedClient.php @@ -344,11 +344,20 @@ class MWMemcached { 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; @@ -471,7 +480,7 @@ class MWMemcached { $this->stats['get_multi'] = 1; } $sock_keys = array(); - + $socks = array(); foreach ( $keys as $key ) { $sock = $this->get_sock( $key ); if ( !is_resource( $sock ) ) { @@ -485,6 +494,7 @@ class MWMemcached { $sock_keys[$sock][] = $key; } + $gather = array(); // Send out the requests foreach ( $socks as $sock ) { $cmd = 'get'; @@ -579,6 +589,7 @@ class MWMemcached { return array(); } + $ret = array(); while ( true ) { $res = fgets( $sock ); $ret[] = $res; @@ -744,6 +755,9 @@ class MWMemcached { $this->_dead_host( $host ); } + /** + * @param $host + */ function _dead_host( $host ) { $parts = explode( ':', $host ); $ip = $parts[0]; @@ -774,8 +788,8 @@ class MWMemcached { } $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++ ) { @@ -851,7 +865,8 @@ class MWMemcached { $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 ); @@ -998,7 +1013,8 @@ class MWMemcached { } } 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 ) ); @@ -1038,7 +1054,8 @@ class MWMemcached { } if ( !$this->_connect_sock( $sock, $host ) ) { - return $this->_dead_host( $host ); + $this->_dead_host( $host ); + return null; } // Do not buffer writes @@ -1049,6 +1066,9 @@ class MWMemcached { return $this->_cache_sock[$host]; } + /** + * @param $str string + */ function _debugprint( $str ) { print( $str ); } @@ -1080,6 +1100,9 @@ class MWMemcached { /** * Original behaviour + * @param $f + * @param $buf + * @param $len bool * @return int */ function _safe_fwrite( $f, $buf, $len = false ) { @@ -1093,6 +1116,7 @@ class MWMemcached { /** * Flush the read buffer of a stream + * @param $f Resource */ function _flush_read_buffer( $f ) { if ( !is_resource( $f ) ) { diff --git a/includes/profiler/Profiler.php b/includes/profiler/Profiler.php index b1ed9b680d..f6d8b3a293 100644 --- a/includes/profiler/Profiler.php +++ b/includes/profiler/Profiler.php @@ -48,14 +48,7 @@ class Profiler { $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(); } /** @@ -114,6 +107,20 @@ class Profiler { } } + /** + * 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() * diff --git a/includes/profiler/ProfilerSimple.php b/includes/profiler/ProfilerSimple.php index 055a0ea0f7..df3736396d 100644 --- a/includes/profiler/ProfilerSimple.php +++ b/includes/profiler/ProfilerSimple.php @@ -15,32 +15,20 @@ class ProfilerSimple extends Profiler { 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' ); } } diff --git a/includes/specials/SpecialBlock.php b/includes/specials/SpecialBlock.php index 5b5b356fd3..a58e62ea27 100644 --- a/includes/specials/SpecialBlock.php +++ b/includes/specials/SpecialBlock.php @@ -381,19 +381,19 @@ class SpecialBlock extends FormSpecialPage { $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 ) ); @@ -404,12 +404,12 @@ class SpecialBlock extends FormSpecialPage { 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 ) ); @@ -421,6 +421,21 @@ class SpecialBlock extends FormSpecialPage { 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? diff --git a/includes/specials/SpecialNewimages.php b/includes/specials/SpecialNewimages.php index b88123dc79..45dbd36d6b 100644 --- a/includes/specials/SpecialNewimages.php +++ b/includes/specials/SpecialNewimages.php @@ -123,7 +123,7 @@ class NewFilesPager extends ReverseChronologicalPager { $this->gallery->add( $title, "$ul
\n" - . htmlspecialchars( $this->getLanguage()->timeanddate( $row->img_timestamp, true ) ) + . htmlspecialchars( $this->getLanguage()->userTimeAndDate( $row->img_timestamp, $this->getUser() ) ) . "
\n" ); } @@ -139,7 +139,7 @@ class NewFilesPager extends ReverseChronologicalPager { ), '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 ), ), @@ -161,9 +161,9 @@ class NewFilesPager extends ReverseChronologicalPager { $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; } diff --git a/includes/specials/SpecialPasswordReset.php b/includes/specials/SpecialPasswordReset.php index 5e58841573..683a71422a 100644 --- a/includes/specials/SpecialPasswordReset.php +++ b/includes/specials/SpecialPasswordReset.php @@ -98,7 +98,7 @@ class SpecialPasswordReset extends FormSpecialPage { } public function alterForm( HTMLForm $form ) { - $form->setSubmitText( wfMessage( "mailmypassword" ) ); + $form->setSubmitTextMsg( 'mailmypassword' ); } protected function preText() { @@ -113,7 +113,7 @@ class SpecialPasswordReset extends FormSpecialPage { if ( isset( $wgPasswordResetRoutes['domain'] ) && $wgPasswordResetRoutes['domain'] ) { $i++; } - return wfMessage( 'passwordreset-pretext', $i )->parseAsBlock(); + return $this->msg( 'passwordreset-pretext', $i )->parseAsBlock(); } /** @@ -234,14 +234,14 @@ class SpecialPasswordReset extends FormSpecialPage { $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, @@ -250,7 +250,7 @@ class SpecialPasswordReset extends FormSpecialPage { round( $wgNewPasswordExpiry / 86400 ) ); - $title = wfMessage( 'passwordreset-emailtitle' ); + $title = $this->msg( 'passwordreset-emailtitle' ); $this->result = $firstUser->sendMail( $title->escaped(), $this->email->escaped() ); diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php index bfc5248dda..c05aaad6f2 100644 --- a/includes/specials/SpecialRecentchanges.php +++ b/includes/specials/SpecialRecentchanges.php @@ -568,14 +568,14 @@ class SpecialRecentChanges extends IncludableSpecialPage { $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 ); diff --git a/includes/specials/SpecialVersion.php b/includes/specials/SpecialVersion.php index 2b44337bb6..9181de0b75 100644 --- a/includes/specials/SpecialVersion.php +++ b/includes/specials/SpecialVersion.php @@ -160,10 +160,14 @@ class SpecialVersion extends SpecialPage { 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 . ' ' . @@ -717,24 +721,8 @@ class SpecialVersion extends SpecialPage { * @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() { diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php index 64a07f14b8..0c5f11cd7c 100644 --- a/includes/specials/SpecialWatchlist.php +++ b/includes/specials/SpecialWatchlist.php @@ -40,22 +40,10 @@ class SpecialWatchlist extends SpecialPage { $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(), @@ -66,6 +54,17 @@ class SpecialWatchlist extends SpecialPage { 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(); diff --git a/languages/Language.php b/languages/Language.php index 854a9f1010..d705b49d77 100644 --- a/languages/Language.php +++ b/languages/Language.php @@ -3758,7 +3758,7 @@ class Language { /** * 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 diff --git a/languages/classes/LanguageKaa.php b/languages/classes/LanguageKaa.php index a40fb7ae43..22e8946394 100644 --- a/languages/classes/LanguageKaa.php +++ b/languages/classes/LanguageKaa.php @@ -41,11 +41,11 @@ class LanguageKaa extends Language { } /** - * 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' ) { diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index 933cff1706..ee8a2ed5c8 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -997,6 +997,9 @@ Please report this to an [[Special:ListUsers/sysop|administrator]], making note '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.', diff --git a/maintenance/deleteDefaultMessages.php b/maintenance/deleteDefaultMessages.php index 989f7bd8fa..4f5889b17e 100644 --- a/maintenance/deleteDefaultMessages.php +++ b/maintenance/deleteDefaultMessages.php @@ -74,7 +74,7 @@ class DeleteDefaultMessages extends Maintenance { $dbw->commit( __METHOD__ ); } - $this->output( 'done!\n', 'msg' ); + $this->output( "done!\n", 'msg' ); } } diff --git a/maintenance/dumpTextPass.php b/maintenance/dumpTextPass.php index c03f3df893..6796c75923 100644 --- a/maintenance/dumpTextPass.php +++ b/maintenance/dumpTextPass.php @@ -41,9 +41,7 @@ class TextPassDumper extends BackupDumper { 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 @@ -71,6 +69,52 @@ class TextPassDumper extends BackupDumper { */ 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; @@ -87,7 +131,19 @@ class TextPassDumper extends BackupDumper { $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 ); @@ -124,7 +180,7 @@ class TextPassDumper extends BackupDumper { $this->input = $url; break; case 'maxtime': - $this->maxTimeAllowed = intval($val)*60; + $this->maxTimeAllowed = intval( $val ) * 60; break; case 'checkpointfile': $this->checkpointFiles[] = $val; @@ -145,7 +201,7 @@ class TextPassDumper extends BackupDumper { } function processFileOpt( $val, $param ) { - $fileURIs = explode(';',$param); + $fileURIs = explode( ';', $param ); foreach ( $fileURIs as $URI ) { switch( $val ) { case "file": @@ -240,19 +296,19 @@ class TextPassDumper extends BackupDumper { 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" ); } } } @@ -275,7 +331,7 @@ class TextPassDumper extends BackupDumper { $offset = 0; // for context extraction on error reporting $bufferSize = 512 * 1024; do { - if ($this->checkIfTimeExceeded()) { + if ( $this->checkIfTimeExceeded() ) { $this->setTimeExceeded(); } $chunk = fread( $input, $bufferSize ); @@ -285,27 +341,27 @@ class TextPassDumper extends BackupDumper { } $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 ); @@ -316,98 +372,142 @@ class TextPassDumper extends BackupDumper { 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 ), @@ -517,7 +617,7 @@ class TextPassDumper extends BackupDumper { $nbytes = intval( $len ); // actual error, not zero-length text - if ($nbytes < 0 ) return false; + if ( $nbytes < 0 ) return false; $text = ""; @@ -584,15 +684,15 @@ class TextPassDumper extends BackupDumper { $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 ); @@ -603,11 +703,11 @@ class TextPassDumper extends BackupDumper { $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 ); @@ -640,9 +740,9 @@ class TextPassDumper extends BackupDumper { } // 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; } diff --git a/maintenance/language/messages.inc b/maintenance/language/messages.inc index c9afbfa87e..d621686792 100644 --- a/maintenance/language/messages.inc +++ b/maintenance/language/messages.inc @@ -408,6 +408,7 @@ $wgMessageStructure = array( 'customjsprotected', 'ns-specialprotected', 'titleprotected', + 'filereadonlyerror' ), 'virus' => array( 'virus-badscanner', diff --git a/maintenance/tables.sql b/maintenance/tables.sql index 60fc7fc1bb..66a1845947 100644 --- a/maintenance/tables.sql +++ b/maintenance/tables.sql @@ -326,7 +326,7 @@ CREATE INDEX /*i*/rev_timestamp ON /*_*/revision (rev_timestamp); 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. @@ -946,44 +946,44 @@ CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timesta -- 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*/; @@ -1237,7 +1237,7 @@ CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_times 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 ( @@ -1275,7 +1275,7 @@ CREATE TABLE /*_*/job ( ) /*$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 diff --git a/mw-config/index.php b/mw-config/index.php index c65be69c0c..edfae92828 100644 --- a/mw-config/index.php +++ b/mw-config/index.php @@ -10,7 +10,7 @@ define( 'MEDIAWIKI_INSTALL', true ); 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' ); } diff --git a/profileinfo.php b/profileinfo.php index 1549349bfd..ef038c1153 100644 --- a/profileinfo.php +++ b/profileinfo.php @@ -29,7 +29,7 @@ ini_set( 'zlib.output_compression', 'off' ); $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' ); } @@ -56,16 +56,20 @@ header( 'Content-Type: text/html; charset=utf-8' ); 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; } diff --git a/resources/mediawiki.action/mediawiki.action.watch.ajax.js b/resources/mediawiki.action/mediawiki.action.watch.ajax.js index f5f09f52a1..090e4c3d1e 100644 --- a/resources/mediawiki.action/mediawiki.action.watch.ajax.js +++ b/resources/mediawiki.action/mediawiki.action.watch.ajax.js @@ -4,152 +4,158 @@ */ ( 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 ) ); diff --git a/resources/mediawiki.api/mediawiki.api.watch.js b/resources/mediawiki.api/mediawiki.api.watch.js index 8ed6832091..302a2d31dc 100644 --- a/resources/mediawiki.api/mediawiki.api.watch.js +++ b/resources/mediawiki.api/mediawiki.api.watch.js @@ -4,47 +4,51 @@ */ ( 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 } ); } } ); diff --git a/skins/Vector.php b/skins/Vector.php index 501a267118..d1b51a688e 100644 --- a/skins/Vector.php +++ b/skins/Vector.php @@ -72,7 +72,7 @@ class VectorTemplate extends BaseTemplate { $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'], ' ' ); diff --git a/tests/phpunit/includes/RecentChangeTest.php b/tests/phpunit/includes/RecentChangeTest.php index 7865ed24b6..fbf271cc5c 100644 --- a/tests/phpunit/includes/RecentChangeTest.php +++ b/tests/phpunit/includes/RecentChangeTest.php @@ -1,4 +1,7 @@ assertEquals( $expected, $this->lang->formatTimePeriod( $seconds, $format ), $desc ); } - + function provideFormattableTimes() { return array( array( 9.45, array(), '9.5s', 'formatTimePeriod() rounding (<10s)' ), @@ -62,7 +62,7 @@ class LanguageTest extends MediaWikiTestCase { 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() { @@ -224,7 +224,7 @@ class LanguageTest extends MediaWikiTestCase { "sprintfDate('$format', '$ts'): $msg" ); - date_default_timezone_set( $oldTZ ); + date_default_timezone_set( $oldTZ ); } function provideSprintfDateSamples() { diff --git a/tests/selenium/installer/README.txt b/tests/selenium/installer/README.txt index 83d1a3468b..bc880a8ba8 100644 --- a/tests/selenium/installer/README.txt +++ b/tests/selenium/installer/README.txt @@ -1,6 +1,6 @@ == 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 diff --git a/thumb.php b/thumb.php index eb0d67df19..6afc7e5044 100644 --- a/thumb.php +++ b/thumb.php @@ -8,7 +8,7 @@ */ 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' ); }