* @file
*/
+use Wikimedia\Rdbms\Database;
use Wikimedia\Rdbms\IDatabase;
use MediaWiki\Linker\LinkTarget;
use MediaWiki\Interwiki\InterwikiLookup;
* @return string The prefixed text
*/
private function prefix( $name ) {
+ global $wgContLang;
+
$p = '';
if ( $this->isExternal() ) {
$p = $this->mInterwiki . ':';
}
if ( 0 != $this->mNamespace ) {
- $p .= $this->getNsText() . ':';
+ $nsText = $this->getNsText();
+
+ if ( $nsText === false ) {
+ // See T165149. Awkward, but better than erroneously linking to the main namespace.
+ $nsText = $wgContLang->getNsText( NS_SPECIAL ) . ":Badtitle/NS{$this->mNamespace}";
+ }
+
+ $p .= $nsText . ':';
}
return $p . $name;
}
return $url;
}
+ /**
+ * Get a url appropriate for making redirects based on an untrusted url arg
+ *
+ * This is basically the same as getFullUrl(), but in the case of external
+ * interwikis, we send the user to a landing page, to prevent possible
+ * phishing attacks and the like.
+ *
+ * @note Uses current protocol by default, since technically relative urls
+ * aren't allowed in redirects per HTTP spec, so this is not suitable for
+ * places where the url gets cached, as might pollute between
+ * https and non-https users.
+ * @see self::getLocalURL for the arguments.
+ * @param array|string $query
+ * @param string $proto Protocol type to use in URL
+ * @return String. A url suitable to use in an HTTP location header.
+ */
+ public function getFullUrlForRedirect( $query = '', $proto = PROTO_CURRENT ) {
+ $target = $this;
+ if ( $this->isExternal() ) {
+ $target = SpecialPage::getTitleFor(
+ 'GoToInterwiki',
+ $this->getPrefixedDBKey()
+ );
+ }
+ return $target->getFullUrl( $query, false, $proto );
+ }
+
/**
* Get a URL with no fragment or server name (relative URL) from a Title object.
* If this page is generated with action=render, however,
private function checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short ) {
# Only 'createaccount' can be performed on special pages,
# which don't actually exist in the DB.
- if ( NS_SPECIAL == $this->mNamespace && $action !== 'createaccount' ) {
+ if ( $this->isSpecialPage() && $action !== 'createaccount' ) {
$errors[] = [ 'ns-specialprotected' ];
}
private function checkCSSandJSPermissions( $action, $user, $errors, $rigor, $short ) {
# Protect css/js subpages of user pages
# XXX: this might be better using restrictions
- # XXX: right 'editusercssjs' is deprecated, for backward compatibility only
- if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' ) ) {
+ if ( $action != 'patrol' ) {
if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
if ( $this->isCssSubpage() && !$user->isAllowedAny( 'editmyusercss', 'editusercss' ) ) {
$errors[] = [ 'mycustomcssprotected', $action ];
) {
$errors[] = [ 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ];
}
+ } elseif ( $action === 'undelete' ) {
+ if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
+ // Undeleting implies editing
+ $errors[] = [ 'undelete-cantedit' ];
+ }
+ if ( !$this->exists()
+ && count( $this->getUserPermissionsErrorsInternal( 'create', $user, $rigor, true ) )
+ ) {
+ // Undeleting where nothing currently exists implies creating
+ $errors[] = [ 'undelete-cantcreate' ];
+ }
}
return $errors;
}
$this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
# We already know that some pages won't be in the database!
- if ( $this->isExternal() || $this->mNamespace == NS_SPECIAL ) {
+ if ( $this->isExternal() || $this->isSpecialPage() ) {
$this->mArticleID = 0;
}
}
/**
- * Get the revision ID of the previous revision
- *
+ * Get next/previous revision ID relative to another revision ID
* @param int $revId Revision ID. Get the revision that was before this one.
* @param int $flags Title::GAID_FOR_UPDATE
- * @return int|bool Old revision ID, or false if none exists
- */
- public function getPreviousRevisionID( $revId, $flags = 0 ) {
- /* This function and getNextRevisionID have bad performance when
- used on a page with many revisions on mysql. An explicit extended
- primary key may help in some cases, if the PRIMARY KEY is banned:
- T159319 */
+ * @param string $dir 'next' or 'prev'
+ * @return int|bool New revision ID, or false if none exists
+ */
+ private function getRelativeRevisionID( $revId, $flags, $dir ) {
+ $revId = (int)$revId;
+ if ( $dir === 'next' ) {
+ $op = '>';
+ $sort = 'ASC';
+ } elseif ( $dir === 'prev' ) {
+ $op = '<';
+ $sort = 'DESC';
+ } else {
+ throw new InvalidArgumentException( '$dir must be "next" or "prev"' );
+ }
+
if ( $flags & self::GAID_FOR_UPDATE ) {
$db = wfGetDB( DB_MASTER );
} else {
$db = wfGetDB( DB_REPLICA, 'contributions' );
}
+
+ // Intentionally not caring if the specified revision belongs to this
+ // page. We only care about the timestamp.
+ $ts = $db->selectField( 'revision', 'rev_timestamp', [ 'rev_id' => $revId ], __METHOD__ );
+ if ( $ts === false ) {
+ $ts = $db->selectField( 'archive', 'ar_timestamp', [ 'ar_rev_id' => $revId ], __METHOD__ );
+ if ( $ts === false ) {
+ // Or should this throw an InvalidArgumentException or something?
+ return false;
+ }
+ }
+ $ts = $db->addQuotes( $ts );
+
$revId = $db->selectField( 'revision', 'rev_id',
[
'rev_page' => $this->getArticleID( $flags ),
- 'rev_id < ' . intval( $revId )
+ "rev_timestamp $op $ts OR (rev_timestamp = $ts AND rev_id $op $revId)"
],
__METHOD__,
- [ 'ORDER BY' => 'rev_id DESC', 'IGNORE INDEX' => 'PRIMARY' ]
+ [
+ 'ORDER BY' => "rev_timestamp $sort, rev_id $sort",
+ 'IGNORE INDEX' => 'rev_timestamp', // Probably needed for T159319
+ ]
);
if ( $revId === false ) {
}
}
+ /**
+ * Get the revision ID of the previous revision
+ *
+ * @param int $revId Revision ID. Get the revision that was before this one.
+ * @param int $flags Title::GAID_FOR_UPDATE
+ * @return int|bool Old revision ID, or false if none exists
+ */
+ public function getPreviousRevisionID( $revId, $flags = 0 ) {
+ return $this->getRelativeRevisionID( $revId, $flags, 'prev' );
+ }
+
/**
* Get the revision ID of the next revision
*
* @return int|bool Next revision ID, or false if none exists
*/
public function getNextRevisionID( $revId, $flags = 0 ) {
- if ( $flags & self::GAID_FOR_UPDATE ) {
- $db = wfGetDB( DB_MASTER );
- } else {
- $db = wfGetDB( DB_REPLICA, 'contributions' );
- }
- $revId = $db->selectField( 'revision', 'rev_id',
- [
- 'rev_page' => $this->getArticleID( $flags ),
- 'rev_id > ' . intval( $revId )
- ],
- __METHOD__,
- [ 'ORDER BY' => 'rev_id', 'IGNORE INDEX' => 'PRIMARY' ]
- );
-
- if ( $revId === false ) {
- return false;
- } else {
- return intval( $revId );
- }
+ return $this->getRelativeRevisionID( $revId, $flags, 'next' );
}
/**
[ 'rev_page' => $pageId ],
__METHOD__,
[
- 'ORDER BY' => 'rev_timestamp ASC',
- 'IGNORE INDEX' => 'rev_timestamp'
+ 'ORDER BY' => 'rev_timestamp ASC, rev_id ASC',
+ 'IGNORE INDEX' => 'rev_timestamp', // See T159319
]
);
if ( $row ) {
}
/**
- * Whether the magic words __INDEX__ and __NOINDEX__ function for this page.
+ * Whether the magic words __INDEX__ and __NOINDEX__ function for this page.
*
* @return bool
*/