summaryrefslogtreecommitdiff
path: root/includes/specials
diff options
context:
space:
mode:
authorPierre Schmitz <pierre@archlinux.de>2014-12-27 15:41:37 +0100
committerPierre Schmitz <pierre@archlinux.de>2014-12-31 11:43:28 +0100
commitc1f9b1f7b1b77776192048005dcc66dcf3df2bfb (patch)
tree2b38796e738dd74cb42ecd9bfd151803108386bc /includes/specials
parentb88ab0086858470dd1f644e64cb4e4f62bb2be9b (diff)
Update to MediaWiki 1.24.1
Diffstat (limited to 'includes/specials')
-rw-r--r--includes/specials/SpecialActiveusers.php237
-rw-r--r--includes/specials/SpecialAllMessages.php (renamed from includes/specials/SpecialAllmessages.php)105
-rw-r--r--includes/specials/SpecialAllPages.php384
-rw-r--r--includes/specials/SpecialAllpages.php573
-rw-r--r--includes/specials/SpecialBlock.php141
-rw-r--r--includes/specials/SpecialBlockList.php65
-rw-r--r--includes/specials/SpecialBooksources.php30
-rw-r--r--includes/specials/SpecialBrokenRedirects.php4
-rw-r--r--includes/specials/SpecialCachedPage.php24
-rw-r--r--includes/specials/SpecialCategories.php72
-rw-r--r--includes/specials/SpecialChangeEmail.php255
-rw-r--r--includes/specials/SpecialChangePassword.php385
-rw-r--r--includes/specials/SpecialComparePages.php4
-rw-r--r--includes/specials/SpecialConfirmemail.php44
-rw-r--r--includes/specials/SpecialContributions.php254
-rw-r--r--includes/specials/SpecialCreateAccount.php56
-rw-r--r--includes/specials/SpecialDeletedContributions.php42
-rw-r--r--includes/specials/SpecialDiff.php61
-rw-r--r--includes/specials/SpecialEditWatchlist.php222
-rw-r--r--includes/specials/SpecialEmailuser.php60
-rw-r--r--includes/specials/SpecialExpandTemplates.php286
-rw-r--r--includes/specials/SpecialExport.php87
-rw-r--r--includes/specials/SpecialFewestrevisions.php2
-rw-r--r--includes/specials/SpecialFileDuplicateSearch.php12
-rw-r--r--includes/specials/SpecialFilepath.php8
-rw-r--r--includes/specials/SpecialImport.php164
-rw-r--r--includes/specials/SpecialJavaScriptTest.php33
-rw-r--r--includes/specials/SpecialLinkSearch.php114
-rw-r--r--includes/specials/SpecialListDuplicatedFiles.php113
-rw-r--r--includes/specials/SpecialListfiles.php223
-rw-r--r--includes/specials/SpecialListgrouprights.php200
-rw-r--r--includes/specials/SpecialListredirects.php11
-rw-r--r--includes/specials/SpecialListusers.php65
-rw-r--r--includes/specials/SpecialLockdb.php10
-rw-r--r--includes/specials/SpecialLog.php33
-rw-r--r--includes/specials/SpecialLonelypages.php52
-rw-r--r--includes/specials/SpecialMIMEsearch.php58
-rw-r--r--includes/specials/SpecialMediaStatistics.php325
-rw-r--r--includes/specials/SpecialMergeHistory.php105
-rw-r--r--includes/specials/SpecialMostinterwikis.php4
-rw-r--r--includes/specials/SpecialMostlinked.php6
-rw-r--r--includes/specials/SpecialMostlinkedcategories.php1
-rw-r--r--includes/specials/SpecialMostlinkedtemplates.php11
-rw-r--r--includes/specials/SpecialMostrevisions.php1
-rw-r--r--includes/specials/SpecialMovepage.php98
-rw-r--r--includes/specials/SpecialMyLanguage.php93
-rw-r--r--includes/specials/SpecialMyRedirectPages.php114
-rw-r--r--includes/specials/SpecialNewimages.php37
-rw-r--r--includes/specials/SpecialNewpages.php68
-rw-r--r--includes/specials/SpecialPageLanguage.php195
-rw-r--r--includes/specials/SpecialPagesWithProp.php45
-rw-r--r--includes/specials/SpecialPasswordReset.php43
-rw-r--r--includes/specials/SpecialPermanentLink.php45
-rw-r--r--includes/specials/SpecialPreferences.php16
-rw-r--r--includes/specials/SpecialPrefixindex.php46
-rw-r--r--includes/specials/SpecialProtectedpages.php406
-rw-r--r--includes/specials/SpecialProtectedtitles.php17
-rw-r--r--includes/specials/SpecialRandomInCategory.php77
-rw-r--r--includes/specials/SpecialRandompage.php9
-rw-r--r--includes/specials/SpecialRecentchanges.php562
-rw-r--r--includes/specials/SpecialRecentchangeslinked.php61
-rw-r--r--includes/specials/SpecialRedirect.php72
-rw-r--r--includes/specials/SpecialResetTokens.php12
-rw-r--r--includes/specials/SpecialRevisiondelete.php286
-rw-r--r--includes/specials/SpecialRunJobs.php112
-rw-r--r--includes/specials/SpecialSearch.php661
-rw-r--r--includes/specials/SpecialShortpages.php21
-rw-r--r--includes/specials/SpecialSpecialpages.php27
-rw-r--r--includes/specials/SpecialStatistics.php168
-rw-r--r--includes/specials/SpecialTags.php10
-rw-r--r--includes/specials/SpecialTrackingCategories.php148
-rw-r--r--includes/specials/SpecialUnblock.php38
-rw-r--r--includes/specials/SpecialUncategorizedimages.php13
-rw-r--r--includes/specials/SpecialUncategorizedpages.php26
-rw-r--r--includes/specials/SpecialUndelete.php393
-rw-r--r--includes/specials/SpecialUnlockdb.php11
-rw-r--r--includes/specials/SpecialUnusedcategories.php27
-rw-r--r--includes/specials/SpecialUnusedimages.php25
-rw-r--r--includes/specials/SpecialUnusedtemplates.php18
-rw-r--r--includes/specials/SpecialUnwatchedpages.php28
-rw-r--r--includes/specials/SpecialUpload.php254
-rw-r--r--includes/specials/SpecialUploadStash.php121
-rw-r--r--includes/specials/SpecialUserlogin.php455
-rw-r--r--includes/specials/SpecialUserlogout.php1
-rw-r--r--includes/specials/SpecialUserrights.php165
-rw-r--r--includes/specials/SpecialVersion.php738
-rw-r--r--includes/specials/SpecialWantedcategories.php68
-rw-r--r--includes/specials/SpecialWantedfiles.php77
-rw-r--r--includes/specials/SpecialWantedpages.php7
-rw-r--r--includes/specials/SpecialWantedtemplates.php22
-rw-r--r--includes/specials/SpecialWatchlist.php652
-rw-r--r--includes/specials/SpecialWhatlinkshere.php216
-rw-r--r--includes/specials/SpecialWithoutinterwiki.php34
93 files changed, 7611 insertions, 4169 deletions
diff --git a/includes/specials/SpecialActiveusers.php b/includes/specials/SpecialActiveusers.php
index 705dab55..ce436525 100644
--- a/includes/specials/SpecialActiveusers.php
+++ b/includes/specials/SpecialActiveusers.php
@@ -31,33 +31,35 @@
* @ingroup SpecialPage
*/
class ActiveUsersPager extends UsersPager {
-
/**
* @var FormOptions
*/
protected $opts;
/**
- * @var Array
+ * @var array
*/
protected $hideGroups = array();
/**
- * @var Array
+ * @var array
*/
protected $hideRights = array();
/**
- * @param $context IContextSource
- * @param $group null Unused
+ * @var array
+ */
+ private $blockStatusByUid;
+
+ /**
+ * @param IContextSource $context
+ * @param null $group Unused
* @param string $par Parameter passed to the page
*/
function __construct( IContextSource $context = null, $group = null, $par = null ) {
- global $wgActiveUserDays;
-
parent::__construct( $context );
- $this->RCMaxAge = $wgActiveUserDays;
+ $this->RCMaxAge = $this->getConfig()->get( 'ActiveUserDays' );
$un = $this->getRequest()->getText( 'username', $par );
$this->requestedUser = '';
if ( $un != '' ) {
@@ -87,39 +89,36 @@ class ActiveUsersPager extends UsersPager {
}
function getIndexField() {
- return 'rc_user_text';
+ return 'qcc_title';
}
function getQueryInfo() {
$dbr = $this->getDatabase();
- $conds = array( 'rc_user > 0' ); // Users - no anons
- $conds[] = 'rc_log_type IS NULL OR rc_log_type != ' . $dbr->addQuotes( 'newusers' );
- $conds[] = 'rc_timestamp >= ' . $dbr->addQuotes(
- $dbr->timestamp( wfTimestamp( TS_UNIX ) - $this->RCMaxAge * 24 * 3600 ) );
-
+ $activeUserSeconds = $this->getConfig()->get( 'ActiveUserDays' ) * 86400;
+ $timestamp = $dbr->timestamp( wfTimestamp( TS_UNIX ) - $activeUserSeconds );
+ $conds = array(
+ 'qcc_type' => 'activeusers',
+ 'qcc_namespace' => NS_USER,
+ 'user_name = qcc_title',
+ 'rc_user_text = qcc_title',
+ 'rc_type != ' . $dbr->addQuotes( RC_EXTERNAL ), // Don't count wikidata.
+ 'rc_log_type IS NULL OR rc_log_type != ' . $dbr->addQuotes( 'newusers' ),
+ 'rc_timestamp >= ' . $dbr->addQuotes( $timestamp ),
+ );
if ( $this->requestedUser != '' ) {
- $conds[] = 'rc_user_text >= ' . $dbr->addQuotes( $this->requestedUser );
+ $conds[] = 'qcc_title >= ' . $dbr->addQuotes( $this->requestedUser );
}
-
if ( !$this->getUser()->isAllowed( 'hideuser' ) ) {
$conds[] = 'NOT EXISTS (' . $dbr->selectSQLText(
- 'ipblocks', '1', array( 'rc_user=ipb_user', 'ipb_deleted' => 1 )
+ 'ipblocks', '1', array( 'ipb_user=user_id', 'ipb_deleted' => 1 )
) . ')';
}
return array(
- 'tables' => array( 'recentchanges' ),
- 'fields' => array(
- 'user_name' => 'rc_user_text', // for Pager inheritance
- 'rc_user_text', // for Pager
- 'user_id' => 'MAX(rc_user)', // Postgres
- 'recentedits' => 'COUNT(*)'
- ),
- 'options' => array(
- 'GROUP BY' => array( 'rc_user_text' ),
- 'USE INDEX' => array( 'recentchanges' => 'rc_user_text' )
- ),
+ 'tables' => array( 'querycachetwo', 'user', 'recentchanges' ),
+ 'fields' => array( 'user_name', 'user_id', 'recentedits' => 'COUNT(*)', 'qcc_title' ),
+ 'options' => array( 'GROUP BY' => array( 'qcc_title' ) ),
'conds' => $conds
);
}
@@ -196,25 +195,34 @@ class ActiveUsersPager extends UsersPager {
}
function getPageHeader() {
- global $wgScript;
-
$self = $this->getTitle();
$limit = $this->mLimit ? Html::hidden( 'limit', $this->mLimit ) : '';
- $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); # Form tag
+ # Form tag
+ $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => wfScript() ) );
$out .= Xml::fieldset( $this->msg( 'activeusers' )->text() ) . "\n";
$out .= Html::hidden( 'title', $self->getPrefixedDBkey() ) . $limit . "\n";
+ # Username field
$out .= Xml::inputLabel( $this->msg( 'activeusers-from' )->text(),
- 'username', 'offset', 20, $this->requestedUser, array( 'tabindex' => 1 ) ) . '<br />';# Username field
+ 'username', 'offset', 20, $this->requestedUser, array( 'tabindex' => 1 ) ) . '<br />';
$out .= Xml::checkLabel( $this->msg( 'activeusers-hidebots' )->text(),
'hidebots', 'hidebots', $this->opts->getValue( 'hidebots' ), array( 'tabindex' => 2 ) );
- $out .= Xml::checkLabel( $this->msg( 'activeusers-hidesysops' )->text(),
- 'hidesysops', 'hidesysops', $this->opts->getValue( 'hidesysops' ), array( 'tabindex' => 3 ) ) . '<br />';
-
- $out .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text(), array( 'tabindex' => 4 ) ) . "\n";# Submit button and form bottom
+ $out .= Xml::checkLabel(
+ $this->msg( 'activeusers-hidesysops' )->text(),
+ 'hidesysops',
+ 'hidesysops',
+ $this->opts->getValue( 'hidesysops' ),
+ array( 'tabindex' => 3 )
+ ) . '<br />';
+
+ # Submit button and form bottom
+ $out .= Xml::submitButton(
+ $this->msg( 'allpagessubmit' )->text(),
+ array( 'tabindex' => 4 )
+ ) . "\n";
$out .= Xml::closeElement( 'fieldset' );
$out .= Xml::closeElement( 'form' );
@@ -237,17 +245,23 @@ class SpecialActiveUsers extends SpecialPage {
/**
* Show the special page
*
- * @param $par Mixed: parameter passed to the page or null
+ * @param string $par Parameter passed to the page or null
*/
public function execute( $par ) {
- global $wgActiveUserDays;
+ $days = $this->getConfig()->get( 'ActiveUserDays' );
$this->setHeaders();
$this->outputHeader();
$out = $this->getOutput();
$out->wrapWikiMsg( "<div class='mw-activeusers-intro'>\n$1\n</div>",
- array( 'activeusers-intro', $this->getLanguage()->formatNum( $wgActiveUserDays ) ) );
+ array( 'activeusers-intro', $this->getLanguage()->formatNum( $days ) ) );
+
+ // Occasionally merge in new updates
+ $seconds = min( self::mergeActiveUsers( 600, $days ), $days * 86400 );
+ // Mention the level of staleness
+ $out->addWikiMsg( 'cachedspecial-viewing-cached-ttl',
+ $this->getLanguage()->formatDuration( $seconds ) );
$up = new ActiveUsersPager( $this->getContext(), null, $par );
@@ -269,4 +283,149 @@ class SpecialActiveUsers extends SpecialPage {
protected function getGroupName() {
return 'users';
}
+
+ /**
+ * @param int $period Seconds (do updates no more often than this)
+ * @param int $days How many days user must be idle before he is considered inactive
+ * @return int How many seconds old the cache is
+ */
+ public static function mergeActiveUsers( $period, $days ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $cTime = $dbr->selectField( 'querycache_info',
+ 'qci_timestamp',
+ array( 'qci_type' => 'activeusers' )
+ );
+
+ if ( !wfReadOnly() ) {
+ if ( !$cTime || ( time() - wfTimestamp( TS_UNIX, $cTime ) ) > $period ) {
+ $dbw = wfGetDB( DB_MASTER );
+ if ( $dbw->estimateRowCount( 'recentchanges' ) <= 10000 ) {
+ $window = $days * 86400; // small wiki
+ } else {
+ $window = $period * 2;
+ }
+ $cTime = self::doQueryCacheUpdate( $dbw, $days, $window ) ?: $cTime;
+ }
+ }
+
+ return ( time() -
+ ( $cTime ? wfTimestamp( TS_UNIX, $cTime ) : $days * 86400 ) );
+ }
+
+ /**
+ * @param DatabaseBase $dbw Passed in from updateSpecialPages.php
+ * @return void
+ */
+ public static function cacheUpdate( DatabaseBase $dbw ) {
+ global $wgActiveUserDays;
+
+ self::doQueryCacheUpdate( $dbw, $wgActiveUserDays, $wgActiveUserDays * 86400 );
+ }
+
+ /**
+ * Update the query cache as needed
+ *
+ * @param DatabaseBase $dbw
+ * @param int $days How many days user must be idle before he is considered inactive
+ * @param int $window Maximum time range of new data to scan (in seconds)
+ * @return int|bool UNIX timestamp the cache is now up-to-date as of (false on error)
+ */
+ protected static function doQueryCacheUpdate( DatabaseBase $dbw, $days, $window ) {
+ $lockKey = wfWikiID() . '-activeusers';
+ if ( !$dbw->lock( $lockKey, __METHOD__, 1 ) ) {
+ return false; // exclusive update (avoids duplicate entries)
+ }
+
+ $now = time();
+ $cTime = $dbw->selectField( 'querycache_info',
+ 'qci_timestamp',
+ array( 'qci_type' => 'activeusers' )
+ );
+ $cTimeUnix = $cTime ? wfTimestamp( TS_UNIX, $cTime ) : 1;
+
+ // Pick the date range to fetch from. This is normally from the last
+ // update to till the present time, but has a limited window for sanity.
+ // If the window is limited, multiple runs are need to fully populate it.
+ $sTimestamp = max( $cTimeUnix, $now - $days * 86400 );
+ $eTimestamp = min( $sTimestamp + $window, $now );
+
+ // Get all the users active since the last update
+ $res = $dbw->select(
+ array( 'recentchanges' ),
+ array( 'rc_user_text', 'lastedittime' => 'MAX(rc_timestamp)' ),
+ array(
+ 'rc_user > 0', // actual accounts
+ 'rc_type != ' . $dbw->addQuotes( RC_EXTERNAL ), // no wikidata
+ 'rc_log_type IS NULL OR rc_log_type != ' . $dbw->addQuotes( 'newusers' ),
+ 'rc_timestamp >= ' . $dbw->addQuotes( $dbw->timestamp( $sTimestamp ) ),
+ 'rc_timestamp <= ' . $dbw->addQuotes( $dbw->timestamp( $eTimestamp ) )
+ ),
+ __METHOD__,
+ array(
+ 'GROUP BY' => array( 'rc_user_text' ),
+ 'ORDER BY' => 'NULL' // avoid filesort
+ )
+ );
+ $names = array();
+ foreach ( $res as $row ) {
+ $names[$row->rc_user_text] = $row->lastedittime;
+ }
+
+ // Rotate out users that have not edited in too long (according to old data set)
+ $dbw->delete( 'querycachetwo',
+ array(
+ 'qcc_type' => 'activeusers',
+ 'qcc_value < ' . $dbw->addQuotes( $now - $days * 86400 ) // TS_UNIX
+ ),
+ __METHOD__
+ );
+
+ // Find which of the recently active users are already accounted for
+ if ( count( $names ) ) {
+ $res = $dbw->select( 'querycachetwo',
+ array( 'user_name' => 'qcc_title' ),
+ array(
+ 'qcc_type' => 'activeusers',
+ 'qcc_namespace' => NS_USER,
+ 'qcc_title' => array_keys( $names ) ),
+ __METHOD__
+ );
+ foreach ( $res as $row ) {
+ unset( $names[$row->user_name] );
+ }
+ }
+
+ // Insert the users that need to be added to the list (which their last edit time
+ if ( count( $names ) ) {
+ $newRows = array();
+ foreach ( $names as $name => $lastEditTime ) {
+ $newRows[] = array(
+ 'qcc_type' => 'activeusers',
+ 'qcc_namespace' => NS_USER,
+ 'qcc_title' => $name,
+ 'qcc_value' => wfTimestamp( TS_UNIX, $lastEditTime ),
+ 'qcc_namespacetwo' => 0, // unused
+ 'qcc_titletwo' => '' // unused
+ );
+ }
+ foreach ( array_chunk( $newRows, 500 ) as $rowBatch ) {
+ $dbw->insert( 'querycachetwo', $rowBatch, __METHOD__ );
+ if ( !$dbw->trxLevel() ) {
+ wfWaitForSlaves();
+ }
+ }
+ }
+
+ // Touch the data freshness timestamp
+ $dbw->replace( 'querycache_info',
+ array( 'qci_type' ),
+ array( 'qci_type' => 'activeusers',
+ 'qci_timestamp' => $dbw->timestamp( $eTimestamp ) ), // not always $now
+ __METHOD__
+ );
+
+ $dbw->unlock( $lockKey, __METHOD__ );
+
+ return $eTimestamp;
+ }
}
diff --git a/includes/specials/SpecialAllmessages.php b/includes/specials/SpecialAllMessages.php
index 35d6a0c0..96be4d03 100644
--- a/includes/specials/SpecialAllmessages.php
+++ b/includes/specials/SpecialAllMessages.php
@@ -27,7 +27,7 @@
* @file
* @ingroup SpecialPage
*/
-class SpecialAllmessages extends SpecialPage {
+class SpecialAllMessages extends SpecialPage {
/**
* @var AllmessagesTablePager
*/
@@ -43,7 +43,7 @@ class SpecialAllmessages extends SpecialPage {
/**
* Show the special page
*
- * @param $par Mixed: parameter passed to the page or null
+ * @param string $par Parameter passed to the page or null
*/
public function execute( $par ) {
$request = $this->getRequest();
@@ -51,15 +51,13 @@ class SpecialAllmessages extends SpecialPage {
$this->setHeaders();
- global $wgUseDatabaseMessages;
- if ( !$wgUseDatabaseMessages ) {
+ if ( !$this->getConfig()->get( 'UseDatabaseMessages' ) ) {
$out->addWikiMsg( 'allmessagesnotsupportedDB' );
return;
- } else {
- $this->outputHeader( 'allmessagestext' );
}
+ $this->outputHeader( 'allmessagestext' );
$out->addModuleStyles( 'mediawiki.special' );
$this->table = new AllmessagesTablePager(
@@ -70,10 +68,8 @@ class SpecialAllmessages extends SpecialPage {
$this->langcode = $this->table->lang->getCode();
- $out->addHTML( $this->table->buildForm() .
- $this->table->getNavigationBar() .
- $this->table->getBody() .
- $this->table->getNavigationBar() );
+ $out->addHTML( $this->table->buildForm() );
+ $out->addParserOutputContent( $this->table->getFullOutput() );
}
protected function getGroupName() {
@@ -85,7 +81,7 @@ class SpecialAllmessages extends SpecialPage {
* Use TablePager for prettified output. We have to pretend that we're
* getting data from a table when in fact not all of it comes from the database.
*/
-class AllmessagesTablePager extends TablePager {
+class AllMessagesTablePager extends TablePager {
protected $filter, $prefix, $langcode, $displayPrefix;
public $mLimitsShown;
@@ -105,7 +101,8 @@ class AllmessagesTablePager extends TablePager {
$this->mIndexField = 'am_title';
$this->mPage = $page;
$this->mConds = $conds;
- $this->mDefaultDirection = true; // always sort ascending
+ // FIXME: Why does this need to be set to DIR_DESCENDING to produce ascending ordering?
+ $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
$this->mLimitsShown = array( 20, 50, 100, 250, 500, 5000 );
global $wgContLang;
@@ -114,7 +111,7 @@ class AllmessagesTablePager extends TablePager {
$this->lang = ( $langObj ? $langObj : $wgContLang );
$this->langcode = $this->lang->getCode();
- $this->foreign = $this->langcode != $wgContLang->getCode();
+ $this->foreign = $this->langcode !== $wgContLang->getCode();
$request = $this->getRequest();
@@ -122,11 +119,14 @@ class AllmessagesTablePager extends TablePager {
if ( $this->filter === 'all' ) {
$this->custom = null; // So won't match in either case
} else {
- $this->custom = ( $this->filter == 'unmodified' );
+ $this->custom = ( $this->filter === 'unmodified' );
}
$prefix = $this->getLanguage()->ucfirst( $request->getVal( 'prefix', '' ) );
- $prefix = $prefix != '' ? Title::makeTitleSafe( NS_MEDIAWIKI, $request->getVal( 'prefix', null ) ) : null;
+ $prefix = $prefix !== '' ?
+ Title::makeTitleSafe( NS_MEDIAWIKI, $request->getVal( 'prefix', null ) ) :
+ null;
+
if ( $prefix !== null ) {
$this->displayPrefix = $prefix->getDBkey();
$this->prefix = '/^' . preg_quote( $this->displayPrefix ) . '/i';
@@ -145,13 +145,15 @@ class AllmessagesTablePager extends TablePager {
}
function buildForm() {
- global $wgScript;
-
$attrs = array( 'id' => 'mw-allmessages-form-lang', 'name' => 'lang' );
$msg = wfMessage( 'allmessages-language' );
$langSelect = Xml::languageSelector( $this->langcode, false, null, $attrs, $msg );
- $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-allmessages-form' ) ) .
+ $out = Xml::openElement( 'form', array(
+ 'method' => 'get',
+ 'action' => $this->getConfig()->get( 'Script' ),
+ 'id' => 'mw-allmessages-form'
+ ) ) .
Xml::fieldset( $this->msg( 'allmessages-filter-legend' )->text() ) .
Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
Xml::openElement( 'table', array( 'class' => 'mw-allmessages-table' ) ) . "\n" .
@@ -160,7 +162,12 @@ class AllmessagesTablePager extends TablePager {
Xml::label( $this->msg( 'allmessages-prefix' )->text(), 'mw-allmessages-form-prefix' ) .
"</td>\n
<td class=\"mw-input\">" .
- Xml::input( 'prefix', 20, str_replace( '_', ' ', $this->displayPrefix ), array( 'id' => 'mw-allmessages-form-prefix' ) ) .
+ Xml::input(
+ 'prefix',
+ 20,
+ str_replace( '_', ' ', $this->displayPrefix ),
+ array( 'id' => 'mw-allmessages-form-prefix' )
+ ) .
"</td>\n
</tr>
<tr>\n
@@ -172,19 +179,19 @@ class AllmessagesTablePager extends TablePager {
'filter',
'unmodified',
'mw-allmessages-form-filter-unmodified',
- ( $this->filter == 'unmodified' )
+ ( $this->filter === 'unmodified' )
) .
Xml::radioLabel( $this->msg( 'allmessages-filter-all' )->text(),
'filter',
'all',
'mw-allmessages-form-filter-all',
- ( $this->filter == 'all' )
+ ( $this->filter === 'all' )
) .
Xml::radioLabel( $this->msg( 'allmessages-filter-modified' )->text(),
'filter',
'modified',
'mw-allmessages-form-filter-modified',
- ( $this->filter == 'modified' )
+ ( $this->filter === 'modified' )
) .
"</td>\n
</tr>
@@ -241,7 +248,7 @@ class AllmessagesTablePager extends TablePager {
* @param array $messageNames
* @param string $langcode What language code
* @param bool $foreign Whether the $langcode is not the content language
- * @return array: a 'pages' and 'talks' array with the keys of existing pages
+ * @return array A 'pages' and 'talks' array with the keys of existing pages
*/
public static function getCustomisedStatuses( $messageNames, $langcode = 'en', $foreign = false ) {
// FIXME: This function should be moved to Language:: or something.
@@ -260,19 +267,23 @@ class AllmessagesTablePager extends TablePager {
foreach ( $res as $s ) {
$exists = false;
+
if ( $foreign ) {
- $title = explode( '/', $s->page_title );
- if ( count( $title ) === 2 && $langcode == $title[1]
- && isset( $xNames[$title[0]] )
+ $titleParts = explode( '/', $s->page_title );
+ if ( count( $titleParts ) === 2 &&
+ $langcode === $titleParts[1] &&
+ isset( $xNames[$titleParts[0]] )
) {
- $exists = $title[0];
+ $exists = $titleParts[0];
}
} elseif ( isset( $xNames[$s->page_title] ) ) {
$exists = $s->page_title;
}
- if ( $exists && $s->page_namespace == NS_MEDIAWIKI ) {
+
+ $title = Title::newFromRow( $s );
+ if ( $exists && $title->inNamespace( NS_MEDIAWIKI ) ) {
$pageFlags[$exists] = true;
- } elseif ( $exists && $s->page_namespace == NS_MEDIAWIKI_TALK ) {
+ } elseif ( $exists && $title->inNamespace( NS_MEDIAWIKI_TALK ) ) {
$talkFlags[$exists] = true;
}
}
@@ -315,7 +326,7 @@ class AllmessagesTablePager extends TablePager {
$count++;
}
- if ( $count == $limit ) {
+ if ( $count === $limit ) {
break;
}
}
@@ -324,7 +335,12 @@ class AllmessagesTablePager extends TablePager {
}
function getStartBody() {
- return Xml::openElement( 'table', array( 'class' => 'mw-datatable TablePager', 'id' => 'mw-allmessagestable' ) ) . "\n" .
+ $tableClass = $this->getTableClass();
+ return Xml::openElement( 'table', array(
+ 'class' => "mw-datatable $tableClass",
+ 'id' => 'mw-allmessagestable'
+ ) ) .
+ "\n" .
"<thead><tr>
<th rowspan=\"2\">" .
$this->msg( 'allmessagesname' )->escaped() . "
@@ -345,6 +361,17 @@ class AllmessagesTablePager extends TablePager {
case 'am_title' :
$title = Title::makeTitle( NS_MEDIAWIKI, $value . $this->suffix );
$talk = Title::makeTitle( NS_MEDIAWIKI_TALK, $value . $this->suffix );
+ $translation = Linker::makeExternalLink(
+ 'https://translatewiki.net/w/i.php?' . wfArrayToCgi( array(
+ 'title' => 'Special:SearchTranslations',
+ 'group' => 'mediawiki',
+ 'grouppath' => 'mediawiki',
+ 'query' => 'language:' . $this->getLanguage()->getCode() . '^25 ' .
+ 'messageid:"MediaWiki:' . $value . '"^10 "' .
+ $this->msg( $value )->inLanguage( 'en' )->plain() . '"'
+ ) ),
+ $this->msg( 'allmessages-filter-translate' )->text()
+ );
if ( $this->mCurrentRow->am_customised ) {
$title = Linker::linkKnown( $title, $this->getLanguage()->lcfirst( $value ) );
@@ -369,12 +396,16 @@ class AllmessagesTablePager extends TablePager {
);
}
- return $title . ' ' . $this->msg( 'parentheses' )->rawParams( $talk )->escaped();
+ return $title . ' '
+ . $this->msg( 'parentheses' )->rawParams( $talk )->escaped()
+ . ' '
+ . $this->msg( 'parentheses' )->rawParams( $translation )->escaped();
case 'am_default' :
case 'am_actual' :
return Sanitizer::escapeHtmlAllowEntities( $value, ENT_QUOTES );
}
+
return '';
}
@@ -386,9 +417,11 @@ class AllmessagesTablePager extends TablePager {
if ( $row->am_customised ) {
$s .= Xml::openElement( 'tr', $this->getRowAttrs( $row, true ) );
$formatted = strval( $this->formatValue( 'am_actual', $row->am_actual ) );
- if ( $formatted == '' ) {
+
+ if ( $formatted === '' ) {
$formatted = '&#160;';
}
+
$s .= Xml::tags( 'td', $this->getCellAttrs( 'am_actual', $row->am_actual ), $formatted )
. "</tr>\n";
}
@@ -398,9 +431,11 @@ class AllmessagesTablePager extends TablePager {
function getRowAttrs( $row, $isSecond = false ) {
$arr = array();
+
if ( $row->am_customised ) {
$arr['class'] = 'allmessages-customised';
}
+
if ( !$isSecond ) {
$arr['id'] = Sanitizer::escapeId( 'msg_' . $this->getLanguage()->lcfirst( $row->am_title ) );
}
@@ -409,9 +444,9 @@ class AllmessagesTablePager extends TablePager {
}
function getCellAttrs( $field, $value ) {
- if ( $this->mCurrentRow->am_customised && $field == 'am_title' ) {
+ if ( $this->mCurrentRow->am_customised && $field === 'am_title' ) {
return array( 'rowspan' => '2', 'class' => $field );
- } elseif ( $field == 'am_title' ) {
+ } elseif ( $field === 'am_title' ) {
return array( 'class' => $field );
} else {
return array( 'lang' => $this->langcode, 'dir' => $this->lang->getDir(), 'class' => $field );
diff --git a/includes/specials/SpecialAllPages.php b/includes/specials/SpecialAllPages.php
new file mode 100644
index 00000000..08b8761a
--- /dev/null
+++ b/includes/specials/SpecialAllPages.php
@@ -0,0 +1,384 @@
+<?php
+/**
+ * Implements Special:Allpages
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * Implements Special:Allpages
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialAllPages extends IncludableSpecialPage {
+
+ /**
+ * Maximum number of pages to show on single subpage.
+ *
+ * @var int $maxPerPage
+ */
+ protected $maxPerPage = 345;
+
+ /**
+ * Determines, which message describes the input field 'nsfrom'.
+ *
+ * @var string $nsfromMsg
+ */
+ protected $nsfromMsg = 'allpagesfrom';
+
+ /**
+ * Constructor
+ *
+ * @param string $name Name of the special page, as seen in links and URLs (default: 'Allpages')
+ */
+ function __construct( $name = 'Allpages' ) {
+ parent::__construct( $name );
+ }
+
+ /**
+ * Entry point : initialise variables and call subfunctions.
+ *
+ * @param string $par Becomes "FOO" when called like Special:Allpages/FOO (default null)
+ */
+ function execute( $par ) {
+ $request = $this->getRequest();
+ $out = $this->getOutput();
+
+ $this->setHeaders();
+ $this->outputHeader();
+ $out->allowClickjacking();
+
+ # GET values
+ $from = $request->getVal( 'from', null );
+ $to = $request->getVal( 'to', null );
+ $namespace = $request->getInt( 'namespace' );
+ $hideredirects = $request->getBool( 'hideredirects', false );
+
+ $namespaces = $this->getContext()->getLanguage()->getNamespaces();
+
+ $out->setPageTitle(
+ ( $namespace > 0 && array_key_exists( $namespace, $namespaces ) ) ?
+ $this->msg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) :
+ $this->msg( 'allarticles' )
+ );
+ $out->addModuleStyles( 'mediawiki.special' );
+
+ if ( $par !== null ) {
+ $this->showChunk( $namespace, $par, $to, $hideredirects );
+ } elseif ( $from !== null && $to === null ) {
+ $this->showChunk( $namespace, $from, $to, $hideredirects );
+ } else {
+ $this->showToplevel( $namespace, $from, $to, $hideredirects );
+ }
+ }
+
+ /**
+ * HTML for the top form
+ *
+ * @param int $namespace A namespace constant (default NS_MAIN).
+ * @param string $from DbKey we are starting listing at.
+ * @param string $to DbKey we are ending listing at.
+ * @param bool $hideredirects Dont show redirects (default false)
+ * @return string
+ */
+ function namespaceForm( $namespace = NS_MAIN, $from = '', $to = '', $hideredirects = false ) {
+ $t = $this->getPageTitle();
+
+ $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
+ $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $this->getConfig()->get( 'Script' ) ) );
+ $out .= Html::hidden( 'title', $t->getPrefixedText() );
+ $out .= Xml::openElement( 'fieldset' );
+ $out .= Xml::element( 'legend', null, $this->msg( 'allpages' )->text() );
+ $out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) );
+ $out .= "<tr>
+ <td class='mw-label'>" .
+ Xml::label( $this->msg( 'allpagesfrom' )->text(), 'nsfrom' ) .
+ " </td>
+ <td class='mw-input'>" .
+ Xml::input( 'from', 30, str_replace( '_', ' ', $from ), array( 'id' => 'nsfrom' ) ) .
+ " </td>
+</tr>
+<tr>
+ <td class='mw-label'>" .
+ Xml::label( $this->msg( 'allpagesto' )->text(), 'nsto' ) .
+ " </td>
+ <td class='mw-input'>" .
+ Xml::input( 'to', 30, str_replace( '_', ' ', $to ), array( 'id' => 'nsto' ) ) .
+ " </td>
+</tr>
+<tr>
+ <td class='mw-label'>" .
+ Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ) .
+ " </td>
+ <td class='mw-input'>" .
+ Html::namespaceSelector(
+ array( 'selected' => $namespace ),
+ array( 'name' => 'namespace', 'id' => 'namespace' )
+ ) . ' ' .
+ Xml::checkLabel(
+ $this->msg( 'allpages-hide-redirects' )->text(),
+ 'hideredirects',
+ 'hideredirects',
+ $hideredirects
+ ) . ' ' .
+ Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) .
+ " </td>
+</tr>";
+ $out .= Xml::closeElement( 'table' );
+ $out .= Xml::closeElement( 'fieldset' );
+ $out .= Xml::closeElement( 'form' );
+ $out .= Xml::closeElement( 'div' );
+
+ return $out;
+ }
+
+ /**
+ * @param int $namespace (default NS_MAIN)
+ * @param string $from List all pages from this name
+ * @param string $to List all pages to this name
+ * @param bool $hideredirects Dont show redirects (default false)
+ */
+ function showToplevel( $namespace = NS_MAIN, $from = '', $to = '', $hideredirects = false ) {
+ $from = Title::makeTitleSafe( $namespace, $from );
+ $to = Title::makeTitleSafe( $namespace, $to );
+ $from = ( $from && $from->isLocal() ) ? $from->getDBkey() : null;
+ $to = ( $to && $to->isLocal() ) ? $to->getDBkey() : null;
+
+ $this->showChunk( $namespace, $from, $to, $hideredirects );
+ }
+
+ /**
+ * @param int $namespace Namespace (Default NS_MAIN)
+ * @param string $from List all pages from this name (default false)
+ * @param string $to List all pages to this name (default false)
+ * @param bool $hideredirects Dont show redirects (default false)
+ */
+ function showChunk( $namespace = NS_MAIN, $from = false, $to = false, $hideredirects = false ) {
+ $output = $this->getOutput();
+
+ $fromList = $this->getNamespaceKeyAndText( $namespace, $from );
+ $toList = $this->getNamespaceKeyAndText( $namespace, $to );
+ $namespaces = $this->getContext()->getLanguage()->getNamespaces();
+ $n = 0;
+
+ if ( !$fromList || !$toList ) {
+ $out = $this->msg( 'allpagesbadtitle' )->parseAsBlock();
+ } elseif ( !array_key_exists( $namespace, $namespaces ) ) {
+ // Show errormessage and reset to NS_MAIN
+ $out = $this->msg( 'allpages-bad-ns', $namespace )->parse();
+ $namespace = NS_MAIN;
+ } else {
+ list( $namespace, $fromKey, $from ) = $fromList;
+ list( , $toKey, $to ) = $toList;
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $conds = array(
+ 'page_namespace' => $namespace,
+ 'page_title >= ' . $dbr->addQuotes( $fromKey )
+ );
+
+ if ( $hideredirects ) {
+ $conds['page_is_redirect'] = 0;
+ }
+
+ if ( $toKey !== "" ) {
+ $conds[] = 'page_title <= ' . $dbr->addQuotes( $toKey );
+ }
+
+ $res = $dbr->select( 'page',
+ array( 'page_namespace', 'page_title', 'page_is_redirect', 'page_id' ),
+ $conds,
+ __METHOD__,
+ array(
+ 'ORDER BY' => 'page_title',
+ 'LIMIT' => $this->maxPerPage + 1,
+ 'USE INDEX' => 'name_title',
+ )
+ );
+
+ if ( $res->numRows() > 0 ) {
+ $out = Xml::openElement( 'ul', array( 'class' => 'mw-allpages-chunk' ) );
+ while ( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
+ $t = Title::newFromRow( $s );
+ if ( $t ) {
+ $out .= '<li' .
+ ( $s->page_is_redirect ? ' class="allpagesredirect"' : '' ) .
+ '>' .
+ Linker::link( $t ) .
+ "</li>\n";
+ } else {
+ $out .= '<li>[[' . htmlspecialchars( $s->page_title ) . "]]</li>\n";
+ }
+ $n++;
+ }
+ $out .= Xml::closeElement( 'ul' );
+ } else {
+ $out = '';
+ }
+ }
+
+ if ( $this->including() ) {
+ $output->addHTML( $out );
+ return;
+ }
+
+ if ( $from == '' ) {
+ // First chunk; no previous link.
+ $prevTitle = null;
+ } else {
+ # Get the last title from previous chunk
+ $dbr = wfGetDB( DB_SLAVE );
+ $res_prev = $dbr->select(
+ 'page',
+ 'page_title',
+ array( 'page_namespace' => $namespace, 'page_title < ' . $dbr->addQuotes( $from ) ),
+ __METHOD__,
+ array( 'ORDER BY' => 'page_title DESC',
+ 'LIMIT' => $this->maxPerPage, 'OFFSET' => ( $this->maxPerPage - 1 )
+ )
+ );
+
+ # Get first title of previous complete chunk
+ if ( $dbr->numrows( $res_prev ) >= $this->maxPerPage ) {
+ $pt = $dbr->fetchObject( $res_prev );
+ $prevTitle = Title::makeTitle( $namespace, $pt->page_title );
+ } else {
+ # The previous chunk is not complete, need to link to the very first title
+ # available in the database
+ $options = array( 'LIMIT' => 1 );
+ if ( !$dbr->implicitOrderby() ) {
+ $options['ORDER BY'] = 'page_title';
+ }
+ $reallyFirstPage_title = $dbr->selectField( 'page', 'page_title',
+ array( 'page_namespace' => $namespace ), __METHOD__, $options );
+ # Show the previous link if it s not the current requested chunk
+ if ( $from != $reallyFirstPage_title ) {
+ $prevTitle = Title::makeTitle( $namespace, $reallyFirstPage_title );
+ } else {
+ $prevTitle = null;
+ }
+ }
+ }
+
+ $self = $this->getPageTitle();
+
+ $topLinks = array(
+ Linker::link( $self, $this->msg( 'allpages' )->escaped() )
+ );
+ $bottomLinks = array();
+
+ # Do we put a previous link ?
+ if ( $prevTitle && $pt = $prevTitle->getText() ) {
+ $query = array( 'from' => $prevTitle->getText() );
+
+ if ( $namespace ) {
+ $query['namespace'] = $namespace;
+ }
+
+ if ( $hideredirects ) {
+ $query['hideredirects'] = $hideredirects;
+ }
+
+ $prevLink = Linker::linkKnown(
+ $self,
+ $this->msg( 'prevpage', $pt )->escaped(),
+ array(),
+ $query
+ );
+ $topLinks[] = $prevLink;
+ $bottomLinks[] = $prevLink;
+ }
+
+ if ( $n == $this->maxPerPage && $s = $res->fetchObject() ) {
+ # $s is the first link of the next chunk
+ $t = Title::makeTitle( $namespace, $s->page_title );
+ $query = array( 'from' => $t->getText() );
+
+ if ( $namespace ) {
+ $query['namespace'] = $namespace;
+ }
+
+ if ( $hideredirects ) {
+ $query['hideredirects'] = $hideredirects;
+ }
+
+ $nextLink = Linker::linkKnown(
+ $self,
+ $this->msg( 'nextpage', $t->getText() )->escaped(),
+ array(),
+ $query
+ );
+ $topLinks[] = $nextLink;
+ $bottomLinks[] = $nextLink;
+ }
+
+ $nsForm = $this->namespaceForm( $namespace, $from, $to, $hideredirects );
+ $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ) .
+ '<tr>
+ <td>' .
+ $nsForm .
+ '</td>
+ <td class="mw-allpages-nav">' .
+ $this->getLanguage()->pipeList( $topLinks ) .
+ '</td></tr></table>';
+
+ $output->addHTML( $out2 . $out );
+
+ if ( count( $bottomLinks ) ) {
+ $output->addHTML(
+ Html::element( 'hr' ) .
+ Html::rawElement( 'div', array( 'class' => 'mw-allpages-nav' ),
+ $this->getLanguage()->pipeList( $bottomLinks )
+ )
+ );
+ }
+ }
+
+ /**
+ * @param int $ns The namespace of the article
+ * @param string $text The name of the article
+ * @return array( int namespace, string dbkey, string pagename ) or null on error
+ */
+ protected function getNamespaceKeyAndText( $ns, $text ) {
+ if ( $text == '' ) {
+ # shortcut for common case
+ return array( $ns, '', '' );
+ }
+
+ $t = Title::makeTitleSafe( $ns, $text );
+ if ( $t && $t->isLocal() ) {
+ return array( $t->getNamespace(), $t->getDBkey(), $t->getText() );
+ } elseif ( $t ) {
+ return null;
+ }
+
+ # try again, in case the problem was an empty pagename
+ $text = preg_replace( '/(#|$)/', 'X$1', $text );
+ $t = Title::makeTitleSafe( $ns, $text );
+ if ( $t && $t->isLocal() ) {
+ return array( $t->getNamespace(), '', '' );
+ } else {
+ return null;
+ }
+ }
+
+ protected function getGroupName() {
+ return 'pages';
+ }
+}
diff --git a/includes/specials/SpecialAllpages.php b/includes/specials/SpecialAllpages.php
deleted file mode 100644
index a0820493..00000000
--- a/includes/specials/SpecialAllpages.php
+++ /dev/null
@@ -1,573 +0,0 @@
-<?php
-/**
- * Implements Special:Allpages
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- */
-
-/**
- * Implements Special:Allpages
- *
- * @ingroup SpecialPage
- */
-class SpecialAllpages extends IncludableSpecialPage {
-
- /**
- * Maximum number of pages to show on single subpage.
- *
- * @var int $maxPerPage
- */
- protected $maxPerPage = 345;
-
- /**
- * Maximum number of pages to show on single index subpage.
- *
- * @var int $maxLineCount
- */
- protected $maxLineCount = 100;
-
- /**
- * Maximum number of chars to show for an entry.
- *
- * @var int $maxPageLength
- */
- protected $maxPageLength = 70;
-
- /**
- * Determines, which message describes the input field 'nsfrom'.
- *
- * @var string $nsfromMsg
- */
- protected $nsfromMsg = 'allpagesfrom';
-
- /**
- * Constructor
- *
- * @param string $name name of the special page, as seen in links and URLs (default: 'Allpages')
- */
- function __construct( $name = 'Allpages' ) {
- parent::__construct( $name );
- }
-
- /**
- * Entry point : initialise variables and call subfunctions.
- *
- * @param string $par becomes "FOO" when called like Special:Allpages/FOO (default NULL)
- */
- function execute( $par ) {
- $request = $this->getRequest();
- $out = $this->getOutput();
-
- $this->setHeaders();
- $this->outputHeader();
- $out->allowClickjacking();
-
- # GET values
- $from = $request->getVal( 'from', null );
- $to = $request->getVal( 'to', null );
- $namespace = $request->getInt( 'namespace' );
- $hideredirects = $request->getBool( 'hideredirects', false );
-
- $namespaces = $this->getContext()->getLanguage()->getNamespaces();
-
- $out->setPageTitle(
- ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces ) ) ) ?
- $this->msg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) :
- $this->msg( 'allarticles' )
- );
- $out->addModuleStyles( 'mediawiki.special' );
-
- if ( $par !== null ) {
- $this->showChunk( $namespace, $par, $to, $hideredirects );
- } elseif ( $from !== null && $to === null ) {
- $this->showChunk( $namespace, $from, $to, $hideredirects );
- } else {
- $this->showToplevel( $namespace, $from, $to, $hideredirects );
- }
- }
-
- /**
- * HTML for the top form
- *
- * @param $namespace Integer: a namespace constant (default NS_MAIN).
- * @param string $from dbKey we are starting listing at.
- * @param string $to dbKey we are ending listing at.
- * @param bool $hideredirects dont show redirects (default FALSE)
- * @return string
- */
- function namespaceForm( $namespace = NS_MAIN, $from = '', $to = '', $hideredirects = false ) {
- global $wgScript;
- $t = $this->getTitle();
-
- $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
- $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
- $out .= Html::hidden( 'title', $t->getPrefixedText() );
- $out .= Xml::openElement( 'fieldset' );
- $out .= Xml::element( 'legend', null, $this->msg( 'allpages' )->text() );
- $out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) );
- $out .= "<tr>
- <td class='mw-label'>" .
- Xml::label( $this->msg( 'allpagesfrom' )->text(), 'nsfrom' ) .
- " </td>
- <td class='mw-input'>" .
- Xml::input( 'from', 30, str_replace( '_', ' ', $from ), array( 'id' => 'nsfrom' ) ) .
- " </td>
-</tr>
-<tr>
- <td class='mw-label'>" .
- Xml::label( $this->msg( 'allpagesto' )->text(), 'nsto' ) .
- " </td>
- <td class='mw-input'>" .
- Xml::input( 'to', 30, str_replace( '_', ' ', $to ), array( 'id' => 'nsto' ) ) .
- " </td>
-</tr>
-<tr>
- <td class='mw-label'>" .
- Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ) .
- " </td>
- <td class='mw-input'>" .
- Html::namespaceSelector(
- array( 'selected' => $namespace ),
- array( 'name' => 'namespace', 'id' => 'namespace' )
- ) . ' ' .
- Xml::checkLabel(
- $this->msg( 'allpages-hide-redirects' )->text(),
- 'hideredirects',
- 'hideredirects',
- $hideredirects
- ) . ' ' .
- Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) .
- " </td>
-</tr>";
- $out .= Xml::closeElement( 'table' );
- $out .= Xml::closeElement( 'fieldset' );
- $out .= Xml::closeElement( 'form' );
- $out .= Xml::closeElement( 'div' );
-
- return $out;
- }
-
- /**
- * @param $namespace Integer (default NS_MAIN)
- * @param string $from list all pages from this name
- * @param string $to list all pages to this name
- * @param bool $hideredirects dont show redirects (default FALSE)
- */
- function showToplevel( $namespace = NS_MAIN, $from = '', $to = '', $hideredirects = false ) {
- $output = $this->getOutput();
-
- # TODO: Either make this *much* faster or cache the title index points
- # in the querycache table.
-
- $dbr = wfGetDB( DB_SLAVE );
- $out = "";
- $where = array( 'page_namespace' => $namespace );
-
- if ( $hideredirects ) {
- $where['page_is_redirect'] = 0;
- }
-
- $from = Title::makeTitleSafe( $namespace, $from );
- $to = Title::makeTitleSafe( $namespace, $to );
- $from = ( $from && $from->isLocal() ) ? $from->getDBkey() : null;
- $to = ( $to && $to->isLocal() ) ? $to->getDBkey() : null;
-
- if ( isset( $from ) ) {
- $where[] = 'page_title >= ' . $dbr->addQuotes( $from );
- }
-
- if ( isset( $to ) ) {
- $where[] = 'page_title <= ' . $dbr->addQuotes( $to );
- }
-
- global $wgMemc;
- $key = wfMemcKey( 'allpages', 'ns', $namespace, sha1( $from ), sha1( $to ) );
- $lines = $wgMemc->get( $key );
-
- $count = $dbr->estimateRowCount( 'page', '*', $where, __METHOD__ );
- $maxPerSubpage = intval( $count / $this->maxLineCount );
- $maxPerSubpage = max( $maxPerSubpage, $this->maxPerPage );
-
- if ( !is_array( $lines ) ) {
- $options = array( 'LIMIT' => 1 );
- $options['ORDER BY'] = 'page_title ASC';
- $firstTitle = $dbr->selectField( 'page', 'page_title', $where, __METHOD__, $options );
- $lastTitle = $firstTitle;
- # This array is going to hold the page_titles in order.
- $lines = array( $firstTitle );
- # If we are going to show n rows, we need n+1 queries to find the relevant titles.
- $done = false;
- while ( !$done ) {
- // Fetch the last title of this chunk and the first of the next
- $chunk = ( $lastTitle === false )
- ? array()
- : array( 'page_title >= ' . $dbr->addQuotes( $lastTitle ) );
- $res = $dbr->select( 'page', /* FROM */
- 'page_title', /* WHAT */
- array_merge( $where, $chunk ),
- __METHOD__,
- array( 'LIMIT' => 2, 'OFFSET' => $maxPerSubpage - 1, 'ORDER BY' => 'page_title ASC' )
- );
-
- $s = $dbr->fetchObject( $res );
- if ( $s ) {
- array_push( $lines, $s->page_title );
- } else {
- // Final chunk, but ended prematurely. Go back and find the end.
- $endTitle = $dbr->selectField( 'page', 'MAX(page_title)',
- array_merge( $where, $chunk ),
- __METHOD__ );
- array_push( $lines, $endTitle );
- $done = true;
- }
-
- $s = $res->fetchObject();
- if ( $s ) {
- array_push( $lines, $s->page_title );
- $lastTitle = $s->page_title;
- } else {
- // This was a final chunk and ended exactly at the limit.
- // Rare but convenient!
- $done = true;
- }
- $res->free();
- }
- $wgMemc->add( $key, $lines, 3600 );
- }
-
- // If there are only two or less sections, don't even display them.
- // Instead, display the first section directly.
- if ( count( $lines ) <= 2 ) {
- if ( !empty( $lines ) ) {
- $this->showChunk( $namespace, $from, $to, $hideredirects );
- } else {
- $output->addHTML( $this->namespaceForm( $namespace, $from, $to, $hideredirects ) );
- }
-
- return;
- }
-
- # At this point, $lines should contain an even number of elements.
- $out .= Xml::openElement( 'table', array( 'class' => 'allpageslist' ) );
- while ( count( $lines ) > 0 ) {
- $inpoint = array_shift( $lines );
- $outpoint = array_shift( $lines );
- $out .= $this->showline( $inpoint, $outpoint, $namespace, $hideredirects );
- }
- $out .= Xml::closeElement( 'table' );
- $nsForm = $this->namespaceForm( $namespace, $from, $to, $hideredirects );
-
- # Is there more?
- if ( $this->including() ) {
- $out2 = '';
- } else {
- if ( isset( $from ) || isset( $to ) ) {
- $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ) .
- '<tr>
- <td>' .
- $nsForm .
- '</td>
- <td class="mw-allpages-nav">' .
- Linker::link( $this->getTitle(), $this->msg( 'allpages' )->escaped(),
- array(), array(), 'known' ) .
- "</td>
- </tr>" .
- Xml::closeElement( 'table' );
- } else {
- $out2 = $nsForm;
- }
- }
- $output->addHTML( $out2 . $out );
- }
-
- /**
- * Show a line of "ABC to DEF" ranges of articles
- *
- * @param string $inpoint lower limit of pagenames
- * @param string $outpoint upper limit of pagenames
- * @param $namespace Integer (Default NS_MAIN)
- * @param bool $hideRedirects don't show redirects. Default: false
- * @return string
- */
- function showline( $inpoint, $outpoint, $namespace = NS_MAIN, $hideRedirects = false ) {
- // Use content language since page titles are considered to use content language
- global $wgContLang;
-
- $inpointf = str_replace( '_', ' ', $inpoint );
- $outpointf = str_replace( '_', ' ', $outpoint );
-
- // Don't let the length runaway
- $inpointf = $wgContLang->truncate( $inpointf, $this->maxPageLength );
- $outpointf = $wgContLang->truncate( $outpointf, $this->maxPageLength );
-
- $queryParams = array(
- 'from' => $inpoint,
- 'to' => $outpoint,
- );
-
- if ( $namespace ) {
- $queryParams['namespace'] = $namespace;
- }
- if ( $hideRedirects ) {
- $queryParams['hideredirects'] = 1;
- }
-
- $url = $this->getTitle()->getLocalURL( $queryParams );
- $inlink = Html::element( 'a', array( 'href' => $url ), $inpointf );
- $outlink = Html::element( 'a', array( 'href' => $url ), $outpointf );
-
- $out = $this->msg( 'alphaindexline' )->rawParams(
- "$inlink</td><td>",
- "</td><td>$outlink"
- )->escaped();
-
- return '<tr><td class="mw-allpages-alphaindexline">' . $out . '</td></tr>';
- }
-
- /**
- * @param int $namespace Namespace (Default NS_MAIN)
- * @param string $from list all pages from this name (default FALSE)
- * @param string $to list all pages to this name (default FALSE)
- * @param bool $hideredirects dont show redirects (default FALSE)
- */
- function showChunk( $namespace = NS_MAIN, $from = false, $to = false, $hideredirects = false ) {
- $output = $this->getOutput();
-
- $fromList = $this->getNamespaceKeyAndText( $namespace, $from );
- $toList = $this->getNamespaceKeyAndText( $namespace, $to );
- $namespaces = $this->getContext()->getLanguage()->getNamespaces();
- $n = 0;
-
- if ( !$fromList || !$toList ) {
- $out = $this->msg( 'allpagesbadtitle' )->parseAsBlock();
- } elseif ( !in_array( $namespace, array_keys( $namespaces ) ) ) {
- // Show errormessage and reset to NS_MAIN
- $out = $this->msg( 'allpages-bad-ns', $namespace )->parse();
- $namespace = NS_MAIN;
- } else {
- list( $namespace, $fromKey, $from ) = $fromList;
- list( , $toKey, $to ) = $toList;
-
- $dbr = wfGetDB( DB_SLAVE );
- $conds = array(
- 'page_namespace' => $namespace,
- 'page_title >= ' . $dbr->addQuotes( $fromKey )
- );
-
- if ( $hideredirects ) {
- $conds['page_is_redirect'] = 0;
- }
-
- if ( $toKey !== "" ) {
- $conds[] = 'page_title <= ' . $dbr->addQuotes( $toKey );
- }
-
- $res = $dbr->select( 'page',
- array( 'page_namespace', 'page_title', 'page_is_redirect', 'page_id' ),
- $conds,
- __METHOD__,
- array(
- 'ORDER BY' => 'page_title',
- 'LIMIT' => $this->maxPerPage + 1,
- 'USE INDEX' => 'name_title',
- )
- );
-
- if ( $res->numRows() > 0 ) {
- $out = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-chunk' ) );
- while ( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
- $t = Title::newFromRow( $s );
- if ( $t ) {
- $link = ( $s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) .
- Linker::link( $t ) .
- ( $s->page_is_redirect ? '</div>' : '' );
- } else {
- $link = '[[' . htmlspecialchars( $s->page_title ) . ']]';
- }
-
- if ( $n % 3 == 0 ) {
- $out .= '<tr>';
- }
-
- $out .= "<td style=\"width:33%\">$link</td>";
- $n++;
- if ( $n % 3 == 0 ) {
- $out .= "</tr>\n";
- }
- }
-
- if ( ( $n % 3 ) != 0 ) {
- $out .= "</tr>\n";
- }
- $out .= Xml::closeElement( 'table' );
- } else {
- $out = '';
- }
- }
-
- if ( $this->including() ) {
- $out2 = '';
- } else {
- if ( $from == '' ) {
- // First chunk; no previous link.
- $prevTitle = null;
- } else {
- # Get the last title from previous chunk
- $dbr = wfGetDB( DB_SLAVE );
- $res_prev = $dbr->select(
- 'page',
- 'page_title',
- array( 'page_namespace' => $namespace, 'page_title < ' . $dbr->addQuotes( $from ) ),
- __METHOD__,
- array( 'ORDER BY' => 'page_title DESC',
- 'LIMIT' => $this->maxPerPage, 'OFFSET' => ( $this->maxPerPage - 1 )
- )
- );
-
- # Get first title of previous complete chunk
- if ( $dbr->numrows( $res_prev ) >= $this->maxPerPage ) {
- $pt = $dbr->fetchObject( $res_prev );
- $prevTitle = Title::makeTitle( $namespace, $pt->page_title );
- } else {
- # The previous chunk is not complete, need to link to the very first title
- # available in the database
- $options = array( 'LIMIT' => 1 );
- if ( !$dbr->implicitOrderby() ) {
- $options['ORDER BY'] = 'page_title';
- }
- $reallyFirstPage_title = $dbr->selectField( 'page', 'page_title',
- array( 'page_namespace' => $namespace ), __METHOD__, $options );
- # Show the previous link if it s not the current requested chunk
- if ( $from != $reallyFirstPage_title ) {
- $prevTitle = Title::makeTitle( $namespace, $reallyFirstPage_title );
- } else {
- $prevTitle = null;
- }
- }
- }
-
- $self = $this->getTitle();
-
- $nsForm = $this->namespaceForm( $namespace, $from, $to, $hideredirects );
- $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ) .
- '<tr>
- <td>' .
- $nsForm .
- '</td>
- <td class="mw-allpages-nav">' .
- Linker::link( $self, $this->msg( 'allpages' )->escaped() );
-
- # Do we put a previous link ?
- if ( isset( $prevTitle ) && $pt = $prevTitle->getText() ) {
- $query = array( 'from' => $prevTitle->getText() );
-
- if ( $namespace ) {
- $query['namespace'] = $namespace;
- }
-
- if ( $hideredirects ) {
- $query['hideredirects'] = $hideredirects;
- }
-
- $prevLink = Linker::linkKnown(
- $self,
- $this->msg( 'prevpage', $pt )->escaped(),
- array(),
- $query
- );
- $out2 = $this->getLanguage()->pipeList( array( $out2, $prevLink ) );
- }
-
- if ( $n == $this->maxPerPage && $s = $res->fetchObject() ) {
- # $s is the first link of the next chunk
- $t = Title::makeTitle( $namespace, $s->page_title );
- $query = array( 'from' => $t->getText() );
-
- if ( $namespace ) {
- $query['namespace'] = $namespace;
- }
-
- if ( $hideredirects ) {
- $query['hideredirects'] = $hideredirects;
- }
-
- $nextLink = Linker::linkKnown(
- $self,
- $this->msg( 'nextpage', $t->getText() )->escaped(),
- array(),
- $query
- );
- $out2 = $this->getLanguage()->pipeList( array( $out2, $nextLink ) );
- }
- $out2 .= "</td></tr></table>";
- }
-
- $output->addHTML( $out2 . $out );
-
- $links = array();
- if ( isset( $prevLink ) ) {
- $links[] = $prevLink;
- }
-
- if ( isset( $nextLink ) ) {
- $links[] = $nextLink;
- }
-
- if ( count( $links ) ) {
- $output->addHTML(
- Html::element( 'hr' ) .
- Html::rawElement( 'div', array( 'class' => 'mw-allpages-nav' ),
- $this->getLanguage()->pipeList( $links )
- )
- );
- }
- }
-
- /**
- * @param $ns Integer: the namespace of the article
- * @param string $text the name of the article
- * @return array( int namespace, string dbkey, string pagename ) or NULL on error
- */
- protected function getNamespaceKeyAndText( $ns, $text ) {
- if ( $text == '' ) {
- # shortcut for common case
- return array( $ns, '', '' );
- }
-
- $t = Title::makeTitleSafe( $ns, $text );
- if ( $t && $t->isLocal() ) {
- return array( $t->getNamespace(), $t->getDBkey(), $t->getText() );
- } elseif ( $t ) {
- return null;
- }
-
- # try again, in case the problem was an empty pagename
- $text = preg_replace( '/(#|$)/', 'X$1', $text );
- $t = Title::makeTitleSafe( $ns, $text );
- if ( $t && $t->isLocal() ) {
- return array( $t->getNamespace(), '', '' );
- } else {
- return null;
- }
- }
-
- protected function getGroupName() {
- return 'pages';
- }
-}
diff --git a/includes/specials/SpecialBlock.php b/includes/specials/SpecialBlock.php
index 3b73a374..3297c17a 100644
--- a/includes/specials/SpecialBlock.php
+++ b/includes/specials/SpecialBlock.php
@@ -28,27 +28,23 @@
* @ingroup SpecialPage
*/
class SpecialBlock extends FormSpecialPage {
- /** The maximum number of edits a user can have and still be hidden
- * TODO: config setting? */
- const HIDEUSER_CONTRIBLIMIT = 1000;
-
- /** @var User user to be blocked, as passed either by parameter (url?wpTarget=Foo)
+ /** @var User User to be blocked, as passed either by parameter (url?wpTarget=Foo)
* or as subpage (Special:Block/Foo) */
protected $target;
- /// @var Block::TYPE_ constant
+ /** @var int Block::TYPE_ constant */
protected $type;
- /// @var User|String the previous block target
+ /** @var User|string The previous block target */
protected $previousTarget;
- /// @var Bool whether the previous submission of the form asked for HideUser
+ /** @var bool Whether the previous submission of the form asked for HideUser */
protected $requestedHideUser;
- /// @var Bool
+ /** @var bool */
protected $alreadyBlocked;
- /// @var Array
+ /** @var array */
protected $preErrors = array();
public function __construct() {
@@ -74,7 +70,7 @@ class SpecialBlock extends FormSpecialPage {
/**
* Handle some magic here
*
- * @param $par String
+ * @param string $par
*/
protected function setParameter( $par ) {
# Extract variables from the request. Try not to get into a situation where we
@@ -88,14 +84,15 @@ class SpecialBlock extends FormSpecialPage {
$this->getSkin()->setRelevantUser( $this->target );
}
- list( $this->previousTarget, /*...*/ ) = Block::parseTarget( $request->getVal( 'wpPreviousTarget' ) );
+ list( $this->previousTarget, /*...*/ ) =
+ Block::parseTarget( $request->getVal( 'wpPreviousTarget' ) );
$this->requestedHideUser = $request->getBool( 'wpHideUser' );
}
/**
* Customizes the HTMLForm a bit
*
- * @param $form HTMLForm
+ * @param HTMLForm $form
*/
protected function alterForm( HTMLForm $form ) {
$form->setWrapperLegendMsg( 'blockip-legend' );
@@ -120,7 +117,7 @@ class SpecialBlock extends FormSpecialPage {
/**
* Get the HTMLForm descriptor array for the block form
- * @return Array
+ * @return array
*/
protected function getFormFields() {
global $wgBlockAllowsUTEdit;
@@ -132,8 +129,7 @@ class SpecialBlock extends FormSpecialPage {
$a = array(
'Target' => array(
'type' => 'text',
- 'label-message' => 'ipadressorusername',
- 'tabindex' => '1',
+ 'label-message' => 'ipaddressorusername',
'id' => 'mw-bi-target',
'size' => '45',
'autofocus' => true,
@@ -144,7 +140,6 @@ class SpecialBlock extends FormSpecialPage {
'type' => !count( $suggestedDurations ) ? 'text' : 'selectorother',
'label-message' => 'ipbexpiry',
'required' => true,
- 'tabindex' => '2',
'options' => $suggestedDurations,
'other' => $this->msg( 'ipbother' )->text(),
'default' => $this->msg( 'ipb-default-expiry' )->inContentLanguage()->text(),
@@ -221,6 +216,9 @@ class SpecialBlock extends FormSpecialPage {
$this->maybeAlterFormDefaults( $a );
+ // Allow extensions to add more fields
+ wfRunHooks( 'SpecialBlockModifyFormFields', array( $this, &$a ) );
+
return $a;
}
@@ -228,7 +226,7 @@ class SpecialBlock extends FormSpecialPage {
* If the user has already been blocked with similar settings, load that block
* and change the defaults for the form fields to match the existing settings.
* @param array $fields HTMLForm descriptor array
- * @return Bool whether fields were altered (that is, whether the target is
+ * @return bool Whether fields were altered (that is, whether the target is
* already blocked)
*/
protected function maybeAlterFormDefaults( &$fields ) {
@@ -293,20 +291,20 @@ class SpecialBlock extends FormSpecialPage {
if ( $this->requestedHideUser ) {
$fields['Confirm']['type'] = 'check';
unset( $fields['Confirm']['default'] );
- $this->preErrors[] = 'ipb-confirmhideuser';
+ $this->preErrors[] = array( 'ipb-confirmhideuser', 'ipb-confirmaction' );
}
# Or if the user is trying to block themselves
if ( (string)$this->target === $this->getUser()->getName() ) {
$fields['Confirm']['type'] = 'check';
unset( $fields['Confirm']['default'] );
- $this->preErrors[] = 'ipb-blockingself';
+ $this->preErrors[] = array( 'ipb-blockingself', 'ipb-confirmaction' );
}
}
/**
* Add header elements like block log entries, etc.
- * @return String
+ * @return string
*/
protected function preText() {
$this->getOutput()->addModules( 'mediawiki.special.block' );
@@ -362,7 +360,10 @@ class SpecialBlock extends FormSpecialPage {
# Link to unblock the specified user, or to a blank unblock form
if ( $this->target instanceof User ) {
- $message = $this->msg( 'ipb-unblock-addr', wfEscapeWikiText( $this->target->getName() ) )->parse();
+ $message = $this->msg(
+ 'ipb-unblock-addr',
+ wfEscapeWikiText( $this->target->getName() )
+ )->parse();
$list = SpecialPage::getTitleFor( 'Unblock', $this->target->getName() );
} else {
$message = $this->msg( 'ipb-unblock' )->parse();
@@ -437,7 +438,7 @@ class SpecialBlock extends FormSpecialPage {
/**
* Get a user page target for things like logs.
* This handles account and IP range targets.
- * @param $target User|string
+ * @param User|string $target
* @return Title|null
*/
protected static function getTargetUserTitle( $target ) {
@@ -452,10 +453,10 @@ class SpecialBlock extends FormSpecialPage {
/**
* Determine the target of the block, and the type of target
- * TODO: should be in Block.php?
- * @param string $par subpage parameter passed to setup, or data value from
+ * @todo Should be in Block.php?
+ * @param string $par Subpage parameter passed to setup, or data value from
* the HTMLForm
- * @param $request WebRequest optionally try and get data from a request too
+ * @param WebRequest $request Optionally try and get data from a request too
* @return array( User|string|null, Block::TYPE_ constant|null )
*/
public static function getTargetAndType( $par, WebRequest $request = null ) {
@@ -504,9 +505,9 @@ class SpecialBlock extends FormSpecialPage {
/**
* HTMLForm field validation-callback for Target field.
* @since 1.18
- * @param $value String
- * @param $alldata Array
- * @param $form HTMLForm
+ * @param string $value
+ * @param array $alldata
+ * @param HTMLForm $form
* @return Message
*/
public static function validateTargetField( $value, $alldata, $form ) {
@@ -584,9 +585,9 @@ class SpecialBlock extends FormSpecialPage {
/**
* Submit callback for an HTMLForm object, will simply pass
- * @param $data array
- * @param $form HTMLForm
- * @return Bool|String
+ * @param array $data
+ * @param HTMLForm $form
+ * @return bool|string
*/
public static function processUIForm( array $data, HTMLForm $form ) {
return self::processForm( $data, $form->getContext() );
@@ -594,12 +595,12 @@ class SpecialBlock extends FormSpecialPage {
/**
* Given the form data, actually implement a block
- * @param $data Array
- * @param $context IContextSource
- * @return Bool|String
+ * @param array $data
+ * @param IContextSource $context
+ * @return bool|string
*/
public static function processForm( array $data, IContextSource $context ) {
- global $wgBlockAllowsUTEdit;
+ global $wgBlockAllowsUTEdit, $wgHideUserContribLimit, $wgContLang;
$performer = $context->getUser();
@@ -627,7 +628,7 @@ class SpecialBlock extends FormSpecialPage {
if ( $target === $performer->getName() &&
( $data['PreviousTarget'] !== $target || !$data['Confirm'] )
) {
- return array( 'ipb-blockingself' );
+ return array( 'ipb-blockingself', 'ipb-confirmaction' );
}
} elseif ( $type == Block::TYPE_RANGE ) {
$userId = 0;
@@ -670,12 +671,15 @@ class SpecialBlock extends FormSpecialPage {
} elseif ( !in_array( $data['Expiry'], array( 'infinite', 'infinity', 'indefinite' ) ) ) {
# Bad expiry.
return array( 'ipb_expiry_temp' );
- } elseif ( $user->getEditCount() > self::HIDEUSER_CONTRIBLIMIT ) {
+ } elseif ( $wgHideUserContribLimit !== false
+ && $user->getEditCount() > $wgHideUserContribLimit
+ ) {
# Typically, the user should have a handful of edits.
# Disallow hiding users with many edits for performance.
- return array( 'ipb_hide_invalid' );
+ return array( array( 'ipb_hide_invalid',
+ Message::numParam( $wgHideUserContribLimit ) ) );
} elseif ( !$data['Confirm'] ) {
- return array( 'ipb-confirmhideuser' );
+ return array( 'ipb-confirmhideuser', 'ipb-confirmaction' );
}
}
@@ -683,7 +687,8 @@ class SpecialBlock extends FormSpecialPage {
$block = new Block();
$block->setTarget( $target );
$block->setBlocker( $performer );
- $block->mReason = $data['Reason'][0];
+ # Truncate reason for whole multibyte characters
+ $block->mReason = $wgContLang->truncate( $data['Reason'][0], 255 );
$block->mExpiry = self::parseExpiryInput( $data['Expiry'] );
$block->prevents( 'createaccount', $data['CreateAccount'] );
$block->prevents( 'editownusertalk', ( !$wgBlockAllowsUTEdit || $data['DisableUTEdit'] ) );
@@ -692,8 +697,9 @@ class SpecialBlock extends FormSpecialPage {
$block->isAutoblocking( $data['AutoBlock'] );
$block->mHideName = $data['HideUser'];
- if ( !wfRunHooks( 'BlockIp', array( &$block, &$performer ) ) ) {
- return array( 'hookaborted' );
+ $reason = array( 'hookaborted' );
+ if ( !wfRunHooks( 'BlockIp', array( &$block, &$performer, &$reason ) ) ) {
+ return $reason;
}
# Try to insert block. Is there a conflicting block?
@@ -726,8 +732,17 @@ class SpecialBlock extends FormSpecialPage {
return array( 'cant-see-hidden-user' );
}
- $currentBlock->delete();
- $status = $block->insert();
+ $currentBlock->isHardblock( $block->isHardblock() );
+ $currentBlock->prevents( 'createaccount', $block->prevents( 'createaccount' ) );
+ $currentBlock->mExpiry = $block->mExpiry;
+ $currentBlock->isAutoblocking( $block->isAutoblocking() );
+ $currentBlock->mHideName = $block->mHideName;
+ $currentBlock->prevents( 'sendemail', $block->prevents( 'sendemail' ) );
+ $currentBlock->prevents( 'editownusertalk', $block->prevents( 'editownusertalk' ) );
+ $currentBlock->mReason = $block->mReason;
+
+ $status = $currentBlock->update();
+
$logaction = 'reblock';
# Unset _deleted fields if requested
@@ -753,7 +768,11 @@ class SpecialBlock extends FormSpecialPage {
# Can't watch a rangeblock
if ( $type != Block::TYPE_RANGE && $data['Watch'] ) {
- WatchAction::doWatch( Title::makeTitle( NS_USER, $target ), $performer, WatchedItem::IGNORE_USER_RIGHTS );
+ WatchAction::doWatch(
+ Title::makeTitle( NS_USER, $target ),
+ $performer,
+ WatchedItem::IGNORE_USER_RIGHTS
+ );
}
# Block constructor sanitizes certain block options on insert
@@ -787,9 +806,9 @@ class SpecialBlock extends FormSpecialPage {
* Get an array of suggested block durations from MediaWiki:Ipboptions
* @todo FIXME: This uses a rather odd syntax for the options, should it be converted
* to the standard "**<duration>|<displayname>" format?
- * @param $lang Language|null the language to get the durations in, or null to use
+ * @param Language|null $lang The language to get the durations in, or null to use
* the wiki's content language
- * @return Array
+ * @return array
*/
public static function getSuggestedDurations( $lang = null ) {
$a = array();
@@ -816,8 +835,8 @@ class SpecialBlock extends FormSpecialPage {
/**
* Convert a submitted expiry time, which may be relative ("2 weeks", etc) or absolute
* ("24 May 2034", etc), into an absolute timestamp we can put into the database.
- * @param string $expiry whatever was typed into the form
- * @return String: timestamp or "infinity" string for the DB implementation
+ * @param string $expiry Whatever was typed into the form
+ * @return string Timestamp or "infinity" string for the DB implementation
*/
public static function parseExpiryInput( $expiry ) {
static $infinity;
@@ -842,8 +861,8 @@ class SpecialBlock extends FormSpecialPage {
/**
* Can we do an email block?
- * @param $user User: the sysop wanting to make a block
- * @return Boolean
+ * @param User $user The sysop wanting to make a block
+ * @return bool
*/
public static function canBlockEmail( $user ) {
global $wgEnableUserEmail, $wgSysopEmailBans;
@@ -855,9 +874,9 @@ class SpecialBlock extends FormSpecialPage {
* bug 15810: blocked admins should not be able to block/unblock
* others, and probably shouldn't be able to unblock themselves
* either.
- * @param $user User|Int|String
- * @param $performer User user doing the request
- * @return Bool|String true or error message key
+ * @param User|int|string $user
+ * @param User $performer User doing the request
+ * @return bool|string True or error message key
*/
public static function checkUnblockSelf( $user, User $performer ) {
if ( is_int( $user ) ) {
@@ -889,8 +908,8 @@ class SpecialBlock extends FormSpecialPage {
/**
* Return a comma-delimited list of "flags" to be passed to the log
* reader for this block, to provide more information in the logs
- * @param array $data from HTMLForm data
- * @param $type Block::TYPE_ constant (USER, RANGE, or IP)
+ * @param array $data From HTMLForm data
+ * @param int $type Block::TYPE_ constant (USER, RANGE, or IP)
* @return string
*/
protected static function blockLogFlags( array $data, $type ) {
@@ -935,8 +954,8 @@ class SpecialBlock extends FormSpecialPage {
/**
* Process the form on POST submission.
- * @param $data Array
- * @return Bool|Array true for success, false for didn't-try, array of errors on failure
+ * @param array $data
+ * @return bool|array True for success, false for didn't-try, array of errors on failure
*/
public function onSubmit( array $data ) {
// This isn't used since we need that HTMLForm that's passed in the
@@ -957,7 +976,3 @@ class SpecialBlock extends FormSpecialPage {
return 'users';
}
}
-
-# BC @since 1.18
-class IPBlockForm extends SpecialBlock {
-}
diff --git a/includes/specials/SpecialBlockList.php b/includes/specials/SpecialBlockList.php
index f1992c0f..456f4ecb 100644
--- a/includes/specials/SpecialBlockList.php
+++ b/includes/specials/SpecialBlockList.php
@@ -27,8 +27,9 @@
* @ingroup SpecialPage
*/
class SpecialBlockList extends SpecialPage {
+ protected $target;
- protected $target, $options;
+ protected $options;
function __construct() {
parent::__construct( 'BlockList' );
@@ -37,7 +38,7 @@ class SpecialBlockList extends SpecialPage {
/**
* Main execution point
*
- * @param string $par title fragment
+ * @param string $par Title fragment
*/
public function execute( $par ) {
$this->setHeaders();
@@ -67,7 +68,7 @@ class SpecialBlockList extends SpecialPage {
$fields = array(
'Target' => array(
'type' => 'text',
- 'label-message' => 'ipadressorusername',
+ 'label-message' => 'ipaddressorusername',
'tabindex' => '1',
'size' => '45',
'default' => $this->target,
@@ -83,7 +84,7 @@ class SpecialBlockList extends SpecialPage {
'flatlist' => true,
),
'Limit' => array(
- 'class' => 'HTMLBlockedUsersItemSelect',
+ 'type' => 'limitselect',
'label-message' => 'table_pager_limit_label',
'options' => array(
$lang->formatNum( 20 ) => 20,
@@ -97,7 +98,7 @@ class SpecialBlockList extends SpecialPage {
),
);
$context = new DerivativeContext( $this->getContext() );
- $context->setTitle( $this->getTitle() ); // Remove subpage
+ $context->setTitle( $this->getPageTitle() ); // Remove subpage
$form = new HTMLForm( $fields, $context );
$form->setMethod( 'get' );
$form->setWrapperLegendMsg( 'ipblocklist-legend' );
@@ -180,11 +181,7 @@ class SpecialBlockList extends SpecialPage {
$pager = new BlockListPager( $this, $conds );
if ( $pager->getNumRows() ) {
- $out->addHTML(
- $pager->getNavigationBar() .
- $pager->getBody() .
- $pager->getNavigationBar()
- );
+ $out->addParserOutputContent( $pager->getFullOutput() );
} elseif ( $this->target ) {
$out->addWikiMsg( 'ipblocklist-no-results' );
} else {
@@ -203,7 +200,11 @@ class SpecialBlockList extends SpecialPage {
foreach ( $otherBlockLink as $link ) {
$list .= Html::rawElement( 'li', array(), $link ) . "\n";
}
- $out->addHTML( Html::rawElement( 'ul', array( 'class' => 'mw-ipblocklist-otherblocks' ), $list ) . "\n" );
+ $out->addHTML( Html::rawElement(
+ 'ul',
+ array( 'class' => 'mw-ipblocklist-otherblocks' ),
+ $list
+ ) . "\n" );
}
}
@@ -217,20 +218,20 @@ class BlockListPager extends TablePager {
protected $page;
/**
- * @param $page SpecialPage
- * @param $conds Array
+ * @param SpecialPage $page
+ * @param array $conds
*/
function __construct( $page, $conds ) {
$this->page = $page;
$this->conds = $conds;
- $this->mDefaultDirection = true;
+ $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
parent::__construct( $page->getContext() );
}
function getFieldNames() {
static $headers = null;
- if ( $headers == array() ) {
+ if ( $headers === null ) {
$headers = array(
'ipb_timestamp' => 'blocklist-timestamp',
'ipb_target' => 'blocklist-target',
@@ -404,7 +405,7 @@ class BlockListPager extends TablePager {
}
public function getTableClass() {
- return 'TablePager mw-blocklist';
+ return parent::getTableClass() . ' mw-blocklist';
}
function getIndexField() {
@@ -452,35 +453,3 @@ class BlockListPager extends TablePager {
wfProfileOut( __METHOD__ );
}
}
-
-/**
- * Items per page dropdown. Essentially a crap workaround for bug 32603.
- *
- * @todo Do not release 1.19 with this.
- */
-class HTMLBlockedUsersItemSelect extends HTMLSelectField {
- /**
- * Basically don't do any validation. If it's a number that's fine. Also,
- * add it to the list if it's not there already
- *
- * @param $value
- * @param $alldata
- * @return bool
- */
- function validate( $value, $alldata ) {
- if ( $value == '' ) {
- return true;
- }
-
- // Let folks pick an explicit limit not from our list, as long as it's a real numbr.
- if ( !in_array( $value, $this->mParams['options'] ) && $value == intval( $value ) && $value > 0 ) {
- // This adds the explicitly requested limit value to the drop-down,
- // then makes sure it's sorted correctly so when we output the list
- // later, the custom option doesn't just show up last.
- $this->mParams['options'][$this->mParent->getLanguage()->formatNum( $value )] = intval( $value );
- asort( $this->mParams['options'] );
- }
-
- return true;
- }
-}
diff --git a/includes/specials/SpecialBooksources.php b/includes/specials/SpecialBooksources.php
index 5ad961c3..72f4e466 100644
--- a/includes/specials/SpecialBooksources.php
+++ b/includes/specials/SpecialBooksources.php
@@ -30,7 +30,6 @@
* @ingroup SpecialPage
*/
class SpecialBookSources extends SpecialPage {
-
/**
* ISBN passed to the page, if any
*/
@@ -55,7 +54,10 @@ class SpecialBookSources extends SpecialPage {
$this->getOutput()->addHTML( $this->makeForm() );
if ( strlen( $this->isbn ) > 0 ) {
if ( !self::isValidISBN( $this->isbn ) ) {
- $this->getOutput()->wrapWikiMsg( "<div class=\"error\">\n$1\n</div>", 'booksources-invalid-isbn' );
+ $this->getOutput()->wrapWikiMsg(
+ "<div class=\"error\">\n$1\n</div>",
+ 'booksources-invalid-isbn'
+ );
}
$this->showList();
}
@@ -115,16 +117,26 @@ class SpecialBookSources extends SpecialPage {
* @return string
*/
private function makeForm() {
- global $wgScript;
-
$form = Html::openElement( 'fieldset' ) . "\n";
- $form .= Html::element( 'legend', array(), $this->msg( 'booksources-search-legend' )->text() ) . "\n";
- $form .= Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . "\n";
- $form .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
- $form .= '<p>' . Xml::inputLabel( $this->msg( 'booksources-isbn' )->text(), 'isbn', 'isbn', 20, $this->isbn, array( 'autofocus' => true ) );
+ $form .= Html::element(
+ 'legend',
+ array(),
+ $this->msg( 'booksources-search-legend' )->text()
+ ) . "\n";
+ $form .= Html::openElement( 'form', array( 'method' => 'get', 'action' => wfScript() ) ) . "\n";
+ $form .= Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) . "\n";
+ $form .= '<p>' . Xml::inputLabel(
+ $this->msg( 'booksources-isbn' )->text(),
+ 'isbn',
+ 'isbn',
+ 20,
+ $this->isbn,
+ array( 'autofocus' => true )
+ );
$form .= '&#160;' . Xml::submitButton( $this->msg( 'booksources-go' )->text() ) . "</p>\n";
$form .= Html::closeElement( 'form' ) . "\n";
$form .= Html::closeElement( 'fieldset' ) . "\n";
+
return $form;
}
@@ -188,6 +200,6 @@ class SpecialBookSources extends SpecialPage {
}
protected function getGroupName() {
- return 'other';
+ return 'wiki';
}
}
diff --git a/includes/specials/SpecialBrokenRedirects.php b/includes/specials/SpecialBrokenRedirects.php
index b2ddc220..1bbdbeab 100644
--- a/includes/specials/SpecialBrokenRedirects.php
+++ b/includes/specials/SpecialBrokenRedirects.php
@@ -28,7 +28,6 @@
* @ingroup SpecialPage
*/
class BrokenRedirectsPage extends QueryPage {
-
function __construct( $name = 'BrokenRedirects' ) {
parent::__construct( $name );
}
@@ -148,7 +147,8 @@ class BrokenRedirectsPage extends QueryPage {
);
}
- $out .= $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList( $links ) )->escaped();
+ $out .= $this->msg( 'parentheses' )->rawParams( $this->getLanguage()
+ ->pipeList( $links ) )->escaped();
$out .= " {$arr} {$to}";
return $out;
diff --git a/includes/specials/SpecialCachedPage.php b/includes/specials/SpecialCachedPage.php
index 39305f01..cb9b07cd 100644
--- a/includes/specials/SpecialCachedPage.php
+++ b/includes/specials/SpecialCachedPage.php
@@ -38,7 +38,6 @@
* @since 1.20
*/
abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
-
/**
* CacheHelper object to which we forward the non-SpecialPage specific caching work.
* Initialized in startCache.
@@ -52,7 +51,7 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
* If the cache is enabled or not.
*
* @since 1.20
- * @var boolean
+ * @var bool
*/
protected $cacheEnabled = true;
@@ -61,7 +60,7 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
*
* @since 1.20
*
- * @param $subPage string|null
+ * @param string|null $subPage
*/
protected function afterExecute( $subPage ) {
$this->saveCache();
@@ -73,7 +72,7 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
* Sets if the cache should be enabled or not.
*
* @since 1.20
- * @param boolean $cacheEnabled
+ * @param bool $cacheEnabled
*/
public function setCacheEnabled( $cacheEnabled ) {
$this->cacheHelper->setCacheEnabled( $cacheEnabled );
@@ -85,8 +84,8 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
*
* @since 1.20
*
- * @param integer|null $cacheExpiry Sets the cache expiry, either ttl in seconds or unix timestamp.
- * @param boolean|null $cacheEnabled Sets if the cache should be enabled or not.
+ * @param int|null $cacheExpiry Sets the cache expiry, either ttl in seconds or unix timestamp.
+ * @param bool|null $cacheEnabled Sets if the cache should be enabled or not.
*/
public function startCache( $cacheExpiry = null, $cacheEnabled = null ) {
if ( !isset( $this->cacheHelper ) ) {
@@ -142,7 +141,11 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
* @param string|null $key
*/
public function addCachedHTML( $computeFunction, $args = array(), $key = null ) {
- $this->getOutput()->addHTML( $this->cacheHelper->getCachedValue( $computeFunction, $args, $key ) );
+ $this->getOutput()->addHTML( $this->cacheHelper->getCachedValue(
+ $computeFunction,
+ $args,
+ $key
+ ) );
}
/**
@@ -158,11 +161,12 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
}
/**
- * Sets the time to live for the cache, in seconds or a unix timestamp indicating the point of expiry.
+ * Sets the time to live for the cache, in seconds or a unix timestamp
+ * indicating the point of expiry.
*
* @since 1.20
*
- * @param integer $cacheExpiry
+ * @param int $cacheExpiry
*/
public function setExpiry( $cacheExpiry ) {
$this->cacheHelper->setExpiry( $cacheExpiry );
@@ -187,7 +191,7 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper {
*
* @since 1.20
*
- * @param boolean $hasCached
+ * @param bool $hasCached
*/
public function onCacheInitialized( $hasCached ) {
if ( $hasCached ) {
diff --git a/includes/specials/SpecialCategories.php b/includes/specials/SpecialCategories.php
index d01bfd7d..95f9efd2 100644
--- a/includes/specials/SpecialCategories.php
+++ b/includes/specials/SpecialCategories.php
@@ -26,18 +26,55 @@
*/
class SpecialCategories extends SpecialPage {
- function __construct() {
+ /**
+ * @var PageLinkRenderer
+ */
+ protected $linkRenderer = null;
+
+ public function __construct() {
parent::__construct( 'Categories' );
+
+ // Since we don't control the constructor parameters, we can't inject services that way.
+ // Instead, we initialize services in the execute() method, and allow them to be overridden
+ // using the initServices() method.
+ }
+
+ /**
+ * Initialize or override the PageLinkRenderer SpecialCategories collaborates with.
+ * Useful mainly for testing.
+ *
+ * @todo the pager should also be injected, and de-coupled from the rendering logic.
+ *
+ * @param PageLinkRenderer $linkRenderer
+ */
+ public function setPageLinkRenderer(
+ PageLinkRenderer $linkRenderer
+ ) {
+ $this->linkRenderer = $linkRenderer;
+ }
+
+ /**
+ * Initialize any services we'll need (unless it has already been provided via a setter).
+ * This allows for dependency injection even though we don't control object creation.
+ */
+ private function initServices() {
+ if ( !$this->linkRenderer ) {
+ $lang = $this->getContext()->getLanguage();
+ $titleFormatter = new MediaWikiTitleCodec( $lang, GenderCache::singleton() );
+ $this->linkRenderer = new MediaWikiPageLinkRenderer( $titleFormatter );
+ }
}
- function execute( $par ) {
+ public function execute( $par ) {
+ $this->initServices();
+
$this->setHeaders();
$this->outputHeader();
$this->getOutput()->allowClickjacking();
$from = $this->getRequest()->getText( 'from', $par );
- $cap = new CategoryPager( $this->getContext(), $from );
+ $cap = new CategoryPager( $this->getContext(), $from, $this->linkRenderer );
$cap->doQuery();
$this->getOutput()->addHTML(
@@ -63,7 +100,19 @@ class SpecialCategories extends SpecialPage {
* @ingroup SpecialPage Pager
*/
class CategoryPager extends AlphabeticPager {
- function __construct( IContextSource $context, $from ) {
+
+ /**
+ * @var PageLinkRenderer
+ */
+ protected $linkRenderer;
+
+ /**
+ * @param IContextSource $context
+ * @param string $from
+ * @param PageLinkRenderer $linkRenderer
+ */
+ public function __construct( IContextSource $context, $from, PageLinkRenderer $linkRenderer
+ ) {
parent::__construct( $context );
$from = str_replace( ' ', '_', $from );
if ( $from !== '' ) {
@@ -71,6 +120,8 @@ class CategoryPager extends AlphabeticPager {
$this->setOffset( $from );
$this->setIncludeOffset( true );
}
+
+ $this->linkRenderer = $linkRenderer;
}
function getQueryInfo() {
@@ -120,19 +171,18 @@ class CategoryPager extends AlphabeticPager {
}
function formatRow( $result ) {
- $title = Title::makeTitle( NS_CATEGORY, $result->cat_title );
- $titleText = Linker::link( $title, htmlspecialchars( $title->getText() ) );
- $count = $this->msg( 'nmembers' )->numParams( $result->cat_pages )->escaped();
+ $title = new TitleValue( NS_CATEGORY, $result->cat_title );
+ $text = $title->getText();
+ $link = $this->linkRenderer->renderHtmlLink( $title, $text );
- return Xml::tags( 'li', null, $this->getLanguage()->specialList( $titleText, $count ) ) . "\n";
+ $count = $this->msg( 'nmembers' )->numParams( $result->cat_pages )->escaped();
+ return Html::rawElement( 'li', null, $this->getLanguage()->specialList( $link, $count ) ) . "\n";
}
public function getStartForm( $from ) {
- global $wgScript;
-
return Xml::tags(
'form',
- array( 'method' => 'get', 'action' => $wgScript ),
+ array( 'method' => 'get', 'action' => wfScript() ),
Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
Xml::fieldset(
$this->msg( 'categories' )->text(),
diff --git a/includes/specials/SpecialChangeEmail.php b/includes/specials/SpecialChangeEmail.php
index aab839fd..e0be838b 100644
--- a/includes/specials/SpecialChangeEmail.php
+++ b/includes/specials/SpecialChangeEmail.php
@@ -26,26 +26,18 @@
*
* @ingroup SpecialPage
*/
-class SpecialChangeEmail extends UnlistedSpecialPage {
-
- /**
- * Users password
- * @var string
- */
- protected $mPassword;
-
+class SpecialChangeEmail extends FormSpecialPage {
/**
- * Users new email address
- * @var string
+ * @var Status
*/
- protected $mNewEmail;
+ private $status;
public function __construct() {
parent::__construct( 'ChangeEmail', 'editmyprivateinfo' );
}
/**
- * @return Bool
+ * @return bool
*/
function isListed() {
global $wgAuth;
@@ -55,40 +47,24 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
/**
* Main execution point
+ * @param string $par
*/
function execute( $par ) {
- global $wgAuth;
-
- $this->setHeaders();
- $this->outputHeader();
-
$out = $this->getOutput();
$out->disallowUserJs();
$out->addModules( 'mediawiki.special.changeemail' );
- if ( !$wgAuth->allowPropChange( 'emailaddress' ) ) {
- $this->error( 'cannotchangeemail' );
-
- return;
- }
-
- $user = $this->getUser();
- $request = $this->getRequest();
-
- if ( !$request->wasPosted() && !$user->isLoggedIn() ) {
- $this->error( 'changeemail-no-info' );
-
- return;
- }
+ return parent::execute( $par );
+ }
- if ( $request->wasPosted() && $request->getBool( 'wpCancel' ) ) {
- $this->doReturnTo();
+ protected function checkExecutePermissions( User $user ) {
+ global $wgAuth;
- return;
+ if ( !$wgAuth->allowPropChange( 'emailaddress' ) ) {
+ throw new ErrorPageError( 'changeemail', 'cannotchangeemail' );
}
- $this->checkReadOnly();
- $this->checkPermissions();
+ $this->requireLogin( 'changeemail-no-info' );
// This could also let someone check the current email address, so
// require both permissions.
@@ -96,156 +72,106 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
throw new PermissionsError( 'viewmyprivateinfo' );
}
- $this->mPassword = $request->getVal( 'wpPassword' );
- $this->mNewEmail = $request->getVal( 'wpNewEmail' );
+ parent::checkExecutePermissions( $user );
+ }
- if ( $request->wasPosted()
- && $user->matchEditToken( $request->getVal( 'token' ) )
- ) {
- $info = $this->attemptChange( $user, $this->mPassword, $this->mNewEmail );
- if ( $info === true ) {
- $this->doReturnTo();
- } elseif ( $info === 'eauth' ) {
- # Notify user that a confirmation email has been sent...
- $out->wrapWikiMsg( "<div class='error' style='clear: both;'>\n$1\n</div>",
- 'eauthentsent', $user->getName() );
- $this->doReturnTo( 'soft' ); // just show the link to go back
- return; // skip form
- }
- }
+ protected function getFormFields() {
+ $user = $this->getUser();
- $this->showForm();
- }
+ $fields = array(
+ 'Name' => array(
+ 'type' => 'info',
+ 'label-message' => 'username',
+ 'default' => $user->getName(),
+ ),
+ 'OldEmail' => array(
+ 'type' => 'info',
+ 'label-message' => 'changeemail-oldemail',
+ 'default' => $user->getEmail() ?: $this->msg( 'changeemail-none' )->text(),
+ ),
+ 'NewEmail' => array(
+ 'type' => 'email',
+ 'label-message' => 'changeemail-newemail',
+ ),
+ );
- /**
- * @param $type string
- */
- protected function doReturnTo( $type = 'hard' ) {
- $titleObj = Title::newFromText( $this->getRequest()->getVal( 'returnto' ) );
- if ( !$titleObj instanceof Title ) {
- $titleObj = Title::newMainPage();
+ if ( $this->getConfig()->get( 'RequirePasswordforEmailChange' ) ) {
+ $fields['Password'] = array(
+ 'type' => 'password',
+ 'label-message' => 'changeemail-password',
+ 'autofocus' => true,
+ );
}
- if ( $type == 'hard' ) {
- $this->getOutput()->redirect( $titleObj->getFullURL() );
- } else {
- $this->getOutput()->addReturnTo( $titleObj );
- }
- }
- /**
- * @param $msg string
- */
- protected function error( $msg ) {
- $this->getOutput()->wrapWikiMsg( "<p class='error'>\n$1\n</p>", $msg );
+ return $fields;
}
- protected function showForm() {
- global $wgRequirePasswordforEmailChange;
- $user = $this->getUser();
+ protected function alterForm( HTMLForm $form ) {
+ $form->setDisplayFormat( 'vform' );
+ $form->setId( 'mw-changeemail-form' );
+ $form->setTableId( 'mw-changeemail-table' );
+ $form->setWrapperLegend( false );
+ $form->setSubmitTextMsg( 'changeemail-submit' );
+ $form->addHiddenField( 'returnto', $this->getRequest()->getVal( 'returnto' ) );
+ }
- $oldEmailText = $user->getEmail()
- ? $user->getEmail()
- : $this->msg( 'changeemail-none' )->text();
-
- $this->getOutput()->addHTML(
- Xml::fieldset( $this->msg( 'changeemail-header' )->text() ) .
- Xml::openElement( 'form',
- array(
- 'method' => 'post',
- 'action' => $this->getTitle()->getLocalURL(),
- 'id' => 'mw-changeemail-form' ) ) . "\n" .
- Html::hidden( 'token', $user->getEditToken() ) . "\n" .
- Html::hidden( 'returnto', $this->getRequest()->getVal( 'returnto' ) ) . "\n" .
- $this->msg( 'changeemail-text' )->parseAsBlock() . "\n" .
- Xml::openElement( 'table', array( 'id' => 'mw-changeemail-table' ) ) . "\n"
- );
- $items = array(
- array( 'wpName', 'username', 'text', $user->getName() ),
- array( 'wpOldEmail', 'changeemail-oldemail', 'text', $oldEmailText ),
- array( 'wpNewEmail', 'changeemail-newemail', 'email', $this->mNewEmail ),
- );
- if ( $wgRequirePasswordforEmailChange ) {
- $items[] = array( 'wpPassword', 'changeemail-password', 'password', $this->mPassword );
+ public function onSubmit( array $data ) {
+ if ( $this->getRequest()->getBool( 'wpCancel' ) ) {
+ $status = Status::newGood( true );
+ } else {
+ $password = isset( $data['Password'] ) ? $data['Password'] : null;
+ $status = $this->attemptChange( $this->getUser(), $password, $data['NewEmail'] );
}
- $this->getOutput()->addHTML(
- $this->pretty( $items ) .
- "\n" .
- "<tr>\n" .
- "<td></td>\n" .
- '<td class="mw-input">' .
- Xml::submitButton( $this->msg( 'changeemail-submit' )->text() ) .
- Xml::submitButton( $this->msg( 'changeemail-cancel' )->text(), array( 'name' => 'wpCancel' ) ) .
- "</td>\n" .
- "</tr>\n" .
- Xml::closeElement( 'table' ) .
- Xml::closeElement( 'form' ) .
- Xml::closeElement( 'fieldset' ) . "\n"
- );
+ $this->status = $status;
+
+ return $status;
}
- /**
- * @param $fields array
- * @return string
- */
- protected function pretty( $fields ) {
- $out = '';
- foreach ( $fields as $list ) {
- list( $name, $label, $type, $value ) = $list;
- if ( $type == 'text' ) {
- $field = htmlspecialchars( $value );
- } else {
- $attribs = array( 'id' => $name );
- if ( $name == 'wpPassword' ) {
- $attribs[] = 'autofocus';
- }
- $field = Html::input( $name, $value, $type, $attribs );
- }
- $out .= "<tr>\n";
- $out .= "\t<td class='mw-label'>";
- if ( $type != 'text' ) {
- $out .= Xml::label( $this->msg( $label )->text(), $name );
- } else {
- $out .= $this->msg( $label )->escaped();
- }
- $out .= "</td>\n";
- $out .= "\t<td class='mw-input'>";
- $out .= $field;
- $out .= "</td>\n";
- $out .= "</tr>";
+ public function onSuccess() {
+ $titleObj = Title::newFromText( $this->getRequest()->getVal( 'returnto' ) );
+ if ( !$titleObj instanceof Title ) {
+ $titleObj = Title::newMainPage();
}
- return $out;
+ if ( $this->status->value === true ) {
+ $this->getOutput()->redirect( $titleObj->getFullURL() );
+ } elseif ( $this->status->value === 'eauth' ) {
+ # Notify user that a confirmation email has been sent...
+ $this->getOutput()->wrapWikiMsg( "<div class='error' style='clear: both;'>\n$1\n</div>",
+ 'eauthentsent', $this->getUser()->getName() );
+ $this->getOutput()->addReturnTo( $titleObj ); // just show the link to go back
+ }
}
/**
- * @param $user User
- * @param $pass string
- * @param $newaddr string
- * @return bool|string true or string on success, false on failure
+ * @param User $user
+ * @param string $pass
+ * @param string $newaddr
+ * @return Status
*/
protected function attemptChange( User $user, $pass, $newaddr ) {
- global $wgAuth, $wgPasswordAttemptThrottle;
+ global $wgAuth;
if ( $newaddr != '' && !Sanitizer::validateEmail( $newaddr ) ) {
- $this->error( 'invalidemailaddress' );
-
- return false;
+ return Status::newFatal( 'invalidemailaddress' );
}
$throttleCount = LoginForm::incLoginThrottle( $user->getName() );
if ( $throttleCount === true ) {
$lang = $this->getLanguage();
- $this->error( array( 'login-throttled', $lang->formatDuration( $wgPasswordAttemptThrottle['seconds'] ) ) );
-
- return false;
+ $throttleInfo = $this->getConfig()->get( 'PasswordAttemptThrottle' );
+ return Status::newFatal(
+ 'changeemail-throttled',
+ $lang->formatDuration( $throttleInfo['seconds'] )
+ );
}
- global $wgRequirePasswordforEmailChange;
- if ( $wgRequirePasswordforEmailChange && !$user->checkTemporaryPassword( $pass ) && !$user->checkPassword( $pass ) ) {
- $this->error( 'wrongpassword' );
-
- return false;
+ if ( $this->getConfig()->get( 'RequirePasswordforEmailChange' )
+ && !$user->checkTemporaryPassword( $pass )
+ && !$user->checkPassword( $pass )
+ ) {
+ return Status::newFatal( 'wrongpassword' );
}
if ( $throttleCount ) {
@@ -255,12 +181,7 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
$oldaddr = $user->getEmail();
$status = $user->setEmailWithConfirmation( $newaddr );
if ( !$status->isGood() ) {
- $this->getOutput()->addHTML(
- '<p class="error">' .
- $this->getOutput()->parseInline( $status->getWikiText( 'mailerror' ) ) .
- '</p>' );
-
- return false;
+ return $status;
}
wfRunHooks( 'PrefsEmailAudit', array( $user, $oldaddr, $newaddr ) );
@@ -269,7 +190,11 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
$wgAuth->updateExternalDB( $user );
- return $status->value;
+ return $status;
+ }
+
+ public function requiresUnblock() {
+ return false;
}
protected function getGroupName() {
diff --git a/includes/specials/SpecialChangePassword.php b/includes/specials/SpecialChangePassword.php
index a75e7e83..24664edb 100644
--- a/includes/specials/SpecialChangePassword.php
+++ b/includes/specials/SpecialChangePassword.php
@@ -26,228 +26,216 @@
*
* @ingroup SpecialPage
*/
-class SpecialChangePassword extends UnlistedSpecialPage {
+class SpecialChangePassword extends FormSpecialPage {
+ protected $mUserName;
+ protected $mDomain;
- protected $mUserName, $mOldpass, $mNewpass, $mRetype, $mDomain;
+ // Optional Wikitext Message to show above the password change form
+ protected $mPreTextMessage = null;
+
+ // label for old password input
+ protected $mOldPassMsg = null;
public function __construct() {
parent::__construct( 'ChangePassword', 'editmyprivateinfo' );
+ $this->listed( false );
}
/**
* Main execution point
+ * @param string|null $par
*/
function execute( $par ) {
- global $wgAuth;
-
- $this->setHeaders();
- $this->outputHeader();
$this->getOutput()->disallowUserJs();
- $request = $this->getRequest();
- $this->mUserName = trim( $request->getVal( 'wpName' ) );
- $this->mOldpass = $request->getVal( 'wpPassword' );
- $this->mNewpass = $request->getVal( 'wpNewPassword' );
- $this->mRetype = $request->getVal( 'wpRetype' );
- $this->mDomain = $request->getVal( 'wpDomain' );
-
- $user = $this->getUser();
-
- if ( !$user->isLoggedIn() && !LoginForm::getLoginToken() ) {
- LoginForm::setLoginToken();
- }
-
- if ( !$request->wasPosted() && !$user->isLoggedIn() ) {
- $this->error( $this->msg( 'resetpass-no-info' )->text() );
-
- return;
- }
-
- if ( $request->wasPosted() && $request->getBool( 'wpCancel' ) ) {
- $titleObj = Title::newFromText( $request->getVal( 'returnto' ) );
- if ( !$titleObj instanceof Title ) {
- $titleObj = Title::newMainPage();
- }
- $query = $request->getVal( 'returntoquery' );
- $this->getOutput()->redirect( $titleObj->getFullURL( $query ) );
+ parent::execute( $par );
+ }
- return;
- }
+ protected function checkExecutePermissions( User $user ) {
+ parent::checkExecutePermissions( $user );
- $this->checkReadOnly();
- $this->checkPermissions();
-
- if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'token' ) ) ) {
- try {
- $this->mDomain = $wgAuth->getDomain();
- if ( !$wgAuth->allowPasswordChange() ) {
- $this->error( $this->msg( 'resetpass_forbidden' )->text() );
-
- return;
- }
-
- if ( !$user->isLoggedIn()
- && $request->getVal( 'wpLoginOnChangeToken' ) !== LoginForm::getLoginToken()
- ) {
- // Potential CSRF (bug 62497)
- $this->error( $this->msg( 'sessionfailure' )->text() );
- return false;
- }
-
- $this->attemptReset( $this->mNewpass, $this->mRetype );
-
- if ( $user->isLoggedIn() ) {
- $this->getOutput()->wrapWikiMsg(
- "<div class=\"successbox\">\n$1\n</div>",
- 'changepassword-success'
- );
- $this->getOutput()->returnToMain();
- } else {
- LoginForm::setLoginToken();
- $token = LoginForm::getLoginToken();
- $data = array(
- 'action' => 'submitlogin',
- 'wpName' => $this->mUserName,
- 'wpDomain' => $this->mDomain,
- 'wpLoginToken' => $token,
- 'wpPassword' => $request->getVal( 'wpNewPassword' ),
- ) + $request->getValues( 'wpRemember', 'returnto', 'returntoquery' );
- $login = new LoginForm( new DerivativeRequest( $request, $data, true ) );
- $login->setContext( $this->getContext() );
- $login->execute( null );
- }
-
- return;
- } catch ( PasswordError $e ) {
- $this->error( $e->getMessage() );
- }
+ if ( !$this->getRequest()->wasPosted() ) {
+ $this->requireLogin( 'resetpass-no-info' );
}
- $this->showForm();
}
/**
- * @param $msg string
+ * Set a message at the top of the Change Password form
+ * @since 1.23
+ * @param Message $msg Message to parse and add to the form header
*/
- function error( $msg ) {
- $this->getOutput()->addHTML( Xml::element( 'p', array( 'class' => 'error' ), $msg ) );
+ public function setChangeMessage( Message $msg ) {
+ $this->mPreTextMessage = $msg;
}
- function showForm() {
- global $wgCookieExpiration;
+ /**
+ * Set a message at the top of the Change Password form
+ * @since 1.23
+ * @param string $msg Message label for old/temp password field
+ */
+ public function setOldPasswordMessage( $msg ) {
+ $this->mOldPassMsg = $msg;
+ }
+ protected function getFormFields() {
$user = $this->getUser();
- if ( !$this->mUserName ) {
- $this->mUserName = $user->getName();
+ $request = $this->getRequest();
+
+ $oldpassMsg = $this->mOldPassMsg;
+ if ( $oldpassMsg === null ) {
+ $oldpassMsg = $user->isLoggedIn() ? 'oldpassword' : 'resetpass-temp-password';
}
- $rememberMe = '';
- if ( !$user->isLoggedIn() ) {
- $rememberMe = '<tr>' .
- '<td></td>' .
- '<td class="mw-input">' .
- Xml::checkLabel(
- $this->msg( 'remembermypassword' )->numParams( ceil( $wgCookieExpiration / ( 3600 * 24 ) ) )->text(),
- 'wpRemember', 'wpRemember',
- $this->getRequest()->getCheck( 'wpRemember' ) ) .
- '</td>' .
- '</tr>';
- $submitMsg = 'resetpass_submit';
- $oldpassMsg = 'resetpass-temp-password';
- } else {
- $oldpassMsg = 'oldpassword';
- $submitMsg = 'resetpass-submit-loggedin';
+
+ $fields = array(
+ 'Name' => array(
+ 'type' => 'info',
+ 'label-message' => 'username',
+ 'default' => $request->getVal( 'wpName', $user->getName() ),
+ ),
+ 'Password' => array(
+ 'type' => 'password',
+ 'label-message' => $oldpassMsg,
+ ),
+ 'NewPassword' => array(
+ 'type' => 'password',
+ 'label-message' => 'newpassword',
+ ),
+ 'Retype' => array(
+ 'type' => 'password',
+ 'label-message' => 'retypenew',
+ ),
+ );
+
+ if ( !$this->getUser()->isLoggedIn() ) {
+ if ( !LoginForm::getLoginToken() ) {
+ LoginForm::setLoginToken();
+ }
+ $fields['LoginOnChangeToken'] = array(
+ 'type' => 'hidden',
+ 'label' => 'Change Password Token',
+ 'default' => LoginForm::getLoginToken(),
+ );
}
+
$extraFields = array();
wfRunHooks( 'ChangePasswordForm', array( &$extraFields ) );
- $prettyFields = array(
- array( 'wpName', 'username', 'text', $this->mUserName ),
- array( 'wpPassword', $oldpassMsg, 'password', $this->mOldpass ),
- array( 'wpNewPassword', 'newpassword', 'password', null ),
- array( 'wpRetype', 'retypenew', 'password', null ),
- );
- $prettyFields = array_merge( $prettyFields, $extraFields );
- $hiddenFields = array(
- 'token' => $user->getEditToken(),
- 'wpName' => $this->mUserName,
- 'wpDomain' => $this->mDomain,
- ) + $this->getRequest()->getValues( 'returnto', 'returntoquery' );
- if ( !$user->isLoggedIn() ) {
- $hiddenFields['wpLoginOnChangeToken'] = LoginForm::getLoginToken();
+ foreach ( $extraFields as $extra ) {
+ list( $name, $label, $type, $default ) = $extra;
+ $fields[$name] = array(
+ 'type' => $type,
+ 'name' => $name,
+ 'label-message' => $label,
+ 'default' => $default,
+ );
}
- $hiddenFieldsStr = '';
- foreach ( $hiddenFields as $fieldname => $fieldvalue ) {
- $hiddenFieldsStr .= Html::hidden( $fieldname, $fieldvalue ) . "\n";
+
+ if ( !$user->isLoggedIn() ) {
+ $fields['Remember'] = array(
+ 'type' => 'check',
+ 'label' => $this->msg( 'remembermypassword' )
+ ->numParams(
+ ceil( $this->getConfig()->get( 'CookieExpiration' ) / ( 3600 * 24 ) )
+ )->text(),
+ 'default' => $request->getVal( 'wpRemember' ),
+ );
}
- $this->getOutput()->addHTML(
- Xml::fieldset( $this->msg( 'resetpass_header' )->text() ) .
- Xml::openElement( 'form',
- array(
- 'method' => 'post',
- 'action' => $this->getTitle()->getLocalURL(),
- 'id' => 'mw-resetpass-form' ) ) . "\n" .
- $hiddenFieldsStr .
- $this->msg( 'resetpass_text' )->parseAsBlock() . "\n" .
- Xml::openElement( 'table', array( 'id' => 'mw-resetpass-table' ) ) . "\n" .
- $this->pretty( $prettyFields ) . "\n" .
- $rememberMe .
- "<tr>\n" .
- "<td></td>\n" .
- '<td class="mw-input">' .
- Xml::submitButton( $this->msg( $submitMsg )->text() ) .
- Xml::submitButton( $this->msg( 'resetpass-submit-cancel' )->text(), array( 'name' => 'wpCancel' ) ) .
- "</td>\n" .
- "</tr>\n" .
- Xml::closeElement( 'table' ) .
- Xml::closeElement( 'form' ) .
- Xml::closeElement( 'fieldset' ) . "\n"
+
+ return $fields;
+ }
+
+ protected function alterForm( HTMLForm $form ) {
+ $form->setId( 'mw-resetpass-form' );
+ $form->setTableId( 'mw-resetpass-table' );
+ $form->setWrapperLegendMsg( 'resetpass_header' );
+ $form->setSubmitTextMsg(
+ $this->getUser()->isLoggedIn()
+ ? 'resetpass-submit-loggedin'
+ : 'resetpass_submit'
);
+ $form->addButton( 'wpCancel', $this->msg( 'resetpass-submit-cancel' )->text() );
+ $form->setHeaderText( $this->msg( 'resetpass_text' )->parseAsBlock() );
+ if ( $this->mPreTextMessage instanceof Message ) {
+ $form->addPreText( $this->mPreTextMessage->parseAsBlock() );
+ }
+ $form->addHiddenFields(
+ $this->getRequest()->getValues( 'wpName', 'wpDomain', 'returnto', 'returntoquery' ) );
}
- /**
- * @param $fields array
- * @return string
- */
- function pretty( $fields ) {
- $out = '';
- foreach ( $fields as $list ) {
- list( $name, $label, $type, $value ) = $list;
- if ( $type == 'text' ) {
- $field = htmlspecialchars( $value );
- } else {
- $attribs = array( 'id' => $name );
- if ( $name == 'wpNewPassword' || $name == 'wpRetype' ) {
- $attribs = array_merge( $attribs,
- User::passwordChangeInputAttribs() );
- }
- if ( $name == 'wpPassword' ) {
- $attribs[] = 'autofocus';
- }
- $field = Html::input( $name, $value, $type, $attribs );
+ public function onSubmit( array $data ) {
+ global $wgAuth;
+
+ $request = $this->getRequest();
+
+ if ( $request->getCheck( 'wpLoginToken' ) ) {
+ // This comes from Special:Userlogin when logging in with a temporary password
+ return false;
+ }
+
+ if ( !$this->getUser()->isLoggedIn()
+ && $request->getVal( 'wpLoginOnChangeToken' ) !== LoginForm::getLoginToken()
+ ) {
+ // Potential CSRF (bug 62497)
+ return false;
+ }
+
+ if ( $request->getCheck( 'wpCancel' ) ) {
+ $titleObj = Title::newFromText( $request->getVal( 'returnto' ) );
+ if ( !$titleObj instanceof Title ) {
+ $titleObj = Title::newMainPage();
}
- $out .= "<tr>\n";
- $out .= "\t<td class='mw-label'>";
+ $query = $request->getVal( 'returntoquery' );
+ $this->getOutput()->redirect( $titleObj->getFullURL( $query ) );
+
+ return true;
+ }
+
+ try {
+ $this->mUserName = $request->getVal( 'wpName', $this->getUser()->getName() );
+ $this->mDomain = $wgAuth->getDomain();
- if ( $type != 'text' ) {
- $out .= Xml::label( $this->msg( $label )->text(), $name );
- } else {
- $out .= $this->msg( $label )->escaped();
+ if ( !$wgAuth->allowPasswordChange() ) {
+ throw new ErrorPageError( 'changepassword', 'resetpass_forbidden' );
}
- $out .= "</td>\n";
- $out .= "\t<td class='mw-input'>";
- $out .= $field;
- $out .= "</td>\n";
- $out .= "</tr>";
+ $this->attemptReset( $data['Password'], $data['NewPassword'], $data['Retype'] );
+
+ return true;
+ } catch ( PasswordError $e ) {
+ return $e->getMessage();
}
+ }
- return $out;
+ public function onSuccess() {
+ if ( $this->getUser()->isLoggedIn() ) {
+ $this->getOutput()->wrapWikiMsg(
+ "<div class=\"successbox\">\n$1\n</div>",
+ 'changepassword-success'
+ );
+ $this->getOutput()->returnToMain();
+ } else {
+ $request = $this->getRequest();
+ LoginForm::setLoginToken();
+ $token = LoginForm::getLoginToken();
+ $data = array(
+ 'action' => 'submitlogin',
+ 'wpName' => $this->mUserName,
+ 'wpDomain' => $this->mDomain,
+ 'wpLoginToken' => $token,
+ 'wpPassword' => $request->getVal( 'wpNewPassword' ),
+ ) + $request->getValues( 'wpRemember', 'returnto', 'returntoquery' );
+ $login = new LoginForm( new DerivativeRequest( $request, $data, true ) );
+ $login->setContext( $this->getContext() );
+ $login->execute( null );
+ }
}
/**
- * @throws PasswordError when cannot set the new password because requirements not met.
+ * @param string $oldpass
+ * @param string $newpass
+ * @param string $retype
+ * @throws PasswordError When cannot set the new password because requirements not met.
*/
- protected function attemptReset( $newpass, $retype ) {
- global $wgPasswordAttemptThrottle;
-
+ protected function attemptReset( $oldpass, $newpass, $retype ) {
$isSelf = ( $this->mUserName === $this->getUser()->getName() );
if ( $isSelf ) {
$user = $this->getUser();
@@ -267,32 +255,40 @@ class SpecialChangePassword extends UnlistedSpecialPage {
$throttleCount = LoginForm::incLoginThrottle( $this->mUserName );
if ( $throttleCount === true ) {
$lang = $this->getLanguage();
- throw new PasswordError( $this->msg( 'login-throttled' )
- ->params( $lang->formatDuration( $wgPasswordAttemptThrottle['seconds'] ) )
+ $throttleInfo = $this->getConfig()->get( 'PasswordAttemptThrottle' );
+ throw new PasswordError( $this->msg( 'changepassword-throttled' )
+ ->params( $lang->formatDuration( $throttleInfo['seconds'] ) )
->text()
);
}
+ // @todo Make these separate messages, since the message is written for both cases
+ if ( !$user->checkTemporaryPassword( $oldpass ) && !$user->checkPassword( $oldpass ) ) {
+ wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'wrongpassword' ) );
+ throw new PasswordError( $this->msg( 'resetpass-wrong-oldpass' )->text() );
+ }
+
+ // User is resetting their password to their old password
+ if ( $oldpass === $newpass ) {
+ throw new PasswordError( $this->msg( 'resetpass-recycled' )->text() );
+ }
+
+ // Do AbortChangePassword after checking mOldpass, so we don't leak information
+ // by possibly aborting a new password before verifying the old password.
$abortMsg = 'resetpass-abort-generic';
- if ( !wfRunHooks( 'AbortChangePassword', array( $user, $this->mOldpass, $newpass, &$abortMsg ) ) ) {
+ if ( !wfRunHooks( 'AbortChangePassword', array( $user, $oldpass, $newpass, &$abortMsg ) ) ) {
wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'abortreset' ) );
throw new PasswordError( $this->msg( $abortMsg )->text() );
}
- if ( !$user->checkTemporaryPassword( $this->mOldpass ) && !$user->checkPassword( $this->mOldpass ) ) {
- wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'wrongpassword' ) );
- throw new PasswordError( $this->msg( 'resetpass-wrong-oldpass' )->text() );
- }
-
// Please reset throttle for successful logins, thanks!
if ( $throttleCount ) {
LoginForm::clearLoginThrottle( $this->mUserName );
}
try {
- $user->setPassword( $this->mNewpass );
+ $user->setPassword( $newpass );
wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'success' ) );
- $this->mNewpass = $this->mOldpass = $this->mRetype = '';
} catch ( PasswordError $e ) {
wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'error' ) );
throw new PasswordError( $e->getMessage() );
@@ -301,12 +297,17 @@ class SpecialChangePassword extends UnlistedSpecialPage {
if ( $isSelf ) {
// This is needed to keep the user connected since
// changing the password also modifies the user's token.
- $user->setCookies();
+ $remember = $this->getRequest()->getCookie( 'Token' ) !== null;
+ $user->setCookies( null, null, $remember );
}
-
+ $user->resetPasswordExpiration();
$user->saveSettings();
}
+ public function requiresUnblock() {
+ return false;
+ }
+
protected function getGroupName() {
return 'users';
}
diff --git a/includes/specials/SpecialComparePages.php b/includes/specials/SpecialComparePages.php
index fc6b0c58..da1a54cd 100644
--- a/includes/specials/SpecialComparePages.php
+++ b/includes/specials/SpecialComparePages.php
@@ -43,8 +43,8 @@ class SpecialComparePages extends SpecialPage {
/**
* Show a form for filtering namespace and username
*
- * @param $par String
- * @return String
+ * @param string $par
+ * @return string
*/
public function execute( $par ) {
$this->setHeaders();
diff --git a/includes/specials/SpecialConfirmemail.php b/includes/specials/SpecialConfirmemail.php
index 3828b1c6..d771589d 100644
--- a/includes/specials/SpecialConfirmemail.php
+++ b/includes/specials/SpecialConfirmemail.php
@@ -45,6 +45,8 @@ class EmailConfirmation extends UnlistedSpecialPage {
$this->checkReadOnly();
$this->checkPermissions();
+ $this->requireLogin( 'confirmemail_needlogin' );
+
// This could also let someone check the current email address, so
// require both permissions.
if ( !$this->getUser()->isAllowed( 'viewmyprivateinfo' ) ) {
@@ -52,22 +54,10 @@ class EmailConfirmation extends UnlistedSpecialPage {
}
if ( $code === null || $code === '' ) {
- if ( $this->getUser()->isLoggedIn() ) {
- if ( Sanitizer::validateEmail( $this->getUser()->getEmail() ) ) {
- $this->showRequestForm();
- } else {
- $this->getOutput()->addWikiMsg( 'confirmemail_noemail' );
- }
+ if ( Sanitizer::validateEmail( $this->getUser()->getEmail() ) ) {
+ $this->showRequestForm();
} else {
- $llink = Linker::linkKnown(
- SpecialPage::getTitleFor( 'Userlogin' ),
- $this->msg( 'loginreqlink' )->escaped(),
- array(),
- array( 'returnto' => $this->getTitle()->getPrefixedText() )
- );
- $this->getOutput()->addHTML(
- $this->msg( 'confirmemail_needlogin' )->rawParams( $llink )->parse()
- );
+ $this->getOutput()->addWikiMsg( 'confirmemail_noemail' );
}
} else {
$this->attemptConfirm( $code );
@@ -90,19 +80,17 @@ class EmailConfirmation extends UnlistedSpecialPage {
} else {
$out->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) );
}
+ } elseif ( $user->isEmailConfirmed() ) {
+ // date and time are separate parameters to facilitate localisation.
+ // $time is kept for backward compat reasons.
+ // 'emailauthenticated' is also used in SpecialPreferences.php
+ $lang = $this->getLanguage();
+ $emailAuthenticated = $user->getEmailAuthenticationTimestamp();
+ $time = $lang->userTimeAndDate( $emailAuthenticated, $user );
+ $d = $lang->userDate( $emailAuthenticated, $user );
+ $t = $lang->userTime( $emailAuthenticated, $user );
+ $out->addWikiMsg( 'emailauthenticated', $time, $d, $t );
} else {
- if ( $user->isEmailConfirmed() ) {
- // date and time are separate parameters to facilitate localisation.
- // $time is kept for backward compat reasons.
- // 'emailauthenticated' is also used in SpecialPreferences.php
- $lang = $this->getLanguage();
- $emailAuthenticated = $user->getEmailAuthenticationTimestamp();
- $time = $lang->userTimeAndDate( $emailAuthenticated, $user );
- $d = $lang->userDate( $emailAuthenticated, $user );
- $t = $lang->userTime( $emailAuthenticated, $user );
- $out->addWikiMsg( 'emailauthenticated', $time, $d, $t );
- }
-
if ( $user->isEmailConfirmationPending() ) {
$out->wrapWikiMsg(
"<div class=\"error mw-confirmemail-pending\">\n$1\n</div>",
@@ -113,7 +101,7 @@ class EmailConfirmation extends UnlistedSpecialPage {
$out->addWikiMsg( 'confirmemail_text' );
$form = Html::openElement(
'form',
- array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL() )
+ array( 'method' => 'post', 'action' => $this->getPageTitle()->getLocalURL() )
) . "\n";
$form .= Html::hidden( 'token', $user->getEditToken() ) . "\n";
$form .= Xml::submitButton( $this->msg( 'confirmemail_send' )->text() ) . "\n";
diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php
index 1fe98190..32a887c4 100644
--- a/includes/specials/SpecialContributions.php
+++ b/includes/specials/SpecialContributions.php
@@ -26,8 +26,7 @@
*
* @ingroup SpecialPage
*/
-
-class SpecialContributions extends SpecialPage {
+class SpecialContributions extends IncludableSpecialPage {
protected $opts;
public function __construct() {
@@ -63,7 +62,9 @@ class SpecialContributions extends SpecialPage {
$this->opts['deletedOnly'] = $request->getBool( 'deletedOnly' );
if ( !strlen( $target ) ) {
- $out->addHTML( $this->getForm() );
+ if ( !$this->including() ) {
+ $out->addHTML( $this->getForm() );
+ }
return;
}
@@ -73,6 +74,7 @@ class SpecialContributions extends SpecialPage {
$this->opts['limit'] = $request->getInt( 'limit', $user->getOption( 'rclimit' ) );
$this->opts['target'] = $target;
$this->opts['topOnly'] = $request->getBool( 'topOnly' );
+ $this->opts['newOnly'] = $request->getBool( 'newOnly' );
$nt = Title::makeTitleSafe( NS_USER, $target );
if ( !$nt ) {
@@ -94,14 +96,14 @@ class SpecialContributions extends SpecialPage {
$out->setHTMLTitle( $this->msg(
'pagetitle',
$this->msg( 'contributions-title', $target )->plain()
- ) );
+ )->inContentLanguage() );
$this->getSkin()->setRelevantUser( $userObj );
} else {
$out->addSubtitle( $this->msg( 'sp-contributions-newbies-sub' ) );
$out->setHTMLTitle( $this->msg(
'pagetitle',
$this->msg( 'sp-contributions-newbies-title' )->plain()
- ) );
+ )->inContentLanguage() );
}
if ( ( $ns = $request->getVal( 'namespace', null ) ) !== null && $ns !== '' ) {
@@ -131,34 +133,40 @@ class SpecialContributions extends SpecialPage {
}
$feedType = $request->getVal( 'feed' );
+
+ $feedParams = array(
+ 'action' => 'feedcontributions',
+ 'user' => $target,
+ );
+ if ( $this->opts['topOnly'] ) {
+ $feedParams['toponly'] = true;
+ }
+ if ( $this->opts['newOnly'] ) {
+ $feedParams['newonly'] = true;
+ }
+ if ( $this->opts['deletedOnly'] ) {
+ $feedParams['deletedonly'] = true;
+ }
+ if ( $this->opts['tagfilter'] !== '' ) {
+ $feedParams['tagfilter'] = $this->opts['tagfilter'];
+ }
+ if ( $this->opts['namespace'] !== '' ) {
+ $feedParams['namespace'] = $this->opts['namespace'];
+ }
+ // Don't use year and month for the feed URL, but pass them on if
+ // we redirect to API (if $feedType is specified)
+ if ( $feedType && $this->opts['year'] !== null ) {
+ $feedParams['year'] = $this->opts['year'];
+ }
+ if ( $feedType && $this->opts['month'] !== null ) {
+ $feedParams['month'] = $this->opts['month'];
+ }
+
if ( $feedType ) {
- // Maintain some level of backwards compatability
+ // Maintain some level of backwards compatibility
// If people request feeds using the old parameters, redirect to API
- $apiParams = array(
- 'action' => 'feedcontributions',
- 'feedformat' => $feedType,
- 'user' => $target,
- );
- if ( $this->opts['topOnly'] ) {
- $apiParams['toponly'] = true;
- }
- if ( $this->opts['deletedOnly'] ) {
- $apiParams['deletedonly'] = true;
- }
- if ( $this->opts['tagfilter'] !== '' ) {
- $apiParams['tagfilter'] = $this->opts['tagfilter'];
- }
- if ( $this->opts['namespace'] !== '' ) {
- $apiParams['namespace'] = $this->opts['namespace'];
- }
- if ( $this->opts['year'] !== null ) {
- $apiParams['year'] = $this->opts['year'];
- }
- if ( $this->opts['month'] !== null ) {
- $apiParams['month'] = $this->opts['month'];
- }
-
- $url = wfAppendQuery( wfScript( 'api' ), $apiParams );
+ $feedParams['feedformat'] = $feedType;
+ $url = wfAppendQuery( wfScript( 'api' ), $feedParams );
$out->redirect( $url, '301' );
@@ -166,11 +174,12 @@ class SpecialContributions extends SpecialPage {
}
// Add RSS/atom links
- $this->addFeedLinks( array( 'action' => 'feedcontributions', 'user' => $target ) );
-
- if ( wfRunHooks( 'SpecialContributionsBeforeMainOutput', array( $id ) ) ) {
- $out->addHTML( $this->getForm() );
+ $this->addFeedLinks( $feedParams );
+ if ( wfRunHooks( 'SpecialContributionsBeforeMainOutput', array( $id, $userObj, $this ) ) ) {
+ if ( !$this->including() ) {
+ $out->addHTML( $this->getForm() );
+ }
$pager = new ContribsPager( $this->getContext(), array(
'target' => $target,
'contribs' => $this->opts['contribs'],
@@ -180,6 +189,7 @@ class SpecialContributions extends SpecialPage {
'month' => $this->opts['month'],
'deletedOnly' => $this->opts['deletedOnly'],
'topOnly' => $this->opts['topOnly'],
+ 'newOnly' => $this->opts['newOnly'],
'nsInvert' => $this->opts['nsInvert'],
'associated' => $this->opts['associated'],
) );
@@ -193,11 +203,13 @@ class SpecialContributions extends SpecialPage {
$out->showLagWarning( $lag );
}
- $out->addHTML(
- '<p>' . $pager->getNavigationBar() . '</p>' .
- $pager->getBody() .
- '<p>' . $pager->getNavigationBar() . '</p>'
- );
+ $output = $pager->getBody();
+ if ( !$this->including() ) {
+ $output = '<p>' . $pager->getNavigationBar() . '</p>' .
+ $output .
+ '<p>' . $pager->getNavigationBar() . '</p>';
+ }
+ $out->addHTML( $output );
}
$out->preventClickjacking( $pager->getPreventClickjacking() );
@@ -214,10 +226,12 @@ class SpecialContributions extends SpecialPage {
}
if ( $message ) {
- if ( !$this->msg( $message, $target )->isDisabled() ) {
- $out->wrapWikiMsg(
- "<div class='mw-contributions-footer'>\n$1\n</div>",
- array( $message, $target ) );
+ if ( !$this->including() ) {
+ if ( !$this->msg( $message, $target )->isDisabled() ) {
+ $out->wrapWikiMsg(
+ "<div class='mw-contributions-footer'>\n$1\n</div>",
+ array( $message, $target ) );
+ }
}
}
}
@@ -225,13 +239,26 @@ class SpecialContributions extends SpecialPage {
/**
* Generates the subheading with links
- * @param $userObj User object for the target
- * @return String: appropriately-escaped HTML to be output literally
+ * @param User $userObj User object for the target
+ * @return string Appropriately-escaped HTML to be output literally
* @todo FIXME: Almost the same as getSubTitle in SpecialDeletedContributions.php.
* Could be combined.
*/
protected function contributionsSub( $userObj ) {
if ( $userObj->isAnon() ) {
+ // Show a warning message that the user being searched for doesn't exists
+ if ( !User::isIP( $userObj->getName() ) ) {
+ $this->getOutput()->wrapWikiMsg(
+ "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
+ array(
+ 'contributions-userdoesnotexist',
+ wfEscapeWikiText( $userObj->getName() ),
+ )
+ );
+ if ( !$this->including() ) {
+ $this->getOutput()->setStatusCode( 404 );
+ }
+ }
$user = htmlspecialchars( $userObj->getName() );
} else {
$user = Linker::link( $userObj->getUserPage(), htmlspecialchars( $userObj->getName() ) );
@@ -246,25 +273,32 @@ class SpecialContributions extends SpecialPage {
// Show a note if the user is blocked and display the last block log entry.
// Do not expose the autoblocks, since that may lead to a leak of accounts' IPs,
// and also this will display a totally irrelevant log entry as a current block.
- if ( $userObj->isBlocked() && $userObj->getBlock()->getType() != Block::TYPE_AUTO ) {
- $out = $this->getOutput(); // showLogExtract() wants first parameter by reference
- LogEventsList::showLogExtract(
- $out,
- 'block',
- $nt,
- '',
- array(
- 'lim' => 1,
- 'showIfEmpty' => false,
- 'msgKey' => array(
- $userObj->isAnon() ?
- 'sp-contributions-blocked-notice-anon' :
- 'sp-contributions-blocked-notice',
- $userObj->getName() # Support GENDER in 'sp-contributions-blocked-notice'
- ),
- 'offset' => '' # don't use WebRequest parameter offset
- )
- );
+ if ( !$this->including() ) {
+ $block = Block::newFromTarget( $userObj, $userObj );
+ if ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) {
+ if ( $block->getType() == Block::TYPE_RANGE ) {
+ $nt = MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget();
+ }
+
+ $out = $this->getOutput(); // showLogExtract() wants first parameter by reference
+ LogEventsList::showLogExtract(
+ $out,
+ 'block',
+ $nt,
+ '',
+ array(
+ 'lim' => 1,
+ 'showIfEmpty' => false,
+ 'msgKey' => array(
+ $userObj->isAnon() ?
+ 'sp-contributions-blocked-notice-anon' :
+ 'sp-contributions-blocked-notice',
+ $userObj->getName() # Support GENDER in 'sp-contributions-blocked-notice'
+ ),
+ 'offset' => '' # don't use WebRequest parameter offset
+ )
+ );
+ }
}
}
@@ -273,9 +307,9 @@ class SpecialContributions extends SpecialPage {
/**
* Links to different places.
- * @param $userpage Title: Target user page
- * @param $talkpage Title: Talk page
- * @param $target User: Target user object
+ * @param Title $userpage Target user page
+ * @param Title $talkpage Talk page
+ * @param User $target Target user object
* @return array
*/
public function getUserLinks( Title $userpage, Title $talkpage, User $target ) {
@@ -311,6 +345,16 @@ class SpecialContributions extends SpecialPage {
array(),
array( 'page' => $userpage->getPrefixedText() )
);
+
+ # Suppression log link (bug 59120)
+ if ( $this->getUser()->isAllowed( 'suppressionlog' ) ) {
+ $tools[] = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Log', 'suppress' ),
+ $this->msg( 'sp-contributions-suppresslog' )->escaped(),
+ array(),
+ array( 'offender' => $username )
+ );
+ }
}
# Uploads
$tools[] = Linker::linkKnown(
@@ -349,12 +393,10 @@ class SpecialContributions extends SpecialPage {
/**
* Generates the namespace selector form with hidden attributes.
- * @return String: HTML fragment
+ * @return string HTML fragment
*/
protected function getForm() {
- global $wgScript;
-
- $this->opts['title'] = $this->getTitle()->getPrefixedText();
+ $this->opts['title'] = $this->getPageTitle()->getPrefixedText();
if ( !isset( $this->opts['target'] ) ) {
$this->opts['target'] = '';
} else {
@@ -397,11 +439,15 @@ class SpecialContributions extends SpecialPage {
$this->opts['topOnly'] = false;
}
+ if ( !isset( $this->opts['newOnly'] ) ) {
+ $this->opts['newOnly'] = false;
+ }
+
$form = Html::openElement(
'form',
array(
'method' => 'get',
- 'action' => $wgScript,
+ 'action' => wfScript(),
'class' => 'mw-contributions-form'
)
);
@@ -416,6 +462,7 @@ class SpecialContributions extends SpecialPage {
'year',
'month',
'topOnly',
+ 'newOnly',
'associated'
);
@@ -548,10 +595,21 @@ class SpecialContributions extends SpecialPage {
array( 'class' => 'mw-input' )
)
);
+ $checkLabelNewOnly = Html::rawElement(
+ 'span',
+ array( 'style' => 'white-space: nowrap' ),
+ Xml::checkLabel(
+ $this->msg( 'sp-contributions-newonly' )->text(),
+ 'newOnly',
+ 'mw-show-new-only',
+ $this->opts['newOnly'],
+ array( 'class' => 'mw-input' )
+ )
+ );
$extraOptions = Html::rawElement(
'td',
array( 'colspan' => 2 ),
- $deletedOnlyCheck . $checkLabelTopOnly
+ $deletedOnlyCheck . $checkLabelTopOnly . $checkLabelNewOnly
);
$dateSelectionAndSubmit = Xml::tags( 'td', array( 'colspan' => 2 ),
@@ -594,13 +652,16 @@ class SpecialContributions extends SpecialPage {
* @ingroup SpecialPage Pager
*/
class ContribsPager extends ReverseChronologicalPager {
- public $mDefaultDirection = true;
+ public $mDefaultDirection = IndexPager::DIR_DESCENDING;
public $messages;
public $target;
public $namespace = '';
public $mDb;
public $preventClickjacking = false;
+ /** @var DatabaseBase */
+ public $mDbSecondary;
+
/**
* @var array
*/
@@ -632,11 +693,16 @@ class ContribsPager extends ReverseChronologicalPager {
$this->deletedOnly = !empty( $options['deletedOnly'] );
$this->topOnly = !empty( $options['topOnly'] );
+ $this->newOnly = !empty( $options['newOnly'] );
$year = isset( $options['year'] ) ? $options['year'] : false;
$month = isset( $options['month'] ) ? $options['month'] : false;
$this->getDateCond( $year, $month );
+ // Most of this code will use the 'contributions' group DB, which can map to slaves
+ // with extra user based indexes or partioning by user. The additional metadata
+ // queries should use a regular slave since the lookup pattern is not all by user.
+ $this->mDbSecondary = wfGetDB( DB_SLAVE ); // any random slave
$this->mDb = wfGetDB( DB_SLAVE, 'contributions' );
}
@@ -651,9 +717,9 @@ class ContribsPager extends ReverseChronologicalPager {
* This method basically executes the exact same code as the parent class, though with
* a hook added, to allow extentions to add additional queries.
*
- * @param string $offset index offset, inclusive
- * @param $limit Integer: exact query limit
- * @param $descending Boolean: query direction, false for ascending, true for descending
+ * @param string $offset Index offset, inclusive
+ * @param int $limit Exact query limit
+ * @param bool $descending Query direction, false for ascending, true for descending
* @return ResultWrapper
*/
function reallyDoQuery( $offset, $limit, $descending ) {
@@ -725,7 +791,7 @@ class ContribsPager extends ReverseChronologicalPager {
// Paranoia: avoid brute force searches (bug 17342)
if ( !$user->isAllowed( 'deletedhistory' ) ) {
$conds[] = $this->mDb->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0';
- } elseif ( !$user->isAllowed( 'suppressrevision' ) ) {
+ } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
$conds[] = $this->mDb->bitAnd( 'rev_deleted', Revision::SUPPRESSED_USER ) .
' != ' . Revision::SUPPRESSED_USER;
}
@@ -735,6 +801,11 @@ class ContribsPager extends ReverseChronologicalPager {
# Get the current user name for accounts
$join_cond['user'] = Revision::userJoinCond();
+ $options = array();
+ if ( $index ) {
+ $options['USE INDEX'] = array( 'revision' => $index );
+ }
+
$queryInfo = array(
'tables' => $tables,
'fields' => array_merge(
@@ -744,7 +815,7 @@ class ContribsPager extends ReverseChronologicalPager {
'page_latest', 'page_is_redirect', 'page_len' )
),
'conds' => $conds,
- 'options' => array( 'USE INDEX' => array( 'revision' => $index ) ),
+ 'options' => $options,
'join_conds' => $join_cond
);
@@ -766,10 +837,10 @@ class ContribsPager extends ReverseChronologicalPager {
$condition = array();
$join_conds = array();
$tables = array( 'revision', 'page', 'user' );
+ $index = false;
if ( $this->contribs == 'newbie' ) {
$max = $this->mDb->selectField( 'user', 'max(user_id)', false, __METHOD__ );
$condition[] = 'rev_user >' . (int)( $max - $max / 100 );
- $index = 'user_timestamp';
# ignore local groups with the bot right
# @todo FIXME: Global groups may have 'bot' rights
$groupsWithBotPermission = User::getGroupsWithPermission( 'bot' );
@@ -802,6 +873,10 @@ class ContribsPager extends ReverseChronologicalPager {
$condition[] = 'rev_id = page_latest';
}
+ if ( $this->newOnly ) {
+ $condition[] = 'rev_parent_id = 0';
+ }
+
return array( $tables, $index, $condition, $join_conds );
}
@@ -851,7 +926,7 @@ class ContribsPager extends ReverseChronologicalPager {
$batch->add( $row->page_namespace, $row->page_title );
}
}
- $this->mParentLens = Revision::getParentLengths( $this->getDatabase(), $revIds );
+ $this->mParentLens = Revision::getParentLengths( $this->mDbSecondary, $revIds );
$batch->execute();
$this->mResult->seek( 0 );
}
@@ -879,7 +954,7 @@ class ContribsPager extends ReverseChronologicalPager {
* was not written by the target user.
*
* @todo This would probably look a lot nicer in a table.
- * @param $row
+ * @param object $row
* @return string
*/
function formatRow( $row ) {
@@ -896,8 +971,12 @@ class ContribsPager extends ReverseChronologicalPager {
* to extensions to subscribe to the hook to parse the row.
*/
wfSuppressWarnings();
- $rev = new Revision( $row );
- $validRevision = (bool)$rev->getId();
+ try {
+ $rev = new Revision( $row );
+ $validRevision = (bool)$rev->getId();
+ } catch ( MWException $e ) {
+ $validRevision = false;
+ }
wfRestoreWarnings();
if ( $validRevision ) {
@@ -986,7 +1065,8 @@ class ContribsPager extends ReverseChronologicalPager {
# Show user names for /newbies as there may be different users.
# Note that we already excluded rows with hidden user names.
if ( $this->contribs == 'newbie' ) {
- $userlink = ' . . ' . $lang->getDirMark() . Linker::userLink( $rev->getUser(), $rev->getUserText() );
+ $userlink = ' . . ' . $lang->getDirMark()
+ . Linker::userLink( $rev->getUser(), $rev->getUserText() );
$userlink .= ' ' . $this->msg( 'parentheses' )->rawParams(
Linker::userTalkLink( $rev->getUser(), $rev->getUserText() ) )->escaped() . ' ';
} else {
@@ -1036,7 +1116,7 @@ class ContribsPager extends ReverseChronologicalPager {
wfRunHooks( 'ContributionsLineEnding', array( $this, &$ret, $row, &$classes ) );
if ( $classes === array() && $ret === '' ) {
- wfDebug( 'Dropping Special:Contribution row that could not be formatted' );
+ wfDebug( "Dropping Special:Contribution row that could not be formatted\n" );
$ret = "<!-- Could not format Special:Contribution row. -->\n";
} else {
$ret = Html::rawElement( 'li', array( 'class' => $classes ), $ret ) . "\n";
diff --git a/includes/specials/SpecialCreateAccount.php b/includes/specials/SpecialCreateAccount.php
new file mode 100644
index 00000000..30e3833c
--- /dev/null
+++ b/includes/specials/SpecialCreateAccount.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * Redirect page: Special:CreateAccount --> Special:UserLogin/signup.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * Redirect page: Special:CreateAccount --> Special:UserLogin/signup.
+ * @todo FIXME: This (and the rest of the login frontend) needs to die a horrible painful death
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialCreateAccount extends SpecialRedirectToSpecial {
+ function __construct() {
+ parent::__construct(
+ 'CreateAccount',
+ 'Userlogin',
+ 'signup',
+ array( 'returnto', 'returntoquery', 'uselang' )
+ );
+ }
+
+ // No reason to hide this link on Special:Specialpages
+ public function isListed() {
+ return true;
+ }
+
+ public function isRestricted() {
+ return !User::groupHasPermission( '*', 'createaccount' );
+ }
+
+ public function userCanExecute( User $user ) {
+ return $user->isAllowed( 'createaccount' );
+ }
+
+ protected function getGroupName() {
+ return 'login';
+ }
+}
diff --git a/includes/specials/SpecialDeletedContributions.php b/includes/specials/SpecialDeletedContributions.php
index 9b9888ad..68f2c469 100644
--- a/includes/specials/SpecialDeletedContributions.php
+++ b/includes/specials/SpecialDeletedContributions.php
@@ -26,7 +26,7 @@
* @ingroup SpecialPage
*/
class DeletedContribsPager extends IndexPager {
- public $mDefaultDirection = true;
+ public $mDefaultDirection = IndexPager::DIR_DESCENDING;
public $messages;
public $target;
public $namespace = '';
@@ -62,7 +62,7 @@ class DeletedContribsPager extends IndexPager {
// Paranoia: avoid brute force searches (bug 17792)
if ( !$user->isAllowed( 'deletedhistory' ) ) {
$conds[] = $this->mDb->bitAnd( 'ar_deleted', Revision::DELETED_USER ) . ' = 0';
- } elseif ( !$user->isAllowed( 'suppressrevision' ) ) {
+ } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
$conds[] = $this->mDb->bitAnd( 'ar_deleted', Revision::SUPPRESSED_USER ) .
' != ' . Revision::SUPPRESSED_USER;
}
@@ -147,7 +147,7 @@ class DeletedContribsPager extends IndexPager {
* written by the target user.
*
* @todo This would probably look a lot nicer in a table.
- * @param $row
+ * @param stdClass $row
* @return string
*/
function formatRow( $row ) {
@@ -276,7 +276,7 @@ class DeletedContribsPager extends IndexPager {
class DeletedContributionsPage extends SpecialPage {
function __construct() {
parent::__construct( 'DeletedContributions', 'deletedhistory',
- /*listed*/true, /*function*/false, /*file*/false );
+ /*listed*/true, /*function*/false, /*file*/false );
}
/**
@@ -286,8 +286,6 @@ class DeletedContributionsPage extends SpecialPage {
* @param string $par (optional) user name of the user for which to show the contributions
*/
function execute( $par ) {
- global $wgQueryPageDefaultLimit;
-
$this->setHeaders();
$this->outputHeader();
@@ -317,7 +315,7 @@ class DeletedContributionsPage extends SpecialPage {
return;
}
- $options['limit'] = $request->getInt( 'limit', $wgQueryPageDefaultLimit );
+ $options['limit'] = $request->getInt( 'limit', $this->getConfig()->get( 'QueryPageDefaultLimit' ) );
$options['target'] = $target;
$userObj = User::newFromName( $target, false );
@@ -375,8 +373,8 @@ class DeletedContributionsPage extends SpecialPage {
/**
* Generates the subheading with links
- * @param $userObj User object for the target
- * @return String: appropriately-escaped HTML to be output literally
+ * @param User $userObj User object for the target
+ * @return string Appropriately-escaped HTML to be output literally
* @todo FIXME: Almost the same as contributionsSub in SpecialContributions.php. Could be combined.
*/
function getSubTitle( $userObj ) {
@@ -427,6 +425,15 @@ class DeletedContributionsPage extends SpecialPage {
'page' => $nt->getPrefixedText()
)
);
+ # Suppression log link (bug 59120)
+ if ( $this->getUser()->isAllowed( 'suppressionlog' ) ) {
+ $tools[] = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Log', 'suppress' ),
+ $this->msg( 'sp-contributions-suppresslog' )->escaped(),
+ array(),
+ array( 'offender' => $userObj->getName() )
+ );
+ }
}
# Uploads
@@ -463,7 +470,12 @@ class DeletedContributionsPage extends SpecialPage {
$links = $this->getLanguage()->pipeList( $tools );
// Show a note if the user is blocked and display the last block log entry.
- if ( $userObj->isBlocked() ) {
+ $block = Block::newFromTarget( $userObj, $userObj );
+ if ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) {
+ if ( $block->getType() == Block::TYPE_RANGE ) {
+ $nt = MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget();
+ }
+
// LogEventsList::showLogExtract() wants the first parameter by ref
$out = $this->getOutput();
LogEventsList::showLogExtract(
@@ -476,7 +488,7 @@ class DeletedContributionsPage extends SpecialPage {
'showIfEmpty' => false,
'msgKey' => array(
'sp-contributions-blocked-notice',
- $nt->getText() # Support GENDER in 'sp-contributions-blocked-notice'
+ $userObj->getName() # Support GENDER in 'sp-contributions-blocked-notice'
),
'offset' => '' # don't use $this->getRequest() parameter offset
)
@@ -489,13 +501,11 @@ class DeletedContributionsPage extends SpecialPage {
/**
* Generates the namespace selector form with hidden attributes.
- * @param array $options the options to be included.
+ * @param array $options The options to be included.
* @return string
*/
function getForm( $options ) {
- global $wgScript;
-
- $options['title'] = $this->getTitle()->getPrefixedText();
+ $options['title'] = $this->getPageTitle()->getPrefixedText();
if ( !isset( $options['target'] ) ) {
$options['target'] = '';
} else {
@@ -514,7 +524,7 @@ class DeletedContributionsPage extends SpecialPage {
$options['target'] = '';
}
- $f = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
+ $f = Xml::openElement( 'form', array( 'method' => 'get', 'action' => wfScript() ) );
foreach ( $options as $name => $value ) {
if ( in_array( $name, array( 'namespace', 'target', 'contribs' ) ) ) {
diff --git a/includes/specials/SpecialDiff.php b/includes/specials/SpecialDiff.php
new file mode 100644
index 00000000..77d23173
--- /dev/null
+++ b/includes/specials/SpecialDiff.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Redirect from Special:Diff/### to index.php?diff=### and
+ * from Special:Diff/###/### to index.php?oldid=###&diff=###.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * Redirect from Special:Diff/### to index.php?diff=### and
+ * from Special:Diff/###/### to index.php?oldid=###&diff=###.
+ *
+ * All of the following are valid usages:
+ * - [[Special:Diff/12345]] (diff of a revision with the previous one)
+ * - [[Special:Diff/12345/prev]] (diff of a revision with the previous one as well)
+ * - [[Special:Diff/12345/next]] (diff of a revision with the next one)
+ * - [[Special:Diff/12345/cur]] (diff of a revision with the latest one of that page)
+ * - [[Special:Diff/12345/98765]] (diff between arbitrary two revisions)
+ *
+ * @ingroup SpecialPage
+ * @since 1.23
+ */
+class SpecialDiff extends RedirectSpecialPage {
+ function __construct() {
+ parent::__construct( 'Diff' );
+ $this->mAllowedRedirectParams = array();
+ }
+
+ function getRedirect( $subpage ) {
+ $parts = explode( '/', $subpage );
+
+ // Try to parse the values given, generating somewhat pretty URLs if possible
+ if ( count( $parts ) === 1 && $parts[0] !== '' ) {
+ $this->mAddedRedirectParams['diff'] = $parts[0];
+ } elseif ( count( $parts ) === 2 ) {
+ $this->mAddedRedirectParams['oldid'] = $parts[0];
+ $this->mAddedRedirectParams['diff'] = $parts[1];
+ } else {
+ // Wrong number of parameters, bail out
+ throw new ErrorPageError( 'nopagetitle', 'nopagetext' );
+ }
+
+ return true;
+ }
+}
diff --git a/includes/specials/SpecialEditWatchlist.php b/includes/specials/SpecialEditWatchlist.php
index 501552e9..3656b9cc 100644
--- a/includes/specials/SpecialEditWatchlist.php
+++ b/includes/specials/SpecialEditWatchlist.php
@@ -36,7 +36,8 @@
*/
class SpecialEditWatchlist extends UnlistedSpecialPage {
/**
- * Editing modes
+ * Editing modes. EDIT_CLEAR is no longer used; the "Clear" link scared people
+ * too much. Now it's passed on to the raw editor, from which it's very easy to clear.
*/
const EDIT_CLEAR = 1;
const EDIT_RAW = 2;
@@ -55,34 +56,21 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
/**
* Main execution point
*
- * @param $mode int
+ * @param int $mode
*/
public function execute( $mode ) {
$this->setHeaders();
- $out = $this->getOutput();
-
# Anons don't get a watchlist
- if ( $this->getUser()->isAnon() ) {
- $out->setPageTitle( $this->msg( 'watchnologin' ) );
- $llink = Linker::linkKnown(
- SpecialPage::getTitleFor( 'Userlogin' ),
- $this->msg( 'loginreqlink' )->escaped(),
- array(),
- array( 'returnto' => $this->getTitle()->getPrefixedText() )
- );
- $out->addHTML( $this->msg( 'watchlistanontext' )->rawParams( $llink )->parse() );
+ $this->requireLogin( 'watchlistanontext' );
- return;
- }
+ $out = $this->getOutput();
$this->checkPermissions();
$this->checkReadOnly();
$this->outputHeader();
-
- $out->addSubtitle( $this->msg( 'watchlistfor2', $this->getUser()->getName() )
- ->rawParams( SpecialEditWatchlist::buildTools( null ) ) );
+ $this->outputSubtitle();
# B/C: $mode used to be waaay down the parameter list, and the first parameter
# was $wgUser
@@ -95,10 +83,6 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$mode = self::getMode( $this->getRequest(), $mode );
switch ( $mode ) {
- case self::EDIT_CLEAR:
- // The "Clear" link scared people too much.
- // Pass on to the raw editor, from which it's very easy to clear.
-
case self::EDIT_RAW:
$out->setPageTitle( $this->msg( 'watchlistedit-raw-title' ) );
$form = $this->getRawForm();
@@ -107,26 +91,73 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) );
}
break;
-
- case self::EDIT_NORMAL:
- default:
- $out->setPageTitle( $this->msg( 'watchlistedit-normal-title' ) );
- $form = $this->getNormalForm();
+ case self::EDIT_CLEAR:
+ $out->setPageTitle( $this->msg( 'watchlistedit-clear-title' ) );
+ $form = $this->getClearForm();
if ( $form->show() ) {
$out->addHTML( $this->successMessage );
$out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) );
- } elseif ( $this->toc !== false ) {
- $out->prependHTML( $this->toc );
}
break;
+
+ case self::EDIT_NORMAL:
+ default:
+ $this->executeViewEditWatchlist();
+ break;
+ }
+ }
+
+ /**
+ * Renders a subheader on the watchlist page.
+ */
+ protected function outputSubtitle() {
+ $out = $this->getOutput();
+ $out->addSubtitle( $this->msg( 'watchlistfor2', $this->getUser()->getName() )
+ ->rawParams( SpecialEditWatchlist::buildTools( null ) ) );
+ }
+
+ /**
+ * Executes an edit mode for the watchlist view, from which you can manage your watchlist
+ *
+ */
+ protected function executeViewEditWatchlist() {
+ $out = $this->getOutput();
+ $out->setPageTitle( $this->msg( 'watchlistedit-normal-title' ) );
+ $form = $this->getNormalForm();
+ if ( $form->show() ) {
+ $out->addHTML( $this->successMessage );
+ $out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) );
+ } elseif ( $this->toc !== false ) {
+ $out->prependHTML( $this->toc );
+ $out->addModules( 'mediawiki.toc' );
}
}
/**
+ * Return an array of subpages beginning with $search that this special page will accept.
+ *
+ * @param string $search Prefix to search for
+ * @param int $limit Maximum number of results to return
+ * @return string[] Matching subpages
+ */
+ public function prefixSearchSubpages( $search, $limit = 10 ) {
+ return self::prefixSearchArray(
+ $search,
+ $limit,
+ // SpecialWatchlist uses SpecialEditWatchlist::getMode, so new types should be added
+ // here and there - no 'edit' here, because that the default for this page
+ array(
+ 'clear',
+ 'raw',
+ )
+ );
+ }
+
+ /**
* Extract a list of titles from a blob of text, returning
* (prefixed) strings; unwatchable titles are ignored
*
- * @param $list String
+ * @param string $list
* @return array
*/
private function extractTitles( $list ) {
@@ -176,14 +207,14 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
}
if ( count( $toWatch ) > 0 ) {
- $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-added'
- )->numParams( count( $toWatch ) )->parse();
+ $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-added' )
+ ->numParams( count( $toWatch ) )->parse();
$this->showTitles( $toWatch, $this->successMessage );
}
if ( count( $toUnwatch ) > 0 ) {
- $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed'
- )->numParams( count( $toUnwatch ) )->parse();
+ $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed' )
+ ->numParams( count( $toUnwatch ) )->parse();
$this->showTitles( $toUnwatch, $this->successMessage );
}
} else {
@@ -204,19 +235,35 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
return true;
}
+ public function submitClear( $data ) {
+ $current = $this->getWatchlist();
+ $this->clearWatchlist();
+ $this->getUser()->invalidateCache();
+ $this->successMessage = $this->msg( 'watchlistedit-clear-done' )->parse();
+ $this->successMessage .= ' ' . $this->msg( 'watchlistedit-clear-removed' )
+ ->numParams( count( $current ) )->parse();
+ $this->showTitles( $current, $this->successMessage );
+
+ return true;
+ }
+
/**
* Print out a list of linked titles
*
* $titles can be an array of strings or Title objects; the former
* is preferred, since Titles are very memory-heavy
*
- * @param array $titles of strings, or Title objects
- * @param $output String
+ * @param array $titles Array of strings, or Title objects
+ * @param string $output
*/
private function showTitles( $titles, &$output ) {
$talk = $this->msg( 'talkpagelinktext' )->escaped();
// Do a batch existence check
$batch = new LinkBatch();
+ if ( count( $titles ) >= 100 ) {
+ $output = wfMessage( 'watchlistedit-too-many' )->parse();
+ return;
+ }
foreach ( $titles as $title ) {
if ( !$title instanceof Title ) {
$title = Title::newFromText( $title );
@@ -300,7 +347,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
*
* @return array
*/
- private function getWatchlistInfo() {
+ protected function getWatchlistInfo() {
$titles = array();
$dbr = wfGetDB( DB_MASTER );
@@ -332,7 +379,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
* @param Title $title
* @param int $namespace
* @param string $dbKey
- * @return bool: Whether this item is valid
+ * @return bool Whether this item is valid
*/
private function checkTitle( $title, $namespace, $dbKey ) {
if ( $title
@@ -403,7 +450,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
* $titles can be an array of strings or Title objects; the former
* is preferred, since Titles are very memory-heavy
*
- * @param array $titles of strings, or Title objects
+ * @param array $titles Array of strings, or Title objects
*/
private function watchTitles( $titles ) {
$dbw = wfGetDB( DB_MASTER );
@@ -439,7 +486,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
* $titles can be an array of strings or Title objects; the former
* is preferred, since Titles are very memory-heavy
*
- * @param array $titles of strings, or Title objects
+ * @param array $titles Array of strings, or Title objects
*/
private function unwatchTitles( $titles ) {
$dbw = wfGetDB( DB_MASTER );
@@ -506,24 +553,35 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$fields = array();
$count = 0;
- foreach ( $this->getWatchlistInfo() as $namespace => $pages ) {
- if ( $namespace >= 0 ) {
- $fields['TitlesNs' . $namespace] = array(
- 'class' => 'EditWatchlistCheckboxSeriesField',
- 'options' => array(),
- 'section' => "ns$namespace",
- );
- }
+ // Allow subscribers to manipulate the list of watched pages (or use it
+ // to preload lots of details at once)
+ $watchlistInfo = $this->getWatchlistInfo();
+ wfRunHooks(
+ 'WatchlistEditorBeforeFormRender',
+ array( &$watchlistInfo )
+ );
+
+ foreach ( $watchlistInfo as $namespace => $pages ) {
+ $options = array();
foreach ( array_keys( $pages ) as $dbkey ) {
$title = Title::makeTitleSafe( $namespace, $dbkey );
if ( $this->checkTitle( $title, $namespace, $dbkey ) ) {
$text = $this->buildRemoveLine( $title );
- $fields['TitlesNs' . $namespace]['options'][$text] = $title->getPrefixedText();
+ $options[$text] = $title->getPrefixedText();
$count++;
}
}
+
+ // checkTitle can filter some options out, avoid empty sections
+ if ( count( $options ) > 0 ) {
+ $fields['TitlesNs' . $namespace] = array(
+ 'class' => 'EditWatchlistCheckboxSeriesField',
+ 'options' => $options,
+ 'section' => "ns$namespace",
+ );
+ }
}
$this->cleanupWatchlist();
@@ -548,10 +606,11 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
}
$context = new DerivativeContext( $this->getContext() );
- $context->setTitle( $this->getTitle() ); // Remove subpage
+ $context->setTitle( $this->getPageTitle() ); // Remove subpage
$form = new EditWatchlistNormalHTMLForm( $fields, $context );
$form->setSubmitTextMsg( 'watchlistedit-normal-submit' );
- # Used message keys: 'accesskey-watchlistedit-normal-submit', 'tooltip-watchlistedit-normal-submit'
+ # Used message keys:
+ # 'accesskey-watchlistedit-normal-submit', 'tooltip-watchlistedit-normal-submit'
$form->setSubmitTooltip( 'watchlistedit-normal-submit' );
$form->setWrapperLegendMsg( 'watchlistedit-normal-legend' );
$form->addHeaderText( $this->msg( 'watchlistedit-normal-explain' )->parse() );
@@ -563,21 +622,16 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
/**
* Build the label for a checkbox, with a link to the title, and various additional bits
*
- * @param $title Title
+ * @param Title $title
* @return string
*/
private function buildRemoveLine( $title ) {
$link = Linker::link( $title );
- if ( $title->isRedirect() ) {
- // Linker already makes class mw-redirect, so this is redundant
- $link = '<span class="watchlistredir">' . $link . '</span>';
- }
-
- $tools[] = Linker::link( $title->getTalkPage(), $this->msg( 'talkpagelinktext' )->escaped() );
+ $tools['talk'] = Linker::link( $title->getTalkPage(), $this->msg( 'talkpagelinktext' )->escaped() );
if ( $title->exists() ) {
- $tools[] = Linker::linkKnown(
+ $tools['history'] = Linker::linkKnown(
$title,
$this->msg( 'history_short' )->escaped(),
array(),
@@ -586,13 +640,21 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
}
if ( $title->getNamespace() == NS_USER && !$title->isSubpage() ) {
- $tools[] = Linker::linkKnown(
+ $tools['contributions'] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Contributions', $title->getText() ),
$this->msg( 'contributions' )->escaped()
);
}
- wfRunHooks( 'WatchlistEditorBuildRemoveLine', array( &$tools, $title, $title->isRedirect(), $this->getSkin() ) );
+ wfRunHooks(
+ 'WatchlistEditorBuildRemoveLine',
+ array( &$tools, $title, $title->isRedirect(), $this->getSkin(), &$link )
+ );
+
+ if ( $title->isRedirect() ) {
+ // Linker already makes class mw-redirect, so this is redundant
+ $link = '<span class="watchlistredir">' . $link . '</span>';
+ }
return $link . " (" . $this->getLanguage()->pipeList( $tools ) . ")";
}
@@ -612,7 +674,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
),
);
$context = new DerivativeContext( $this->getContext() );
- $context->setTitle( $this->getTitle( 'raw' ) ); // Reset subpage
+ $context->setTitle( $this->getPageTitle( 'raw' ) ); // Reset subpage
$form = new HTMLForm( $fields, $context );
$form->setSubmitTextMsg( 'watchlistedit-raw-submit' );
# Used message keys: 'accesskey-watchlistedit-raw-submit', 'tooltip-watchlistedit-raw-submit'
@@ -625,11 +687,30 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
}
/**
+ * Get a form for clearing the watchlist
+ *
+ * @return HTMLForm
+ */
+ protected function getClearForm() {
+ $context = new DerivativeContext( $this->getContext() );
+ $context->setTitle( $this->getPageTitle( 'clear' ) ); // Reset subpage
+ $form = new HTMLForm( array(), $context );
+ $form->setSubmitTextMsg( 'watchlistedit-clear-submit' );
+ # Used message keys: 'accesskey-watchlistedit-clear-submit', 'tooltip-watchlistedit-clear-submit'
+ $form->setSubmitTooltip( 'watchlistedit-clear-submit' );
+ $form->setWrapperLegendMsg( 'watchlistedit-clear-legend' );
+ $form->addHeaderText( $this->msg( 'watchlistedit-clear-explain' )->parse() );
+ $form->setSubmitCallback( array( $this, 'submitClear' ) );
+
+ return $form;
+ }
+
+ /**
* Determine whether we are editing the watchlist, and if so, what
* kind of editing operation
*
- * @param $request WebRequest
- * @param $par mixed
+ * @param WebRequest $request
+ * @param string $par
* @return int
*/
public static function getMode( $request, $par ) {
@@ -654,7 +735,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
* Build a set of links for convenient navigation
* between watchlist viewing and editing modes
*
- * @param $unused
+ * @param null $unused
* @return string
*/
public static function buildTools( $unused ) {
@@ -665,6 +746,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
'view' => array( 'Watchlist', false ),
'edit' => array( 'EditWatchlist', false ),
'raw' => array( 'EditWatchlist', 'raw' ),
+ 'clear' => array( 'EditWatchlist', 'clear' ),
);
foreach ( $modes as $mode => $arr ) {
@@ -683,10 +765,6 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
}
}
-# B/C since 1.18
-class WatchlistEditor extends SpecialEditWatchlist {
-}
-
/**
* Extend HTMLForm purely so we can have a more sane way of getting the section headers
*/
@@ -712,9 +790,9 @@ class EditWatchlistCheckboxSeriesField extends HTMLMultiSelectField {
* form is open (bug 32126), but we know that invalid items will
* be harmless so we can override it here.
*
- * @param string $value the value the field was submitted with
- * @param array $alldata the data collected from the form
- * @return Mixed Bool true on success, or String error to display.
+ * @param string $value The value the field was submitted with
+ * @param array $alldata The data collected from the form
+ * @return bool|string Bool true on success, or String error to display.
*/
function validate( $value, $alldata ) {
// Need to call into grandparent to be a good citizen. :)
diff --git a/includes/specials/SpecialEmailuser.php b/includes/specials/SpecialEmailuser.php
index 2e90d996..20532a92 100644
--- a/includes/specials/SpecialEmailuser.php
+++ b/includes/specials/SpecialEmailuser.php
@@ -113,7 +113,8 @@ class SpecialEmailUser extends UnlistedSpecialPage {
// error out if sending user cannot do this
$error = self::getPermissionsError(
$this->getUser(),
- $this->getRequest()->getVal( 'wpEditToken' )
+ $this->getRequest()->getVal( 'wpEditToken' ),
+ $this->getConfig()
);
switch ( $error ) {
@@ -138,18 +139,19 @@ class SpecialEmailUser extends UnlistedSpecialPage {
$ret = self::getTarget( $this->mTarget );
if ( !$ret instanceof User ) {
if ( $this->mTarget != '' ) {
+ // Messages used here: notargettext, noemailtext, nowikiemailtext
$ret = ( $ret == 'notarget' ) ? 'emailnotarget' : ( $ret . 'text' );
$out->wrapWikiMsg( "<p class='error'>$1</p>", $ret );
}
$out->addHTML( $this->userForm( $this->mTarget ) );
- return false;
+ return;
}
$this->mTargetObj = $ret;
$context = new DerivativeContext( $this->getContext() );
- $context->setTitle( $this->getTitle() ); // Remove subpage
+ $context->setTitle( $this->getPageTitle() ); // Remove subpage
$form = new HTMLForm( $this->getFormFields(), $context );
// By now we are supposed to be sure that $this->mTarget is a user name
$form->addPreText( $this->msg( 'emailpagetext', $this->mTarget )->parse() );
@@ -159,7 +161,7 @@ class SpecialEmailUser extends UnlistedSpecialPage {
$form->loadData();
if ( !wfRunHooks( 'EmailUserForm', array( &$form ) ) ) {
- return false;
+ return;
}
$result = $form->show();
@@ -174,8 +176,8 @@ class SpecialEmailUser extends UnlistedSpecialPage {
/**
* Validate target User
*
- * @param string $target target user name
- * @return User object on success or a string on error
+ * @param string $target Target user name
+ * @return User User object on success or a string on error
*/
public static function getTarget( $target ) {
if ( $target == '' ) {
@@ -205,14 +207,17 @@ class SpecialEmailUser extends UnlistedSpecialPage {
/**
* Check whether a user is allowed to send email
*
- * @param $user User object
- * @param string $editToken edit token
- * @return null on success or string on error
+ * @param User $user
+ * @param string $editToken Edit token
+ * @param Config $config optional for backwards compatibility
+ * @return string|null Null on success or string on error
*/
- public static function getPermissionsError( $user, $editToken ) {
- global $wgEnableEmail, $wgEnableUserEmail;
-
- if ( !$wgEnableEmail || !$wgEnableUserEmail ) {
+ public static function getPermissionsError( $user, $editToken, Config $config = null ) {
+ if ( $config === null ) {
+ wfDebug( __METHOD__ . ' called without a Config instance passed to it' );
+ $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
+ }
+ if ( !$config->get( 'EnableEmail' ) || !$config->get( 'EnableUserEmail' ) ) {
return 'usermaildisabled';
}
@@ -251,16 +256,15 @@ class SpecialEmailUser extends UnlistedSpecialPage {
/**
* Form to ask for target user name.
*
- * @param string $name user name submitted.
- * @return String: form asking for user name.
+ * @param string $name User name submitted.
+ * @return string Form asking for user name.
*/
protected function userForm( $name ) {
- global $wgScript;
$string = Xml::openElement(
'form',
- array( 'method' => 'get', 'action' => $wgScript, 'id' => 'askusername' )
+ array( 'method' => 'get', 'action' => wfScript(), 'id' => 'askusername' )
) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
+ Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) .
Xml::openElement( 'fieldset' ) .
Html::rawElement( 'legend', null, $this->msg( 'emailtarget' )->parse() ) .
Xml::inputLabel(
@@ -282,8 +286,8 @@ class SpecialEmailUser extends UnlistedSpecialPage {
* Submit callback for an HTMLForm object, will simply call submit().
*
* @since 1.20
- * @param $data array
- * @param $form HTMLForm object
+ * @param array $data
+ * @param HTMLForm $form
* @return Status|string|bool
*/
public static function uiSubmit( array $data, HTMLForm $form ) {
@@ -297,19 +301,20 @@ class SpecialEmailUser extends UnlistedSpecialPage {
*
* @param array $data
* @param IContextSource $context
- * @return Mixed: Status object, or potentially a String on error
+ * @return Status|string|bool Status object, or potentially a String on error
* or maybe even true on success if anything uses the EmailUser hook.
*/
public static function submit( array $data, IContextSource $context ) {
- global $wgUserEmailUseReplyTo;
+ $config = $context->getConfig();
$target = self::getTarget( $data['Target'] );
if ( !$target instanceof User ) {
+ // Messages used here: notargettext, noemailtext, nowikiemailtext
return $context->msg( $target . 'text' )->parseAsBlock();
}
- $to = new MailAddress( $target );
- $from = new MailAddress( $context->getUser() );
+ $to = MailAddress::newFromUser( $target );
+ $from = MailAddress::newFromUser( $context->getUser() );
$subject = $data['Subject'];
$text = $data['Text'];
@@ -323,16 +328,15 @@ class SpecialEmailUser extends UnlistedSpecialPage {
return $error;
}
- if ( $wgUserEmailUseReplyTo ) {
+ if ( $config->get( 'UserEmailUseReplyTo' ) ) {
// Put the generic wiki autogenerated address in the From:
// header and reserve the user for Reply-To.
//
// This is a bit ugly, but will serve to differentiate
// wiki-borne mails from direct mails and protects against
// SPF and bounce problems with some mailers (see below).
- global $wgPasswordSender, $wgPasswordSenderName;
-
- $mailFrom = new MailAddress( $wgPasswordSender, $wgPasswordSenderName );
+ $mailFrom = new MailAddress( $config->get( 'PasswordSender' ),
+ wfMessage( 'emailsender' )->inContentLanguage()->text() );
$replyTo = $from;
} else {
// Put the sending user's e-mail address in the From: header.
diff --git a/includes/specials/SpecialExpandTemplates.php b/includes/specials/SpecialExpandTemplates.php
new file mode 100644
index 00000000..62f957fc
--- /dev/null
+++ b/includes/specials/SpecialExpandTemplates.php
@@ -0,0 +1,286 @@
+<?php
+/**
+ * Implements Special:ExpandTemplates
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * A special page that expands submitted templates, parser functions,
+ * and variables, allowing easier debugging of these.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialExpandTemplates extends SpecialPage {
+
+ /** @var bool Whether or not to show the XML parse tree */
+ protected $generateXML;
+
+ /** @var bool Whether or not to show the raw HTML code */
+ protected $generateRawHtml;
+
+ /** @var bool Whether or not to remove comments in the expanded wikitext */
+ protected $removeComments;
+
+ /** @var bool Whether or not to remove <nowiki> tags in the expanded wikitext */
+ protected $removeNowiki;
+
+ /** @var int Maximum size in bytes to include. 50MB allows fixing those huge pages */
+ const MAX_INCLUDE_SIZE = 50000000;
+
+ function __construct() {
+ parent::__construct( 'ExpandTemplates' );
+ }
+
+ /**
+ * Show the special page
+ * @param string|null $subpage
+ */
+ function execute( $subpage ) {
+ global $wgParser;
+
+ $this->setHeaders();
+
+ $request = $this->getRequest();
+ $titleStr = $request->getText( 'wpContextTitle' );
+ $title = Title::newFromText( $titleStr );
+
+ if ( !$title ) {
+ $title = $this->getPageTitle();
+ }
+ $input = $request->getText( 'wpInput' );
+ $this->generateXML = $request->getBool( 'wpGenerateXml' );
+ $this->generateRawHtml = $request->getBool( 'wpGenerateRawHtml' );
+
+ if ( strlen( $input ) ) {
+ $this->removeComments = $request->getBool( 'wpRemoveComments', false );
+ $this->removeNowiki = $request->getBool( 'wpRemoveNowiki', false );
+ $options = ParserOptions::newFromContext( $this->getContext() );
+ $options->setRemoveComments( $this->removeComments );
+ $options->setTidy( true );
+ $options->setMaxIncludeSize( self::MAX_INCLUDE_SIZE );
+
+ if ( $this->generateXML ) {
+ $wgParser->startExternalParse( $title, $options, OT_PREPROCESS );
+ $dom = $wgParser->preprocessToDom( $input );
+
+ if ( method_exists( $dom, 'saveXML' ) ) {
+ $xml = $dom->saveXML();
+ } else {
+ $xml = $dom->__toString();
+ }
+ }
+
+ $output = $wgParser->preprocess( $input, $title, $options );
+ } else {
+ $this->removeComments = $request->getBool( 'wpRemoveComments', true );
+ $this->removeNowiki = $request->getBool( 'wpRemoveNowiki', false );
+ $output = false;
+ }
+
+ $out = $this->getOutput();
+ $out->addWikiMsg( 'expand_templates_intro' );
+ $out->addHTML( $this->makeForm( $titleStr, $input ) );
+
+ if ( $output !== false ) {
+ if ( $this->generateXML && strlen( $output ) > 0 ) {
+ $out->addHTML( $this->makeOutput( $xml, 'expand_templates_xml_output' ) );
+ }
+
+ $tmp = $this->makeOutput( $output );
+
+ if ( $this->removeNowiki ) {
+ $tmp = preg_replace(
+ array( '_&lt;nowiki&gt;_', '_&lt;/nowiki&gt;_', '_&lt;nowiki */&gt;_' ),
+ '',
+ $tmp
+ );
+ }
+
+ $config = $this->getConfig();
+ if ( ( $config->get( 'UseTidy' ) && $options->getTidy() ) || $config->get( 'AlwaysUseTidy' ) ) {
+ $tmp = MWTidy::tidy( $tmp );
+ }
+
+ $out->addHTML( $tmp );
+
+ $pout = $this->generateHtml( $title, $output );
+ $rawhtml = $pout->getText();
+ if ( $this->generateRawHtml && strlen( $rawhtml ) > 0 ) {
+ $out->addHTML( $this->makeOutput( $rawhtml, 'expand_templates_html_output' ) );
+ }
+
+ $this->showHtmlPreview( $title, $pout, $out );
+ }
+ }
+
+ /**
+ * Generate a form allowing users to enter information
+ *
+ * @param string $title Value for context title field
+ * @param string $input Value for input textbox
+ * @return string
+ */
+ private function makeForm( $title, $input ) {
+ $self = $this->getPageTitle();
+ $request = $this->getRequest();
+ $user = $this->getUser();
+
+ $form = Xml::openElement(
+ 'form',
+ array( 'method' => 'post', 'action' => $self->getLocalUrl() )
+ );
+ $form .= "<fieldset><legend>" . $this->msg( 'expandtemplates' )->escaped() . "</legend>\n";
+
+ $form .= '<p>' . Xml::inputLabel(
+ $this->msg( 'expand_templates_title' )->plain(),
+ 'wpContextTitle',
+ 'contexttitle',
+ 60,
+ $title,
+ array( 'autofocus' => true )
+ ) . '</p>';
+ $form .= '<p>' . Xml::label(
+ $this->msg( 'expand_templates_input' )->text(),
+ 'input'
+ ) . '</p>';
+ $form .= Xml::textarea(
+ 'wpInput',
+ $input,
+ 10,
+ 10,
+ array( 'id' => 'input' )
+ );
+
+ $form .= '<p>' . Xml::checkLabel(
+ $this->msg( 'expand_templates_remove_comments' )->text(),
+ 'wpRemoveComments',
+ 'removecomments',
+ $this->removeComments
+ ) . '</p>';
+ $form .= '<p>' . Xml::checkLabel(
+ $this->msg( 'expand_templates_remove_nowiki' )->text(),
+ 'wpRemoveNowiki',
+ 'removenowiki',
+ $this->removeNowiki
+ ) . '</p>';
+ $form .= '<p>' . Xml::checkLabel(
+ $this->msg( 'expand_templates_generate_xml' )->text(),
+ 'wpGenerateXml',
+ 'generate_xml',
+ $this->generateXML
+ ) . '</p>';
+ $form .= '<p>' . Xml::checkLabel(
+ $this->msg( 'expand_templates_generate_rawhtml' )->text(),
+ 'wpGenerateRawHtml',
+ 'generate_rawhtml',
+ $this->generateRawHtml
+ ) . '</p>';
+ $form .= '<p>' . Xml::submitButton(
+ $this->msg( 'expand_templates_ok' )->text(),
+ array( 'accesskey' => 's' )
+ ) . '</p>';
+ $form .= "</fieldset>\n";
+ $form .= Html::hidden( 'wpEditToken', $user->getEditToken( '', $request ) );
+ $form .= Xml::closeElement( 'form' );
+
+ return $form;
+ }
+
+ /**
+ * Generate a nice little box with a heading for output
+ *
+ * @param string $output Wiki text output
+ * @param string $heading
+ * @return string
+ */
+ private function makeOutput( $output, $heading = 'expand_templates_output' ) {
+ $out = "<h2>" . $this->msg( $heading )->escaped() . "</h2>\n";
+ $out .= Xml::textarea(
+ 'output',
+ $output,
+ 10,
+ 10,
+ array( 'id' => 'output', 'readonly' => 'readonly' )
+ );
+
+ return $out;
+ }
+
+ /**
+ * Renders the supplied wikitext as html
+ *
+ * @param Title $title
+ * @param string $text
+ * @return ParserOutput
+ */
+ private function generateHtml( Title $title, $text ) {
+ global $wgParser;
+
+ $popts = ParserOptions::newFromContext( $this->getContext() );
+ $popts->setTargetLanguage( $title->getPageLanguage() );
+ return $wgParser->parse( $text, $title, $popts );
+ }
+
+ /**
+ * Wraps the provided html code in a div and outputs it to the page
+ *
+ * @param Title $title
+ * @param ParserOutput $pout
+ * @param OutputPage $out
+ */
+ private function showHtmlPreview( Title $title, ParserOutput $pout, OutputPage $out ) {
+ $lang = $title->getPageViewLanguage();
+ $out->addHTML( "<h2>" . $this->msg( 'expand_templates_preview' )->escaped() . "</h2>\n" );
+
+ if ( $this->getConfig()->get( 'RawHtml' ) ) {
+ $request = $this->getRequest();
+ $user = $this->getUser();
+
+ // To prevent cross-site scripting attacks, don't show the preview if raw HTML is
+ // allowed and a valid edit token is not provided (bug 71111). However, MediaWiki
+ // does not currently provide logged-out users with CSRF protection; in that case,
+ // do not show the preview unless anonymous editing is allowed.
+ if ( $user->isAnon() && !$user->isAllowed( 'edit' ) ) {
+ $error = array( 'expand_templates_preview_fail_html_anon' );
+ } elseif ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ), '', $request ) ) {
+ $error = array( 'expand_templates_preview_fail_html' );
+ } else {
+ $error = false;
+ }
+
+ if ( $error ) {
+ $out->wrapWikiMsg( "<div class='previewnote'>\n$1\n</div>", $error );
+ return;
+ }
+ }
+
+ $out->addHTML( Html::openElement( 'div', array(
+ 'class' => 'mw-content-' . $lang->getDir(),
+ 'dir' => $lang->getDir(),
+ 'lang' => $lang->getHtmlCode(),
+ ) ) );
+ $out->addParserOutputContent( $pout );
+ $out->addHTML( Html::closeElement( 'div' ) );
+ }
+
+ protected function getGroupName() {
+ return 'wiki';
+ }
+}
diff --git a/includes/specials/SpecialExport.php b/includes/specials/SpecialExport.php
index 61ed34d4..38c52a01 100644
--- a/includes/specials/SpecialExport.php
+++ b/includes/specials/SpecialExport.php
@@ -37,12 +37,9 @@ class SpecialExport extends SpecialPage {
}
public function execute( $par ) {
- global $wgSitename, $wgExportAllowListContributors, $wgExportFromNamespaces;
- global $wgExportAllowHistory, $wgExportMaxHistory, $wgExportMaxLinkDepth;
- global $wgExportAllowAll;
-
$this->setHeaders();
$this->outputHeader();
+ $config = $this->getConfig();
// Set some variables
$this->curonly = true;
@@ -74,7 +71,7 @@ class SpecialExport extends SpecialPage {
}
}
}
- } elseif ( $request->getCheck( 'addns' ) && $wgExportFromNamespaces ) {
+ } elseif ( $request->getCheck( 'addns' ) && $config->get( 'ExportFromNamespaces' ) ) {
$page = $request->getText( 'pages' );
$nsindex = $request->getText( 'nsindex', '' );
@@ -87,7 +84,7 @@ class SpecialExport extends SpecialPage {
$page .= "\n" . implode( "\n", $nspages );
}
}
- } elseif ( $request->getCheck( 'exportall' ) && $wgExportAllowAll ) {
+ } elseif ( $request->getCheck( 'exportall' ) && $config->get( 'ExportAllowAll' ) ) {
$this->doExport = true;
$exportall = true;
@@ -108,19 +105,20 @@ class SpecialExport extends SpecialPage {
$offset = null;
}
+ $maxHistory = $config->get( 'ExportMaxHistory' );
$limit = $request->getInt( 'limit' );
$dir = $request->getVal( 'dir' );
$history = array(
'dir' => 'asc',
'offset' => false,
- 'limit' => $wgExportMaxHistory,
+ 'limit' => $maxHistory,
);
$historyCheck = $request->getCheck( 'history' );
if ( $this->curonly ) {
$history = WikiExporter::CURRENT;
} elseif ( !$historyCheck ) {
- if ( $limit > 0 && ( $wgExportMaxHistory == 0 || $limit < $wgExportMaxHistory ) ) {
+ if ( $limit > 0 && ( $maxHistory == 0 || $limit < $maxHistory ) ) {
$history['limit'] = $limit;
}
@@ -152,13 +150,13 @@ class SpecialExport extends SpecialPage {
}
}
- if ( !$wgExportAllowHistory ) {
+ if ( !$config->get( 'ExportAllowHistory' ) ) {
// Override
$history = WikiExporter::CURRENT;
}
$list_authors = $request->getCheck( 'listauthors' );
- if ( !$this->curonly || !$wgExportAllowListContributors ) {
+ if ( !$this->curonly || !$config->get( 'ExportAllowListContributors' ) ) {
$list_authors = false;
}
@@ -172,7 +170,7 @@ class SpecialExport extends SpecialPage {
if ( $request->getCheck( 'wpDownload' ) ) {
// Provide a sane filename suggestion
- $filename = urlencode( $wgSitename . '-' . wfTimestampNow() . '.xml' );
+ $filename = urlencode( $config->get( 'Sitename' ) . '-' . wfTimestampNow() . '.xml' );
$request->response()->header( "Content-disposition: attachment;filename={$filename}" );
}
@@ -185,7 +183,7 @@ class SpecialExport extends SpecialPage {
$out->addWikiMsg( 'exporttext' );
$form = Xml::openElement( 'form', array( 'method' => 'post',
- 'action' => $this->getTitle()->getLocalURL( 'action=submit' ) ) );
+ 'action' => $this->getPageTitle()->getLocalURL( 'action=submit' ) ) );
$form .= Xml::inputLabel(
$this->msg( 'export-addcattext' )->text(),
'catname',
@@ -197,7 +195,7 @@ class SpecialExport extends SpecialPage {
array( 'name' => 'addcat' )
) . '<br />';
- if ( $wgExportFromNamespaces ) {
+ if ( $config->get( 'ExportFromNamespaces' ) ) {
$form .= Html::namespaceSelector(
array(
'selected' => $nsindex,
@@ -214,7 +212,7 @@ class SpecialExport extends SpecialPage {
) . '<br />';
}
- if ( $wgExportAllowAll ) {
+ if ( $config->get( 'ExportAllowAll' ) ) {
$form .= Xml::checkLabel(
$this->msg( 'exportall' )->text(),
'exportall',
@@ -231,7 +229,7 @@ class SpecialExport extends SpecialPage {
);
$form .= '<br />';
- if ( $wgExportAllowHistory ) {
+ if ( $config->get( 'ExportAllowHistory' ) ) {
$form .= Xml::checkLabel(
$this->msg( 'exportcuronly' )->text(),
'curonly',
@@ -249,7 +247,7 @@ class SpecialExport extends SpecialPage {
$request->wasPosted() ? $request->getCheck( 'templates' ) : false
) . '<br />';
- if ( $wgExportMaxLinkDepth || $this->userCanOverrideExportDepth() ) {
+ if ( $config->get( 'ExportMaxLinkDepth' ) || $this->userCanOverrideExportDepth() ) {
$form .= Xml::inputLabel(
$this->msg( 'export-pagelinks' )->text(),
'pagelink-depth',
@@ -259,8 +257,14 @@ class SpecialExport extends SpecialPage {
) . '<br />';
}
- // Enable this when we can do something useful exporting/importing image information. :)
- //$form .= Xml::checkLabel( $this->msg( 'export-images' )->text(), 'images', 'wpExportImages', false ) . '<br />';
+ /* Enable this when we can do something useful exporting/importing image information.
+ $form .= Xml::checkLabel(
+ $this->msg( 'export-images' )->text(),
+ 'images',
+ 'wpExportImages',
+ false
+ ) . '<br />';
+ */
$form .= Xml::checkLabel(
$this->msg( 'export-download' )->text(),
'wpDownload',
@@ -268,7 +272,7 @@ class SpecialExport extends SpecialPage {
$request->wasPosted() ? $request->getCheck( 'wpDownload' ) : true
) . '<br />';
- if ( $wgExportAllowListContributors ) {
+ if ( $config->get( 'ExportAllowListContributors' ) ) {
$form .= Xml::checkLabel(
$this->msg( 'exportlistauthors' )->text(),
'listauthors',
@@ -296,11 +300,11 @@ class SpecialExport extends SpecialPage {
/**
* Do the actual page exporting
*
- * @param string $page user input on what page(s) to export
- * @param $history Mixed: one of the WikiExporter history export constants
- * @param $list_authors Boolean: Whether to add distinct author list (when
- * not returning full history)
- * @param $exportall Boolean: Whether to export everything
+ * @param string $page User input on what page(s) to export
+ * @param int $history One of the WikiExporter history export constants
+ * @param bool $list_authors Whether to add distinct author list (when
+ * not returning full history)
+ * @param bool $exportall Whether to export everything
*/
private function doExport( $page, $history, $list_authors, $exportall ) {
@@ -315,7 +319,7 @@ class SpecialExport extends SpecialPage {
foreach ( explode( "\n", $page ) as $pageName ) {
$pageName = trim( $pageName );
$title = Title::newFromText( $pageName );
- if ( $title && $title->getInterwiki() == '' && $title->getText() !== '' ) {
+ if ( $title && !$title->isExternal() && $title->getText() !== '' ) {
// Only record each page once!
$pageSet[$title->getPrefixedText()] = true;
}
@@ -397,7 +401,7 @@ class SpecialExport extends SpecialPage {
}
/**
- * @param $title Title
+ * @param Title $title
* @return array
*/
private function getPagesFromCategory( $title ) {
@@ -430,7 +434,7 @@ class SpecialExport extends SpecialPage {
}
/**
- * @param $nsindex int
+ * @param int $nsindex
* @return array
*/
private function getPagesFromNamespace( $nsindex ) {
@@ -463,9 +467,9 @@ class SpecialExport extends SpecialPage {
/**
* Expand a list of pages to include templates used in those pages.
- * @param $inputPages array, list of titles to look up
- * @param $pageSet array, associative array indexed by titles for output
- * @return array associative array index by titles
+ * @param array $inputPages List of titles to look up
+ * @param array $pageSet Associative array indexed by titles for output
+ * @return array Associative array index by titles
*/
private function getTemplates( $inputPages, $pageSet ) {
return $this->getLinks( $inputPages, $pageSet,
@@ -477,19 +481,18 @@ class SpecialExport extends SpecialPage {
/**
* Validate link depth setting, if available.
- * @param $depth int
+ * @param int $depth
* @return int
*/
private function validateLinkDepth( $depth ) {
- global $wgExportMaxLinkDepth;
-
if ( $depth < 0 ) {
return 0;
}
if ( !$this->userCanOverrideExportDepth() ) {
- if ( $depth > $wgExportMaxLinkDepth ) {
- return $wgExportMaxLinkDepth;
+ $maxLinkDepth = $this->getConfig()->get( 'ExportMaxLinkDepth' );
+ if ( $depth > $maxLinkDepth ) {
+ return $maxLinkDepth;
}
}
@@ -504,13 +507,15 @@ class SpecialExport extends SpecialPage {
/**
* Expand a list of pages to include pages linked to from that page.
- * @param $inputPages array
- * @param $pageSet array
- * @param $depth int
+ * @param array $inputPages
+ * @param array $pageSet
+ * @param int $depth
* @return array
*/
private function getPageLinks( $inputPages, $pageSet, $depth ) {
+ // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect
for ( ; $depth > 0; --$depth ) {
+ // @codingStandardsIgnoreEnd
$pageSet = $this->getLinks(
$inputPages, $pageSet, 'pagelinks',
array( 'namespace' => 'pl_namespace', 'title' => 'pl_title' ),
@@ -525,10 +530,10 @@ class SpecialExport extends SpecialPage {
/**
* Expand a list of pages to include images used in those pages.
*
- * @param $inputPages array, list of titles to look up
- * @param $pageSet array, associative array indexed by titles for output
+ * @param array $inputPages List of titles to look up
+ * @param array $pageSet Associative array indexed by titles for output
*
- * @return array associative array index by titles
+ * @return array Associative array index by titles
*/
private function getImages( $inputPages, $pageSet ) {
return $this->getLinks(
diff --git a/includes/specials/SpecialFewestrevisions.php b/includes/specials/SpecialFewestrevisions.php
index 47a4d75f..dc9d57c2 100644
--- a/includes/specials/SpecialFewestrevisions.php
+++ b/includes/specials/SpecialFewestrevisions.php
@@ -71,7 +71,7 @@ class FewestrevisionsPage extends QueryPage {
/**
* @param Skin $skin
* @param object $result Database row
- * @return String
+ * @return string
*/
function formatResult( $skin, $result ) {
global $wgContLang;
diff --git a/includes/specials/SpecialFileDuplicateSearch.php b/includes/specials/SpecialFileDuplicateSearch.php
index 4c6593b2..fc26c903 100644
--- a/includes/specials/SpecialFileDuplicateSearch.php
+++ b/includes/specials/SpecialFileDuplicateSearch.php
@@ -59,7 +59,7 @@ class FileDuplicateSearchPage extends QueryPage {
/**
* Fetch dupes from all connected file repositories.
*
- * @return array of File objects
+ * @return array Array of File objects
*/
function getDupes() {
return RepoGroup::singleton()->findBySha1( $this->hash );
@@ -67,7 +67,7 @@ class FileDuplicateSearchPage extends QueryPage {
/**
*
- * @param array $dupes of File objects
+ * @param array $dupes Array of File objects
*/
function showList( $dupes ) {
$html = array();
@@ -96,12 +96,10 @@ class FileDuplicateSearchPage extends QueryPage {
}
function execute( $par ) {
- global $wgScript;
-
$this->setHeaders();
$this->outputHeader();
- $this->filename = isset( $par ) ? $par : $this->getRequest()->getText( 'filename' );
+ $this->filename = $par !== null ? $par : $this->getRequest()->getText( 'filename' );
$this->file = null;
$this->hash = '';
$title = Title::newFromText( $this->filename, NS_FILE );
@@ -115,9 +113,9 @@ class FileDuplicateSearchPage extends QueryPage {
$out->addHTML(
Html::openElement(
'form',
- array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => $wgScript )
+ array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => wfScript() )
) . "\n" .
- Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . "\n" .
+ Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) . "\n" .
Html::openElement( 'fieldset' ) . "\n" .
Html::element( 'legend', null, $this->msg( 'fileduplicatesearch-legend' )->text() ) . "\n" .
Xml::inputLabel(
diff --git a/includes/specials/SpecialFilepath.php b/includes/specials/SpecialFilepath.php
index e7ced52a..5860f636 100644
--- a/includes/specials/SpecialFilepath.php
+++ b/includes/specials/SpecialFilepath.php
@@ -36,7 +36,13 @@ class SpecialFilepath extends RedirectSpecialPage {
// implement by redirecting through Special:Redirect/file
function getRedirect( $par ) {
$file = $par ?: $this->getRequest()->getText( 'file' );
- return SpecialPage::getSafeTitleFor( 'Redirect', 'file/' . $file );
+
+ if ( $file ) {
+ $argument = "file/$file";
+ } else {
+ $argument = 'file';
+ }
+ return SpecialPage::getSafeTitleFor( 'Redirect', $argument );
}
protected function getGroupName() {
diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php
index d7d860de..eab4784c 100644
--- a/includes/specials/SpecialImport.php
+++ b/includes/specials/SpecialImport.php
@@ -3,7 +3,7 @@
* Implements Special:Import
*
* Copyright © 2003,2005 Brion Vibber <brion@pobox.com>
- * http://www.mediawiki.org/
+ * https://www.mediawiki.org/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -31,6 +31,8 @@
*/
class SpecialImport extends SpecialPage {
private $interwiki = false;
+ private $subproject;
+ private $fullInterwikiPrefix;
private $namespace;
private $rootpage = '';
private $frompage = '';
@@ -44,17 +46,19 @@ class SpecialImport extends SpecialPage {
*/
public function __construct() {
parent::__construct( 'Import', 'import' );
- global $wgImportTargetNamespace;
- $this->namespace = $wgImportTargetNamespace;
+ $this->namespace = $this->getConfig()->get( 'ImportTargetNamespace' );
}
/**
* Execute
+ * @param string|null $par
*/
function execute( $par ) {
$this->setHeaders();
$this->outputHeader();
+ $this->getOutput()->addModules( 'mediawiki.special.import' );
+
$user = $this->getUser();
if ( !$user->isAllowedAny( 'import', 'importupload' ) ) {
throw new PermissionsError( 'import' );
@@ -64,11 +68,11 @@ class SpecialImport extends SpecialPage {
# @todo FIXME: Title::checkSpecialsAndNSPermissions() has a very wierd expectation of what
# getUserPermissionsErrors() might actually be used for, hence the 'ns-specialprotected'
$errors = wfMergeErrorArrays(
- $this->getTitle()->getUserPermissionsErrors(
+ $this->getPageTitle()->getUserPermissionsErrors(
'import', $user, true,
array( 'ns-specialprotected', 'badaccess-group0', 'badaccess-groups' )
),
- $this->getTitle()->getUserPermissionsErrors(
+ $this->getPageTitle()->getUserPermissionsErrors(
'importupload', $user, true,
array( 'ns-specialprotected', 'badaccess-group0', 'badaccess-groups' )
)
@@ -91,15 +95,15 @@ class SpecialImport extends SpecialPage {
* Do the actual import
*/
private function doImport() {
- global $wgImportSources, $wgExportMaxLinkDepth;
-
$isUpload = false;
$request = $this->getRequest();
$this->namespace = $request->getIntOrNull( 'namespace' );
$sourceName = $request->getVal( "source" );
$this->logcomment = $request->getText( 'log-comment' );
- $this->pageLinkDepth = $wgExportMaxLinkDepth == 0 ? 0 : $request->getIntOrNull( 'pagelink-depth' );
+ $this->pageLinkDepth = $this->getConfig()->get( 'ExportMaxLinkDepth' ) == 0
+ ? 0
+ : $request->getIntOrNull( 'pagelink-depth' );
$this->rootpage = $request->getText( 'rootpage' );
$user = $this->getUser();
@@ -116,19 +120,30 @@ class SpecialImport extends SpecialPage {
if ( !$user->isAllowed( 'import' ) ) {
throw new PermissionsError( 'import' );
}
- $this->interwiki = $request->getVal( 'interwiki' );
- if ( !in_array( $this->interwiki, $wgImportSources ) ) {
+ $this->interwiki = $this->fullInterwikiPrefix = $request->getVal( 'interwiki' );
+ // does this interwiki have subprojects?
+ $importSources = $this->getConfig()->get( 'ImportSources' );
+ $hasSubprojects = array_key_exists( $this->interwiki, $importSources );
+ if ( !$hasSubprojects && !in_array( $this->interwiki, $importSources ) ) {
$source = Status::newFatal( "import-invalid-interwiki" );
} else {
- $this->history = $request->getCheck( 'interwikiHistory' );
- $this->frompage = $request->getText( "frompage" );
- $this->includeTemplates = $request->getCheck( 'interwikiTemplates' );
- $source = ImportStreamSource::newFromInterwiki(
- $this->interwiki,
- $this->frompage,
- $this->history,
- $this->includeTemplates,
- $this->pageLinkDepth );
+ if ( $hasSubprojects ) {
+ $this->subproject = $request->getVal( 'subproject' );
+ $this->fullInterwikiPrefix .= ':' . $request->getVal( 'subproject' );
+ }
+ if ( $hasSubprojects && !in_array( $this->subproject, $importSources[$this->interwiki] ) ) {
+ $source = Status::newFatal( "import-invalid-interwiki" );
+ } else {
+ $this->history = $request->getCheck( 'interwikiHistory' );
+ $this->frompage = $request->getText( "frompage" );
+ $this->includeTemplates = $request->getCheck( 'interwikiTemplates' );
+ $source = ImportStreamSource::newFromInterwiki(
+ $this->fullInterwikiPrefix,
+ $this->frompage,
+ $this->history,
+ $this->includeTemplates,
+ $this->pageLinkDepth );
+ }
}
} else {
$source = Status::newFatal( "importunknownsource" );
@@ -166,7 +181,7 @@ class SpecialImport extends SpecialPage {
$reporter = new ImportReporter(
$importer,
$isUpload,
- $this->interwiki,
+ $this->fullInterwikiPrefix,
$this->logcomment
);
$reporter->setContext( $this->getContext() );
@@ -201,11 +216,10 @@ class SpecialImport extends SpecialPage {
}
private function showForm() {
- global $wgImportSources, $wgExportMaxLinkDepth;
-
- $action = $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) );
+ $action = $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) );
$user = $this->getUser();
$out = $this->getOutput();
+ $importSources = $this->getConfig()->get( 'ImportSources' );
if ( $user->isAllowed( 'importupload' ) ) {
$out->addHTML(
@@ -242,7 +256,10 @@ class SpecialImport extends SpecialPage {
</tr>
<tr>
<td class='mw-label'>" .
- Xml::label( $this->msg( 'import-interwiki-rootpage' )->text(), 'mw-interwiki-rootpage-upload' ) .
+ Xml::label(
+ $this->msg( 'import-interwiki-rootpage' )->text(),
+ 'mw-interwiki-rootpage-upload'
+ ) .
"</td>
<td class='mw-input'>" .
Xml::input( 'rootpage', 50, $this->rootpage,
@@ -261,15 +278,15 @@ class SpecialImport extends SpecialPage {
Xml::closeElement( 'fieldset' )
);
} else {
- if ( empty( $wgImportSources ) ) {
+ if ( empty( $importSources ) ) {
$out->addWikiMsg( 'importnosources' );
}
}
- if ( $user->isAllowed( 'import' ) && !empty( $wgImportSources ) ) {
+ if ( $user->isAllowed( 'import' ) && !empty( $importSources ) ) {
# Show input field for import depth only if $wgExportMaxLinkDepth > 0
$importDepth = '';
- if ( $wgExportMaxLinkDepth > 0 ) {
+ if ( $this->getConfig()->get( 'ExportMaxLinkDepth' ) > 0 ) {
$importDepth = "<tr>
<td class='mw-label'>" .
$this->msg( 'export-pagelinks' )->parse() .
@@ -297,7 +314,7 @@ class SpecialImport extends SpecialPage {
Xml::openElement( 'table', array( 'id' => 'mw-import-table-interwiki' ) ) .
"<tr>
<td class='mw-label'>" .
- Xml::label( $this->msg( 'import-interwiki-source' )->text(), 'interwiki' ) .
+ Xml::label( $this->msg( 'import-interwiki-sourcewiki' )->text(), 'interwiki' ) .
"</td>
<td class='mw-input'>" .
Xml::openElement(
@@ -306,13 +323,63 @@ class SpecialImport extends SpecialPage {
)
);
- foreach ( $wgImportSources as $prefix ) {
- $selected = ( $this->interwiki === $prefix ) ? ' selected="selected"' : '';
- $out->addHTML( Xml::option( $prefix, $prefix, $selected ) );
+ $needSubprojectField = false;
+ foreach ( $importSources as $key => $value ) {
+ if ( is_int( $key ) ) {
+ $key = $value;
+ } elseif ( $value !== $key ) {
+ $needSubprojectField = true;
+ }
+
+ $attribs = array(
+ 'value' => $key,
+ );
+ if ( is_array( $value ) ) {
+ $attribs['data-subprojects'] = implode( ' ', $value );
+ }
+ if ( $this->interwiki === $key ) {
+ $attribs['selected'] = 'selected';
+ }
+ $out->addHTML( Html::element( 'option', $attribs, $key ) );
+ }
+
+ $out->addHTML(
+ Xml::closeElement( 'select' )
+ );
+
+ if ( $needSubprojectField ) {
+ $out->addHTML(
+ Xml::openElement(
+ 'select',
+ array( 'name' => 'subproject', 'id' => 'subproject' )
+ )
+ );
+
+ $subprojectsToAdd = array();
+ foreach ( $importSources as $key => $value ) {
+ if ( is_array( $value ) ) {
+ $subprojectsToAdd = array_merge( $subprojectsToAdd, $value );
+ }
+ }
+ $subprojectsToAdd = array_unique( $subprojectsToAdd );
+ sort( $subprojectsToAdd );
+ foreach ( $subprojectsToAdd as $subproject ) {
+ $out->addHTML( Xml::option( $subproject, $subproject, $this->subproject === $subproject ) );
+ }
+
+ $out->addHTML(
+ Xml::closeElement( 'select' )
+ );
}
$out->addHTML(
- Xml::closeElement( 'select' ) .
+ "</td>
+ </tr>
+ <tr>
+ <td class='mw-label'>" .
+ Xml::label( $this->msg( 'import-interwiki-sourcepage' )->text(), 'frompage' ) .
+ "</td>
+ <td class='mw-input'>" .
Xml::input( 'frompage', 50, $this->frompage, array( 'id' => 'frompage' ) ) .
"</td>
</tr>
@@ -411,11 +478,10 @@ class ImportReporter extends ContextSource {
private $mOriginalPageOutCallback = null;
private $mLogItemCount = 0;
-
/**
* @param WikiImporter $importer
- * @param $upload
- * @param $interwiki
+ * @param bool $upload
+ * @param string $interwiki
* @param string|bool $reason
*/
function __construct( $importer, $upload, $interwiki, $reason = false ) {
@@ -435,7 +501,9 @@ class ImportReporter extends ContextSource {
}
function reportNotice( $msg, array $params ) {
- $this->getOutput()->addHTML( Html::element( 'li', array(), $this->msg( $msg, $params )->text() ) );
+ $this->getOutput()->addHTML(
+ Html::element( 'li', array(), $this->msg( $msg, $params )->text() )
+ );
}
function reportLogItem( /* ... */ ) {
@@ -449,8 +517,8 @@ class ImportReporter extends ContextSource {
* @param Title $title
* @param Title $origTitle
* @param int $revisionCount
- * @param $successCount
- * @param $pageInfo
+ * @param int $successCount
+ * @param array $pageInfo
* @return void
*/
function reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo ) {
@@ -476,7 +544,8 @@ class ImportReporter extends ContextSource {
$detail = $this->msg( 'import-logentry-upload-detail' )->numParams(
$successCount )->inContentLanguage()->text();
if ( $this->reason ) {
- $detail .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $this->reason;
+ $detail .= $this->msg( 'colon-separator' )->inContentLanguage()->text()
+ . $this->reason;
}
$log->addEntry( 'upload', $title, $detail, array(), $this->getUser() );
} else {
@@ -485,7 +554,8 @@ class ImportReporter extends ContextSource {
$detail = $this->msg( 'import-logentry-interwiki-detail' )->numParams(
$successCount )->params( $interwiki )->inContentLanguage()->text();
if ( $this->reason ) {
- $detail .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $this->reason;
+ $detail .= $this->msg( 'colon-separator' )->inContentLanguage()->text()
+ . $this->reason;
}
$log->addEntry( 'interwiki', $title, $detail, array(), $this->getUser() );
}
@@ -493,13 +563,23 @@ class ImportReporter extends ContextSource {
$comment = $detail; // quick
$dbw = wfGetDB( DB_MASTER );
$latest = $title->getLatestRevID();
- $nullRevision = Revision::newNullRevision( $dbw, $title->getArticleID(), $comment, true );
+ $nullRevision = Revision::newNullRevision(
+ $dbw,
+ $title->getArticleID(),
+ $comment,
+ true,
+ $this->getUser()
+ );
+
if ( !is_null( $nullRevision ) ) {
$nullRevision->insertOn( $dbw );
$page = WikiPage::factory( $title );
# Update page record
$page->updateRevisionOn( $dbw, $nullRevision );
- wfRunHooks( 'NewRevisionFromEditComplete', array( $page, $nullRevision, $latest, $this->getUser() ) );
+ wfRunHooks(
+ 'NewRevisionFromEditComplete',
+ array( $page, $nullRevision, $latest, $this->getUser() )
+ );
}
} else {
$this->getOutput()->addHTML( "<li>" . Linker::linkKnown( $title ) . " " .
diff --git a/includes/specials/SpecialJavaScriptTest.php b/includes/specials/SpecialJavaScriptTest.php
index 7069d527..0efebb3e 100644
--- a/includes/specials/SpecialJavaScriptTest.php
+++ b/includes/specials/SpecialJavaScriptTest.php
@@ -25,13 +25,12 @@
* @ingroup SpecialPage
*/
class SpecialJavaScriptTest extends SpecialPage {
-
/**
- * @var $frameworks Array: Mapping of framework ids and their initilizer methods
+ * @var array Mapping of framework ids and their initilizer methods
* in this class. If a framework is requested but not in this array,
* the 'unknownframework' error is served.
*/
- static $frameworks = array(
+ private static $frameworks = array(
'qunit' => 'initQUnitTesting',
);
@@ -68,7 +67,7 @@ class SpecialJavaScriptTest extends SpecialPage {
$this->msg( "javascripttest-$framework-name" )->plain()
) );
$out->setSubtitle( $this->msg( 'javascripttest-backlink' )
- ->rawParams( Linker::linkKnown( $this->getTitle() ) ) );
+ ->rawParams( Linker::linkKnown( $this->getPageTitle() ) ) );
$this->{self::$frameworks[$framework]}();
} else {
// Framework not found, display error
@@ -97,7 +96,7 @@ class SpecialJavaScriptTest extends SpecialPage {
'li',
array(),
Linker::link(
- $this->getTitle( $framework ),
+ $this->getPageTitle( $framework ),
// Message: javascripttest-qunit-name
$this->msg( "javascripttest-$framework-name" )->escaped()
)
@@ -135,16 +134,15 @@ class SpecialJavaScriptTest extends SpecialPage {
* Initialize the page for QUnit.
*/
private function initQUnitTesting() {
- global $wgJavaScriptTestConfig;
-
$out = $this->getOutput();
+ $testConfig = $this->getConfig()->get( 'JavaScriptTestConfig' );
- $out->addModules( 'mediawiki.tests.qunit.testrunner' );
+ $out->addModules( 'test.mediawiki.qunit.testrunner' );
$qunitTestModules = $out->getResourceLoader()->getTestModuleNames( 'qunit' );
$out->addModules( $qunitTestModules );
$summary = $this->msg( 'javascripttest-qunit-intro' )
- ->params( $wgJavaScriptTestConfig['qunit']['documentation'] )
+ ->params( $testConfig['qunit']['documentation'] )
->parseAsBlock();
$header = $this->msg( 'javascripttest-qunit-heading' )->escaped();
$userDir = $this->getLanguage()->getDir();
@@ -170,7 +168,22 @@ HTML;
// $wgJavaScriptTestConfig in DefaultSettings.php
$out->addJsConfigVars(
'QUnitTestSwarmInjectJSPath',
- $wgJavaScriptTestConfig['qunit']['testswarm-injectjs']
+ $testConfig['qunit']['testswarm-injectjs']
+ );
+ }
+
+ /**
+ * Return an array of subpages beginning with $search that this special page will accept.
+ *
+ * @param string $search Prefix to search for
+ * @param int $limit Maximum number of results to return
+ * @return string[] Matching subpages
+ */
+ public function prefixSearchSubpages( $search, $limit = 10 ) {
+ return self::prefixSearchArray(
+ $search,
+ $limit,
+ array_keys( self::$frameworks )
);
}
diff --git a/includes/specials/SpecialLinkSearch.php b/includes/specials/SpecialLinkSearch.php
index 5b0c56e5..371469bb 100644
--- a/includes/specials/SpecialLinkSearch.php
+++ b/includes/specials/SpecialLinkSearch.php
@@ -27,6 +27,12 @@
* @ingroup SpecialPage
*/
class LinkSearchPage extends QueryPage {
+
+ /**
+ * @var PageLinkRenderer
+ */
+ protected $linkRenderer = null;
+
function setParams( $params ) {
$this->mQuery = $params['query'];
$this->mNs = $params['namespace'];
@@ -35,6 +41,36 @@ class LinkSearchPage extends QueryPage {
function __construct( $name = 'LinkSearch' ) {
parent::__construct( $name );
+
+ // Since we don't control the constructor parameters, we can't inject services that way.
+ // Instead, we initialize services in the execute() method, and allow them to be overridden
+ // using the setServices() method.
+ }
+
+ /**
+ * Initialize or override the PageLinkRenderer LinkSearchPage collaborates with.
+ * Useful mainly for testing.
+ *
+ * @todo query logic and rendering logic should be split and also injected
+ *
+ * @param PageLinkRenderer $linkRenderer
+ */
+ public function setPageLinkRenderer(
+ PageLinkRenderer $linkRenderer
+ ) {
+ $this->linkRenderer = $linkRenderer;
+ }
+
+ /**
+ * Initialize any services we'll need (unless it has already been provided via a setter).
+ * This allows for dependency injection even though we don't control object creation.
+ */
+ private function initServices() {
+ if ( !$this->linkRenderer ) {
+ $lang = $this->getContext()->getLanguage();
+ $titleFormatter = new MediaWikiTitleCodec( $lang, GenderCache::singleton() );
+ $this->linkRenderer = new MediaWikiPageLinkRenderer( $titleFormatter );
+ }
}
function isCacheable() {
@@ -42,7 +78,7 @@ class LinkSearchPage extends QueryPage {
}
function execute( $par ) {
- global $wgUrlProtocols, $wgMiserMode, $wgScript;
+ $this->initServices();
$this->setHeaders();
$this->outputHeader();
@@ -55,32 +91,26 @@ class LinkSearchPage extends QueryPage {
$namespace = $request->getIntorNull( 'namespace', null );
$protocols_list = array();
- foreach ( $wgUrlProtocols as $prot ) {
+ foreach ( $this->getConfig()->get( 'UrlProtocols' ) as $prot ) {
if ( $prot !== '//' ) {
$protocols_list[] = $prot;
}
}
$target2 = $target;
- $protocol = '';
- $pr_sl = strpos( $target2, '//' );
- $pr_cl = strpos( $target2, ':' );
- if ( $pr_sl ) {
- // For protocols with '//'
- $protocol = substr( $target2, 0, $pr_sl + 2 );
- $target2 = substr( $target2, $pr_sl + 2 );
- } elseif ( !$pr_sl && $pr_cl ) {
- // For protocols without '//' like 'mailto:'
- $protocol = substr( $target2, 0, $pr_cl + 1 );
- $target2 = substr( $target2, $pr_cl + 1 );
- } elseif ( $protocol == '' && $target2 != '' ) {
- // default
- $protocol = 'http://';
- }
- if ( $protocol != '' && !in_array( $protocol, $protocols_list ) ) {
- // unsupported protocol, show original search request
- $target2 = $target;
- $protocol = '';
+ // Get protocol, default is http://
+ $protocol = 'http://';
+ $bits = wfParseUrl( $target );
+ if ( isset( $bits['scheme'] ) && isset( $bits['delimiter'] ) ) {
+ $protocol = $bits['scheme'] . $bits['delimiter'];
+ // Make sure wfParseUrl() didn't make some well-intended correction in the
+ // protocol
+ if ( strcasecmp( $protocol, substr( $target, 0, strlen( $protocol ) ) ) === 0 ) {
+ $target2 = substr( $target, strlen( $protocol ) );
+ } else {
+ // If it did, let LinkFilter::makeLikeArray() handle this
+ $protocol = '';
+ }
}
$out->addWikiMsg(
@@ -90,9 +120,9 @@ class LinkSearchPage extends QueryPage {
);
$s = Html::openElement(
'form',
- array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => $wgScript )
+ array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => wfScript() )
) . "\n" .
- Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . "\n" .
+ Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) . "\n" .
Html::openElement( 'fieldset' ) . "\n" .
Html::element( 'legend', array(), $this->msg( 'linksearch' )->text() ) . "\n" .
Xml::inputLabel(
@@ -100,10 +130,14 @@ class LinkSearchPage extends QueryPage {
'target',
'target',
50,
- $target
+ $target,
+ array(
+ // URLs are always ltr
+ 'dir' => 'ltr',
+ )
) . "\n";
- if ( !$wgMiserMode ) {
+ if ( !$this->getConfig()->get( 'MiserMode' ) ) {
$s .= Html::namespaceSelector(
array(
'selected' => $namespace,
@@ -145,18 +179,26 @@ class LinkSearchPage extends QueryPage {
/**
* Return an appropriately formatted LIKE query and the clause
*
- * @param string $query
- * @param string $prot
+ * @param string $query Search pattern to search for
+ * @param string $prot Protocol, e.g. 'http://'
+ *
* @return array
*/
static function mungeQuery( $query, $prot ) {
$field = 'el_index';
- $rv = LinkFilter::makeLikeArray( $query, $prot );
+ $dbr = wfGetDB( DB_SLAVE );
+
+ if ( $query === '*' && $prot !== '' ) {
+ // Allow queries like 'ftp://*' to find all ftp links
+ $rv = array( $prot, $dbr->anyString() );
+ } else {
+ $rv = LinkFilter::makeLikeArray( $query, $prot );
+ }
+
if ( $rv === false ) {
// LinkFilter doesn't handle wildcard in IP, so we'll have to munge here.
$pattern = '/^(:?[0-9]{1,3}\.)+\*\s*$|^(:?[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]*\*\s*$/';
if ( preg_match( $pattern, $query ) ) {
- $dbr = wfGetDB( DB_SLAVE );
$rv = array( $prot . rtrim( $query, " \t*" ), $dbr->anyString() );
$field = 'el_to';
}
@@ -166,10 +208,9 @@ class LinkSearchPage extends QueryPage {
}
function linkParameters() {
- global $wgMiserMode;
$params = array();
$params['target'] = $this->mProt . $this->mQuery;
- if ( isset( $this->mNs ) && !$wgMiserMode ) {
+ if ( $this->mNs !== null && !$this->getConfig()->get( 'MiserMode' ) ) {
$params['namespace'] = $this->mNs;
}
@@ -177,7 +218,6 @@ class LinkSearchPage extends QueryPage {
}
function getQueryInfo() {
- global $wgMiserMode;
$dbr = wfGetDB( DB_SLAVE );
// strip everything past first wildcard, so that
// index-based-only lookup would be done
@@ -204,7 +244,7 @@ class LinkSearchPage extends QueryPage {
'options' => array( 'USE INDEX' => $clause )
);
- if ( isset( $this->mNs ) && !$wgMiserMode ) {
+ if ( $this->mNs !== null && !$this->getConfig()->get( 'MiserMode' ) ) {
$retval['conds']['page_namespace'] = $this->mNs;
}
@@ -217,9 +257,10 @@ class LinkSearchPage extends QueryPage {
* @return string
*/
function formatResult( $skin, $result ) {
- $title = Title::makeTitle( $result->namespace, $result->title );
+ $title = new TitleValue( (int)$result->namespace, $result->title );
+ $pageLink = $this->linkRenderer->renderHtmlLink( $title );
+
$url = $result->url;
- $pageLink = Linker::linkKnown( $title );
$urlLink = Linker::makeExternalLink( $url, $url );
return $this->msg( 'linksearch-line' )->rawParams( $urlLink, $pageLink )->escaped();
@@ -227,6 +268,9 @@ class LinkSearchPage extends QueryPage {
/**
* Override to check query validity.
+ *
+ * @param mixed $offset Numerical offset or false for no offset
+ * @param mixed $limit Numerical limit or false for no limit
*/
function doQuery( $offset = false, $limit = false ) {
list( $this->mMungedQuery, ) = LinkSearchPage::mungeQuery( $this->mQuery, $this->mProt );
diff --git a/includes/specials/SpecialListDuplicatedFiles.php b/includes/specials/SpecialListDuplicatedFiles.php
new file mode 100644
index 00000000..26672706
--- /dev/null
+++ b/includes/specials/SpecialListDuplicatedFiles.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * Implements Special:ListDuplicatedFiles
+ *
+ * Copyright © 2013 Brian Wolff
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Brian Wolff
+ */
+
+/**
+ * Special:ListDuplicatedFiles Lists all files where the current version is
+ * a duplicate of the current version of some other file.
+ * @ingroup SpecialPage
+ */
+class ListDuplicatedFilesPage extends QueryPage {
+ function __construct( $name = 'ListDuplicatedFiles' ) {
+ parent::__construct( $name );
+ }
+
+ function isExpensive() {
+ return true;
+ }
+
+ function isSyndicated() {
+ return false;
+ }
+
+ /**
+ * Get all the duplicates by grouping on sha1s.
+ *
+ * A cheaper (but less useful) version of this
+ * query would be to not care how many duplicates a
+ * particular file has, and do a self-join on image table.
+ * However this version should be no more expensive then
+ * Special:MostLinked, which seems to get handled fine
+ * with however we are doing cached special pages.
+ * @return array
+ */
+ function getQueryInfo() {
+ return array(
+ 'tables' => array( 'image' ),
+ 'fields' => array(
+ 'namespace' => NS_FILE,
+ 'title' => 'MIN(img_name)',
+ 'value' => 'count(*)'
+ ),
+ 'options' => array(
+ 'GROUP BY' => 'img_sha1',
+ 'HAVING' => 'count(*) > 1',
+ ),
+ );
+ }
+
+ /**
+ * Pre-fill the link cache
+ *
+ * @param DatabaseBase $db
+ * @param ResultWrapper $res
+ */
+ function preprocessResults( $db, $res ) {
+ if ( $res->numRows() > 0 ) {
+ $linkBatch = new LinkBatch();
+
+ foreach ( $res as $row ) {
+ $linkBatch->add( $row->namespace, $row->title );
+ }
+
+ $res->seek( 0 );
+ $linkBatch->execute();
+ }
+ }
+
+
+ /**
+ * @param Skin $skin
+ * @param object $result Result row
+ * @return string
+ */
+ function formatResult( $skin, $result ) {
+ // Future version might include a list of the first 5 duplicates
+ // perhaps separated by an "↔".
+ $image1 = Title::makeTitle( $result->namespace, $result->title );
+ $dupeSearch = SpecialPage::getTitleFor( 'FileDuplicateSearch', $image1->getDBKey() );
+
+ $msg = $this->msg( 'listduplicatedfiles-entry' )
+ ->params( $image1->getText() )
+ ->numParams( $result->value - 1 )
+ ->params( $dupeSearch->getPrefixedDBKey() );
+
+ return $msg->parse();
+ }
+
+ protected function getGroupName() {
+ return 'media';
+ }
+}
diff --git a/includes/specials/SpecialListfiles.php b/includes/specials/SpecialListfiles.php
index dff1cf70..04a83c8f 100644
--- a/includes/specials/SpecialListfiles.php
+++ b/includes/specials/SpecialListfiles.php
@@ -33,6 +33,7 @@ class SpecialListFiles extends IncludableSpecialPage {
if ( $this->including() ) {
$userName = $par;
$search = '';
+ $showAll = false;
} else {
$userName = $this->getRequest()->getText( 'user', $par );
$search = $this->getRequest()->getText( 'ilsearch', '' );
@@ -47,15 +48,13 @@ class SpecialListFiles extends IncludableSpecialPage {
$showAll
);
+ $out = $this->getOutput();
if ( $this->including() ) {
- $html = $pager->getBody();
+ $out->addParserOutputContent( $pager->getBodyOutput() );
} else {
- $form = $pager->getForm();
- $body = $pager->getBody();
- $nav = $pager->getNavigationBar();
- $html = "$form<br />\n$body<br />\n$nav";
+ $out->addHTML( $pager->getForm() );
+ $out->addParserOutputContent( $pager->getFullOutput() );
}
- $this->getOutput()->addHTML( $html );
}
protected function getGroupName() {
@@ -67,20 +66,24 @@ class SpecialListFiles extends IncludableSpecialPage {
* @ingroup SpecialPage Pager
*/
class ImageListPager extends TablePager {
- var $mFieldNames = null;
+ protected $mFieldNames = null;
+
// Subclasses should override buildQueryConds instead of using $mQueryConds variable.
- var $mQueryConds = array();
- var $mUserName = null;
- var $mSearch = '';
- var $mIncluding = false;
- var $mShowAll = false;
- var $mTableName = 'image';
+ protected $mQueryConds = array();
+
+ protected $mUserName = null;
+
+ protected $mSearch = '';
+
+ protected $mIncluding = false;
+
+ protected $mShowAll = false;
+
+ protected $mTableName = 'image';
function __construct( IContextSource $context, $userName = null, $search = '',
$including = false, $showAll = false
) {
- global $wgMiserMode;
-
$this->mIncluding = $including;
$this->mShowAll = $showAll;
@@ -91,7 +94,7 @@ class ImageListPager extends TablePager {
}
}
- if ( $search !== '' && !$wgMiserMode ) {
+ if ( $search !== '' && !$this->getConfig()->get( 'MiserMode' ) ) {
$this->mSearch = $search;
$nt = Title::newFromURL( $this->mSearch );
@@ -105,12 +108,12 @@ class ImageListPager extends TablePager {
if ( !$including ) {
if ( $context->getRequest()->getText( 'sort', 'img_date' ) == 'img_date' ) {
- $this->mDefaultDirection = true;
+ $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
} else {
- $this->mDefaultDirection = false;
+ $this->mDefaultDirection = IndexPager::DIR_ASCENDING;
}
} else {
- $this->mDefaultDirection = true;
+ $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
}
parent::__construct( $context );
@@ -120,7 +123,7 @@ class ImageListPager extends TablePager {
* Build the where clause of the query.
*
* Replaces the older mQueryConds member variable.
- * @param $table String Either "image" or "oldimage"
+ * @param string $table Either "image" or "oldimage"
* @return array The query conditions.
*/
protected function buildQueryConds( $table ) {
@@ -128,7 +131,7 @@ class ImageListPager extends TablePager {
$conds = array();
if ( !is_null( $this->mUserName ) ) {
- $conds[ $prefix . '_user_text' ] = $this->mUserName;
+ $conds[$prefix . '_user_text'] = $this->mUserName;
}
if ( $this->mSearch !== '' ) {
@@ -153,20 +156,24 @@ class ImageListPager extends TablePager {
}
/**
- * @return Array
+ * @return array
*/
function getFieldNames() {
if ( !$this->mFieldNames ) {
- global $wgMiserMode;
$this->mFieldNames = array(
'img_timestamp' => $this->msg( 'listfiles_date' )->text(),
'img_name' => $this->msg( 'listfiles_name' )->text(),
'thumb' => $this->msg( 'listfiles_thumb' )->text(),
'img_size' => $this->msg( 'listfiles_size' )->text(),
- 'img_user_text' => $this->msg( 'listfiles_user' )->text(),
- 'img_description' => $this->msg( 'listfiles_description' )->text(),
);
- if ( !$wgMiserMode && !$this->mShowAll ) {
+ if ( is_null( $this->mUserName ) ) {
+ // Do not show username if filtering by username
+ $this->mFieldNames['img_user_text'] = $this->msg( 'listfiles_user' )->text();
+ }
+ // img_description down here, in order so that its still after the username field.
+ $this->mFieldNames['img_description'] = $this->msg( 'listfiles_description' )->text();
+
+ if ( !$this->getConfig()->get( 'MiserMode' ) && !$this->mShowAll ) {
$this->mFieldNames['count'] = $this->msg( 'listfiles_count' )->text();
}
if ( $this->mShowAll ) {
@@ -178,7 +185,6 @@ class ImageListPager extends TablePager {
}
function isFieldSortable( $field ) {
- global $wgMiserMode;
if ( $this->mIncluding ) {
return false;
}
@@ -190,14 +196,14 @@ class ImageListPager extends TablePager {
* In particular that means we cannot sort by timestamp when not filtering
* by user and including old images in the results. Which is sad.
*/
- if ( $wgMiserMode && !is_null( $this->mUserName ) ) {
+ if ( $this->getConfig()->get( 'MiserMode' ) && !is_null( $this->mUserName ) ) {
// If we're sorting by user, the index only supports sorting by time.
if ( $field === 'img_timestamp' ) {
return true;
} else {
return false;
}
- } elseif ( $wgMiserMode && $this->mShowAll /* && mUserName === null */ ) {
+ } elseif ( $this->getConfig()->get( 'MiserMode' ) && $this->mShowAll /* && mUserName === null */ ) {
// no oi_timestamp index, so only alphabetical sorting in this case.
if ( $field === 'img_name' ) {
return true;
@@ -214,6 +220,7 @@ class ImageListPager extends TablePager {
// for two different tables, without reimplementing
// the pager class.
$qi = $this->getQueryInfoReal( $this->mTableName );
+
return $qi;
}
@@ -224,7 +231,7 @@ class ImageListPager extends TablePager {
*
* This is a bit hacky.
*
- * @param $table String Either 'image' or 'oldimage'
+ * @param string $table Either 'image' or 'oldimage'
* @return array Query info
*/
protected function getQueryInfoReal( $table ) {
@@ -240,7 +247,7 @@ class ImageListPager extends TablePager {
}
$field = $prefix . substr( $field, 3 ) . ' AS ' . $field;
}
- $fields[array_search('top', $fields)] = "'no' AS top";
+ $fields[array_search( 'top', $fields )] = "'no' AS top";
} else {
if ( $this->mShowAll ) {
$fields[array_search( 'top', $fields )] = "'yes' AS top";
@@ -289,11 +296,16 @@ class ImageListPager extends TablePager {
* @note $asc is named $descending in IndexPager base class. However
* it is true when the order is ascending, and false when the order
* is descending, so I renamed it to $asc here.
+ * @param int $offset
+ * @param int $limit
+ * @param bool $asc
+ * @return array
*/
function reallyDoQuery( $offset, $limit, $asc ) {
$prevTableName = $this->mTableName;
$this->mTableName = 'image';
- list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo( $offset, $limit, $asc );
+ list( $tables, $fields, $conds, $fname, $options, $join_conds ) =
+ $this->buildQueryInfo( $offset, $limit, $asc );
$imageRes = $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds );
$this->mTableName = $prevTableName;
@@ -310,7 +322,8 @@ class ImageListPager extends TablePager {
}
$this->mIndexField = 'oi_' . substr( $this->mIndexField, 4 );
- list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo( $offset, $limit, $asc );
+ list( $tables, $fields, $conds, $fname, $options, $join_conds ) =
+ $this->buildQueryInfo( $offset, $limit, $asc );
$oldimageRes = $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds );
$this->mTableName = $prevTableName;
@@ -324,10 +337,10 @@ class ImageListPager extends TablePager {
*
* Note: This will throw away some results
*
- * @param $res1 ResultWrapper
- * @param $res2 ResultWrapper
- * @param $limit int
- * @param $ascending boolean See note about $asc in $this->reallyDoQuery
+ * @param ResultWrapper $res1
+ * @param ResultWrapper $res2
+ * @param int $limit
+ * @param bool $ascending See note about $asc in $this->reallyDoQuery
* @return FakeResultWrapper $res1 and $res2 combined
*/
protected function combineResult( $res1, $res2, $limit, $ascending ) {
@@ -337,7 +350,7 @@ class ImageListPager extends TablePager {
$topRes2 = $res2->next();
$resultArray = array();
for ( $i = 0; $i < $limit && $topRes1 && $topRes2; $i++ ) {
- if ( strcmp( $topRes1->{ $this->mIndexField }, $topRes2->{ $this->mIndexField } ) > 0 ) {
+ if ( strcmp( $topRes1->{$this->mIndexField}, $topRes2->{$this->mIndexField} ) > 0 ) {
if ( !$ascending ) {
$resultArray[] = $topRes1;
$topRes1 = $res1->next();
@@ -355,20 +368,26 @@ class ImageListPager extends TablePager {
}
}
}
+
+ // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect
for ( ; $i < $limit && $topRes1; $i++ ) {
+ // @codingStandardsIgnoreEnd
$resultArray[] = $topRes1;
$topRes1 = $res1->next();
}
+
+ // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect
for ( ; $i < $limit && $topRes2; $i++ ) {
+ // @codingStandardsIgnoreEnd
$resultArray[] = $topRes2;
$topRes2 = $res2->next();
}
+
return new FakeResultWrapper( $resultArray );
}
function getDefaultSort() {
- global $wgMiserMode;
- if ( $this->mShowAll && $wgMiserMode && is_null( $this->mUserName ) ) {
+ if ( $this->mShowAll && $this->getConfig()->get( 'MiserMode' ) && is_null( $this->mUserName ) ) {
// Unfortunately no index on oi_timestamp.
return 'img_name';
} else {
@@ -386,6 +405,20 @@ class ImageListPager extends TablePager {
UserCache::singleton()->doQuery( $userIds, array( 'userpage' ), __METHOD__ );
}
+ /**
+ * @param string $field
+ * @param string $value
+ * @return Message|string|int The return type depends on the value of $field:
+ * - thumb: string
+ * - img_timestamp: string
+ * - img_name: string
+ * - img_user_text: string
+ * - img_size: string
+ * - img_description: string
+ * - count: int
+ * - top: Message
+ * @throws MWException
+ */
function formatValue( $field, $value ) {
switch ( $field ) {
case 'thumb':
@@ -394,6 +427,7 @@ class ImageListPager extends TablePager {
// If statement for paranoia
if ( $file ) {
$thumb = $file->transform( array( 'width' => 180, 'height' => 360 ) );
+
return $thumb->toHtml( array( 'desc-link' => true ) );
} else {
return htmlspecialchars( $value );
@@ -420,6 +454,19 @@ class ImageListPager extends TablePager {
);
$download = $this->msg( 'parentheses' )->rawParams( $download )->escaped();
+ // Add delete links if allowed
+ // From https://github.com/Wikia/app/pull/3859
+ if ( $filePage->userCan( 'delete', $this->getUser() ) ) {
+ $deleteMsg = $this->msg( 'listfiles-delete' )->escaped();
+
+ $delete = Linker::linkKnown(
+ $filePage, $deleteMsg, array(), array( 'action' => 'delete' )
+ );
+ $delete = $this->msg( 'parentheses' )->rawParams( $delete )->escaped();
+
+ return "$link $download $delete";
+ }
+
return "$link $download";
} else {
return htmlspecialchars( $value );
@@ -445,58 +492,80 @@ class ImageListPager extends TablePager {
case 'top':
// Messages: listfiles-latestversion-yes, listfiles-latestversion-no
return $this->msg( 'listfiles-latestversion-' . $value );
+ default:
+ throw new MWException( "Unknown field '$field'" );
}
}
function getForm() {
- global $wgScript, $wgMiserMode;
- $inputForm = array();
- $inputForm['table_pager_limit_label'] = $this->getLimitSelect( array( 'tabindex' => 1 ) );
- if ( !$wgMiserMode ) {
- $inputForm['listfiles_search_for'] = Html::input(
- 'ilsearch',
- $this->mSearch,
- 'text',
- array(
- 'size' => '40',
- 'maxlength' => '255',
- 'id' => 'mw-ilsearch',
- 'tabindex' => 2,
- )
+ $fields = array();
+ $fields['limit'] = array(
+ 'type' => 'select',
+ 'name' => 'limit',
+ 'label-message' => 'table_pager_limit_label',
+ 'options' => $this->getLimitSelectList(),
+ 'default' => $this->mLimit,
+ );
+
+ if ( !$this->getConfig()->get( 'MiserMode' ) ) {
+ $fields['ilsearch'] = array(
+ 'type' => 'text',
+ 'name' => 'ilsearch',
+ 'id' => 'mw-ilsearch',
+ 'label-message' => 'listfiles_search_for',
+ 'default' => $this->mSearch,
+ 'size' => '40',
+ 'maxlength' => '255',
);
}
- $inputForm['username'] = Html::input( 'user', $this->mUserName, 'text', array(
+
+ $fields['user'] = array(
+ 'type' => 'text',
+ 'name' => 'user',
+ 'id' => 'mw-listfiles-user',
+ 'label-message' => 'username',
+ 'default' => $this->mUserName,
'size' => '40',
'maxlength' => '255',
- 'id' => 'mw-listfiles-user',
- 'tabindex' => 3,
- ) );
-
- $inputForm['listfiles-show-all'] = Html::input( 'ilshowall', 1, 'checkbox', array(
- 'checked' => $this->mShowAll,
- 'tabindex' => 4,
- ) );
- return Html::openElement( 'form',
- array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listfiles-form' )
- ) .
- Xml::fieldset( $this->msg( 'listfiles' )->text() ) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
- Xml::buildForm( $inputForm, 'table_pager_limit_submit', array( 'tabindex' => 5 ) ) .
- $this->getHiddenFields( array( 'limit', 'ilsearch', 'user', 'title', 'ilshowall' ) ) .
- Html::closeElement( 'fieldset' ) .
- Html::closeElement( 'form' ) . "\n";
+ );
+
+ $fields['ilshowall'] = array(
+ 'type' => 'check',
+ 'name' => 'ilshowall',
+ 'id' => 'mw-listfiles-show-all',
+ 'label-message' => 'listfiles-show-all',
+ 'default' => $this->mShowAll,
+ );
+
+ $query = $this->getRequest()->getQueryValues();
+ unset( $query['title'] );
+ unset( $query['limit'] );
+ unset( $query['ilsearch'] );
+ unset( $query['user'] );
+
+ $form = new HTMLForm( $fields, $this->getContext() );
+
+ $form->setMethod( 'get' );
+ $form->setTitle( $this->getTitle() );
+ $form->setId( 'mw-listfiles-form' );
+ $form->setWrapperLegendMsg( 'listfiles' );
+ $form->setSubmitTextMsg( 'table_pager_limit_submit' );
+ $form->addHiddenFields( $query );
+
+ $form->prepareForm();
+ $form->displayForm( '' );
}
function getTableClass() {
- return 'listfiles ' . parent::getTableClass();
+ return parent::getTableClass() . ' listfiles';
}
function getNavClass() {
- return 'listfiles_nav ' . parent::getNavClass();
+ return parent::getNavClass() . ' listfiles_nav';
}
function getSortHeaderClass() {
- return 'listfiles_sort ' . parent::getSortHeaderClass();
+ return parent::getSortHeaderClass() . ' listfiles_sort';
}
function getPagingQueries() {
@@ -504,7 +573,9 @@ class ImageListPager extends TablePager {
if ( !is_null( $this->mUserName ) ) {
# Append the username to the query string
foreach ( $queries as &$query ) {
- $query['user'] = $this->mUserName;
+ if ( $query !== false ) {
+ $query['user'] = $this->mUserName;
+ }
}
}
diff --git a/includes/specials/SpecialListgrouprights.php b/includes/specials/SpecialListgrouprights.php
index 82a4f70f..5bae28f0 100644
--- a/includes/specials/SpecialListgrouprights.php
+++ b/includes/specials/SpecialListgrouprights.php
@@ -29,21 +29,15 @@
* @author Petr Kadlec <mormegil@centrum.cz>
*/
class SpecialListGroupRights extends SpecialPage {
- /**
- * Constructor
- */
function __construct() {
parent::__construct( 'Listgrouprights' );
}
/**
* Show the special page
+ * @param string|null $par
*/
public function execute( $par ) {
- global $wgImplicitGroups;
- global $wgGroupPermissions, $wgRevokePermissions, $wgAddGroups, $wgRemoveGroups;
- global $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
-
$this->setHeaders();
$this->outputHeader();
@@ -60,19 +54,26 @@ class SpecialListGroupRights extends SpecialPage {
'</tr>'
);
+ $config = $this->getConfig();
+ $groupPermissions = $config->get( 'GroupPermissions' );
+ $revokePermissions = $config->get( 'RevokePermissions' );
+ $addGroups = $config->get( 'AddGroups' );
+ $removeGroups = $config->get( 'RemoveGroups' );
+ $groupsAddToSelf = $config->get( 'GroupsAddToSelf' );
+ $groupsRemoveFromSelf = $config->get( 'GroupsRemoveFromSelf' );
$allGroups = array_unique( array_merge(
- array_keys( $wgGroupPermissions ),
- array_keys( $wgRevokePermissions ),
- array_keys( $wgAddGroups ),
- array_keys( $wgRemoveGroups ),
- array_keys( $wgGroupsAddToSelf ),
- array_keys( $wgGroupsRemoveFromSelf )
+ array_keys( $groupPermissions ),
+ array_keys( $revokePermissions ),
+ array_keys( $addGroups ),
+ array_keys( $removeGroups ),
+ array_keys( $groupsAddToSelf ),
+ array_keys( $groupsRemoveFromSelf )
) );
asort( $allGroups );
foreach ( $allGroups as $group ) {
- $permissions = isset( $wgGroupPermissions[$group] )
- ? $wgGroupPermissions[$group]
+ $permissions = isset( $groupPermissions[$group] )
+ ? $groupPermissions[$group]
: array();
$groupname = ( $group == '*' ) // Replace * with a more descriptive groupname
? 'all'
@@ -102,7 +103,7 @@ class SpecialListGroupRights extends SpecialPage {
SpecialPage::getTitleFor( 'Listusers' ),
$this->msg( 'listgrouprights-members' )->escaped()
);
- } elseif ( !in_array( $group, $wgImplicitGroups ) ) {
+ } elseif ( !in_array( $group, $config->get( 'ImplicitGroups' ) ) ) {
$grouplink = '<br />' . Linker::linkKnown(
SpecialPage::getTitleFor( 'Listusers' ),
$this->msg( 'listgrouprights-members' )->escaped(),
@@ -114,15 +115,16 @@ class SpecialListGroupRights extends SpecialPage {
$grouplink = '';
}
- $revoke = isset( $wgRevokePermissions[$group] ) ? $wgRevokePermissions[$group] : array();
- $addgroups = isset( $wgAddGroups[$group] ) ? $wgAddGroups[$group] : array();
- $removegroups = isset( $wgRemoveGroups[$group] ) ? $wgRemoveGroups[$group] : array();
- $addgroupsSelf = isset( $wgGroupsAddToSelf[$group] ) ? $wgGroupsAddToSelf[$group] : array();
- $removegroupsSelf = isset( $wgGroupsRemoveFromSelf[$group] ) ? $wgGroupsRemoveFromSelf[$group] : array();
+ $revoke = isset( $revokePermissions[$group] ) ? $revokePermissions[$group] : array();
+ $addgroups = isset( $addGroups[$group] ) ? $addGroups[$group] : array();
+ $removegroups = isset( $removeGroups[$group] ) ? $removeGroups[$group] : array();
+ $addgroupsSelf = isset( $groupsAddToSelf[$group] ) ? $groupsAddToSelf[$group] : array();
+ $removegroupsSelf = isset( $groupsRemoveFromSelf[$group] )
+ ? $groupsRemoveFromSelf[$group]
+ : array();
$id = $group == '*' ? false : Sanitizer::escapeId( $group );
- $out->addHTML( Html::rawElement( 'tr', array( 'id' => $id ),
- "
+ $out->addHTML( Html::rawElement( 'tr', array( 'id' => $id ), "
<td>$grouppage$grouplink</td>
<td>" .
$this->formatPermissions( $permissions, $revoke, $addgroups, $removegroups,
@@ -132,17 +134,100 @@ class SpecialListGroupRights extends SpecialPage {
) );
}
$out->addHTML( Xml::closeElement( 'table' ) );
+ $this->outputNamespaceProtectionInfo();
+ }
+
+ private function outputNamespaceProtectionInfo() {
+ global $wgParser, $wgContLang;
+ $out = $this->getOutput();
+ $namespaceProtection = $this->getConfig()->get( 'NamespaceProtection' );
+
+ if ( count( $namespaceProtection ) == 0 ) {
+ return;
+ }
+
+ $header = $this->msg( 'listgrouprights-namespaceprotection-header' )->parse();
+ $out->addHTML(
+ Html::rawElement( 'h2', array(), Html::element( 'span', array(
+ 'class' => 'mw-headline',
+ 'id' => $wgParser->guessSectionNameFromWikiText( $header )
+ ), $header ) ) .
+ Xml::openElement( 'table', array( 'class' => 'wikitable' ) ) .
+ Html::element(
+ 'th',
+ array(),
+ $this->msg( 'listgrouprights-namespaceprotection-namespace' )->text()
+ ) .
+ Html::element(
+ 'th',
+ array(),
+ $this->msg( 'listgrouprights-namespaceprotection-restrictedto' )->text()
+ )
+ );
+
+ ksort( $namespaceProtection );
+ foreach ( $namespaceProtection as $namespace => $rights ) {
+ if ( !in_array( $namespace, MWNamespace::getValidNamespaces() ) ) {
+ continue;
+ }
+
+ if ( $namespace == NS_MAIN ) {
+ $namespaceText = $this->msg( 'blanknamespace' )->text();
+ } else {
+ $namespaceText = $wgContLang->convertNamespace( $namespace );
+ }
+
+ $out->addHTML(
+ Xml::openElement( 'tr' ) .
+ Html::rawElement(
+ 'td',
+ array(),
+ Linker::link(
+ SpecialPage::getTitleFor( 'Allpages' ),
+ $namespaceText,
+ array(),
+ array( 'namespace' => $namespace )
+ )
+ ) .
+ Xml::openElement( 'td' ) . Xml::openElement( 'ul' )
+ );
+
+ if ( !is_array( $rights ) ) {
+ $rights = array( $rights );
+ }
+
+ foreach ( $rights as $right ) {
+ $out->addHTML(
+ Html::rawElement( 'li', array(), $this->msg(
+ 'listgrouprights-right-display',
+ User::getRightDescription( $right ),
+ Html::element(
+ 'span',
+ array( 'class' => 'mw-listgrouprights-right-name' ),
+ $right
+ )
+ )->parse() )
+ );
+ }
+
+ $out->addHTML(
+ Xml::closeElement( 'ul' ) .
+ Xml::closeElement( 'td' ) .
+ Xml::closeElement( 'tr' )
+ );
+ }
+ $out->addHTML( Xml::closeElement( 'table' ) );
}
/**
* Create a user-readable list of permissions from the given array.
*
- * @param array $permissions of permission => bool (from $wgGroupPermissions items)
- * @param array $revoke of permission => bool (from $wgRevokePermissions items)
- * @param array $add of groups this group is allowed to add or true
- * @param array $remove of groups this group is allowed to remove or true
- * @param array $addSelf of groups this group is allowed to add to self or true
- * @param array $removeSelf of group this group is allowed to remove from self or true
+ * @param array $permissions Array of permission => bool (from $wgGroupPermissions items)
+ * @param array $revoke Array of permission => bool (from $wgRevokePermissions items)
+ * @param array $add Array of groups this group is allowed to add or true
+ * @param array $remove Array of groups this group is allowed to remove or true
+ * @param array $addSelf Array of groups this group is allowed to add to self or true
+ * @param array $removeSelf Array of group this group is allowed to remove from self or true
* @return string List of all granted permissions, separated by comma separator
*/
private function formatPermissions( $permissions, $revoke, $add, $remove, $addSelf, $removeSelf ) {
@@ -170,45 +255,54 @@ class SpecialListGroupRights extends SpecialPage {
sort( $r );
$lang = $this->getLanguage();
+ $allGroups = User::getAllGroups();
if ( $add === true ) {
$r[] = $this->msg( 'listgrouprights-addgroup-all' )->escaped();
- } elseif ( is_array( $add ) && count( $add ) ) {
- $add = array_values( array_unique( $add ) );
- $r[] = $this->msg( 'listgrouprights-addgroup',
- $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $add ) ),
- count( $add )
- )->parse();
+ } elseif ( is_array( $add ) ) {
+ $add = array_intersect( array_values( array_unique( $add ) ), $allGroups );
+ if ( count( $add ) ) {
+ $r[] = $this->msg( 'listgrouprights-addgroup',
+ $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $add ) ),
+ count( $add )
+ )->parse();
+ }
}
if ( $remove === true ) {
$r[] = $this->msg( 'listgrouprights-removegroup-all' )->escaped();
- } elseif ( is_array( $remove ) && count( $remove ) ) {
- $remove = array_values( array_unique( $remove ) );
- $r[] = $this->msg( 'listgrouprights-removegroup',
- $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $remove ) ),
- count( $remove )
- )->parse();
+ } elseif ( is_array( $remove ) ) {
+ $remove = array_intersect( array_values( array_unique( $remove ) ), $allGroups );
+ if ( count( $remove ) ) {
+ $r[] = $this->msg( 'listgrouprights-removegroup',
+ $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $remove ) ),
+ count( $remove )
+ )->parse();
+ }
}
if ( $addSelf === true ) {
$r[] = $this->msg( 'listgrouprights-addgroup-self-all' )->escaped();
- } elseif ( is_array( $addSelf ) && count( $addSelf ) ) {
- $addSelf = array_values( array_unique( $addSelf ) );
- $r[] = $this->msg( 'listgrouprights-addgroup-self',
- $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $addSelf ) ),
- count( $addSelf )
- )->parse();
+ } elseif ( is_array( $addSelf ) ) {
+ $addSelf = array_intersect( array_values( array_unique( $addSelf ) ), $allGroups );
+ if ( count( $addSelf ) ) {
+ $r[] = $this->msg( 'listgrouprights-addgroup-self',
+ $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $addSelf ) ),
+ count( $addSelf )
+ )->parse();
+ }
}
if ( $removeSelf === true ) {
$r[] = $this->msg( 'listgrouprights-removegroup-self-all' )->parse();
- } elseif ( is_array( $removeSelf ) && count( $removeSelf ) ) {
- $removeSelf = array_values( array_unique( $removeSelf ) );
- $r[] = $this->msg( 'listgrouprights-removegroup-self',
- $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $removeSelf ) ),
- count( $removeSelf )
- )->parse();
+ } elseif ( is_array( $removeSelf ) ) {
+ $removeSelf = array_intersect( array_values( array_unique( $removeSelf ) ), $allGroups );
+ if ( count( $removeSelf ) ) {
+ $r[] = $this->msg( 'listgrouprights-removegroup-self',
+ $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $removeSelf ) ),
+ count( $removeSelf )
+ )->parse();
+ }
}
if ( empty( $r ) ) {
diff --git a/includes/specials/SpecialListredirects.php b/includes/specials/SpecialListredirects.php
index 2c8792ff..de05be41 100644
--- a/includes/specials/SpecialListredirects.php
+++ b/includes/specials/SpecialListredirects.php
@@ -76,20 +76,19 @@ class ListredirectsPage extends QueryPage {
* @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {
- $batch = new LinkBatch;
+ if ( !$res->numRows() ) {
+ return;
+ }
+ $batch = new LinkBatch;
foreach ( $res as $row ) {
$batch->add( $row->namespace, $row->title );
$batch->addObj( $this->getRedirectTarget( $row ) );
}
-
$batch->execute();
// Back to start for display
- if ( $res->numRows() > 0 ) {
- // If there are no rows we get an error seeking.
- $db->dataSeek( $res, 0 );
- }
+ $res->seek( 0 );
}
protected function getRedirectTarget( $row ) {
diff --git a/includes/specials/SpecialListusers.php b/includes/specials/SpecialListusers.php
index 8cd9173c..dad9074d 100644
--- a/includes/specials/SpecialListusers.php
+++ b/includes/specials/SpecialListusers.php
@@ -35,9 +35,9 @@
class UsersPager extends AlphabeticPager {
/**
- * @param $context IContextSource
+ * @param IContextSource $context
* @param array $par (Default null)
- * @param $including boolean Whether this page is being transcluded in
+ * @param bool $including Whether this page is being transcluded in
* another page
*/
function __construct( IContextSource $context = null, $par = null, $including = null ) {
@@ -69,7 +69,9 @@ class UsersPager extends AlphabeticPager {
$this->editsOnly = $request->getBool( 'editsOnly' );
$this->creationSort = $request->getBool( 'creationSort' );
$this->including = $including;
- $this->mDefaultDirection = $request->getBool( 'desc' );
+ $this->mDefaultDirection = $request->getBool( 'desc' )
+ ? IndexPager::DIR_DESCENDING
+ : IndexPager::DIR_ASCENDING;
$this->requestedUser = '';
@@ -92,7 +94,7 @@ class UsersPager extends AlphabeticPager {
}
/**
- * @return Array
+ * @return array
*/
function getQueryInfo() {
$dbr = wfGetDB( DB_SLAVE );
@@ -154,8 +156,8 @@ class UsersPager extends AlphabeticPager {
}
/**
- * @param $row Object
- * @return String
+ * @param stdClass $row
+ * @return string
*/
function formatRow( $row ) {
if ( $row->user_id == 0 ) { #Bug 16487
@@ -191,12 +193,9 @@ class UsersPager extends AlphabeticPager {
}
$edits = '';
- global $wgEdititis;
- if ( !$this->including && $wgEdititis ) {
- // @fixme i18n issue: Hardcoded square brackets.
- $edits = ' [' .
- $this->msg( 'usereditcount' )->numParams( $row->edits )->escaped() .
- ']';
+ if ( !$this->including && $this->getConfig()->get( 'Edititis' ) ) {
+ $count = $this->msg( 'usereditcount' )->numParams( $row->edits )->escaped();
+ $edits = $this->msg( 'word-separator' )->escaped() . $this->msg( 'brackets', $count )->escaped();
}
$created = '';
@@ -232,14 +231,12 @@ class UsersPager extends AlphabeticPager {
* @return string
*/
function getPageHeader() {
- global $wgScript;
-
list( $self ) = explode( '/', $this->getTitle()->getPrefixedDBkey() );
# Form tag
$out = Xml::openElement(
'form',
- array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listusers-form' )
+ array( 'method' => 'get', 'action' => wfScript(), 'id' => 'mw-listusers-form' )
) .
Xml::fieldset( $this->msg( 'listusers' )->text() ) .
Html::hidden( 'title', $self );
@@ -333,7 +330,7 @@ class UsersPager extends AlphabeticPager {
/**
* Get a list of groups the specified user belongs to
*
- * @param $uid Integer: user id
+ * @param int $uid User id
* @return array
*/
protected static function getGroups( $uid ) {
@@ -346,7 +343,7 @@ class UsersPager extends AlphabeticPager {
/**
* Format a link to a group description page
*
- * @param string $group group name
+ * @param string $group Group name
* @param string $username Username
* @return string
*/
@@ -399,7 +396,41 @@ class SpecialListUsers extends IncludableSpecialPage {
$this->getOutput()->addHTML( $s );
}
+ /**
+ * Return an array of subpages beginning with $search that this special page will accept.
+ *
+ * @param string $search Prefix to search for
+ * @param int $limit Maximum number of results to return
+ * @return string[] Matching subpages
+ */
+ public function prefixSearchSubpages( $search, $limit = 10 ) {
+ $subpages = User::getAllGroups();
+ return self::prefixSearchArray( $search, $limit, $subpages );
+ }
+
protected function getGroupName() {
return 'users';
}
}
+
+/**
+ * Redirect page: Special:ListAdmins --> Special:ListUsers/sysop.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialListAdmins extends SpecialRedirectToSpecial {
+ function __construct() {
+ parent::__construct( 'Listadmins', 'Listusers', 'sysop' );
+ }
+}
+
+/**
+ * Redirect page: Special:ListBots --> Special:ListUsers/bot.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialListBots extends SpecialRedirectToSpecial {
+ function __construct() {
+ parent::__construct( 'Listbots', 'Listusers', 'bot' );
+ }
+}
diff --git a/includes/specials/SpecialLockdb.php b/includes/specials/SpecialLockdb.php
index 95ef9510..1c1f1250 100644
--- a/includes/specials/SpecialLockdb.php
+++ b/includes/specials/SpecialLockdb.php
@@ -27,7 +27,7 @@
* @ingroup SpecialPage
*/
class SpecialLockdb extends FormSpecialPage {
- var $reason = '';
+ protected $reason = '';
public function __construct() {
parent::__construct( 'Lockdb', 'siteadmin' );
@@ -38,11 +38,9 @@ class SpecialLockdb extends FormSpecialPage {
}
public function checkExecutePermissions( User $user ) {
- global $wgReadOnlyFile;
-
parent::checkExecutePermissions( $user );
# If the lock file isn't writable, we can do sweet bugger all
- if ( !is_writable( dirname( $wgReadOnlyFile ) ) ) {
+ if ( !is_writable( dirname( $this->getConfig()->get( 'ReadOnlyFile' ) ) ) ) {
throw new ErrorPageError( 'lockdb', 'lockfilenotwritable' );
}
}
@@ -69,14 +67,14 @@ class SpecialLockdb extends FormSpecialPage {
}
public function onSubmit( array $data ) {
- global $wgContLang, $wgReadOnlyFile;
+ global $wgContLang;
if ( !$data['Confirm'] ) {
return Status::newFatal( 'locknoconfirm' );
}
wfSuppressWarnings();
- $fp = fopen( $wgReadOnlyFile, 'w' );
+ $fp = fopen( $this->getConfig()->get( 'ReadOnlyFile' ), 'w' );
wfRestoreWarnings();
if ( false === $fp ) {
diff --git a/includes/specials/SpecialLog.php b/includes/specials/SpecialLog.php
index 2ffdd89d..dc33801d 100644
--- a/includes/specials/SpecialLog.php
+++ b/includes/specials/SpecialLog.php
@@ -45,8 +45,6 @@ class SpecialLog extends SpecialPage {
}
public function execute( $par ) {
- global $wgLogRestrictions;
-
$this->setHeaders();
$this->outputHeader();
@@ -77,11 +75,14 @@ class SpecialLog extends SpecialPage {
// If the user doesn't have the right permission to view the specific
// log type, throw a PermissionsError
// If the log type is invalid, just show all public logs
+ $logRestrictions = $this->getConfig()->get( 'LogRestrictions' );
$type = $opts->getValue( 'type' );
if ( !LogPage::isLogType( $type ) ) {
$opts->setValue( 'type', '' );
- } elseif ( isset( $wgLogRestrictions[$type] ) && !$this->getUser()->isAllowed( $wgLogRestrictions[$type] ) ) {
- throw new PermissionsError( $wgLogRestrictions[$type] );
+ } elseif ( isset( $logRestrictions[$type] )
+ && !$this->getUser()->isAllowed( $logRestrictions[$type] )
+ ) {
+ throw new PermissionsError( $logRestrictions[$type] );
}
# Handle type-specific inputs
@@ -98,6 +99,7 @@ class SpecialLog extends SpecialPage {
# Some log types are only for a 'User:' title but we might have been given
# only the username instead of the full title 'User:username'. This part try
# to lookup for a user by that name and eventually fix user input. See bug 1697.
+ wfRunHooks( 'GetLogTypesOnUser', array( &$this->typeOnUser ) );
if ( in_array( $opts->getValue( 'type' ), $this->typeOnUser ) ) {
# ok we have a type of log which expect a user title.
$target = Title::newFromText( $opts->getValue( 'page' ) );
@@ -112,14 +114,26 @@ class SpecialLog extends SpecialPage {
$this->show( $opts, $qc );
}
- private function parseParams( FormOptions $opts, $par ) {
- global $wgLogTypes;
+ /**
+ * Return an array of subpages beginning with $search that this special page will accept.
+ *
+ * @param string $search Prefix to search for
+ * @param int $limit Maximum number of results to return
+ * @return string[] Matching subpages
+ */
+ public function prefixSearchSubpages( $search, $limit = 10 ) {
+ $subpages = $this->getConfig()->get( 'LogTypes' );
+ $subpages[] = 'all';
+ sort( $subpages );
+ return self::prefixSearchArray( $search, $limit, $subpages );
+ }
+ private function parseParams( FormOptions $opts, $par ) {
# Get parameters
$parms = explode( '/', ( $par = ( $par !== null ) ? $par : '' ) );
$symsForAll = array( '*', 'all' );
if ( $parms[0] != '' &&
- ( in_array( $par, $wgLogTypes ) || in_array( $par, $symsForAll ) )
+ ( in_array( $par, $this->getConfig()->get( 'LogTypes' ) ) || in_array( $par, $symsForAll ) )
) {
$opts->setValue( 'type', $par );
} elseif ( count( $parms ) == 2 ) {
@@ -193,10 +207,9 @@ class SpecialLog extends SpecialPage {
}
# Show button to hide log entries
- global $wgScript;
$s = Html::openElement(
'form',
- array( 'action' => $wgScript, 'id' => 'mw-log-deleterevision-submit' )
+ array( 'action' => wfScript(), 'id' => 'mw-log-deleterevision-submit' )
) . "\n";
$s .= Html::hidden( 'title', SpecialPage::getTitleFor( 'Revisiondelete' ) ) . "\n";
$s .= Html::hidden( 'target', SpecialPage::getTitleFor( 'Log' ) ) . "\n";
@@ -217,7 +230,7 @@ class SpecialLog extends SpecialPage {
/**
* Set page title and show header for this log type
- * @param $type string
+ * @param string $type
* @since 1.19
*/
protected function addHeader( $type ) {
diff --git a/includes/specials/SpecialLonelypages.php b/includes/specials/SpecialLonelypages.php
index 7c7771d7..f533234f 100644
--- a/includes/specials/SpecialLonelypages.php
+++ b/includes/specials/SpecialLonelypages.php
@@ -49,36 +49,40 @@ class LonelyPagesPage extends PageQueryPage {
}
function getQueryInfo() {
- return array(
- 'tables' => array(
- 'page', 'pagelinks',
- 'templatelinks'
+ $tables = array( 'page', 'pagelinks', 'templatelinks' );
+ $conds = array(
+ 'pl_namespace IS NULL',
+ 'page_namespace' => MWNamespace::getContentNamespaces(),
+ 'page_is_redirect' => 0,
+ 'tl_namespace IS NULL'
+ );
+ $joinConds = array(
+ 'pagelinks' => array(
+ 'LEFT JOIN', array(
+ 'pl_namespace = page_namespace',
+ 'pl_title = page_title'
+ )
),
+ 'templatelinks' => array(
+ 'LEFT JOIN', array(
+ 'tl_namespace = page_namespace',
+ 'tl_title = page_title'
+ )
+ )
+ );
+
+ // Allow extensions to modify the query
+ wfRunHooks( 'LonelyPagesQuery', array( &$tables, &$conds, &$joinConds ) );
+
+ return array(
+ 'tables' => $tables,
'fields' => array(
'namespace' => 'page_namespace',
'title' => 'page_title',
'value' => 'page_title'
),
- 'conds' => array(
- 'pl_namespace IS NULL',
- 'page_namespace' => MWNamespace::getContentNamespaces(),
- 'page_is_redirect' => 0,
- 'tl_namespace IS NULL'
- ),
- 'join_conds' => array(
- 'pagelinks' => array(
- 'LEFT JOIN', array(
- 'pl_namespace = page_namespace',
- 'pl_title = page_title'
- )
- ),
- 'templatelinks' => array(
- 'LEFT JOIN', array(
- 'tl_namespace = page_namespace',
- 'tl_title = page_title'
- )
- )
- )
+ 'conds' => $conds,
+ 'join_conds' => $joinConds
);
}
diff --git a/includes/specials/SpecialMIMEsearch.php b/includes/specials/SpecialMIMEsearch.php
index 3eeae310..60225ea5 100644
--- a/includes/specials/SpecialMIMEsearch.php
+++ b/includes/specials/SpecialMIMEsearch.php
@@ -28,7 +28,7 @@
* @ingroup SpecialPage
*/
class MIMEsearchPage extends QueryPage {
- protected $major, $minor;
+ protected $major, $minor, $mime;
function __construct( $name = 'MIMEsearch' ) {
parent::__construct( $name );
@@ -51,6 +51,11 @@ class MIMEsearchPage extends QueryPage {
}
public function getQueryInfo() {
+ $minorType = array();
+ if ( $this->minor !== '*' ) {
+ // Allow wildcard searching
+ $minorType['img_minor_mime'] = $this->minor;
+ }
$qi = array(
'tables' => array( 'image' ),
'fields' => array(
@@ -67,7 +72,6 @@ class MIMEsearchPage extends QueryPage {
),
'conds' => array(
'img_major_mime' => $this->major,
- 'img_minor_mime' => $this->minor,
// This is in order to trigger using
// the img_media_mime index in "range" mode.
'img_media_type' => array(
@@ -82,8 +86,9 @@ class MIMEsearchPage extends QueryPage {
MEDIATYPE_EXECUTABLE,
MEDIATYPE_ARCHIVE,
),
- ),
+ ) + $minorType,
);
+
return $qi;
}
@@ -94,38 +99,42 @@ class MIMEsearchPage extends QueryPage {
* that this report gives results in a logical order). As an aditional
* note, mysql seems to by default order things by img_name ASC, which
* is what we ideally want, so everything works out fine anyhow.
+ * @return array
*/
function getOrderFields() {
return array();
}
- function execute( $par ) {
- global $wgScript;
-
- $mime = $par ? $par : $this->getRequest()->getText( 'mime' );
+ /**
+ * Return HTML to put just before the results.
+ */
+ function getPageHeader() {
- $this->setHeaders();
- $this->outputHeader();
- $this->getOutput()->addHTML(
- Xml::openElement(
+ return Xml::openElement(
'form',
- array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => $wgScript )
+ array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => wfScript() )
) .
- Xml::openElement( 'fieldset' ) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
- Xml::element( 'legend', null, $this->msg( 'mimesearch' )->text() ) .
- Xml::inputLabel( $this->msg( 'mimetype' )->text(), 'mime', 'mime', 20, $mime ) .
- ' ' .
- Xml::submitButton( $this->msg( 'ilsubmit' )->text() ) .
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' )
- );
+ Xml::openElement( 'fieldset' ) .
+ Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) .
+ Xml::element( 'legend', null, $this->msg( 'mimesearch' )->text() ) .
+ Xml::inputLabel( $this->msg( 'mimetype' )->text(), 'mime', 'mime', 20, $this->mime ) .
+ ' ' .
+ Xml::submitButton( $this->msg( 'ilsubmit' )->text() ) .
+ Xml::closeElement( 'fieldset' ) .
+ Xml::closeElement( 'form' );
+ }
- list( $this->major, $this->minor ) = File::splitMime( $mime );
+ function execute( $par ) {
+ $this->mime = $par ? $par : $this->getRequest()->getText( 'mime' );
+ $this->mime = trim( $this->mime );
+ list( $this->major, $this->minor ) = File::splitMime( $this->mime );
if ( $this->major == '' || $this->minor == '' || $this->minor == 'unknown' ||
!self::isValidType( $this->major )
) {
+ $this->setHeaders();
+ $this->outputHeader();
+ $this->getOutput()->addHTML( $this->getPageHeader() );
return;
}
@@ -165,7 +174,7 @@ class MIMEsearchPage extends QueryPage {
}
/**
- * @param $type string
+ * @param string $type
* @return bool
*/
protected static function isValidType( $type ) {
@@ -179,7 +188,8 @@ class MIMEsearchPage extends QueryPage {
'video',
'message',
'model',
- 'multipart'
+ 'multipart',
+ 'chemical'
);
return in_array( $type, $types );
diff --git a/includes/specials/SpecialMediaStatistics.php b/includes/specials/SpecialMediaStatistics.php
new file mode 100644
index 00000000..681c332f
--- /dev/null
+++ b/includes/specials/SpecialMediaStatistics.php
@@ -0,0 +1,325 @@
+<?php
+/**
+ * Implements Special:MediaStatistics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Brian Wolff
+ */
+
+/**
+ * @ingroup SpecialPage
+ */
+class MediaStatisticsPage extends QueryPage {
+ protected $totalCount = 0, $totalBytes = 0;
+
+ function __construct( $name = 'MediaStatistics' ) {
+ parent::__construct( $name );
+ // Generally speaking there is only a small number of file types,
+ // so just show all of them.
+ $this->limit = 5000;
+ $this->shownavigation = false;
+ }
+
+ function isExpensive() {
+ return true;
+ }
+
+ /**
+ * Query to do.
+ *
+ * This abuses the query cache table by storing mime types as "titles".
+ *
+ * This will store entries like [[Media:BITMAP;image/jpeg;200;20000]]
+ * where the form is Media type;mime type;count;bytes.
+ *
+ * This relies on the behaviour that when value is tied, the order things
+ * come out of querycache table is the order they went in. Which is hacky.
+ * However, other special pages like Special:Deadendpages and
+ * Special:BrokenRedirects also rely on this.
+ */
+ public function getQueryInfo() {
+ $dbr = wfGetDB( DB_SLAVE );
+ $fakeTitle = $dbr->buildConcat( array(
+ 'img_media_type',
+ $dbr->addQuotes( ';' ),
+ 'img_major_mime',
+ $dbr->addQuotes( '/' ),
+ 'img_minor_mime',
+ $dbr->addQuotes( ';' ),
+ 'COUNT(*)',
+ $dbr->addQuotes( ';' ),
+ 'SUM( img_size )'
+ ) );
+ return array(
+ 'tables' => array( 'image' ),
+ 'fields' => array(
+ 'title' => $fakeTitle,
+ 'namespace' => NS_MEDIA, /* needs to be something */
+ 'value' => '1'
+ ),
+ 'options' => array(
+ 'GROUP BY' => array(
+ 'img_media_type',
+ 'img_major_mime',
+ 'img_minor_mime',
+ )
+ )
+ );
+ }
+
+ /**
+ * How to sort the results
+ *
+ * It's important that img_media_type come first, otherwise the
+ * tables will be fragmented.
+ * @return Array Fields to sort by
+ */
+ function getOrderFields() {
+ return array( 'img_media_type', 'count(*)', 'img_major_mime', 'img_minor_mime' );
+ }
+
+ /**
+ * Output the results of the query.
+ *
+ * @param $out OutputPage
+ * @param $skin Skin (deprecated presumably)
+ * @param $dbr DatabaseBase
+ * @param $res ResultWrapper Results from query
+ * @param $num integer Number of results
+ * @param $offset integer Paging offset (Should always be 0 in our case)
+ */
+ protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
+ $prevMediaType = null;
+ foreach ( $res as $row ) {
+ list( $mediaType, $mime, $totalCount, $totalBytes ) = $this->splitFakeTitle( $row->title );
+ if ( $prevMediaType !== $mediaType ) {
+ if ( $prevMediaType !== null ) {
+ // We're not at beginning, so we have to
+ // close the previous table.
+ $this->outputTableEnd();
+ }
+ $this->outputMediaType( $mediaType );
+ $this->outputTableStart( $mediaType );
+ $prevMediaType = $mediaType;
+ }
+ $this->outputTableRow( $mime, intval( $totalCount ), intval( $totalBytes ) );
+ }
+ if ( $prevMediaType !== null ) {
+ $this->outputTableEnd();
+ }
+ }
+
+ /**
+ * Output closing </table>
+ */
+ protected function outputTableEnd() {
+ $this->getOutput()->addHtml( Html::closeElement( 'table' ) );
+ }
+
+ /**
+ * Output a row of the stats table
+ *
+ * @param $mime String mime type (e.g. image/jpeg)
+ * @param $count integer Number of images of this type
+ * @param $totalBytes integer Total space for images of this type
+ */
+ protected function outputTableRow( $mime, $count, $bytes ) {
+ $mimeSearch = SpecialPage::getTitleFor( 'MIMEsearch', $mime );
+ $row = Html::rawElement(
+ 'td',
+ array(),
+ Linker::link( $mimeSearch, htmlspecialchars( $mime ) )
+ );
+ $row .= Html::element(
+ 'td',
+ array(),
+ $this->getExtensionList( $mime )
+ );
+ $row .= Html::rawElement(
+ 'td',
+ array(),
+ $this->msg( 'mediastatistics-nfiles' )
+ ->numParams( $count )
+ /** @todo Check to be sure this really should have number formatting */
+ ->numParams( $this->makePercentPretty( $count / $this->totalCount ) )
+ ->parse()
+ );
+ $row .= Html::rawElement(
+ 'td',
+ // Make sure js sorts it in numeric order
+ array( 'data-sort-value' => $bytes ),
+ $this->msg( 'mediastatistics-nbytes' )
+ ->numParams( $bytes )
+ ->sizeParams( $bytes )
+ /** @todo Check to be sure this really should have number formatting */
+ ->numParams( $this->makePercentPretty( $bytes / $this->totalBytes ) )
+ ->parse()
+ );
+
+ $this->getOutput()->addHTML( Html::rawElement( 'tr', array(), $row ) );
+ }
+
+ /**
+ * @param float $decimal A decimal percentage (ie for 12.3%, this would be 0.123)
+ * @return String The percentage formatted so that 3 significant digits are shown.
+ */
+ protected function makePercentPretty( $decimal ) {
+ $decimal *= 100;
+ // Always show three useful digits
+ if ( $decimal == 0 ) {
+ return '0';
+ }
+ $percent = sprintf( "%." . max( 0, 2 - floor( log10( $decimal ) ) ) . "f", $decimal );
+ // Then remove any trailing 0's
+ return preg_replace( '/\.?0*$/', '', $percent );
+ }
+
+ /**
+ * Given a mime type, return a comma separated list of allowed extensions.
+ *
+ * @param $mime String mime type
+ * @return String Comma separated list of allowed extensions (e.g. ".ogg, .oga")
+ */
+ private function getExtensionList( $mime ) {
+ $exts = MimeMagic::singleton()->getExtensionsForType( $mime );
+ if ( $exts === null ) {
+ return '';
+ }
+ $extArray = explode( ' ', $exts );
+ $extArray = array_unique( $extArray );
+ foreach ( $extArray as &$ext ) {
+ $ext = '.' . $ext;
+ }
+
+ return $this->getLanguage()->commaList( $extArray );
+ }
+
+ /**
+ * Output the start of the table
+ *
+ * Including opening <table>, and first <tr> with column headers.
+ */
+ protected function outputTableStart( $mediaType ) {
+ $this->getOutput()->addHTML(
+ Html::openElement(
+ 'table',
+ array( 'class' => array(
+ 'mw-mediastats-table',
+ 'mw-mediastats-table-' . strtolower( $mediaType ),
+ 'sortable',
+ 'wikitable'
+ ))
+ )
+ );
+ $this->getOutput()->addHTML( $this->getTableHeaderRow() );
+ }
+
+ /**
+ * Get (not output) the header row for the table
+ *
+ * @return String the header row of the able
+ */
+ protected function getTableHeaderRow() {
+ $headers = array( 'mimetype', 'extensions', 'count', 'totalbytes' );
+ $ths = '';
+ foreach ( $headers as $header ) {
+ $ths .= Html::rawElement(
+ 'th',
+ array(),
+ // for grep:
+ // mediastatistics-table-mimetype, mediastatistics-table-extensions
+ // tatistics-table-count, mediastatistics-table-totalbytes
+ $this->msg( 'mediastatistics-table-' . $header )->parse()
+ );
+ }
+ return Html::rawElement( 'tr', array(), $ths );
+ }
+
+ /**
+ * Output a header for a new media type section
+ *
+ * @param $mediaType string A media type (e.g. from the MEDIATYPE_xxx constants)
+ */
+ protected function outputMediaType( $mediaType ) {
+ $this->getOutput()->addHTML(
+ Html::element(
+ 'h2',
+ array( 'class' => array(
+ 'mw-mediastats-mediatype',
+ 'mw-mediastats-mediatype-' . strtolower( $mediaType )
+ )),
+ // for grep
+ // mediastatistics-header-unknown, mediastatistics-header-bitmap,
+ // mediastatistics-header-drawing, mediastatistics-header-audio,
+ // mediastatistics-header-video, mediastatistics-header-multimedia,
+ // mediastatistics-header-office, mediastatistics-header-text,
+ // mediastatistics-header-executable, mediastatistics-header-archive,
+ $this->msg( 'mediastatistics-header-' . strtolower( $mediaType ) )->text()
+ )
+ );
+ /** @todo Possibly could add a message here explaining what the different types are.
+ * not sure if it is needed though.
+ */
+ }
+
+ /**
+ * parse the fake title format that this special page abuses querycache with.
+ *
+ * @param $fakeTitle String A string formatted as <media type>;<mime type>;<count>;<bytes>
+ * @return Array The constituant parts of $fakeTitle
+ */
+ private function splitFakeTitle( $fakeTitle ) {
+ return explode( ';', $fakeTitle, 4 );
+ }
+
+ /**
+ * What group to put the page in
+ * @return string
+ */
+ protected function getGroupName() {
+ return 'media';
+ }
+
+ /**
+ * This method isn't used, since we override outputResults, but
+ * we need to implement since abstract in parent class.
+ *
+ * @param $skin Skin
+ * @param $result stdObject Result row
+ */
+ public function formatResult( $skin, $result ) {
+ throw new MWException( "unimplemented" );
+ }
+
+ /**
+ * Initialize total values so we can figure out percentages later.
+ *
+ * @param $dbr DatabaseBase
+ * @param $res ResultWrapper
+ */
+ public function preprocessResults( $dbr, $res ) {
+ $this->totalCount = $this->totalBytes = 0;
+ foreach ( $res as $row ) {
+ list( , , $count, $bytes ) = $this->splitFakeTitle( $row->title );
+ $this->totalCount += $count;
+ $this->totalBytes += $bytes;
+ }
+ $res->seek( 0 );
+ }
+}
diff --git a/includes/specials/SpecialMergeHistory.php b/includes/specials/SpecialMergeHistory.php
index fb5ea657..43f5a1ba 100644
--- a/includes/specials/SpecialMergeHistory.php
+++ b/includes/specials/SpecialMergeHistory.php
@@ -28,12 +28,38 @@
* @ingroup SpecialPage
*/
class SpecialMergeHistory extends SpecialPage {
- var $mAction, $mTarget, $mDest, $mTimestamp, $mTargetID, $mDestID, $mComment;
+ /** @var string */
+ protected $mAction;
- /**
- * @var Title
- */
- var $mTargetObj, $mDestObj;
+ /** @var string */
+ protected $mTarget;
+
+ /** @var string */
+ protected $mDest;
+
+ /** @var string */
+ protected $mTimestamp;
+
+ /** @var int */
+ protected $mTargetID;
+
+ /** @var int */
+ protected $mDestID;
+
+ /** @var string */
+ protected $mComment;
+
+ /** @var bool Was posted? */
+ protected $mMerge;
+
+ /** @var bool Was submitted? */
+ protected $mSubmitted;
+
+ /** @var Title */
+ protected $mTargetObj;
+
+ /** @var Title */
+ protected $mDestObj;
public function __construct() {
parent::__construct( 'MergeHistory', 'mergehistory' );
@@ -57,7 +83,9 @@ class SpecialMergeHistory extends SpecialPage {
}
$this->mComment = $request->getText( 'wpComment' );
- $this->mMerge = $request->wasPosted() && $this->getUser()->matchEditToken( $request->getVal( 'wpEditToken' ) );
+ $this->mMerge = $request->wasPosted()
+ && $this->getUser()->matchEditToken( $request->getVal( 'wpEditToken' ) );
+
// target page
if ( $this->mSubmitted ) {
$this->mTargetObj = Title::newFromURL( $this->mTarget );
@@ -105,7 +133,7 @@ class SpecialMergeHistory extends SpecialPage {
if ( !$this->mTargetObj instanceof Title ) {
$errors[] = $this->msg( 'mergehistory-invalid-source' )->parseAsBlock();
} elseif ( !$this->mTargetObj->exists() ) {
- $errors[] = $this->msg( 'mergehistory-no-source', array( 'parse' ),
+ $errors[] = $this->msg( 'mergehistory-no-source',
wfEscapeWikiText( $this->mTargetObj->getPrefixedText() )
)->parseAsBlock();
}
@@ -113,7 +141,7 @@ class SpecialMergeHistory extends SpecialPage {
if ( !$this->mDestObj instanceof Title ) {
$errors[] = $this->msg( 'mergehistory-invalid-destination' )->parseAsBlock();
} elseif ( !$this->mDestObj->exists() ) {
- $errors[] = $this->msg( 'mergehistory-no-destination', array( 'parse' ),
+ $errors[] = $this->msg( 'mergehistory-no-destination',
wfEscapeWikiText( $this->mDestObj->getPrefixedText() )
)->parseAsBlock();
}
@@ -131,18 +159,16 @@ class SpecialMergeHistory extends SpecialPage {
}
function showMergeForm() {
- global $wgScript;
-
$this->getOutput()->addWikiMsg( 'mergehistory-header' );
$this->getOutput()->addHTML(
Xml::openElement( 'form', array(
'method' => 'get',
- 'action' => $wgScript ) ) .
+ 'action' => wfScript() ) ) .
'<fieldset>' .
Xml::element( 'legend', array(),
$this->msg( 'mergehistory-box' )->text() ) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
+ Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) .
Html::hidden( 'submitted', '1' ) .
Html::hidden( 'mergepoint', $this->mTimestamp ) .
Xml::openElement( 'table' ) .
@@ -171,7 +197,7 @@ class SpecialMergeHistory extends SpecialPage {
$haveRevisions = $revisions && $revisions->getNumRows() > 0;
$out = $this->getOutput();
- $titleObj = $this->getTitle();
+ $titleObj = $this->getPageTitle();
$action = $titleObj->getLocalURL( array( 'action' => 'submit' ) );
# Start the form here
$top = Xml::openElement(
@@ -203,7 +229,10 @@ class SpecialMergeHistory extends SpecialPage {
<tr>
<td>&#160;</td>
<td class="mw-submit">' .
- Xml::submitButton( $this->msg( 'mergehistory-submit' )->text(), array( 'name' => 'merge', 'id' => 'mw-merge-submit' ) ) .
+ Xml::submitButton(
+ $this->msg( 'mergehistory-submit' )->text(),
+ array( 'name' => 'merge', 'id' => 'mw-merge-submit' )
+ ) .
'</td>
</tr>' .
Xml::closeElement( 'table' ) .
@@ -252,7 +281,7 @@ class SpecialMergeHistory extends SpecialPage {
$last = $this->message['last'];
$ts = wfTimestamp( TS_MW, $row->rev_timestamp );
- $checkBox = Xml::radio( 'mergepoint', $ts, false );
+ $checkBox = Xml::radio( 'mergepoint', $ts, ( $this->mTimestamp === $ts ) );
$user = $this->getUser();
@@ -290,9 +319,22 @@ class SpecialMergeHistory extends SpecialPage {
$comment = Linker::revComment( $rev );
return Html::rawElement( 'li', array(),
- $this->msg( 'mergehistory-revisionrow' )->rawParams( $checkBox, $last, $pageLink, $userLink, $stxt, $comment )->escaped() );
+ $this->msg( 'mergehistory-revisionrow' )
+ ->rawParams( $checkBox, $last, $pageLink, $userLink, $stxt, $comment )->escaped() );
}
+ /**
+ * Actually attempt the history move
+ *
+ * @todo if all versions of page A are moved to B and then a user
+ * tries to do a reverse-merge via the "unmerge" log link, then page
+ * A will still be a redirect (as it was after the original merge),
+ * though it will have the old revisions back from before (as expected).
+ * The user may have to "undo" the redirect manually to finish the "unmerge".
+ * Maybe this should delete redirects at the target page of merges?
+ *
+ * @return bool Success
+ */
function merge() {
# Get the titles directly from the IDs, in case the target page params
# were spoofed. The queries are done based on the IDs, so it's best to
@@ -336,7 +378,7 @@ class SpecialMergeHistory extends SpecialPage {
return false;
}
- # Update the revisions
+ # Get the timestamp pivot condition
if ( $this->mTimestamp ) {
$timewhere = "rev_timestamp <= {$this->mTimestamp}";
$timestampLimit = wfTimestamp( TS_MW, $this->mTimestamp );
@@ -344,6 +386,18 @@ class SpecialMergeHistory extends SpecialPage {
$timewhere = "rev_timestamp <= {$maxtimestamp}";
$timestampLimit = wfTimestamp( TS_MW, $lasttimestamp );
}
+ # Check that there are not too many revisions to move
+ $limit = 5000; // avoid too much slave lag
+ $count = $dbw->selectRowCount( 'revision', '1',
+ array( 'rev_page' => $this->mTargetID, $timewhere ),
+ __METHOD__,
+ array( 'LIMIT' => $limit + 1 )
+ );
+ if ( $count > $limit ) {
+ $this->getOutput()->addWikiMsg( 'mergehistory-fail-toobig' );
+
+ return false;
+ }
# Do the moving...
$dbw->update(
'revision',
@@ -396,6 +450,7 @@ class SpecialMergeHistory extends SpecialPage {
$dbw->insert( 'pagelinks',
array(
'pl_from' => $this->mDestID,
+ 'pl_from_namespace' => $destTitle->getNamespace(),
'pl_namespace' => $destTitle->getNamespace(),
'pl_title' => $destTitle->getDBkey() ),
__METHOD__
@@ -420,8 +475,10 @@ class SpecialMergeHistory extends SpecialPage {
array( $destTitle->getPrefixedText(), $timestampLimit ), $this->getUser()
);
- $this->getOutput()->addWikiMsg( 'mergehistory-success',
- $targetTitle->getPrefixedText(), $destTitle->getPrefixedText(), $count );
+ # @todo message should use redirect=no
+ $this->getOutput()->addWikiText( $this->msg( 'mergehistory-success',
+ $targetTitle->getPrefixedText(), $destTitle->getPrefixedText() )->numParams(
+ $count )->text() );
wfRunHooks( 'ArticleMergeComplete', array( $targetTitle, $destTitle ) );
@@ -434,9 +491,13 @@ class SpecialMergeHistory extends SpecialPage {
}
class MergeHistoryPager extends ReverseChronologicalPager {
- public $mForm, $mConds;
+ /** @var IContextSource */
+ public $mForm;
+
+ /** @var array */
+ public $mConds;
- function __construct( $form, $conds = array(), $source, $dest ) {
+ function __construct( $form, $conds, $source, $dest ) {
$this->mForm = $form;
$this->mConds = $conds;
$this->title = $source;
@@ -490,7 +551,7 @@ class MergeHistoryPager extends ReverseChronologicalPager {
function getQueryInfo() {
$conds = $this->mConds;
$conds['rev_page'] = $this->articleID;
- $conds[] = "rev_timestamp < {$this->maxTimestamp}";
+ $conds[] = "rev_timestamp < " . $this->mDb->addQuotes( $this->maxTimestamp );
return array(
'tables' => array( 'revision', 'page', 'user' ),
diff --git a/includes/specials/SpecialMostinterwikis.php b/includes/specials/SpecialMostinterwikis.php
index 98dd68e9..30ccbe5a 100644
--- a/includes/specials/SpecialMostinterwikis.php
+++ b/includes/specials/SpecialMostinterwikis.php
@@ -92,8 +92,8 @@ class MostinterwikisPage extends QueryPage {
}
/**
- * @param $skin Skin
- * @param $result
+ * @param Skin $skin
+ * @param object $result
* @return string
*/
function formatResult( $skin, $result ) {
diff --git a/includes/specials/SpecialMostlinked.php b/includes/specials/SpecialMostlinked.php
index 37593bf9..99f0ecf5 100644
--- a/includes/specials/SpecialMostlinked.php
+++ b/includes/specials/SpecialMostlinked.php
@@ -93,9 +93,9 @@ class MostlinkedPage extends QueryPage {
/**
* Make a link to "what links here" for the specified title
*
- * @param $title Title being queried
- * @param string $caption text to display on the link
- * @return String
+ * @param Title $title Title being queried
+ * @param string $caption Text to display on the link
+ * @return string
*/
function makeWlhLink( $title, $caption ) {
$wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedDBkey() );
diff --git a/includes/specials/SpecialMostlinkedcategories.php b/includes/specials/SpecialMostlinkedcategories.php
index 0d4641b1..f61a1158 100644
--- a/includes/specials/SpecialMostlinkedcategories.php
+++ b/includes/specials/SpecialMostlinkedcategories.php
@@ -44,6 +44,7 @@ class MostlinkedCategoriesPage extends QueryPage {
'fields' => array( 'title' => 'cat_title',
'namespace' => NS_CATEGORY,
'value' => 'cat_pages' ),
+ 'conds' => array( 'cat_pages > 0' ),
);
}
diff --git a/includes/specials/SpecialMostlinkedtemplates.php b/includes/specials/SpecialMostlinkedtemplates.php
index c90acb1f..8e6a596d 100644
--- a/includes/specials/SpecialMostlinkedtemplates.php
+++ b/includes/specials/SpecialMostlinkedtemplates.php
@@ -36,7 +36,7 @@ class MostlinkedTemplatesPage extends QueryPage {
/**
* Is this report expensive, i.e should it be cached?
*
- * @return Boolean
+ * @return bool
*/
public function isExpensive() {
return true;
@@ -45,7 +45,7 @@ class MostlinkedTemplatesPage extends QueryPage {
/**
* Is there a feed available?
*
- * @return Boolean
+ * @return bool
*/
public function isSyndicated() {
return false;
@@ -54,7 +54,7 @@ class MostlinkedTemplatesPage extends QueryPage {
/**
* Sort the results in descending order?
*
- * @return Boolean
+ * @return bool
*/
public function sortDescending() {
return true;
@@ -68,7 +68,6 @@ class MostlinkedTemplatesPage extends QueryPage {
'title' => 'tl_title',
'value' => 'COUNT(*)'
),
- 'conds' => array( 'tl_namespace' => NS_TEMPLATE ),
'options' => array( 'GROUP BY' => array( 'tl_namespace', 'tl_title' ) )
);
}
@@ -76,7 +75,7 @@ class MostlinkedTemplatesPage extends QueryPage {
/**
* Pre-cache page existence to speed up link generation
*
- * @param $db DatabaseBase connection
+ * @param DatabaseBase $db
* @param ResultWrapper $res
*/
public function preprocessResults( $db, $res ) {
@@ -125,7 +124,7 @@ class MostlinkedTemplatesPage extends QueryPage {
*
* @param Title $title Title to make the link for
* @param object $result Result row
- * @return String
+ * @return string
*/
private function makeWlhLink( $title, $result ) {
$wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() );
diff --git a/includes/specials/SpecialMostrevisions.php b/includes/specials/SpecialMostrevisions.php
index ad6b788d..0471cafe 100644
--- a/includes/specials/SpecialMostrevisions.php
+++ b/includes/specials/SpecialMostrevisions.php
@@ -23,6 +23,7 @@
* @ingroup SpecialPage
* @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
*/
+
class MostrevisionsPage extends FewestrevisionsPage {
function __construct( $name = 'Mostrevisions' ) {
parent::__construct( $name );
diff --git a/includes/specials/SpecialMovepage.php b/includes/specials/SpecialMovepage.php
index 253e6cc3..ec9593f7 100644
--- a/includes/specials/SpecialMovepage.php
+++ b/includes/specials/SpecialMovepage.php
@@ -27,15 +27,35 @@
* @ingroup SpecialPage
*/
class MovePageForm extends UnlistedSpecialPage {
- /**
- * Objects
- * @var Title
- */
- var $oldTitle, $newTitle;
- // Text input
- var $reason;
+ /** @var Title */
+ protected $oldTitle;
+
+ /** @var Title */
+ protected $newTitle;
+
+
+ /** @var string Text input */
+ protected $reason;
+
// Checks
- var $moveTalk, $deleteAndMove, $moveSubpages, $fixRedirects, $leaveRedirect, $moveOverShared;
+
+ /** @var bool */
+ protected $moveTalk;
+
+ /** @var bool */
+ protected $deleteAndMove;
+
+ /** @var bool */
+ protected $moveSubpages;
+
+ /** @var bool */
+ protected $fixRedirects;
+
+ /** @var bool */
+ protected $leaveRedirect;
+
+ /** @var bool */
+ protected $moveOverShared;
private $watch = false;
@@ -106,12 +126,12 @@ class MovePageForm extends UnlistedSpecialPage {
/**
* Show the form
*
- * @param array $err error messages. Each item is an error message.
+ * @param array $err Error messages. Each item is an error message.
* It may either be a string message name or array message name and
* parameters, like the second argument to OutputPage::wrapWikiMsg().
*/
function showForm( $err ) {
- global $wgContLang, $wgFixDoubleRedirects, $wgMaximumMovedPages;
+ global $wgContLang;
$this->getSkin()->setRelevantTitle( $this->oldTitle );
@@ -163,9 +183,14 @@ class MovePageForm extends UnlistedSpecialPage {
"<div class=\"error mw-moveuserpage-warning\">\n$1\n</div>",
'moveuserpage-warning'
);
+ } elseif ( $this->oldTitle->getNamespace() == NS_CATEGORY ) {
+ $out->wrapWikiMsg(
+ "<div class=\"error mw-movecategorypage-warning\">\n$1\n</div>",
+ 'movecategorypage-warning'
+ );
}
- $out->addWikiMsg( $wgFixDoubleRedirects ?
+ $out->addWikiMsg( $this->getConfig()->get( 'FixDoubleRedirects' ) ?
'movepagetext' :
'movepagetext-noredirectfixer'
);
@@ -196,7 +221,7 @@ class MovePageForm extends UnlistedSpecialPage {
|| ( $oldTitleTalkSubpages && $canMoveSubpage ) );
$dbr = wfGetDB( DB_SLAVE );
- if ( $wgFixDoubleRedirects ) {
+ if ( $this->getConfig()->get( 'FixDoubleRedirects' ) ) {
$hasRedirects = $dbr->selectField( 'redirect', '1',
array(
'rd_namespace' => $this->oldTitle->getNamespace(),
@@ -281,7 +306,7 @@ class MovePageForm extends UnlistedSpecialPage {
'form',
array(
'method' => 'post',
- 'action' => $this->getTitle()->getLocalURL( 'action=submit' ),
+ 'action' => $this->getPageTitle()->getLocalURL( 'action=submit' ),
'id' => 'movepage'
)
) .
@@ -351,7 +376,16 @@ class MovePageForm extends UnlistedSpecialPage {
);
}
- if ( $user->isAllowed( 'suppressredirect' ) && $handler->supportsRedirects() ) {
+ if ( $user->isAllowed( 'suppressredirect' ) ) {
+ if ( $handler->supportsRedirects() ) {
+ $isChecked = $this->leaveRedirect;
+ $options = array();
+ } else {
+ $isChecked = false;
+ $options = array(
+ 'disabled' => 'disabled'
+ );
+ }
$out->addHTML( "
<tr>
<td></td>
@@ -360,7 +394,8 @@ class MovePageForm extends UnlistedSpecialPage {
$this->msg( 'move-leave-redirect' )->text(),
'wpLeaveRedirect',
'wpLeaveRedirect',
- $this->leaveRedirect
+ $isChecked,
+ $options
) .
"</td>
</tr>"
@@ -384,6 +419,7 @@ class MovePageForm extends UnlistedSpecialPage {
}
if ( $canMoveSubpage ) {
+ $maximumMovedPages = $this->getConfig()->get( 'MaximumMovedPages' );
$out->addHTML( "
<tr>
<td></td>
@@ -400,7 +436,7 @@ class MovePageForm extends UnlistedSpecialPage {
( $this->oldTitle->hasSubpages()
? 'move-subpages'
: 'move-talk-subpages' )
- )->numParams( $wgMaximumMovedPages )->params( $wgMaximumMovedPages )->parse()
+ )->numParams( $maximumMovedPages )->params( $maximumMovedPages )->parse()
) .
"</td>
</tr>"
@@ -445,8 +481,6 @@ class MovePageForm extends UnlistedSpecialPage {
}
function doSubmit() {
- global $wgMaximumMovedPages, $wgFixDoubleRedirects;
-
$user = $this->getUser();
if ( $user->pingLimiter( 'move' ) ) {
@@ -457,7 +491,7 @@ class MovePageForm extends UnlistedSpecialPage {
$nt = $this->newTitle;
# don't allow moving to pages with # in
- if ( !$nt || $nt->getFragment() != '' ) {
+ if ( !$nt || $nt->hasFragment() ) {
$this->showForm( array( array( 'badtitletext' ) ) );
return;
@@ -522,7 +556,7 @@ class MovePageForm extends UnlistedSpecialPage {
return;
}
- if ( $wgFixDoubleRedirects && $this->fixRedirects ) {
+ if ( $this->getConfig()->get( 'FixDoubleRedirects' ) && $this->fixRedirects ) {
DoubleRedirectJob::fixRedirects( 'move', $ot, $nt );
}
@@ -532,10 +566,14 @@ class MovePageForm extends UnlistedSpecialPage {
$oldLink = Linker::link(
$ot,
null,
- array(),
+ array( 'id' => 'movepage-oldlink' ),
array( 'redirect' => 'no' )
);
- $newLink = Linker::linkKnown( $nt );
+ $newLink = Linker::linkKnown(
+ $nt,
+ null,
+ array( 'id' => 'movepage-newlink' )
+ );
$oldText = $ot->getPrefixedText();
$newText = $nt->getPrefixedText();
@@ -583,8 +621,8 @@ class MovePageForm extends UnlistedSpecialPage {
$dbr = wfGetDB( DB_MASTER );
if ( $this->moveSubpages && (
MWNamespace::hasSubpages( $nt->getNamespace() ) || (
- $this->moveTalk &&
- MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() )
+ $this->moveTalk
+ && MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() )
)
) ) {
$conds = array(
@@ -670,17 +708,21 @@ class MovePageForm extends UnlistedSpecialPage {
);
$newLink = Linker::linkKnown( $newSubpage );
- $extraOutput[] = $this->msg( 'movepage-page-moved' )->rawParams( $oldLink, $newLink )->escaped();
+ $extraOutput[] = $this->msg( 'movepage-page-moved' )
+ ->rawParams( $oldLink, $newLink )->escaped();
++$count;
- if ( $count >= $wgMaximumMovedPages ) {
- $extraOutput[] = $this->msg( 'movepage-max-pages' )->numParams( $wgMaximumMovedPages )->escaped();
+ $maximumMovedPages = $this->getConfig()->get( 'MaximumMovedPages' );
+ if ( $count >= $maximumMovedPages ) {
+ $extraOutput[] = $this->msg( 'movepage-max-pages' )
+ ->numParams( $maximumMovedPages )->escaped();
break;
}
} else {
$oldLink = Linker::linkKnown( $oldSubpage );
$newLink = Linker::link( $newSubpage );
- $extraOutput[] = $this->msg( 'movepage-page-unmoved' )->rawParams( $oldLink, $newLink )->escaped();
+ $extraOutput[] = $this->msg( 'movepage-page-unmoved' )
+ ->rawParams( $oldLink, $newLink )->escaped();
}
}
}
diff --git a/includes/specials/SpecialMyLanguage.php b/includes/specials/SpecialMyLanguage.php
new file mode 100644
index 00000000..71b18930
--- /dev/null
+++ b/includes/specials/SpecialMyLanguage.php
@@ -0,0 +1,93 @@
+<?php
+/**
+ * Implements Special:MyLanguage
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @author Niklas Laxström
+ * @author Siebrand Mazeland
+ * @copyright Copyright © 2010-2013 Niklas Laxström, Siebrand Mazeland
+ */
+
+/**
+ * Unlisted special page just to redirect the user to the translated version of
+ * a page, if it exists.
+ *
+ * Usage: [[Special:MyLanguage/Page name|link text]]
+ *
+ * @since 1.24
+ * @ingroup SpecialPage
+ */
+class SpecialMyLanguage extends RedirectSpecialArticle {
+ public function __construct() {
+ parent::__construct( 'MyLanguage' );
+ }
+
+ /**
+ * If the special page is a redirect, then get the Title object it redirects to.
+ * False otherwise.
+ *
+ * @param string $par Subpage string
+ * @return Title|bool
+ */
+ public function getRedirect( $par ) {
+ $title = $this->findTitle( $par );
+ // Go to the main page if given invalid title.
+ if ( !$title ) {
+ $title = Title::newMainPage();
+ }
+ return $title;
+ }
+
+ /**
+ * Assuming the user's interface language is fi. Given input Page, it
+ * returns Page/fi if it exists, otherwise Page. Given input Page/de,
+ * it returns Page/fi if it exists, otherwise Page/de if it exists,
+ * otherwise Page.
+ *
+ * @param string $par
+ * @return Title|null
+ */
+ public function findTitle( $par ) {
+ // base = title without language code suffix
+ // provided = the title as it was given
+ $base = $provided = Title::newFromText( $par );
+
+ if ( $base && strpos( $par, '/' ) !== false ) {
+ $pos = strrpos( $par, '/' );
+ $basepage = substr( $par, 0, $pos );
+ $code = substr( $par, $pos + 1 );
+ if ( strlen( $code ) && Language::isKnownLanguageTag( $code ) ) {
+ $base = Title::newFromText( $basepage );
+ }
+ }
+
+ if ( !$base ) {
+ return null;
+ }
+
+ $uiCode = $this->getLanguage()->getCode();
+ $proposed = $base->getSubpage( $uiCode );
+ if ( $uiCode !== $this->getConfig()->get( 'LanguageCode' ) && $proposed && $proposed->exists() ) {
+ return $proposed;
+ } elseif ( $provided && $provided->exists() ) {
+ return $provided;
+ } else {
+ return $base;
+ }
+ }
+}
diff --git a/includes/specials/SpecialMyRedirectPages.php b/includes/specials/SpecialMyRedirectPages.php
new file mode 100644
index 00000000..9b8d52bb
--- /dev/null
+++ b/includes/specials/SpecialMyRedirectPages.php
@@ -0,0 +1,114 @@
+<?php
+/**
+ * Special pages that are used to get user independent links pointing to
+ * current user's pages (user page, talk page, contributions, etc.).
+ * This can let us cache a single copy of some generated content for all
+ * users or be linked in wikitext help pages.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * Special page pointing to current user's user page.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialMypage extends RedirectSpecialArticle {
+ function __construct() {
+ parent::__construct( 'Mypage' );
+ }
+
+ function getRedirect( $subpage ) {
+ if ( strval( $subpage ) !== '' ) {
+ return Title::makeTitle( NS_USER, $this->getUser()->getName() . '/' . $subpage );
+ } else {
+ return Title::makeTitle( NS_USER, $this->getUser()->getName() );
+ }
+ }
+}
+
+/**
+ * Special page pointing to current user's talk page.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialMytalk extends RedirectSpecialArticle {
+ function __construct() {
+ parent::__construct( 'Mytalk' );
+ }
+
+ function getRedirect( $subpage ) {
+ if ( strval( $subpage ) !== '' ) {
+ return Title::makeTitle( NS_USER_TALK, $this->getUser()->getName() . '/' . $subpage );
+ } else {
+ return Title::makeTitle( NS_USER_TALK, $this->getUser()->getName() );
+ }
+ }
+}
+
+/**
+ * Special page pointing to current user's contributions.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialMycontributions extends RedirectSpecialPage {
+ function __construct() {
+ parent::__construct( 'Mycontributions' );
+ $this->mAllowedRedirectParams = array( 'limit', 'namespace', 'tagfilter',
+ 'offset', 'dir', 'year', 'month', 'feed' );
+ }
+
+ function getRedirect( $subpage ) {
+ return SpecialPage::getTitleFor( 'Contributions', $this->getUser()->getName() );
+ }
+}
+
+/**
+ * Special page pointing to current user's uploaded files.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialMyuploads extends RedirectSpecialPage {
+ function __construct() {
+ parent::__construct( 'Myuploads' );
+ $this->mAllowedRedirectParams = array( 'limit', 'ilshowall', 'ilsearch' );
+ }
+
+ function getRedirect( $subpage ) {
+ return SpecialPage::getTitleFor( 'Listfiles', $this->getUser()->getName() );
+ }
+}
+
+/**
+ * Special page pointing to current user's uploaded files (including old versions).
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialAllMyUploads extends RedirectSpecialPage {
+ function __construct() {
+ parent::__construct( 'AllMyUploads' );
+ $this->mAllowedRedirectParams = array( 'limit', 'ilsearch' );
+ }
+
+ function getRedirect( $subpage ) {
+ $this->mAddedRedirectParams['ilshowall'] = 1;
+
+ return SpecialPage::getTitleFor( 'Listfiles', $this->getUser()->getName() );
+ }
+}
diff --git a/includes/specials/SpecialNewimages.php b/includes/specials/SpecialNewimages.php
index 37d29734..546c1914 100644
--- a/includes/specials/SpecialNewimages.php
+++ b/includes/specials/SpecialNewimages.php
@@ -20,6 +20,7 @@
* @file
* @ingroup SpecialPage
*/
+
class SpecialNewFiles extends IncludableSpecialPage {
public function __construct() {
parent::__construct( 'Newimages' );
@@ -32,6 +33,7 @@ class SpecialNewFiles extends IncludableSpecialPage {
$pager = new NewFilesPager( $this->getContext(), $par );
if ( !$this->including() ) {
+ $this->setTopText();
$form = $pager->getForm();
$form->prepareForm();
$form->displayForm( '' );
@@ -46,6 +48,25 @@ class SpecialNewFiles extends IncludableSpecialPage {
protected function getGroupName() {
return 'changes';
}
+
+ /**
+ * Send the text to be displayed above the options
+ */
+ function setTopText() {
+ global $wgContLang;
+
+ $message = $this->msg( 'newimagestext' )->inContentLanguage();
+ if ( !$message->isDisabled() ) {
+ $this->getOutput()->addWikiText(
+ Html::rawElement( 'p',
+ array( 'lang' => $wgContLang->getCode(), 'dir' => $wgContLang->getDir() ),
+ "\n" . $message->plain() . "\n"
+ ),
+ /* $lineStart */ false,
+ /* $interface */ false
+ );
+ }
+ }
}
/**
@@ -55,7 +76,7 @@ class NewFilesPager extends ReverseChronologicalPager {
/**
* @var ImageGallery
*/
- var $gallery;
+ protected $gallery;
function __construct( IContextSource $context, $par = null ) {
$this->like = $context->getRequest()->getText( 'like' );
@@ -68,7 +89,6 @@ class NewFilesPager extends ReverseChronologicalPager {
}
function getQueryInfo() {
- global $wgMiserMode;
$conds = $jconds = array();
$tables = array( 'image' );
@@ -88,7 +108,7 @@ class NewFilesPager extends ReverseChronologicalPager {
}
}
- if ( !$wgMiserMode && $this->like !== null ) {
+ if ( !$this->getConfig()->get( 'MiserMode' ) && $this->like !== null ) {
$dbr = wfGetDB( DB_SLAVE );
$likeObj = Title::newFromURL( $this->like );
if ( $likeObj instanceof Title ) {
@@ -120,12 +140,11 @@ class NewFilesPager extends ReverseChronologicalPager {
// Note that null for mode is taken to mean use default.
$mode = $this->getRequest()->getVal( 'gallerymode', null );
try {
- $this->gallery = ImageGalleryBase::factory( $mode );
+ $this->gallery = ImageGalleryBase::factory( $mode, $this->getContext() );
} catch ( MWException $e ) {
// User specified something invalid, fallback to default.
- $this->gallery = ImageGalleryBase::factory();
+ $this->gallery = ImageGalleryBase::factory( false, $this->getContext() );
}
- $this->gallery->setContext( $this->getContext() );
}
return '';
@@ -152,8 +171,6 @@ class NewFilesPager extends ReverseChronologicalPager {
}
function getForm() {
- global $wgMiserMode;
-
$fields = array(
'like' => array(
'type' => 'text',
@@ -162,7 +179,7 @@ class NewFilesPager extends ReverseChronologicalPager {
),
'showbots' => array(
'type' => 'check',
- 'label' => $this->msg( 'showhidebots', $this->msg( 'show' )->plain() )->escaped(),
+ 'label-message' => 'newimages-showbots',
'name' => 'showbots',
),
'limit' => array(
@@ -177,7 +194,7 @@ class NewFilesPager extends ReverseChronologicalPager {
),
);
- if ( $wgMiserMode ) {
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
unset( $fields['like'] );
}
diff --git a/includes/specials/SpecialNewpages.php b/includes/specials/SpecialNewpages.php
index 43d48558..0b70bb7e 100644
--- a/includes/specials/SpecialNewpages.php
+++ b/includes/specials/SpecialNewpages.php
@@ -27,15 +27,12 @@
* @ingroup SpecialPage
*/
class SpecialNewpages extends IncludableSpecialPage {
- // Stored objects
-
/**
* @var FormOptions
*/
protected $opts;
protected $customFilters;
- // Some internal settings
protected $showNavigation = false;
public function __construct() {
@@ -43,8 +40,6 @@ class SpecialNewpages extends IncludableSpecialPage {
}
protected function setup( $par ) {
- global $wgEnableNewpagesUserFilter;
-
// Options
$opts = new FormOptions();
$this->opts = $opts; // bind
@@ -74,9 +69,6 @@ class SpecialNewpages extends IncludableSpecialPage {
// Validate
$opts->validateIntBounds( 'limit', 0, 5000 );
- if ( !$wgEnableNewpagesUserFilter ) {
- $opts->setValue( 'username', '' );
- }
}
protected function parseParams( $par ) {
@@ -124,8 +116,7 @@ class SpecialNewpages extends IncludableSpecialPage {
/**
* Show a form for filtering namespace and username
*
- * @param $par String
- * @return String
+ * @param string $par
*/
public function execute( $par ) {
$out = $this->getOutput();
@@ -194,7 +185,7 @@ class SpecialNewpages extends IncludableSpecialPage {
$changed = $this->opts->getChangedValues();
unset( $changed['offset'] ); // Reset offset if query type changes
- $self = $this->getTitle();
+ $self = $this->getPageTitle();
foreach ( $filters as $key => $msg ) {
$onoff = 1 - $this->opts->getValue( $key );
$link = Linker::link( $self, $showhide[$onoff], array(),
@@ -207,8 +198,6 @@ class SpecialNewpages extends IncludableSpecialPage {
}
protected function form() {
- global $wgEnableNewpagesUserFilter, $wgScript;
-
// Consume values
$this->opts->consumeValue( 'offset' ); // don't carry offset, DWIW
$namespace = $this->opts->consumeValue( 'namespace' );
@@ -232,8 +221,8 @@ class SpecialNewpages extends IncludableSpecialPage {
list( $tagFilterLabel, $tagFilterSelector ) = $tagFilter;
}
- $form = Xml::openElement( 'form', array( 'action' => $wgScript ) ) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
+ $form = Xml::openElement( 'form', array( 'action' => wfScript() ) ) .
+ Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) .
Xml::fieldset( $this->msg( 'newpages' )->text() ) .
Xml::openElement( 'table', array( 'id' => 'mw-newpages-table' ) ) .
'<tr>
@@ -268,15 +257,14 @@ class SpecialNewpages extends IncludableSpecialPage {
$tagFilterSelector .
'</td>
</tr>' ) : '' ) .
- ( $wgEnableNewpagesUserFilter ?
- '<tr>
+ '<tr>
<td class="mw-label">' .
Xml::label( $this->msg( 'newpages-username' )->text(), 'mw-np-username' ) .
'</td>
<td class="mw-input">' .
Xml::input( 'username', 30, $userText, array( 'id' => 'mw-np-username' ) ) .
'</td>
- </tr>' : '' ) .
+ </tr>' .
'<tr> <td></td>
<td class="mw-submit">' .
Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) .
@@ -301,7 +289,7 @@ class SpecialNewpages extends IncludableSpecialPage {
* size, user links, and a comment
*
* @param object $result Result row
- * @return String
+ * @return string
*/
public function formatRow( $result ) {
$title = Title::newFromRow( $result );
@@ -394,14 +382,15 @@ class SpecialNewpages extends IncludableSpecialPage {
$oldTitleText = $this->msg( 'rc-old-title' )->params( $oldTitleText )->escaped();
}
- return "<li{$css}>{$time} {$dm}{$plink} {$hist} {$dm}{$length} {$dm}{$ulink} {$comment} {$tagDisplay} {$oldTitleText}</li>\n";
+ return "<li{$css}>{$time} {$dm}{$plink} {$hist} {$dm}{$length} "
+ . "{$dm}{$ulink} {$comment} {$tagDisplay} {$oldTitleText}</li>\n";
}
/**
* Should a specific result row provide "patrollable" links?
*
* @param object $result Result row
- * @return Boolean
+ * @return bool
*/
protected function patrollable( $result ) {
return ( $this->getUser()->useNPPatrol() && !$result->rc_patrolled );
@@ -410,32 +399,31 @@ class SpecialNewpages extends IncludableSpecialPage {
/**
* Output a subscription feed listing recent edits to this page.
*
- * @param $type String
+ * @param string $type
*/
protected function feed( $type ) {
- global $wgFeed, $wgFeedClasses, $wgFeedLimit;
-
- if ( !$wgFeed ) {
+ if ( !$this->getConfig()->get( 'Feed' ) ) {
$this->getOutput()->addWikiMsg( 'feed-unavailable' );
return;
}
- if ( !isset( $wgFeedClasses[$type] ) ) {
+ $feedClasses = $this->getConfig()->get( 'FeedClasses' );
+ if ( !isset( $feedClasses[$type] ) ) {
$this->getOutput()->addWikiMsg( 'feed-invalid' );
return;
}
- $feed = new $wgFeedClasses[$type](
+ $feed = new $feedClasses[$type](
$this->feedTitle(),
$this->msg( 'tagline' )->text(),
- $this->getTitle()->getFullURL()
+ $this->getPageTitle()->getFullURL()
);
$pager = new NewPagesPager( $this, $this->opts );
$limit = $this->opts->getValue( 'limit' );
- $pager->mLimit = min( $limit, $wgFeedLimit );
+ $pager->mLimit = min( $limit, $this->getConfig()->get( 'FeedLimit' ) );
$feed->outHeader();
if ( $pager->getNumRows() > 0 ) {
@@ -447,10 +435,11 @@ class SpecialNewpages extends IncludableSpecialPage {
}
protected function feedTitle() {
- global $wgLanguageCode, $wgSitename;
$desc = $this->getDescription();
+ $code = $this->getConfig()->get( 'LanguageCode' );
+ $sitename = $this->getConfig()->get( 'Sitename' );
- return "$wgSitename - $desc [$wgLanguageCode]";
+ return "$sitename - $desc [$code]";
}
protected function feedItem( $row ) {
@@ -514,7 +503,6 @@ class NewPagesPager extends ReverseChronologicalPager {
}
function getQueryInfo() {
- global $wgEnableNewpagesUserFilter;
$conds = array();
$conds['rc_new'] = 1;
@@ -524,19 +512,17 @@ class NewPagesPager extends ReverseChronologicalPager {
$username = $this->opts->getValue( 'username' );
$user = Title::makeTitleSafe( NS_USER, $username );
+ $rcIndexes = array();
+
if ( $namespace !== false ) {
if ( $this->opts->getValue( 'invert' ) ) {
$conds[] = 'rc_namespace != ' . $this->mDb->addQuotes( $namespace );
} else {
$conds['rc_namespace'] = $namespace;
}
- $rcIndexes = array( 'new_name_timestamp' );
- } else {
- $rcIndexes = array( 'rc_timestamp' );
}
- # $wgEnableNewpagesUserFilter - temp WMF hack
- if ( $wgEnableNewpagesUserFilter && $user ) {
+ if ( $user ) {
$conds['rc_user_text'] = $user->getText();
$rcIndexes = 'rc_user_text';
} elseif ( User::groupHasPermission( '*', 'createpage' ) &&
@@ -572,11 +558,17 @@ class NewPagesPager extends ReverseChronologicalPager {
wfRunHooks( 'SpecialNewpagesConditions',
array( &$this, $this->opts, &$conds, &$tables, &$fields, &$join_conds ) );
+ $options = array();
+
+ if ( $rcIndexes ) {
+ $options = array( 'USE INDEX' => array( 'recentchanges' => $rcIndexes ) );
+ }
+
$info = array(
'tables' => $tables,
'fields' => $fields,
'conds' => $conds,
- 'options' => array( 'USE INDEX' => array( 'recentchanges' => $rcIndexes ) ),
+ 'options' => $options,
'join_conds' => $join_conds
);
diff --git a/includes/specials/SpecialPageLanguage.php b/includes/specials/SpecialPageLanguage.php
new file mode 100644
index 00000000..2acf23cd
--- /dev/null
+++ b/includes/specials/SpecialPageLanguage.php
@@ -0,0 +1,195 @@
+<?php
+/**
+ * Implements Special:PageLanguage
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Kunal Grover
+ * @since 1.24
+ */
+
+/**
+ * Special page for changing the content language of a page
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialPageLanguage extends FormSpecialPage {
+ /**
+ * @var string URL to go to if language change successful
+ */
+ private $goToUrl;
+
+ public function __construct() {
+ parent::__construct( 'PageLanguage', 'pagelang' );
+ }
+
+ protected function preText() {
+ $this->getOutput()->addModules( 'mediawiki.special.pageLanguage' );
+ }
+
+ protected function getFormFields() {
+ // Get default from the subpage of Special page
+ $defaultName = $this->par;
+
+ $page = array();
+ $page['pagename'] = array(
+ 'type' => 'text',
+ 'label-message' => 'pagelang-name',
+ 'default' => $defaultName,
+ );
+
+ // Options for whether to use the default language or select language
+ $selectoptions = array(
+ (string)$this->msg( 'pagelang-use-default' )->escaped() => 1,
+ (string)$this->msg( 'pagelang-select-lang' )->escaped() => 2,
+ );
+ $page['selectoptions'] = array(
+ 'id' => 'mw-pl-options',
+ 'type' => 'radio',
+ 'options' => $selectoptions,
+ 'default' => 1
+ );
+
+ // Building a language selector
+ $userLang = $this->getLanguage()->getCode();
+ $languages = Language::fetchLanguageNames( $userLang, 'mwfile' );
+ ksort( $languages );
+ $options = array();
+ foreach ( $languages as $code => $name ) {
+ $options["$code - $name"] = $code;
+ }
+
+ $page['language'] = array(
+ 'id' => 'mw-pl-languageselector',
+ 'cssclass' => 'mw-languageselector',
+ 'type' => 'select',
+ 'options' => $options,
+ 'label-message' => 'pagelang-language',
+ 'default' => $this->getConfig()->get( 'LanguageCode' ),
+ );
+
+ return $page;
+ }
+
+ protected function postText() {
+ return $this->showLogFragment( $this->par );
+ }
+
+ public function alterForm( HTMLForm $form ) {
+ $form->setDisplayFormat( 'vform' );
+ $form->setWrapperLegend( false );
+ wfRunHooks( 'LanguageSelector', array( $this->getOutput(), 'mw-languageselector' ) );
+ }
+
+ /**
+ *
+ * @param array $data
+ * @return bool
+ */
+ public function onSubmit( array $data ) {
+ $title = Title::newFromText( $data['pagename'] );
+
+ // Check if title is valid
+ if ( !$title ) {
+ return false;
+ }
+
+ // Get the default language for the wiki
+ // Returns the default since the page is not loaded from DB
+ $defLang = $title->getPageLanguage()->getCode();
+
+ $pageId = $title->getArticleID();
+
+ // Check if article exists
+ if ( !$pageId ) {
+ return false;
+ }
+
+ // Load the page language from DB
+ $dbw = wfGetDB( DB_MASTER );
+ $langOld = $dbw->selectField(
+ 'page',
+ 'page_lang',
+ array( 'page_id' => $pageId ),
+ __METHOD__
+ );
+
+ // Url to redirect to after the operation
+ $this->goToUrl = $title->getFullURL();
+
+ // Check if user wants to use default language
+ if ( $data['selectoptions'] == 1 ) {
+ $langNew = null;
+ } else {
+ $langNew = $data['language'];
+ }
+
+ // No change in language
+ if ( $langNew === $langOld ) {
+ return false;
+ }
+
+ // Hardcoded [def] if the language is set to null
+ $logOld = $langOld ? $langOld : $defLang . '[def]';
+ $logNew = $langNew ? $langNew : $defLang . '[def]';
+
+ // Writing new page language to database
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update(
+ 'page',
+ array( 'page_lang' => $langNew ),
+ array(
+ 'page_id' => $pageId,
+ 'page_lang' => $langOld
+ ),
+ __METHOD__
+ );
+
+ if ( !$dbw->affectedRows() ) {
+ return false;
+ }
+
+ // Logging change of language
+ $logParams = array(
+ '4::oldlanguage' => $logOld,
+ '5::newlanguage' => $logNew
+ );
+ $entry = new ManualLogEntry( 'pagelang', 'pagelang' );
+ $entry->setPerformer( $this->getUser() );
+ $entry->setTarget( $title );
+ $entry->setParameters( $logParams );
+
+ $logid = $entry->insert();
+ $entry->publish( $logid );
+
+ return true;
+ }
+
+ public function onSuccess() {
+ // Success causes a redirect
+ $this->getOutput()->redirect( $this->goToUrl );
+ }
+
+ function showLogFragment( $title ) {
+ $moveLogPage = new LogPage( 'pagelang' );
+ $out1 = Xml::element( 'h2', null, $moveLogPage->getName()->text() );
+ $out2 = '';
+ LogEventsList::showLogExtract( $out2, 'pagelang', $title );
+ return $out1 . $out2;
+ }
+}
diff --git a/includes/specials/SpecialPagesWithProp.php b/includes/specials/SpecialPagesWithProp.php
index e22b42a3..f5b19cc6 100644
--- a/includes/specials/SpecialPagesWithProp.php
+++ b/includes/specials/SpecialPagesWithProp.php
@@ -30,6 +30,7 @@
*/
class SpecialPagesWithProp extends QueryPage {
private $propName = null;
+ private $existingPropNames = null;
function __construct( $name = 'PagesWithProp' ) {
parent::__construct( $name );
@@ -47,18 +48,7 @@ class SpecialPagesWithProp extends QueryPage {
$request = $this->getRequest();
$propname = $request->getVal( 'propname', $par );
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select(
- 'page_props',
- 'pp_propname',
- '',
- __METHOD__,
- array( 'DISTINCT', 'ORDER BY' => 'pp_propname' )
- );
- $propnames = array();
- foreach ( $res as $row ) {
- $propnames[$row->pp_propname] = $row->pp_propname;
- }
+ $propnames = $this->getExistingPropNames();
$form = new HTMLForm( array(
'propname' => array(
@@ -89,6 +79,18 @@ class SpecialPagesWithProp extends QueryPage {
}
/**
+ * Return an array of subpages beginning with $search that this special page will accept.
+ *
+ * @param string $search Prefix to search for
+ * @param int $limit Maximum number of results to return
+ * @return string[] Matching subpages
+ */
+ public function prefixSearchSubpages( $search, $limit = 10 ) {
+ $subpages = array_keys( $this->getExistingPropNames() );
+ return self::prefixSearchArray( $search, $limit, $subpages );
+ }
+
+ /**
* Disable RSS/Atom feeds
* @return bool
*/
@@ -150,6 +152,25 @@ class SpecialPagesWithProp extends QueryPage {
return $ret;
}
+ public function getExistingPropNames() {
+ if ( $this->existingPropNames === null ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select(
+ 'page_props',
+ 'pp_propname',
+ '',
+ __METHOD__,
+ array( 'DISTINCT', 'ORDER BY' => 'pp_propname' )
+ );
+ $propnames = array();
+ foreach ( $res as $row ) {
+ $propnames[$row->pp_propname] = $row->pp_propname;
+ }
+ $this->existingPropNames = $propnames;
+ }
+ return $this->existingPropNames;
+ }
+
protected function getGroupName() {
return 'pages';
}
diff --git a/includes/specials/SpecialPasswordReset.php b/includes/specials/SpecialPasswordReset.php
index d9faacca..3061c85b 100644
--- a/includes/specials/SpecialPasswordReset.php
+++ b/includes/specials/SpecialPasswordReset.php
@@ -62,9 +62,10 @@ class SpecialPasswordReset extends FormSpecialPage {
}
protected function getFormFields() {
- global $wgPasswordResetRoutes, $wgAuth;
+ global $wgAuth;
+ $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
$a = array();
- if ( isset( $wgPasswordResetRoutes['username'] ) && $wgPasswordResetRoutes['username'] ) {
+ if ( isset( $resetRoutes['username'] ) && $resetRoutes['username'] ) {
$a['Username'] = array(
'type' => 'text',
'label-message' => 'passwordreset-username',
@@ -75,14 +76,14 @@ class SpecialPasswordReset extends FormSpecialPage {
}
}
- if ( isset( $wgPasswordResetRoutes['email'] ) && $wgPasswordResetRoutes['email'] ) {
+ if ( isset( $resetRoutes['email'] ) && $resetRoutes['email'] ) {
$a['Email'] = array(
'type' => 'email',
'label-message' => 'passwordreset-email',
);
}
- if ( isset( $wgPasswordResetRoutes['domain'] ) && $wgPasswordResetRoutes['domain'] ) {
+ if ( isset( $resetRoutes['domain'] ) && $resetRoutes['domain'] ) {
$domains = $wgAuth->domainList();
$a['Domain'] = array(
'type' => 'select',
@@ -103,7 +104,7 @@ class SpecialPasswordReset extends FormSpecialPage {
}
public function alterForm( HTMLForm $form ) {
- global $wgPasswordResetRoutes;
+ $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
$form->setDisplayFormat( 'vform' );
// Turn the old-school line around the form off.
@@ -112,14 +113,16 @@ class SpecialPasswordReset extends FormSpecialPage {
// from a FormSpecialPage class.
$form->setWrapperLegend( false );
+ $form->addHiddenFields( $this->getRequest()->getValues( 'returnto', 'returntoquery' ) );
+
$i = 0;
- if ( isset( $wgPasswordResetRoutes['username'] ) && $wgPasswordResetRoutes['username'] ) {
+ if ( isset( $resetRoutes['username'] ) && $resetRoutes['username'] ) {
$i++;
}
- if ( isset( $wgPasswordResetRoutes['email'] ) && $wgPasswordResetRoutes['email'] ) {
+ if ( isset( $resetRoutes['email'] ) && $resetRoutes['email'] ) {
$i++;
}
- if ( isset( $wgPasswordResetRoutes['domain'] ) && $wgPasswordResetRoutes['domain'] ) {
+ if ( isset( $resetRoutes['domain'] ) && $resetRoutes['domain'] ) {
$i++;
}
@@ -133,10 +136,10 @@ class SpecialPasswordReset extends FormSpecialPage {
* Process the form. At this point we know that the user passes all the criteria in
* userCanExecute(), and if the data array contains 'Username', etc, then Username
* resets are allowed.
- * @param $data array
+ * @param array $data
* @throws MWException
* @throws ThrottledError|PermissionsError
- * @return Bool|Array
+ * @return bool|array
*/
public function onSubmit( array $data ) {
global $wgAuth;
@@ -220,19 +223,16 @@ class SpecialPasswordReset extends FormSpecialPage {
// Check against password throttle
foreach ( $users as $user ) {
if ( $user->isPasswordReminderThrottled() ) {
- global $wgPasswordReminderResendTime;
# Round the time in hours to 3 d.p., in case someone is specifying
# minutes or seconds.
return array( array(
'throttled-mailpassword',
- round( $wgPasswordReminderResendTime, 3 )
+ round( $this->getConfig()->get( 'PasswordReminderResendTime' ), 3 )
) );
}
}
- global $wgNewPasswordExpiry;
-
// All the users will have the same email address
if ( $firstUser->getEmail() == '' ) {
// This won't be reachable from the email route, so safe to expose the username
@@ -271,12 +271,12 @@ class SpecialPasswordReset extends FormSpecialPage {
$passwordBlock,
count( $passwords ),
'<' . Title::newMainPage()->getCanonicalURL() . '>',
- round( $wgNewPasswordExpiry / 86400 )
+ round( $this->getConfig()->get( 'NewPasswordExpiry' ) / 86400 )
);
$title = $this->msg( 'passwordreset-emailtitle' );
- $this->result = $firstUser->sendMail( $title->escaped(), $this->email->text() );
+ $this->result = $firstUser->sendMail( $title->text(), $this->email->text() );
if ( isset( $data['Capture'] ) && $data['Capture'] ) {
// Save the user, will be used if an error occurs when sending the email
@@ -318,11 +318,12 @@ class SpecialPasswordReset extends FormSpecialPage {
}
protected function canChangePassword( User $user ) {
- global $wgPasswordResetRoutes, $wgEnableEmail, $wgAuth;
+ global $wgAuth;
+ $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
// Maybe password resets are disabled, or there are no allowable routes
- if ( !is_array( $wgPasswordResetRoutes ) ||
- !in_array( true, array_values( $wgPasswordResetRoutes ) )
+ if ( !is_array( $resetRoutes ) ||
+ !in_array( true, array_values( $resetRoutes ) )
) {
return 'passwordreset-disabled';
}
@@ -333,7 +334,7 @@ class SpecialPasswordReset extends FormSpecialPage {
}
// Maybe email features have been disabled
- if ( !$wgEnableEmail ) {
+ if ( !$this->getConfig()->get( 'EnableEmail' ) ) {
return 'passwordreset-emaildisabled';
}
@@ -348,7 +349,7 @@ class SpecialPasswordReset extends FormSpecialPage {
/**
* Hide the password reset page if resets are disabled.
- * @return Bool
+ * @return bool
*/
function isListed() {
if ( $this->canChangePassword( $this->getUser() ) === true ) {
diff --git a/includes/specials/SpecialPermanentLink.php b/includes/specials/SpecialPermanentLink.php
new file mode 100644
index 00000000..17115e88
--- /dev/null
+++ b/includes/specials/SpecialPermanentLink.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * Redirect from Special:PermanentLink/### to index.php?oldid=###.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * Redirect from Special:PermanentLink/### to index.php?oldid=###.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialPermanentLink extends RedirectSpecialPage {
+ function __construct() {
+ parent::__construct( 'PermanentLink' );
+ $this->mAllowedRedirectParams = array();
+ }
+
+ function getRedirect( $subpage ) {
+ $subpage = intval( $subpage );
+ if ( $subpage === 0 ) {
+ # throw an error page when no subpage was given
+ throw new ErrorPageError( 'nopagetitle', 'nopagetext' );
+ }
+ $this->mAddedRedirectParams['oldid'] = $subpage;
+
+ return true;
+ }
+}
diff --git a/includes/specials/SpecialPreferences.php b/includes/specials/SpecialPreferences.php
index ecee0bb7..cea00fa6 100644
--- a/includes/specials/SpecialPreferences.php
+++ b/includes/specials/SpecialPreferences.php
@@ -37,14 +37,7 @@ class SpecialPreferences extends SpecialPage {
$out = $this->getOutput();
$out->disallowUserJs(); # Prevent hijacked user scripts from sniffing passwords etc.
- $user = $this->getUser();
- if ( $user->isAnon() ) {
- throw new ErrorPageError(
- 'prefsnologin',
- 'prefsnologintext',
- array( $this->getTitle()->getPrefixedDBkey() )
- );
- }
+ $this->requireLogin( 'prefsnologintext2' );
$this->checkReadOnly();
if ( $par == 'reset' ) {
@@ -62,7 +55,7 @@ class SpecialPreferences extends SpecialPage {
);
}
- $htmlForm = Preferences::getFormObject( $user, $this->getContext() );
+ $htmlForm = Preferences::getFormObject( $this->getUser(), $this->getContext() );
$htmlForm->setSubmitCallback( array( 'Preferences', 'tryUISubmit' ) );
$htmlForm->show();
@@ -76,10 +69,11 @@ class SpecialPreferences extends SpecialPage {
$this->getOutput()->addWikiMsg( 'prefs-reset-intro' );
$context = new DerivativeContext( $this->getContext() );
- $context->setTitle( $this->getTitle( 'reset' ) ); // Reset subpage
+ $context->setTitle( $this->getPageTitle( 'reset' ) ); // Reset subpage
$htmlForm = new HTMLForm( array(), $context, 'prefs-restore' );
$htmlForm->setSubmitTextMsg( 'restoreprefs' );
+ $htmlForm->setSubmitDestructive();
$htmlForm->setSubmitCallback( array( $this, 'submitReset' ) );
$htmlForm->suppressReset();
@@ -95,7 +89,7 @@ class SpecialPreferences extends SpecialPage {
$user->resetOptions( 'all', $this->getContext() );
$user->saveSettings();
- $url = $this->getTitle()->getFullURL( 'success' );
+ $url = $this->getPageTitle()->getFullURL( 'success' );
$this->getOutput()->redirect( $url );
diff --git a/includes/specials/SpecialPrefixindex.php b/includes/specials/SpecialPrefixindex.php
index 0d065b09..2e67e2b5 100644
--- a/includes/specials/SpecialPrefixindex.php
+++ b/includes/specials/SpecialPrefixindex.php
@@ -26,7 +26,7 @@
*
* @ingroup SpecialPage
*/
-class SpecialPrefixindex extends SpecialAllpages {
+class SpecialPrefixindex extends SpecialAllPages {
/**
* Whether to remove the searched prefix from the displayed link. Useful
@@ -36,6 +36,9 @@ class SpecialPrefixindex extends SpecialAllpages {
protected $hideRedirects = false;
+ // number of columns in output table
+ protected $columns = 3;
+
// Inherit $maxPerPage
function __construct() {
@@ -44,7 +47,7 @@ class SpecialPrefixindex extends SpecialAllpages {
/**
* Entry point : initialise variables and call subfunctions.
- * @param string $par becomes "FOO" when called like Special:Prefixindex/FOO (default null)
+ * @param string $par Becomes "FOO" when called like Special:Prefixindex/FOO (default null)
*/
function execute( $par ) {
global $wgContLang;
@@ -63,16 +66,17 @@ class SpecialPrefixindex extends SpecialAllpages {
$namespace = (int)$ns; // if no namespace given, use 0 (NS_MAIN).
$this->hideRedirects = $request->getBool( 'hideredirects', $this->hideRedirects );
$this->stripPrefix = $request->getBool( 'stripprefix', $this->stripPrefix );
+ $this->columns = $request->getInt( 'columns', $this->columns );
$namespaces = $wgContLang->getNamespaces();
$out->setPageTitle(
- ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces ) ) )
+ ( $namespace > 0 && array_key_exists( $namespace, $namespaces ) )
? $this->msg( 'prefixindex-namespace', str_replace( '_', ' ', $namespaces[$namespace] ) )
: $this->msg( 'prefixindex' )
);
$showme = '';
- if ( isset( $par ) ) {
+ if ( $par !== null ) {
$showme = $par;
} elseif ( $prefix != '' ) {
$showme = $prefix;
@@ -92,16 +96,14 @@ class SpecialPrefixindex extends SpecialAllpages {
/**
* HTML for the top form
- * @param $namespace Integer: a namespace constant (default NS_MAIN).
- * @param string $from dbKey we are starting listing at.
+ * @param int $namespace A namespace constant (default NS_MAIN).
+ * @param string $from DbKey we are starting listing at.
* @return string
*/
protected function namespacePrefixForm( $namespace = NS_MAIN, $from = '' ) {
- global $wgScript;
-
$out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
- $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
- $out .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() );
+ $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $this->getConfig()->get( 'Script' ) ) );
+ $out .= Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() );
$out .= Xml::openElement( 'fieldset' );
$out .= Xml::element( 'legend', null, $this->msg( 'allpages' )->text() );
$out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) );
@@ -149,9 +151,9 @@ class SpecialPrefixindex extends SpecialAllpages {
}
/**
- * @param $namespace Integer, default NS_MAIN
- * @param $prefix String
- * @param string $from list all pages from this name (default FALSE)
+ * @param int $namespace Default NS_MAIN
+ * @param string $prefix
+ * @param string $from List all pages from this name (default false)
*/
protected function showPrefixChunk( $namespace = NS_MAIN, $prefix, $from = null ) {
global $wgContLang;
@@ -163,10 +165,11 @@ class SpecialPrefixindex extends SpecialAllpages {
$fromList = $this->getNamespaceKeyAndText( $namespace, $from );
$prefixList = $this->getNamespaceKeyAndText( $namespace, $prefix );
$namespaces = $wgContLang->getNamespaces();
+ $res = null;
if ( !$prefixList || !$fromList ) {
$out = $this->msg( 'allpagesbadtitle' )->parseAsBlock();
- } elseif ( !in_array( $namespace, array_keys( $namespaces ) ) ) {
+ } elseif ( !array_key_exists( $namespace, $namespaces ) ) {
// Show errormessage and reset to NS_MAIN
$out = $this->msg( 'allpages-bad-ns', $namespace )->parse();
$namespace = NS_MAIN;
@@ -203,7 +206,7 @@ class SpecialPrefixindex extends SpecialAllpages {
$n = 0;
if ( $res->numRows() > 0 ) {
- $out = Xml::openElement( 'table', array( 'id' => 'mw-prefixindex-list-table' ) );
+ $out = Xml::openElement( 'table', array( 'class' => 'mw-prefixindex-list-table' ) );
$prefixLength = strlen( $prefix );
while ( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
@@ -224,17 +227,17 @@ class SpecialPrefixindex extends SpecialAllpages {
} else {
$link = '[[' . htmlspecialchars( $s->page_title ) . ']]';
}
- if ( $n % 3 == 0 ) {
+ if ( $n % $this->columns == 0 ) {
$out .= '<tr>';
}
$out .= "<td>$link</td>";
$n++;
- if ( $n % 3 == 0 ) {
+ if ( $n % $this->columns == 0 ) {
$out .= '</tr>';
}
}
- if ( $n % 3 != 0 ) {
+ if ( $n % $this->columns != 0 ) {
$out .= '</tr>';
}
@@ -249,7 +252,7 @@ class SpecialPrefixindex extends SpecialAllpages {
$out2 = '';
} else {
$nsForm = $this->namespacePrefixForm( $namespace, $prefix );
- $self = $this->getTitle();
+ $self = $this->getPageTitle();
$out2 = Xml::openElement( 'table', array( 'id' => 'mw-prefixindex-nav-table' ) ) .
'<tr>
<td>' .
@@ -257,14 +260,13 @@ class SpecialPrefixindex extends SpecialAllpages {
'</td>
<td id="mw-prefixindex-nav-form" class="mw-prefixindex-nav">';
- if ( isset( $res ) && $res && ( $n == $this->maxPerPage ) &&
- ( $s = $res->fetchObject() )
- ) {
+ if ( $res && ( $n == $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
$query = array(
'from' => $s->page_title,
'prefix' => $prefix,
'hideredirects' => $this->hideRedirects,
'stripprefix' => $this->stripPrefix,
+ 'columns' => $this->columns,
);
if ( $namespace || $prefix == '' ) {
diff --git a/includes/specials/SpecialProtectedpages.php b/includes/specials/SpecialProtectedpages.php
index 3de6ea24..0ba73857 100644
--- a/includes/specials/SpecialProtectedpages.php
+++ b/includes/specials/SpecialProtectedpages.php
@@ -27,7 +27,6 @@
* @ingroup SpecialPage
*/
class SpecialProtectedpages extends SpecialPage {
-
protected $IdLevel = 'level';
protected $IdType = 'type';
@@ -38,6 +37,7 @@ class SpecialProtectedpages extends SpecialPage {
public function execute( $par ) {
$this->setHeaders();
$this->outputHeader();
+ $this->getOutput()->addModuleStyles( 'mediawiki.special' );
// Purge expired entries on one in every 10 queries
if ( !mt_rand( 0, 10 ) ) {
@@ -52,6 +52,7 @@ class SpecialProtectedpages extends SpecialPage {
$ns = $request->getIntOrNull( 'namespace' );
$indefOnly = $request->getBool( 'indefonly' ) ? 1 : 0;
$cascadeOnly = $request->getBool( 'cascadeonly' ) ? 1 : 0;
+ $noRedirect = $request->getBool( 'noredirect' ) ? 1 : 0;
$pager = new ProtectedPagesPager(
$this,
@@ -62,7 +63,8 @@ class SpecialProtectedpages extends SpecialPage {
$sizetype,
$size,
$indefOnly,
- $cascadeOnly
+ $cascadeOnly,
+ $noRedirect
);
$this->getOutput()->addHTML( $this->showOptions(
@@ -72,137 +74,34 @@ class SpecialProtectedpages extends SpecialPage {
$sizetype,
$size,
$indefOnly,
- $cascadeOnly
+ $cascadeOnly,
+ $noRedirect
) );
if ( $pager->getNumRows() ) {
- $this->getOutput()->addHTML(
- $pager->getNavigationBar() .
- '<ul>' . $pager->getBody() . '</ul>' .
- $pager->getNavigationBar()
- );
+ $this->getOutput()->addParserOutputContent( $pager->getFullOutput() );
} else {
$this->getOutput()->addWikiMsg( 'protectedpagesempty' );
}
}
/**
- * Callback function to output a restriction
- * @param Title $row Protected title
- * @return string Formatted "<li>" element
- */
- public function formatRow( $row ) {
- wfProfileIn( __METHOD__ );
-
- static $infinity = null;
-
- if ( is_null( $infinity ) ) {
- $infinity = wfGetDB( DB_SLAVE )->getInfinity();
- }
-
- $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
- if ( !$title ) {
- wfProfileOut( __METHOD__ );
-
- return Html::rawElement(
- 'li',
- array(),
- Html::element(
- 'span',
- array( 'class' => 'mw-invalidtitle' ),
- Linker::getInvalidTitleDescription(
- $this->getContext(),
- $row->page_namespace,
- $row->page_title
- )
- )
- ) . "\n";
- }
-
- $link = Linker::link( $title );
-
- $description_items = array();
-
- // Messages: restriction-level-sysop, restriction-level-autoconfirmed
- $protType = $this->msg( 'restriction-level-' . $row->pr_level )->escaped();
-
- $description_items[] = $protType;
-
- if ( $row->pr_cascade ) {
- $description_items[] = $this->msg( 'protect-summary-cascade' )->text();
- }
-
- $stxt = '';
- $lang = $this->getLanguage();
-
- $expiry = $lang->formatExpiry( $row->pr_expiry, TS_MW );
- if ( $expiry != $infinity ) {
- $user = $this->getUser();
- $description_items[] = $this->msg(
- 'protect-expiring-local',
- $lang->userTimeAndDate( $expiry, $user ),
- $lang->userDate( $expiry, $user ),
- $lang->userTime( $expiry, $user )
- )->escaped();
- }
-
- if ( !is_null( $size = $row->page_len ) ) {
- $stxt = $lang->getDirMark() . ' ' . Linker::formatRevisionSize( $size );
- }
-
- // Show a link to the change protection form for allowed users otherwise
- // a link to the protection log
- if ( $this->getUser()->isAllowed( 'protect' ) ) {
- $changeProtection = Linker::linkKnown(
- $title,
- $this->msg( 'protect_change' )->escaped(),
- array(),
- array( 'action' => 'unprotect' )
- );
- } else {
- $ltitle = SpecialPage::getTitleFor( 'Log' );
- $changeProtection = Linker::linkKnown(
- $ltitle,
- $this->msg( 'protectlogpage' )->escaped(),
- array(),
- array(
- 'type' => 'protect',
- 'page' => $title->getPrefixedText()
- )
- );
- }
-
- $changeProtection = ' ' . $this->msg( 'parentheses' )->rawParams( $changeProtection )
- ->escaped();
-
- wfProfileOut( __METHOD__ );
-
- return Html::rawElement(
- 'li',
- array(),
- $lang->specialList( $link . $stxt, $lang->commaList( $description_items ), false ) .
- $changeProtection
- ) . "\n";
- }
-
- /**
- * @param $namespace Integer
- * @param string $type restriction type
- * @param string $level restriction level
+ * @param int $namespace
+ * @param string $type Restriction type
+ * @param string $level Restriction level
* @param string $sizetype "min" or "max"
- * @param $size Integer
- * @param $indefOnly Boolean: only indefinie protection
- * @param $cascadeOnly Boolean: only cascading protection
- * @return String: input form
+ * @param int $size
+ * @param bool $indefOnly Only indefinite protection
+ * @param bool $cascadeOnly Only cascading protection
+ * @param bool $noRedirect Don't show redirects
+ * @return string Input form
*/
protected function showOptions( $namespace, $type = 'edit', $level, $sizetype,
- $size, $indefOnly, $cascadeOnly
+ $size, $indefOnly, $cascadeOnly, $noRedirect
) {
- global $wgScript;
+ $title = $this->getPageTitle();
- $title = $this->getTitle();
-
- return Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) .
+ return Xml::openElement( 'form', array( 'method' => 'get', 'action' => wfScript() ) ) .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', array(), $this->msg( 'protectedpages' )->text() ) .
Html::hidden( 'title', $title->getPrefixedDBkey() ) . "\n" .
@@ -212,6 +111,7 @@ class SpecialProtectedpages extends SpecialPage {
"<br /><span style='white-space: nowrap'>" .
$this->getExpiryCheck( $indefOnly ) . "&#160;\n" .
$this->getCascadeCheck( $cascadeOnly ) . "&#160;\n" .
+ $this->getRedirectCheck( $noRedirect ) . "&#160;\n" .
"</span><br /><span style='white-space: nowrap'>" .
$this->getSizeLimit( $sizetype, $size ) . "&#160;\n" .
"</span>" .
@@ -224,8 +124,8 @@ class SpecialProtectedpages extends SpecialPage {
* Prepare the namespace filter drop-down; standard namespace
* selector, sans the MediaWiki namespace
*
- * @param $namespace Mixed: pre-select namespace
- * @return String
+ * @param string|null $namespace Pre-select namespace
+ * @return string
*/
protected function getNamespaceMenu( $namespace = null ) {
return Html::rawElement( 'span', array( 'style' => 'white-space: nowrap;' ),
@@ -270,6 +170,19 @@ class SpecialProtectedpages extends SpecialPage {
}
/**
+ * @param bool $noRedirect
+ * @return string Formatted HTML
+ */
+ protected function getRedirectCheck( $noRedirect ) {
+ return Xml::checkLabel(
+ $this->msg( 'protectedpages-noredirect' )->text(),
+ 'noredirect',
+ 'noredirect',
+ $noRedirect
+ ) . "\n";
+ }
+
+ /**
* @param string $sizetype "min" or "max"
* @param mixed $size
* @return string Formatted HTML
@@ -300,7 +213,7 @@ class SpecialProtectedpages extends SpecialPage {
/**
* Creates the input label of the restriction type
- * @param $pr_type string Protection type
+ * @param string $pr_type Protection type
* @return string Formatted HTML
*/
protected function getTypeMenu( $pr_type ) {
@@ -329,18 +242,16 @@ class SpecialProtectedpages extends SpecialPage {
/**
* Creates the input label of the restriction level
- * @param $pr_level string Protection level
+ * @param string $pr_level Protection level
* @return string Formatted HTML
*/
protected function getLevelMenu( $pr_level ) {
- global $wgRestrictionLevels;
-
// Temporary array
$m = array( $this->msg( 'restriction-level-all' )->text() => 0 );
$options = array();
// First pass to load the log names
- foreach ( $wgRestrictionLevels as $type ) {
+ foreach ( $this->getConfig()->get( 'RestrictionLevels' ) as $type ) {
// Messages used can be 'restriction-level-sysop' and 'restriction-level-autoconfirmed'
if ( $type != '' && $type != '*' ) {
$text = $this->msg( "restriction-level-$type" )->text();
@@ -370,12 +281,12 @@ class SpecialProtectedpages extends SpecialPage {
* @todo document
* @ingroup Pager
*/
-class ProtectedPagesPager extends AlphabeticPager {
+class ProtectedPagesPager extends TablePager {
public $mForm, $mConds;
- private $type, $level, $namespace, $sizetype, $size, $indefonly;
+ private $type, $level, $namespace, $sizetype, $size, $indefonly, $cascadeonly, $noredirect;
function __construct( $form, $conds = array(), $type, $level, $namespace,
- $sizetype = '', $size = 0, $indefonly = false, $cascadeonly = false
+ $sizetype = '', $size = 0, $indefonly = false, $cascadeonly = false, $noredirect = false
) {
$this->mForm = $form;
$this->mConds = $conds;
@@ -386,28 +297,203 @@ class ProtectedPagesPager extends AlphabeticPager {
$this->size = intval( $size );
$this->indefonly = (bool)$indefonly;
$this->cascadeonly = (bool)$cascadeonly;
+ $this->noredirect = (bool)$noredirect;
parent::__construct( $form->getContext() );
}
- function getStartBody() {
+ function preprocessResults( $result ) {
# Do a link batch query
$lb = new LinkBatch;
- foreach ( $this->mResult as $row ) {
+ $userids = array();
+
+ foreach ( $result as $row ) {
$lb->add( $row->page_namespace, $row->page_title );
+ // field is nullable, maybe null on old protections
+ if ( $row->log_user !== null ) {
+ $userids[] = $row->log_user;
+ }
+ }
+
+ // fill LinkBatch with user page and user talk
+ if ( count( $userids ) ) {
+ $userCache = UserCache::singleton();
+ $userCache->doQuery( $userids, array(), __METHOD__ );
+ foreach ( $userids as $userid ) {
+ $name = $userCache->getProp( $userid, 'name' );
+ if ( $name !== false ) {
+ $lb->add( NS_USER, $name );
+ $lb->add( NS_USER_TALK, $name );
+ }
+ }
}
+
$lb->execute();
+ }
+
+ function getFieldNames() {
+ static $headers = null;
+
+ if ( $headers == array() ) {
+ $headers = array(
+ 'log_timestamp' => 'protectedpages-timestamp',
+ 'pr_page' => 'protectedpages-page',
+ 'pr_expiry' => 'protectedpages-expiry',
+ 'log_user' => 'protectedpages-performer',
+ 'pr_params' => 'protectedpages-params',
+ 'log_comment' => 'protectedpages-reason',
+ );
+ foreach ( $headers as $key => $val ) {
+ $headers[$key] = $this->msg( $val )->text();
+ }
+ }
- return '';
+ return $headers;
}
- function formatRow( $row ) {
- return $this->mForm->formatRow( $row );
+ /**
+ * @param string $field
+ * @param string $value
+ * @return string
+ * @throws MWException
+ */
+ function formatValue( $field, $value ) {
+ /** @var $row object */
+ $row = $this->mCurrentRow;
+
+ $formatted = '';
+
+ switch ( $field ) {
+ case 'log_timestamp':
+ // when timestamp is null, this is a old protection row
+ if ( $value === null ) {
+ $formatted = Html::rawElement(
+ 'span',
+ array( 'class' => 'mw-protectedpages-unknown' ),
+ $this->msg( 'protectedpages-unknown-timestamp' )->escaped()
+ );
+ } else {
+ $formatted = $this->getLanguage()->userTimeAndDate( $value, $this->getUser() );
+ }
+ break;
+
+ case 'pr_page':
+ $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
+ if ( !$title ) {
+ $formatted = Html::element(
+ 'span',
+ array( 'class' => 'mw-invalidtitle' ),
+ Linker::getInvalidTitleDescription(
+ $this->getContext(),
+ $row->page_namespace,
+ $row->page_title
+ )
+ );
+ } else {
+ $formatted = Linker::link( $title );
+ }
+ if ( !is_null( $row->page_len ) ) {
+ $formatted .= $this->getLanguage()->getDirMark() .
+ ' ' . Html::rawElement(
+ 'span',
+ array( 'class' => 'mw-protectedpages-length' ),
+ Linker::formatRevisionSize( $row->page_len )
+ );
+ }
+ break;
+
+ case 'pr_expiry':
+ $formatted = $this->getLanguage()->formatExpiry( $value, /* User preference timezone */true );
+ $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
+ if ( $this->getUser()->isAllowed( 'protect' ) && $title ) {
+ $changeProtection = Linker::linkKnown(
+ $title,
+ $this->msg( 'protect_change' )->escaped(),
+ array(),
+ array( 'action' => 'unprotect' )
+ );
+ $formatted .= ' ' . Html::rawElement(
+ 'span',
+ array( 'class' => 'mw-protectedpages-actions' ),
+ $this->msg( 'parentheses' )->rawParams( $changeProtection )->escaped()
+ );
+ }
+ break;
+
+ case 'log_user':
+ // when timestamp is null, this is a old protection row
+ if ( $row->log_timestamp === null ) {
+ $formatted = Html::rawElement(
+ 'span',
+ array( 'class' => 'mw-protectedpages-unknown' ),
+ $this->msg( 'protectedpages-unknown-performer' )->escaped()
+ );
+ } else {
+ $username = UserCache::singleton()->getProp( $value, 'name' );
+ if ( LogEventsList::userCanBitfield(
+ $row->log_deleted,
+ LogPage::DELETED_USER,
+ $this->getUser()
+ ) ) {
+ if ( $username === false ) {
+ $formatted = htmlspecialchars( $value );
+ } else {
+ $formatted = Linker::userLink( $value, $username )
+ . Linker::userToolLinks( $value, $username );
+ }
+ } else {
+ $formatted = $this->msg( 'rev-deleted-user' )->escaped();
+ }
+ if ( LogEventsList::isDeleted( $row, LogPage::DELETED_USER ) ) {
+ $formatted = '<span class="history-deleted">' . $formatted . '</span>';
+ }
+ }
+ break;
+
+ case 'pr_params':
+ $params = array();
+ // Messages: restriction-level-sysop, restriction-level-autoconfirmed
+ $params[] = $this->msg( 'restriction-level-' . $row->pr_level )->escaped();
+ if ( $row->pr_cascade ) {
+ $params[] = $this->msg( 'protect-summary-cascade' )->text();
+ }
+ $formatted = $this->getLanguage()->commaList( $params );
+ break;
+
+ case 'log_comment':
+ // when timestamp is null, this is an old protection row
+ if ( $row->log_timestamp === null ) {
+ $formatted = Html::rawElement(
+ 'span',
+ array( 'class' => 'mw-protectedpages-unknown' ),
+ $this->msg( 'protectedpages-unknown-reason' )->escaped()
+ );
+ } else {
+ if ( LogEventsList::userCanBitfield(
+ $row->log_deleted,
+ LogPage::DELETED_COMMENT,
+ $this->getUser()
+ ) ) {
+ $formatted = Linker::formatComment( $value !== null ? $value : '' );
+ } else {
+ $formatted = $this->msg( 'rev-deleted-comment' )->escaped();
+ }
+ if ( LogEventsList::isDeleted( $row, LogPage::DELETED_COMMENT ) ) {
+ $formatted = '<span class="history-deleted">' . $formatted . '</span>';
+ }
+ }
+ break;
+
+ default:
+ throw new MWException( "Unknown field '$field'" );
+ }
+
+ return $formatted;
}
function getQueryInfo() {
$conds = $this->mConds;
- $conds[] = '(pr_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() ) .
- 'OR pr_expiry IS NULL)';
+ $conds[] = 'pr_expiry > ' . $this->mDb->addQuotes( $this->mDb->timestamp() ) .
+ 'OR pr_expiry IS NULL';
$conds[] = 'page_id=pr_page';
$conds[] = 'pr_type=' . $this->mDb->addQuotes( $this->type );
@@ -424,6 +510,9 @@ class ProtectedPagesPager extends AlphabeticPager {
if ( $this->cascadeonly ) {
$conds[] = 'pr_cascade = 1';
}
+ if ( $this->noredirect ) {
+ $conds[] = 'page_is_redirect = 0';
+ }
if ( $this->level ) {
$conds[] = 'pr_level=' . $this->mDb->addQuotes( $this->level );
@@ -433,14 +522,51 @@ class ProtectedPagesPager extends AlphabeticPager {
}
return array(
- 'tables' => array( 'page_restrictions', 'page' ),
- 'fields' => array( 'pr_id', 'page_namespace', 'page_title', 'page_len',
- 'pr_type', 'pr_level', 'pr_expiry', 'pr_cascade' ),
- 'conds' => $conds
+ 'tables' => array( 'page', 'page_restrictions', 'log_search', 'logging' ),
+ 'fields' => array(
+ 'pr_id',
+ 'page_namespace',
+ 'page_title',
+ 'page_len',
+ 'pr_type',
+ 'pr_level',
+ 'pr_expiry',
+ 'pr_cascade',
+ 'log_timestamp',
+ 'log_user',
+ 'log_comment',
+ 'log_deleted',
+ ),
+ 'conds' => $conds,
+ 'join_conds' => array(
+ 'log_search' => array(
+ 'LEFT JOIN', array(
+ 'ls_field' => 'pr_id', 'ls_value = pr_id'
+ )
+ ),
+ 'logging' => array(
+ 'LEFT JOIN', array(
+ 'ls_log_id = log_id'
+ )
+ )
+ )
);
}
+ public function getTableClass() {
+ return parent::getTableClass() . ' mw-protectedpages';
+ }
+
function getIndexField() {
return 'pr_id';
}
+
+ function getDefaultSort() {
+ return 'pr_id';
+ }
+
+ function isFieldSortable( $field ) {
+ // no index for sorting exists
+ return false;
+ }
}
diff --git a/includes/specials/SpecialProtectedtitles.php b/includes/specials/SpecialProtectedtitles.php
index 078e7b12..a40da87d 100644
--- a/includes/specials/SpecialProtectedtitles.php
+++ b/includes/specials/SpecialProtectedtitles.php
@@ -126,16 +126,15 @@ class SpecialProtectedtitles extends SpecialPage {
}
/**
- * @param $namespace Integer:
- * @param $type string
- * @param $level string
+ * @param int $namespace
+ * @param string $type
+ * @param string $level
* @return string
* @private
*/
function showOptions( $namespace, $type = 'edit', $level ) {
- global $wgScript;
- $action = htmlspecialchars( $wgScript );
- $title = $this->getTitle();
+ $action = htmlspecialchars( wfScript() );
+ $title = $this->getPageTitle();
$special = htmlspecialchars( $title->getPrefixedDBkey() );
return "<form action=\"$action\" method=\"get\">\n" .
@@ -152,7 +151,7 @@ class SpecialProtectedtitles extends SpecialPage {
* Prepare the namespace filter drop-down; standard namespace
* selector, sans the MediaWiki namespace
*
- * @param $namespace Mixed: pre-select namespace
+ * @param string|null $namespace Pre-select namespace
* @return string
*/
function getNamespaceMenu( $namespace = null ) {
@@ -175,14 +174,12 @@ class SpecialProtectedtitles extends SpecialPage {
* @private
*/
function getLevelMenu( $pr_level ) {
- global $wgRestrictionLevels;
-
// Temporary array
$m = array( $this->msg( 'restriction-level-all' )->text() => 0 );
$options = array();
// First pass to load the log names
- foreach ( $wgRestrictionLevels as $type ) {
+ foreach ( $this->getConfig()->get( 'RestrictionLevels' ) as $type ) {
if ( $type != '' && $type != '*' ) {
// Messages: restriction-level-sysop, restriction-level-autoconfirmed
$text = $this->msg( "restriction-level-$type" )->text();
diff --git a/includes/specials/SpecialRandomInCategory.php b/includes/specials/SpecialRandomInCategory.php
index 0e022bfa..570ab3bf 100644
--- a/includes/specials/SpecialRandomInCategory.php
+++ b/includes/specials/SpecialRandomInCategory.php
@@ -46,7 +46,7 @@
*
* @ingroup SpecialPage
*/
-class SpecialRandomInCategory extends SpecialPage {
+class SpecialRandomInCategory extends FormSpecialPage {
protected $extra = array(); // Extra SQL statements
protected $category = false; // Title object of category
protected $maxOffset = 30; // Max amount to fudge randomness by.
@@ -67,12 +67,35 @@ class SpecialRandomInCategory extends SpecialPage {
$this->minTimestamp = null;
}
- public function execute( $par ) {
- global $wgScript;
+ protected function getFormFields() {
+ $form = array(
+ 'category' => array(
+ 'type' => 'text',
+ 'label-message' => 'randomincategory-category',
+ 'required' => true,
+ )
+ );
+
+ return $form;
+ }
+
+ public function requiresWrite() {
+ return false;
+ }
+
+ public function requiresUnblock() {
+ return false;
+ }
+ protected function setParameter( $par ) {
+ // if subpage present, fake form submission
+ $this->onSubmit( array( 'category' => $par ) );
+ }
+
+ public function onSubmit( array $data ) {
$cat = false;
- $categoryStr = $this->getRequest()->getText( 'category', $par );
+ $categoryStr = $data['category'];
if ( $categoryStr ) {
$cat = Title::newFromText( $categoryStr, NS_CATEGORY );
@@ -87,48 +110,31 @@ class SpecialRandomInCategory extends SpecialPage {
$this->setCategory( $cat );
}
-
if ( !$this->category && $categoryStr ) {
- $this->setHeaders();
- $this->getOutput()->addWikiMsg( 'randomincategory-invalidcategory',
+ $msg = $this->msg( 'randomincategory-invalidcategory',
wfEscapeWikiText( $categoryStr ) );
- return;
+ return Status::newFatal( $msg );
+
} elseif ( !$this->category ) {
- $this->setHeaders();
- $input = Html::input( 'category' );
- $submitText = $this->msg( 'randomincategory-selectcategory-submit' )->text();
- $submit = Html::input( '', $submitText, 'submit' );
-
- $msg = $this->msg( 'randomincategory-selectcategory' );
- $form = Html::rawElement( 'form', array( 'action' => $wgScript ),
- Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
- $msg->rawParams( $input, $submit )->parse()
- );
- $this->getOutput()->addHtml( $form );
-
- return;
+ return; // no data sent
}
$title = $this->getRandomTitle();
if ( is_null( $title ) ) {
- $this->setHeaders();
- $this->getOutput()->addWikiMsg( 'randomincategory-nopages',
+ $msg = $this->msg( 'randomincategory-nopages',
$this->category->getText() );
- return;
+ return Status::newFatal( $msg );
}
- $query = $this->getRequest()->getValues();
- unset( $query['title'] );
- unset( $query['category'] );
- $this->getOutput()->redirect( $title->getFullURL( $query ) );
+ $this->getOutput()->redirect( $title->getFullURL() );
}
/**
* Choose a random title.
- * @return Title object (or null if nothing to choose from)
+ * @return Title|null Title object (or null if nothing to choose from)
*/
public function getRandomTitle() {
// Convert to float, since we do math with the random number.
@@ -178,7 +184,7 @@ class SpecialRandomInCategory extends SpecialPage {
* was a large gap in the distribution of cl_timestamp values. This way instead
* of things to the right of the gap being favoured, both sides of the gap
* are favoured.
- * @return Array Query information.
+ * @return array Query information.
*/
protected function getQueryInfo( $rand, $offset, $up ) {
$op = $up ? '>=' : '<=';
@@ -208,6 +214,7 @@ class SpecialRandomInCategory extends SpecialPage {
$qi['conds'][] = 'cl_timestamp ' . $op . ' ' .
$dbr->addQuotes( $dbr->timestamp( $minClTime ) );
}
+
return $qi;
}
@@ -230,6 +237,7 @@ class SpecialRandomInCategory extends SpecialPage {
}
$ts = ( $this->maxTimestamp - $this->minTimestamp ) * $rand + $this->minTimestamp;
+
return intval( $ts );
}
@@ -237,8 +245,8 @@ class SpecialRandomInCategory extends SpecialPage {
* Get the lowest and highest timestamp for a category.
*
* @param Title $category
- * @return Array The lowest and highest timestamp
- * @throws MWException if category has no entries.
+ * @return array The lowest and highest timestamp
+ * @throws MWException If category has no entries.
*/
protected function getMinAndMaxForCat( Title $category ) {
$dbr = wfGetDB( DB_SLAVE );
@@ -259,6 +267,7 @@ class SpecialRandomInCategory extends SpecialPage {
if ( !$res ) {
throw new MWException( 'No entries in category' );
}
+
return array( wfTimestamp( TS_UNIX, $res->low ), wfTimestamp( TS_UNIX, $res->high ) );
}
@@ -266,8 +275,8 @@ class SpecialRandomInCategory extends SpecialPage {
* @param float $rand A random number that is converted to a random timestamp
* @param int $offset A small offset to make the result seem more "random"
* @param bool $up Get the result above the random value
- * @param String $fname The name of the calling method
- * @return Array Info for the title selected.
+ * @param string $fname The name of the calling method
+ * @return array Info for the title selected.
*/
private function selectRandomPageFromDB( $rand, $offset, $up, $fname = __METHOD__ ) {
$dbr = wfGetDB( DB_SLAVE );
diff --git a/includes/specials/SpecialRandompage.php b/includes/specials/SpecialRandompage.php
index c94d2b35..6d8f59b5 100644
--- a/includes/specials/SpecialRandompage.php
+++ b/includes/specials/SpecialRandompage.php
@@ -56,7 +56,9 @@ class RandomPage extends SpecialPage {
public function execute( $par ) {
global $wgContLang;
- if ( $par ) {
+ if ( is_string( $par ) ) {
+ // Testing for stringiness since we want to catch
+ // the empty string to mean main namespace only.
$this->setNamespace( $wgContLang->getNsIndex( $par ) );
}
@@ -80,7 +82,7 @@ class RandomPage extends SpecialPage {
/**
* Get a comma-delimited list of namespaces we don't have
* any pages in
- * @return String
+ * @return string
*/
private function getNsList() {
global $wgContLang;
@@ -98,7 +100,7 @@ class RandomPage extends SpecialPage {
/**
* Choose a random title.
- * @return Title object (or null if nothing to choose from)
+ * @return Title|null Title object (or null if nothing to choose from)
*/
public function getRandomTitle() {
$randstr = wfRandom();
@@ -144,7 +146,6 @@ class RandomPage extends SpecialPage {
), $this->extra ),
'options' => array(
'ORDER BY' => 'page_random',
- 'USE INDEX' => 'page_random',
'LIMIT' => 1,
),
'join_conds' => array()
diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php
index a42a2171..e6d8f1c3 100644
--- a/includes/specials/SpecialRecentchanges.php
+++ b/includes/specials/SpecialRecentchanges.php
@@ -26,12 +26,38 @@
*
* @ingroup SpecialPage
*/
-class SpecialRecentChanges extends IncludableSpecialPage {
- var $rcOptions, $rcSubpage;
- protected $customFilters;
+class SpecialRecentChanges extends ChangesListSpecialPage {
+ // @codingStandardsIgnoreStart Needed "useless" override to change parameters.
+ public function __construct( $name = 'Recentchanges', $restriction = '' ) {
+ parent::__construct( $name, $restriction );
+ }
+ // @codingStandardsIgnoreEnd
+
+ /**
+ * Main execution point
+ *
+ * @param string $subpage
+ */
+ public function execute( $subpage ) {
+ // Backwards-compatibility: redirect to new feed URLs
+ $feedFormat = $this->getRequest()->getVal( 'feed' );
+ if ( !$this->including() && $feedFormat ) {
+ $query = $this->getFeedQuery();
+ $query['feedformat'] = $feedFormat === 'atom' ? 'atom' : 'rss';
+ $this->getOutput()->redirect( wfAppendQuery( wfScript( 'api' ), $query ) );
+
+ return;
+ }
+
+ // 10 seconds server-side caching max
+ $this->getOutput()->setSquidMaxage( 10 );
+ // Check if the client has a cached version
+ $lastmod = $this->checkLastModified();
+ if ( $lastmod === false ) {
+ return;
+ }
- public function __construct( $name = 'Recentchanges' ) {
- parent::__construct( $name );
+ parent::execute( $subpage );
}
/**
@@ -40,7 +66,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* @return FormOptions
*/
public function getDefaultOptions() {
- $opts = new FormOptions();
+ $opts = parent::getDefaultOptions();
$user = $this->getUser();
$opts->add( 'days', $user->getIntOption( 'rcdays' ) );
@@ -54,10 +80,6 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$opts->add( 'hidepatrolled', $user->getBoolOption( 'hidepatrolled' ) );
$opts->add( 'hidemyself', false );
- $opts->add( 'namespace', '', FormOptions::INTNULL );
- $opts->add( 'invert', false );
- $opts->add( 'associated', false );
-
$opts->add( 'categories', '' );
$opts->add( 'categories_any', false );
$opts->add( 'tagfilter', '' );
@@ -66,148 +88,21 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
/**
- * Create a FormOptions object with options as specified by the user
- *
- * @param array $parameters
- *
- * @return FormOptions
- */
- public function setup( $parameters ) {
- $opts = $this->getDefaultOptions();
-
- foreach ( $this->getCustomFilters() as $key => $params ) {
- $opts->add( $key, $params['default'] );
- }
-
- $opts->fetchValuesFromRequest( $this->getRequest() );
-
- // Give precedence to subpage syntax
- if ( $parameters !== null ) {
- $this->parseParameters( $parameters, $opts );
- }
-
- $opts->validateIntBounds( 'limit', 0, 5000 );
-
- return $opts;
- }
-
- /**
* Get custom show/hide filters
*
* @return array Map of filter URL param names to properties (msg/default)
*/
protected function getCustomFilters() {
if ( $this->customFilters === null ) {
- $this->customFilters = array();
- wfRunHooks( 'SpecialRecentChangesFilters', array( $this, &$this->customFilters ) );
+ $this->customFilters = parent::getCustomFilters();
+ wfRunHooks( 'SpecialRecentChangesFilters', array( $this, &$this->customFilters ), '1.23' );
}
return $this->customFilters;
}
/**
- * Create a FormOptions object specific for feed requests and return it
- *
- * @return FormOptions
- */
- public function feedSetup() {
- global $wgFeedLimit;
- $opts = $this->getDefaultOptions();
- $opts->fetchValuesFromRequest( $this->getRequest() );
- $opts->validateIntBounds( 'limit', 0, $wgFeedLimit );
-
- return $opts;
- }
-
- /**
- * Get the current FormOptions for this request
- */
- public function getOptions() {
- if ( $this->rcOptions === null ) {
- if ( $this->including() ) {
- $isFeed = false;
- } else {
- $isFeed = (bool)$this->getRequest()->getVal( 'feed' );
- }
- $this->rcOptions = $isFeed ? $this->feedSetup() : $this->setup( $this->rcSubpage );
- }
-
- return $this->rcOptions;
- }
-
- /**
- * Main execution point
- *
- * @param string $subpage
- */
- public function execute( $subpage ) {
- $this->rcSubpage = $subpage;
- $feedFormat = $this->including() ? null : $this->getRequest()->getVal( 'feed' );
-
- # 10 seconds server-side caching max
- $this->getOutput()->setSquidMaxage( 10 );
- # Check if the client has a cached version
- $lastmod = $this->checkLastModified( $feedFormat );
- if ( $lastmod === false ) {
- return;
- }
-
- $opts = $this->getOptions();
- $this->setHeaders();
- $this->outputHeader();
- $this->addModules();
-
- // Fetch results, prepare a batch link existence check query
- $conds = $this->buildMainQueryConds( $opts );
- $rows = $this->doMainQuery( $conds, $opts );
- if ( $rows === false ) {
- if ( !$this->including() ) {
- $this->doHeader( $opts );
- }
-
- return;
- }
-
- if ( !$feedFormat ) {
- $batch = new LinkBatch;
- foreach ( $rows as $row ) {
- $batch->add( NS_USER, $row->rc_user_text );
- $batch->add( NS_USER_TALK, $row->rc_user_text );
- $batch->add( $row->rc_namespace, $row->rc_title );
- }
- $batch->execute();
- }
- if ( $feedFormat ) {
- list( $changesFeed, $formatter ) = $this->getFeedObject( $feedFormat );
- /** @var ChangesFeed $changesFeed */
- $changesFeed->execute( $formatter, $rows, $lastmod, $opts );
- } else {
- $this->webOutput( $rows, $opts );
- }
-
- $rows->free();
- }
-
- /**
- * Return an array with a ChangesFeed object and ChannelFeed object
- *
- * @param string $feedFormat Feed's format (either 'rss' or 'atom')
- * @return array
- */
- public function getFeedObject( $feedFormat ) {
- $changesFeed = new ChangesFeed( $feedFormat, 'rcfeed' );
- $formatter = $changesFeed->getFeedObject(
- $this->msg( 'recentchanges' )->inContentLanguage()->text(),
- $this->msg( 'recentchanges-feed-description' )->inContentLanguage()->text(),
- $this->getTitle()->getFullURL()
- );
-
- return array( $changesFeed, $formatter );
- }
-
- /**
- * Process $par and put options found if $opts
- * Mainly used when including the page
+ * Process $par and put options found in $opts. Used when including the page.
*
* @param string $par
* @param FormOptions $opts
@@ -257,25 +152,9 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
}
- /**
- * Get last modified date, for client caching
- * Don't use this if we are using the patrol feature, patrol changes don't
- * update the timestamp
- *
- * @param string $feedFormat
- * @return string|bool
- */
- public function checkLastModified( $feedFormat ) {
- $dbr = wfGetDB( DB_SLAVE );
- $lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', false, __METHOD__ );
- if ( $feedFormat || !$this->getUser()->useRCPatrol() ) {
- if ( $lastmod && $this->getOutput()->checkLastModified( $lastmod ) ) {
- # Client cache fresh and headers sent, nothing more to do.
- return false;
- }
- }
-
- return $lastmod;
+ public function validateOptions( FormOptions $opts ) {
+ $opts->validateIntBounds( 'limit', 0, 5000 );
+ parent::validateOptions( $opts );
}
/**
@@ -285,21 +164,8 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* @return array
*/
public function buildMainQueryConds( FormOptions $opts ) {
- $dbr = wfGetDB( DB_SLAVE );
- $conds = array();
-
- # It makes no sense to hide both anons and logged-in users
- # Where this occurs, force anons to be shown
- $forcebot = false;
- if ( $opts['hideanons'] && $opts['hideliu'] ) {
- # Check if the user wants to show bots only
- if ( $opts['hidebots'] ) {
- $opts['hideanons'] = false;
- } else {
- $forcebot = true;
- $opts['hidebots'] = false;
- }
- }
+ $dbr = $this->getDB();
+ $conds = parent::buildMainQueryConds( $opts );
// Calculate cutoff
$cutoff_unixtime = time() - ( $opts['days'] * 86400 );
@@ -315,59 +181,6 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$conds[] = 'rc_timestamp >= ' . $dbr->addQuotes( $cutoff );
- $hidePatrol = $this->getUser()->useRCPatrol() && $opts['hidepatrolled'];
- $hideLoggedInUsers = $opts['hideliu'] && !$forcebot;
- $hideAnonymousUsers = $opts['hideanons'] && !$forcebot;
-
- if ( $opts['hideminor'] ) {
- $conds['rc_minor'] = 0;
- }
- if ( $opts['hidebots'] ) {
- $conds['rc_bot'] = 0;
- }
- if ( $hidePatrol ) {
- $conds['rc_patrolled'] = 0;
- }
- if ( $forcebot ) {
- $conds['rc_bot'] = 1;
- }
- if ( $hideLoggedInUsers ) {
- $conds[] = 'rc_user = 0';
- }
- if ( $hideAnonymousUsers ) {
- $conds[] = 'rc_user != 0';
- }
-
- if ( $opts['hidemyself'] ) {
- if ( $this->getUser()->getId() ) {
- $conds[] = 'rc_user != ' . $dbr->addQuotes( $this->getUser()->getId() );
- } else {
- $conds[] = 'rc_user_text != ' . $dbr->addQuotes( $this->getUser()->getName() );
- }
- }
-
- # Namespace filtering
- if ( $opts['namespace'] !== '' ) {
- $selectedNS = $dbr->addQuotes( $opts['namespace'] );
- $operator = $opts['invert'] ? '!=' : '=';
- $boolean = $opts['invert'] ? 'AND' : 'OR';
-
- # namespace association (bug 2429)
- if ( !$opts['associated'] ) {
- $condition = "rc_namespace $operator $selectedNS";
- } else {
- # Also add the associated namespace
- $associatedNS = $dbr->addQuotes(
- MWNamespace::getAssociated( $opts['namespace'] )
- );
- $condition = "(rc_namespace $operator $selectedNS "
- . $boolean
- . " rc_namespace $operator $associatedNS)";
- }
-
- $conds[] = $condition;
- }
-
return $conds;
}
@@ -379,37 +192,32 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* @return bool|ResultWrapper Result or false (for Recentchangeslinked only)
*/
public function doMainQuery( $conds, $opts ) {
+ $dbr = $this->getDB();
+ $user = $this->getUser();
+
$tables = array( 'recentchanges' );
+ $fields = RecentChange::selectFields();
+ $query_options = array();
$join_conds = array();
- $query_options = array(
- 'USE INDEX' => array( 'recentchanges' => 'rc_timestamp' )
- );
-
- $uid = $this->getUser()->getId();
- $dbr = wfGetDB( DB_SLAVE );
- $limit = $opts['limit'];
- $namespace = $opts['namespace'];
- $invert = $opts['invert'];
- $associated = $opts['associated'];
- $fields = RecentChange::selectFields();
// JOIN on watchlist for users
- if ( $uid && $this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
+ if ( $user->getId() && $user->isAllowed( 'viewmywatchlist' ) ) {
$tables[] = 'watchlist';
$fields[] = 'wl_user';
$fields[] = 'wl_notificationtimestamp';
$join_conds['watchlist'] = array( 'LEFT JOIN', array(
- 'wl_user' => $uid,
+ 'wl_user' => $user->getId(),
'wl_title=rc_title',
'wl_namespace=rc_namespace'
) );
}
- if ( $this->getUser()->isAllowed( 'rollback' ) ) {
+
+ if ( $user->isAllowed( 'rollback' ) ) {
$tables[] = 'page';
$fields[] = 'page_latest';
$join_conds['page'] = array( 'LEFT JOIN', 'rc_cur_id=page_id' );
}
- // Tag stuff.
+
ChangeTags::modifyDisplayQuery(
$tables,
$fields,
@@ -419,48 +227,81 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$opts['tagfilter']
);
- if ( !wfRunHooks( 'SpecialRecentChangesQuery',
- array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$fields ) )
+ if ( !$this->runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds,
+ $opts )
) {
return false;
}
// rc_new is not an ENUM, but adding a redundant rc_new IN (0,1) gives mysql enough
// knowledge to use an index merge if it wants (it may use some other index though).
- return $dbr->select(
+ $rows = $dbr->select(
$tables,
$fields,
$conds + array( 'rc_new' => array( 0, 1 ) ),
__METHOD__,
- array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit ) + $query_options,
+ array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $opts['limit'] ) + $query_options,
$join_conds
);
+
+ // Build the final data
+ if ( $this->getConfig()->get( 'AllowCategorizedRecentChanges' ) ) {
+ $this->filterByCategories( $rows, $opts );
+ }
+
+ return $rows;
+ }
+
+ protected function runMainQueryHook( &$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts ) {
+ return parent::runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds, $opts )
+ && wfRunHooks(
+ 'SpecialRecentChangesQuery',
+ array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$fields ),
+ '1.23'
+ );
+ }
+
+ public function outputFeedLinks() {
+ $this->addFeedLinks( $this->getFeedQuery() );
}
/**
- * Send output to the OutputPage object, only called if not used feeds
+ * Get URL query parameters for action=feedrecentchanges API feed of current recent changes view.
+ *
+ * @return array
+ */
+ private function getFeedQuery() {
+ $query = array_filter( $this->getOptions()->getAllValues(), function ( $value ) {
+ // API handles empty parameters in a different way
+ return $value !== '';
+ } );
+ $query['action'] = 'feedrecentchanges';
+ $feedLimit = $this->getConfig()->get( 'FeedLimit' );
+ if ( $query['limit'] > $feedLimit ) {
+ $query['limit'] = $feedLimit;
+ }
+
+ return $query;
+ }
+
+ /**
+ * Build and output the actual changes list.
*
* @param array $rows Database rows
* @param FormOptions $opts
*/
- public function webOutput( $rows, $opts ) {
- global $wgRCShowWatchingUsers, $wgShowUpdatedMarker, $wgAllowCategorizedRecentChanges;
-
- // Build the final data
-
- if ( $wgAllowCategorizedRecentChanges ) {
- $this->filterByCategories( $rows, $opts );
- }
-
+ public function outputChangesList( $rows, $opts ) {
$limit = $opts['limit'];
- $showWatcherCount = $wgRCShowWatchingUsers && $this->getUser()->getOption( 'shownumberswatching' );
+ $showWatcherCount = $this->getConfig()->get( 'RCShowWatchingUsers' )
+ && $this->getUser()->getOption( 'shownumberswatching' );
$watcherCache = array();
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = $this->getDB();
$counter = 1;
$list = ChangesList::newFromContext( $this->getContext() );
+ $list->initChangesListRows( $rows );
$rclistOutput = $list->beginRecentChangesList();
foreach ( $rows as $obj ) {
@@ -470,7 +311,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$rc = RecentChange::newFromRow( $obj );
$rc->counter = $counter++;
# Check if the page has been updated since the last visit
- if ( $wgShowUpdatedMarker && !empty( $obj->wl_notificationtimestamp ) ) {
+ if ( $this->getConfig()->get( 'ShowUpdatedMarker' ) && !empty( $obj->wl_notificationtimestamp ) ) {
$rc->notificationtimestamp = ( $obj->rc_timestamp >= $obj->wl_notificationtimestamp );
} else {
$rc->notificationtimestamp = false; // Default
@@ -501,68 +342,35 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
$rclistOutput .= $list->endRecentChangesList();
- // Print things out
-
- if ( !$this->including() ) {
- // Output options box
- $this->doHeader( $opts );
- }
-
- // And now for the content
- $feedQuery = $this->getFeedQuery();
- if ( $feedQuery !== '' ) {
- $this->getOutput()->setFeedAppendQuery( $feedQuery );
- } else {
- $this->getOutput()->setFeedAppendQuery( false );
- }
-
if ( $rows->numRows() === 0 ) {
- $this->getOutput()->wrapWikiMsg(
- "<div class='mw-changeslist-empty'>\n$1\n</div>", 'recentchanges-noresult'
+ $this->getOutput()->addHtml(
+ '<div class="mw-changeslist-empty">' .
+ $this->msg( 'recentchanges-noresult' )->parse() .
+ '</div>'
);
+ if ( !$this->including() ) {
+ $this->getOutput()->setStatusCode( 404 );
+ }
} else {
$this->getOutput()->addHTML( $rclistOutput );
}
}
/**
- * Get the query string to append to feed link URLs.
- *
- * @return string
- */
- public function getFeedQuery() {
- global $wgFeedLimit;
-
- $this->getOptions()->validateIntBounds( 'limit', 0, $wgFeedLimit );
- $options = $this->getOptions()->getChangedValues();
-
- // wfArrayToCgi() omits options set to null or false
- foreach ( $options as &$value ) {
- if ( $value === false ) {
- $value = '0';
- }
- }
- unset( $value );
-
- return wfArrayToCgi( $options );
- }
-
- /**
- * Return the text to be displayed above the changes
+ * Set the text to be displayed above the changes
*
* @param FormOptions $opts
- * @return string XHTML
+ * @param int $numRows Number of rows in the result to show after this header
*/
- public function doHeader( $opts ) {
- global $wgScript;
-
+ public function doHeader( $opts, $numRows ) {
$this->setTopText( $opts );
$defaults = $opts->getAllValues();
$nondefaults = $opts->getChangedValues();
$panel = array();
- $panel[] = $this->optionsPanel( $defaults, $nondefaults );
+ $panel[] = self::makeLegend( $this->getContext() );
+ $panel[] = $this->optionsPanel( $defaults, $nondefaults, $numRows );
$panel[] = '<hr />';
$extraOpts = $this->getExtraOptions( $opts );
@@ -604,9 +412,9 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$out .= Html::hidden( $key, $value );
}
- $t = $this->getTitle();
+ $t = $this->getPageTitle();
$out .= Html::hidden( 'title', $t->getPrefixedText() );
- $form = Xml::tags( 'form', array( 'action' => $wgScript ), $out );
+ $form = Xml::tags( 'form', array( 'action' => wfScript() ), $out );
$panel[] = $form;
$panelString = implode( "\n", $panel );
@@ -622,6 +430,27 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
/**
+ * Send the text to be displayed above the options
+ *
+ * @param FormOptions $opts Unused
+ */
+ function setTopText( FormOptions $opts ) {
+ global $wgContLang;
+
+ $message = $this->msg( 'recentchangestext' )->inContentLanguage();
+ if ( !$message->isDisabled() ) {
+ $this->getOutput()->addWikiText(
+ Html::rawElement( 'p',
+ array( 'lang' => $wgContLang->getCode(), 'dir' => $wgContLang->getDir() ),
+ "\n" . $message->plain() . "\n"
+ ),
+ /* $lineStart */ false,
+ /* $interface */ false
+ );
+ }
+ }
+
+ /**
* Get options to be displayed in a form
*
* @param FormOptions $opts
@@ -635,8 +464,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$extraOpts = array();
$extraOpts['namespace'] = $this->namespaceFilterForm( $opts );
- global $wgAllowCategorizedRecentChanges;
- if ( $wgAllowCategorizedRecentChanges ) {
+ if ( $this->getConfig()->get( 'AllowCategorizedRecentChanges' ) ) {
$extraOpts['category'] = $this->categoryFilterForm( $opts );
}
@@ -654,38 +482,31 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
/**
- * Send the text to be displayed above the options
- *
- * @param FormOptions $opts Unused
+ * Add page-specific modules.
*/
- function setTopText( FormOptions $opts ) {
- global $wgContLang;
-
- $message = $this->msg( 'recentchangestext' )->inContentLanguage();
- if ( !$message->isDisabled() ) {
- $this->getOutput()->addWikiText(
- Html::rawElement( 'p',
- array( 'lang' => $wgContLang->getCode(), 'dir' => $wgContLang->getDir() ),
- "\n" . $message->plain() . "\n"
- ),
- /* $lineStart */ false,
- /* $interface */ false
- );
- }
+ protected function addModules() {
+ parent::addModules();
+ $out = $this->getOutput();
+ $out->addModules( 'mediawiki.special.recentchanges' );
}
/**
- * Send the text to be displayed after the options, for use in subclasses.
+ * Get last modified date, for client caching
+ * Don't use this if we are using the patrol feature, patrol changes don't
+ * update the timestamp
*
- * @param FormOptions $opts
+ * @return string|bool
*/
- function setBottomText( FormOptions $opts ) {
+ public function checkLastModified() {
+ $dbr = $this->getDB();
+ $lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', false, __METHOD__ );
+
+ return $lastmod;
}
/**
* Creates the choose namespace selection
*
- * @todo Uses radio buttons (HASHAR)
* @param FormOptions $opts
* @return string
*/
@@ -710,7 +531,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
/**
- * Create a input to filter changes by categories
+ * Create an input to filter changes by categories
*
* @param FormOptions $opts
* @return array
@@ -728,7 +549,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
/**
* Filter $rows by categories set in $opts
*
- * @param array $rows Database rows
+ * @param ResultWrapper $rows Database rows
* @param FormOptions $opts
*/
function filterByCategories( &$rows, FormOptions $opts ) {
@@ -774,9 +595,9 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
# Look up
- $c = new Categoryfinder;
- $c->seed( $articles, $cats, $opts['categories_any'] ? 'OR' : 'AND' );
- $match = $c->run();
+ $catFind = new CategoryFinder;
+ $catFind->seed( $articles, $cats, $opts['categories_any'] ? 'OR' : 'AND' );
+ $match = $catFind->run();
# Filter
$newrows = array();
@@ -815,7 +636,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$text = '<strong>' . $text . '</strong>';
}
- return Linker::linkKnown( $this->getTitle(), $text, array(), $params );
+ return Linker::linkKnown( $this->getPageTitle(), $text, array(), $params );
}
/**
@@ -823,11 +644,10 @@ class SpecialRecentChanges extends IncludableSpecialPage {
*
* @param array $defaults
* @param array $nondefaults
+ * @param int $numRows Number of rows in the result to show after this header
* @return string
*/
- function optionsPanel( $defaults, $nondefaults ) {
- global $wgRCLinkLimits, $wgRCLinkDays;
-
+ function optionsPanel( $defaults, $nondefaults, $numRows ) {
$options = $nondefaults + $defaults;
$note = '';
@@ -839,19 +659,24 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$lang = $this->getLanguage();
$user = $this->getUser();
if ( $options['from'] ) {
- $note .= $this->msg( 'rcnotefrom' )->numParams( $options['limit'] )->params(
- $lang->userTimeAndDate( $options['from'], $user ),
- $lang->userDate( $options['from'], $user ),
- $lang->userTime( $options['from'], $user ) )->parse() . '<br />';
+ $note .= $this->msg( 'rcnotefrom' )
+ ->numParams( $options['limit'] )
+ ->params(
+ $lang->userTimeAndDate( $options['from'], $user ),
+ $lang->userDate( $options['from'], $user ),
+ $lang->userTime( $options['from'], $user )
+ )
+ ->numParams( $numRows )
+ ->parse() . '<br />';
}
# Sort data for display and make sure it's unique after we've added user data.
- $linkLimits = $wgRCLinkLimits;
+ $linkLimits = $this->getConfig()->get( 'RCLinkLimits' );
$linkLimits[] = $options['limit'];
sort( $linkLimits );
$linkLimits = array_unique( $linkLimits );
- $linkDays = $wgRCLinkDays;
+ $linkDays = $this->getConfig()->get( 'RCLinkDays' );
$linkDays[] = $options['days'];
sort( $linkDays );
$linkDays = array_unique( $linkDays );
@@ -873,7 +698,6 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$dl = $lang->pipeList( $dl );
// show/hide links
- $showhide = array( $this->msg( 'show' )->text(), $this->msg( 'hide' )->text() );
$filters = array(
'hideminor' => 'rcshowhideminor',
'hidebots' => 'rcshowhidebots',
@@ -882,6 +706,9 @@ class SpecialRecentChanges extends IncludableSpecialPage {
'hidepatrolled' => 'rcshowhidepatr',
'hidemyself' => 'rcshowhidemine'
);
+
+ $showhide = array( 'show', 'hide' );
+
foreach ( $this->getCustomFilters() as $key => $params ) {
$filters[$key] = $params['msg'];
}
@@ -892,35 +719,42 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$links = array();
foreach ( $filters as $key => $msg ) {
- $link = $this->makeOptionsLink( $showhide[1 - $options[$key]],
+ // The following messages are used here:
+ // rcshowhideminor-show, rcshowhideminor-hide, rcshowhidebots-show, rcshowhidebots-hide,
+ // rcshowhideanons-show, rcshowhideanons-hide, rcshowhideliu-show, rcshowhideliu-hide,
+ // rcshowhidepatr-show, rcshowhidepatr-hide, rcshowhidemine-show, rcshowhidemine-hide.
+ $linkMessage = $this->msg( $msg . '-' . $showhide[1 - $options[$key]] );
+ // Extensions can define additional filters, but don't need to define the corresponding
+ // messages. If they don't exist, just fall back to 'show' and 'hide'.
+ if ( !$linkMessage->exists() ) {
+ $linkMessage = $this->msg( $showhide[1 - $options[$key]] );
+ }
+
+ $link = $this->makeOptionsLink( $linkMessage->text(),
array( $key => 1 - $options[$key] ), $nondefaults );
- $links[] = $this->msg( $msg )->rawParams( $link )->escaped();
+ $links[] = "<span class=\"$msg rcshowhideoption\">" . $this->msg( $msg )->rawParams( $link )->escaped() . '</span>';
}
// show from this onward link
$timestamp = wfTimestampNow();
$now = $lang->userTimeAndDate( $timestamp, $user );
- $tl = $this->makeOptionsLink(
- $now, array( 'from' => $timestamp ), $nondefaults
- );
+ $timenow = $lang->userTime( $timestamp, $user );
+ $datenow = $lang->userDate( $timestamp, $user );
+ $pipedLinks = '<span class="rcshowhide">' . $lang->pipeList( $links ) . '</span>';
- $rclinks = $this->msg( 'rclinks' )->rawParams( $cl, $dl, $lang->pipeList( $links ) )
- ->parse();
- $rclistfrom = $this->msg( 'rclistfrom' )->rawParams( $tl )->parse();
+ $rclinks = '<span class="rclinks">' . $this->msg( 'rclinks' )->rawParams( $cl, $dl, $pipedLinks )
+ ->parse() . '</span>';
- return "{$note}$rclinks<br />$rclistfrom";
- }
+ $rclistfrom = '<span class="rclistfrom">' . $this->makeOptionsLink(
+ $this->msg( 'rclistfrom' )->rawParams( $now, $timenow, $datenow )->parse(),
+ array( 'from' => $timestamp ),
+ $nondefaults
+ ) . '</span>';
- /**
- * Add page-specific modules.
- */
- protected function addModules() {
- $this->getOutput()->addModules( array(
- 'mediawiki.special.recentchanges',
- ) );
+ return "{$note}$rclinks<br />$rclistfrom";
}
- protected function getGroupName() {
- return 'changes';
+ public function isIncludable() {
+ return true;
}
}
diff --git a/includes/specials/SpecialRecentchangeslinked.php b/includes/specials/SpecialRecentchangeslinked.php
index a8447046..3ad9f0f4 100644
--- a/includes/specials/SpecialRecentchangeslinked.php
+++ b/includes/specials/SpecialRecentchangeslinked.php
@@ -26,8 +26,9 @@
*
* @ingroup SpecialPage
*/
-class SpecialRecentchangeslinked extends SpecialRecentChanges {
- var $rclTargetTitle;
+class SpecialRecentChangesLinked extends SpecialRecentChanges {
+ /** @var bool|Title */
+ protected $rclTargetTitle;
function __construct() {
parent::__construct( 'Recentchangeslinked' );
@@ -37,6 +38,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$opts = parent::getDefaultOptions();
$opts->add( 'target', '' );
$opts->add( 'showlinkedto', false );
+
return $opts;
}
@@ -44,23 +46,6 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$opts['target'] = $par;
}
- public function feedSetup() {
- $opts = parent::feedSetup();
- $opts['target'] = $this->getRequest()->getVal( 'target' );
- return $opts;
- }
-
- public function getFeedObject( $feedFormat ) {
- $feed = new ChangesFeed( $feedFormat, false );
- $feedObj = $feed->getFeedObject(
- $this->msg( 'recentchangeslinked-title', $this->getTargetTitle()->getPrefixedText() )
- ->inContentLanguage()->text(),
- $this->msg( 'recentchangeslinked-feed' )->inContentLanguage()->text(),
- $this->getTitle()->getFullURL()
- );
- return array( $feed, $feedObj );
- }
-
public function doMainQuery( $conds, $opts ) {
$target = $opts['target'];
$showlinkedto = $opts['showlinkedto'];
@@ -71,8 +56,10 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
}
$outputPage = $this->getOutput();
$title = Title::newFromURL( $target );
- if ( !$title || $title->getInterwiki() != '' ) {
- $outputPage->wrapWikiMsg( "<div class=\"errorbox\">\n$1\n</div>", 'allpagesbadtitle' );
+ if ( !$title || $title->isExternal() ) {
+ $outputPage->addHtml( '<div class="errorbox">' . $this->msg( 'allpagesbadtitle' )
+ ->parse() . '</div>' );
+
return false;
}
@@ -106,7 +93,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
'wl_user' => $uid,
'wl_title=rc_title',
'wl_namespace=rc_namespace'
- ));
+ ) );
}
if ( $this->getUser()->isAllowed( 'rollback' ) ) {
$tables[] = 'page';
@@ -122,7 +109,9 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$opts['tagfilter']
);
- if ( !wfRunHooks( 'SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$select ) ) ) {
+ if ( !$this->runMainQueryHook( $tables, $select, $conds, $query_options, $join_conds,
+ $opts )
+ ) {
return false;
}
@@ -145,14 +134,20 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
}
// field name prefixes for all the various tables we might want to join with
- $prefix = array( 'pagelinks' => 'pl', 'templatelinks' => 'tl', 'categorylinks' => 'cl', 'imagelinks' => 'il' );
+ $prefix = array(
+ 'pagelinks' => 'pl',
+ 'templatelinks' => 'tl',
+ 'categorylinks' => 'cl',
+ 'imagelinks' => 'il'
+ );
$subsql = array(); // SELECT statements to combine with UNION
foreach ( $link_tables as $link_table ) {
$pfx = $prefix[$link_table];
- // imagelinks and categorylinks tables have no xx_namespace field, and have xx_to instead of xx_title
+ // imagelinks and categorylinks tables have no xx_namespace field,
+ // and have xx_to instead of xx_title
if ( $link_table == 'imagelinks' ) {
$link_ns = NS_FILE;
} elseif ( $link_table == 'categorylinks' ) {
@@ -225,6 +220,14 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
return $res;
}
+ function setTopText( FormOptions $opts ) {
+ $target = $this->getTargetTitle();
+ if ( $target ) {
+ $this->getOutput()->addBacklinkSubtitle( $target );
+ $this->getSkin()->setRelevantTitle( $target );
+ }
+ }
+
/**
* Get options to be displayed in a form
*
@@ -256,13 +259,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$this->rclTargetTitle = false;
}
}
- return $this->rclTargetTitle;
- }
- function setTopText( FormOptions $opts ) {
- $target = $this->getTargetTitle();
- if ( $target ) {
- $this->getOutput()->addBacklinkSubtitle( $target );
- }
+ return $this->rclTargetTitle;
}
}
diff --git a/includes/specials/SpecialRedirect.php b/includes/specials/SpecialRedirect.php
index f05dacbc..2022d748 100644
--- a/includes/specials/SpecialRedirect.php
+++ b/includes/specials/SpecialRedirect.php
@@ -55,6 +55,7 @@ class SpecialRedirect extends FormSpecialPage {
/**
* Set $mType and $mValue based on parsed value of $subpage.
+ * @param string $subpage
*/
function setParameter( $subpage ) {
// parse $subpage to pull out the parts
@@ -66,7 +67,7 @@ class SpecialRedirect extends FormSpecialPage {
/**
* Handle Special:Redirect/user/xxxx (by redirecting to User:YYYY)
*
- * @return string|null url to redirect to, or null if $mValue is invalid.
+ * @return string|null Url to redirect to, or null if $mValue is invalid.
*/
function dispatchUser() {
if ( !ctype_digit( $this->mValue ) ) {
@@ -78,18 +79,19 @@ class SpecialRedirect extends FormSpecialPage {
return null;
}
$userpage = Title::makeTitle( NS_USER, $username );
+
return $userpage->getFullURL( '', false, PROTO_CURRENT );
}
/**
* Handle Special:Redirect/file/xxxx
*
- * @return string|null url to redirect to, or null if $mValue is not found.
+ * @return string|null Url to redirect to, or null if $mValue is not found.
*/
function dispatchFile() {
$title = Title::makeTitleSafe( NS_FILE, $this->mValue );
- if ( ! $title instanceof Title ) {
+ if ( !$title instanceof Title ) {
return null;
}
$file = wfFindFile( $title );
@@ -112,6 +114,7 @@ class SpecialRedirect extends FormSpecialPage {
$url = $mto->getURL();
}
}
+
return $url;
}
@@ -119,7 +122,7 @@ class SpecialRedirect extends FormSpecialPage {
* Handle Special:Redirect/revision/xxx
* (by redirecting to index.php?oldid=xxx)
*
- * @return string|null url to redirect to, or null if $mValue is invalid.
+ * @return string|null Url to redirect to, or null if $mValue is invalid.
*/
function dispatchRevision() {
$oldid = $this->mValue;
@@ -130,46 +133,73 @@ class SpecialRedirect extends FormSpecialPage {
if ( $oldid === 0 ) {
return null;
}
+
return wfAppendQuery( wfScript( 'index' ), array(
'oldid' => $oldid
) );
}
/**
+ * Handle Special:Redirect/page/xxx (by redirecting to index.php?curid=xxx)
+ *
+ * @return string|null Url to redirect to, or null if $mValue is invalid.
+ */
+ function dispatchPage() {
+ $curid = $this->mValue;
+ if ( !ctype_digit( $curid ) ) {
+ return null;
+ }
+ $curid = (int)$curid;
+ if ( $curid === 0 ) {
+ return null;
+ }
+
+ return wfAppendQuery( wfScript( 'index' ), array(
+ 'curid' => $curid
+ ) );
+ }
+
+ /**
* Use appropriate dispatch* method to obtain a redirection URL,
* and either: redirect, set a 404 error code and error message,
* or do nothing (if $mValue wasn't set) allowing the form to be
* displayed.
*
- * @return bool true if a redirect was successfully handled.
+ * @return bool True if a redirect was successfully handled.
*/
function dispatch() {
// the various namespaces supported by Special:Redirect
switch ( $this->mType ) {
- case 'user':
- $url = $this->dispatchUser();
- break;
- case 'file':
- $url = $this->dispatchFile();
- break;
- case 'revision':
- $url = $this->dispatchRevision();
- break;
- default:
- $this->getOutput()->setStatusCode( 404 );
- $url = null;
- break;
+ case 'user':
+ $url = $this->dispatchUser();
+ break;
+ case 'file':
+ $url = $this->dispatchFile();
+ break;
+ case 'revision':
+ $url = $this->dispatchRevision();
+ break;
+ case 'page':
+ $url = $this->dispatchPage();
+ break;
+ default:
+ $this->getOutput()->setStatusCode( 404 );
+ $url = null;
+ break;
}
if ( $url ) {
$this->getOutput()->redirect( $url );
+
return true;
}
if ( !is_null( $this->mValue ) ) {
$this->getOutput()->setStatusCode( 404 );
// Message: redirect-not-exists
$msg = $this->getMessagePrefix() . '-not-exists';
+
return Status::newFatal( $msg );
}
+
return false;
}
@@ -177,8 +207,10 @@ class SpecialRedirect extends FormSpecialPage {
$mp = $this->getMessagePrefix();
$ns = array(
// subpage => message
- // Messages: redirect-user, redirect-revision, redirect-file
+ // Messages: redirect-user, redirect-page, redirect-revision,
+ // redirect-file
'user' => $mp . '-user',
+ 'page' => $mp . '-page',
'revision' => $mp . '-revision',
'file' => $mp . '-file',
);
@@ -204,6 +236,7 @@ class SpecialRedirect extends FormSpecialPage {
if ( !empty( $this->mValue ) ) {
$a['value']['default'] = $this->mValue;
}
+
return $a;
}
@@ -211,6 +244,7 @@ class SpecialRedirect extends FormSpecialPage {
if ( !empty( $data['type'] ) && !empty( $data['value'] ) ) {
$this->setParameter( $data['type'] . '/' . $data['value'] );
}
+
/* if this returns false, will show the form */
return $this->dispatch();
}
diff --git a/includes/specials/SpecialResetTokens.php b/includes/specials/SpecialResetTokens.php
index ef2a45da..4add7421 100644
--- a/includes/specials/SpecialResetTokens.php
+++ b/includes/specials/SpecialResetTokens.php
@@ -40,16 +40,15 @@ class SpecialResetTokens extends FormSpecialPage {
* @return array
*/
protected function getTokensList() {
- global $wgHiddenPrefs;
-
if ( !isset( $this->tokensList ) ) {
$tokens = array(
array( 'preference' => 'watchlisttoken', 'label-message' => 'resettokens-watchlist-token' ),
);
wfRunHooks( 'SpecialResetTokensTokens', array( &$tokens ) );
- $tokens = array_filter( $tokens, function ( $tok ) use ( $wgHiddenPrefs ) {
- return !in_array( $tok['preference'], $wgHiddenPrefs );
+ $hiddenPrefs = $this->getConfig()->get( 'HiddenPrefs' );
+ $tokens = array_filter( $tokens, function ( $tok ) use ( $hiddenPrefs ) {
+ return !in_array( $tok['preference'], $hiddenPrefs );
} );
$this->tokensList = $tokens;
@@ -61,6 +60,7 @@ class SpecialResetTokens extends FormSpecialPage {
public function execute( $par ) {
// This is a preferences page, so no user JS for y'all.
$this->getOutput()->disallowUserJs();
+ $this->requireLogin();
parent::execute( $par );
@@ -77,6 +77,7 @@ class SpecialResetTokens extends FormSpecialPage {
/**
* Display appropriate message if there's nothing to do.
* The submit button is also suppressed in this case (see alterForm()).
+ * @return array
*/
protected function getFormFields() {
$user = $this->getUser();
@@ -89,7 +90,7 @@ class SpecialResetTokens extends FormSpecialPage {
->rawParams( $this->msg( $tok['label-message'] )->parse() )
->params( $user->getTokenFromOption( $tok['preference'] ) )
->escaped();
- $tokensForForm[ $label ] = $tok['preference'];
+ $tokensForForm[$label] = $tok['preference'];
}
$desc = array(
@@ -112,6 +113,7 @@ class SpecialResetTokens extends FormSpecialPage {
/**
* Suppress the submit button if there's nothing to do;
* provide additional message on it otherwise.
+ * @param HTMLForm $form
*/
protected function alterForm( HTMLForm $form ) {
if ( $this->getTokensList() ) {
diff --git a/includes/specials/SpecialRevisiondelete.php b/includes/specials/SpecialRevisiondelete.php
index 825be6c4..7eea71da 100644
--- a/includes/specials/SpecialRevisiondelete.php
+++ b/includes/specials/SpecialRevisiondelete.php
@@ -28,61 +28,80 @@
* @ingroup SpecialPage
*/
class SpecialRevisionDelete extends UnlistedSpecialPage {
- /** True if the submit button was clicked, and the form was posted */
- var $submitClicked;
+ /** @var bool Was the DB modified in this request */
+ protected $wasSaved = false;
- /** Target ID list */
- var $ids;
+ /** @var bool True if the submit button was clicked, and the form was posted */
+ private $submitClicked;
- /** Archive name, for reviewing deleted files */
- var $archiveName;
+ /** @var array Target ID list */
+ private $ids;
- /** Edit token for securing image views against XSS */
- var $token;
+ /** @var string Archive name, for reviewing deleted files */
+ private $archiveName;
- /** Title object for target parameter */
- var $targetObj;
+ /** @var string Edit token for securing image views against XSS */
+ private $token;
- /** Deletion type, may be revision, archive, oldimage, filearchive, logging. */
- var $typeName;
+ /** @var Title Title object for target parameter */
+ private $targetObj;
- /** Array of checkbox specs (message, name, deletion bits) */
- var $checks;
+ /** @var string Deletion type, may be revision, archive, oldimage, filearchive, logging. */
+ private $typeName;
- /** UI Labels about the current type */
- var $typeLabels;
+ /** @var array Array of checkbox specs (message, name, deletion bits) */
+ private $checks;
- /** The RevDel_List object, storing the list of items to be deleted/undeleted */
- var $list;
+ /** @var array UI Labels about the current type */
+ private $typeLabels;
+
+ /** @var RevDelList RevDelList object, storing the list of items to be deleted/undeleted */
+ private $revDelList;
+
+ /** @var bool Whether user is allowed to perform the action */
+ private $mIsAllowed;
+
+ /** @var string */
+ private $otherReason;
/**
* UI labels for each type.
*/
- static $UILabels = array(
+ private static $UILabels = array(
'revision' => array(
- 'check-label' => 'revdelete-hide-text',
- 'success' => 'revdelete-success',
- 'failure' => 'revdelete-failure',
+ 'check-label' => 'revdelete-hide-text',
+ 'success' => 'revdelete-success',
+ 'failure' => 'revdelete-failure',
+ 'text' => 'revdelete-text-text',
+ 'selected'=> 'revdelete-selected-text',
),
'archive' => array(
- 'check-label' => 'revdelete-hide-text',
- 'success' => 'revdelete-success',
- 'failure' => 'revdelete-failure',
+ 'check-label' => 'revdelete-hide-text',
+ 'success' => 'revdelete-success',
+ 'failure' => 'revdelete-failure',
+ 'text' => 'revdelete-text-text',
+ 'selected'=> 'revdelete-selected-text',
),
'oldimage' => array(
- 'check-label' => 'revdelete-hide-image',
- 'success' => 'revdelete-success',
- 'failure' => 'revdelete-failure',
+ 'check-label' => 'revdelete-hide-image',
+ 'success' => 'revdelete-success',
+ 'failure' => 'revdelete-failure',
+ 'text' => 'revdelete-text-file',
+ 'selected'=> 'revdelete-selected-file',
),
'filearchive' => array(
- 'check-label' => 'revdelete-hide-image',
- 'success' => 'revdelete-success',
- 'failure' => 'revdelete-failure',
+ 'check-label' => 'revdelete-hide-image',
+ 'success' => 'revdelete-success',
+ 'failure' => 'revdelete-failure',
+ 'text' => 'revdelete-text-file',
+ 'selected'=> 'revdelete-selected-file',
),
'logging' => array(
- 'check-label' => 'revdelete-hide-name',
- 'success' => 'logdelete-success',
- 'failure' => 'logdelete-failure',
+ 'check-label' => 'revdelete-hide-name',
+ 'success' => 'logdelete-success',
+ 'failure' => 'logdelete-failure',
+ 'text' => 'logdelete-text',
+ 'selected' => 'logdelete-selected',
),
);
@@ -113,7 +132,9 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
// $this->ids = array_map( 'intval', $this->ids );
$this->ids = array_unique( array_filter( $this->ids ) );
- if ( $request->getVal( 'action' ) == 'historysubmit' || $request->getVal( 'action' ) == 'revisiondelete' ) {
+ if ( $request->getVal( 'action' ) == 'historysubmit'
+ || $request->getVal( 'action' ) == 'revisiondelete'
+ ) {
// For show/hide form submission from history page
// Since we are access through index.php?title=XXX&action=historysubmit
// getFullTitle() will contain the target title and not our title
@@ -129,6 +150,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$this->token = $request->getVal( 'token' );
if ( $this->archiveName && $this->targetObj ) {
$this->tryShowFile( $this->archiveName );
+
return;
}
@@ -138,16 +160,29 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
if ( !$this->typeName || count( $this->ids ) == 0 ) {
throw new ErrorPageError( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
}
- $this->typeLabels = self::$UILabels[$this->typeName];
- $this->mIsAllowed = $user->isAllowed( RevisionDeleter::getRestriction( $this->typeName ) );
# Allow the list type to adjust the passed target
- $this->targetObj = RevisionDeleter::suggestTarget( $this->typeName, $this->targetObj, $this->ids );
+ $this->targetObj = RevisionDeleter::suggestTarget(
+ $this->typeName,
+ $this->targetObj,
+ $this->ids
+ );
+
+ $this->typeLabels = self::$UILabels[$this->typeName];
+ $list = $this->getList();
+ $list->reset();
+ $bitfield = $list->current()->getBits();
+ $this->mIsAllowed = $user->isAllowed( RevisionDeleter::getRestriction( $this->typeName ) );
+ $canViewSuppressedOnly = $this->getUser()->isAllowed( 'viewsuppressed' ) &&
+ !$this->getUser()->isAllowed( 'suppressrevision' );
+ $pageIsSuppressed = $bitfield & Revision::DELETED_RESTRICTED;
+ $this->mIsAllowed = $this->mIsAllowed && !( $canViewSuppressedOnly && $pageIsSuppressed );
$this->otherReason = $request->getVal( 'wpReason' );
# We need a target page!
if ( is_null( $this->targetObj ) ) {
$output->addWikiMsg( 'undelete-header' );
+
return;
}
# Give a link to the logs/hist for this page
@@ -155,6 +190,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
# Initialise checkboxes
$this->checks = array(
+ # Messages: revdelete-hide-text, revdelete-hide-image, revdelete-hide-name
array( $this->typeLabels['check-label'], 'wpHidePrimary',
RevisionDeleter::getRevdelConstant( $this->typeName )
),
@@ -177,14 +213,24 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
# Show relevant lines from the deletion log
$deleteLogPage = new LogPage( 'delete' );
$output->addHTML( "<h2>" . $deleteLogPage->getName()->escaped() . "</h2>\n" );
- LogEventsList::showLogExtract( $output, 'delete',
- $this->targetObj, '', array( 'lim' => 25, 'conds' => $qc ) );
+ LogEventsList::showLogExtract(
+ $output,
+ 'delete',
+ $this->targetObj,
+ '', /* user */
+ array( 'lim' => 25, 'conds' => $qc, 'useMaster' => $this->wasSaved )
+ );
# Show relevant lines from the suppression log
if ( $user->isAllowed( 'suppressionlog' ) ) {
$suppressLogPage = new LogPage( 'suppress' );
$output->addHTML( "<h2>" . $suppressLogPage->getName()->escaped() . "</h2>\n" );
- LogEventsList::showLogExtract( $output, 'suppress',
- $this->targetObj, '', array( 'lim' => 25, 'conds' => $qc ) );
+ LogEventsList::showLogExtract(
+ $output,
+ 'suppress',
+ $this->targetObj,
+ '',
+ array( 'lim' => 25, 'conds' => $qc, 'useMaster' => $this->wasSaved )
+ );
}
}
@@ -194,6 +240,9 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
protected function showConvenienceLinks() {
# Give a link to the logs/hist for this page
if ( $this->targetObj ) {
+ // Also set header tabs to be for the target.
+ $this->getSkin()->setRelevantTitle( $this->targetObj );
+
$links = array();
$links[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Log' ),
@@ -236,12 +285,14 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$conds['log_action'] = $this->getList()->getLogAction();
$conds['ls_field'] = RevisionDeleter::getRelationType( $this->typeName );
$conds['ls_value'] = $this->ids;
+
return $conds;
}
/**
* Show a deleted file version requested by the visitor.
- * TODO Mostly copied from Special:Undelete. Refactor.
+ * @todo Mostly copied from Special:Undelete. Refactor.
+ * @param string $archiveName
*/
protected function tryShowFile( $archiveName ) {
$repo = RepoGroup::singleton()->getLocalRepo();
@@ -250,6 +301,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
// Check if user is allowed to see this file
if ( !$oimage->exists() ) {
$this->getOutput()->addWikiMsg( 'revdelete-no-file' );
+
return;
}
$user = $this->getUser();
@@ -269,7 +321,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$this->getOutput()->addHTML(
Xml::openElement( 'form', array(
'method' => 'POST',
- 'action' => $this->getTitle()->getLocalURL( array(
+ 'action' => $this->getPageTitle()->getLocalURL( array(
'target' => $this->targetObj->getPrefixedDBkey(),
'file' => $archiveName,
'token' => $user->getEditToken( $archiveName ),
@@ -279,6 +331,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
Xml::submitButton( $this->msg( 'revdelete-show-file-submit' )->text() ) .
'</form>'
);
+
return;
}
$this->getOutput()->disable();
@@ -287,7 +340,9 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
# a user without appropriate permissions can toddle off and
# nab the image, and Squid will serve it
$this->getRequest()->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
- $this->getRequest()->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
+ $this->getRequest()->response()->header(
+ 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate'
+ );
$this->getRequest()->response()->header( 'Pragma: no-cache' );
$key = $oimage->getStorageKey();
@@ -297,14 +352,16 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
/**
* Get the list object for this request
+ * @return RevDelList
*/
protected function getList() {
- if ( is_null( $this->list ) ) {
- $this->list = RevisionDeleter::createList(
+ if ( is_null( $this->revDelList ) ) {
+ $this->revDelList = RevisionDeleter::createList(
$this->typeName, $this->getContext(), $this->targetObj, $this->ids
);
}
- return $this->list;
+
+ return $this->revDelList;
}
/**
@@ -312,28 +369,29 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
* which will allow the user to choose new visibility settings.
*/
protected function showForm() {
- $UserAllowed = true;
+ $userAllowed = true;
- if ( $this->typeName == 'logging' ) {
- $this->getOutput()->addWikiMsg( 'logdelete-selected', $this->getLanguage()->formatNum( count( $this->ids ) ) );
- } else {
- $this->getOutput()->addWikiMsg( 'revdelete-selected',
- $this->targetObj->getPrefixedText(), count( $this->ids ) );
- }
+ // Messages: revdelete-selected-text, revdelete-selected-file, logdelete-selected
+ $this->getOutput()->wrapWikiMsg( "<strong>$1</strong>", array( $this->typeLabels['selected'],
+ $this->getLanguage()->formatNum( count( $this->ids ) ), $this->targetObj->getPrefixedText() ) );
$this->getOutput()->addHTML( "<ul>" );
$numRevisions = 0;
// Live revisions...
$list = $this->getList();
+ // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
for ( $list->reset(); $list->current(); $list->next() ) {
+ // @codingStandardsIgnoreEnd
$item = $list->current();
+
if ( !$item->canView() ) {
if ( !$this->submitClicked ) {
throw new PermissionsError( 'suppressrevision' );
}
- $UserAllowed = false;
+ $userAllowed = false;
}
+
$numRevisions++;
$this->getOutput()->addHTML( $item->getHTML() );
}
@@ -347,14 +405,14 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$this->addUsageText();
// Normal sysops can always see what they did, but can't always change it
- if ( !$UserAllowed ) {
+ if ( !$userAllowed ) {
return;
}
// Show form if the user can submit
if ( $this->mIsAllowed ) {
$out = Xml::openElement( 'form', array( 'method' => 'post',
- 'action' => $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) ),
+ 'action' => $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) ),
'id' => 'mw-revdel-form-revisions' ) ) .
Xml::fieldset( $this->msg( 'revdelete-legend' )->text() ) .
$this->buildCheckBoxes() .
@@ -367,7 +425,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
Xml::listDropDown( 'wpRevDeleteReasonList',
$this->msg( 'revdelete-reason-dropdown' )->inContentLanguage()->text(),
$this->msg( 'revdelete-reasonotherlist' )->inContentLanguage()->text(),
- $this->getRequest()->getText( 'wpRevDeleteReasonList', 'other' ), 'wpReasonDropDown', 1
+ $this->getRequest()->getText( 'wpRevDeleteReasonList', 'other' ), 'wpReasonDropDown'
) .
'</td>' .
"</tr><tr>\n" .
@@ -375,7 +433,12 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
Xml::label( $this->msg( 'revdelete-otherreason' )->text(), 'wpReason' ) .
'</td>' .
'<td class="mw-input">' .
- Xml::input( 'wpReason', 60, $this->otherReason, array( 'id' => 'wpReason', 'maxlength' => 100 ) ) .
+ Xml::input(
+ 'wpReason',
+ 60,
+ $this->otherReason,
+ array( 'id' => 'wpReason', 'maxlength' => 100 )
+ ) .
'</td>' .
"</tr><tr>\n" .
'<td></td>' .
@@ -389,12 +452,8 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
Html::hidden( 'target', $this->targetObj->getPrefixedText() ) .
Html::hidden( 'type', $this->typeName ) .
Html::hidden( 'ids', implode( ',', $this->ids ) ) .
- Xml::closeElement( 'fieldset' ) . "\n";
- } else {
- $out = '';
- }
- if ( $this->mIsAllowed ) {
- $out .= Xml::closeElement( 'form' ) . "\n";
+ Xml::closeElement( 'fieldset' ) . "\n" .
+ Xml::closeElement( 'form' ) . "\n";
// Show link to edit the dropdown reasons
if ( $this->getUser()->isAllowed( 'editinterface' ) ) {
$title = Title::makeTitle( NS_MEDIAWIKI, 'Revdelete-reason-dropdown' );
@@ -406,6 +465,8 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
);
$out .= Xml::tags( 'p', array( 'class' => 'mw-revdel-editreasons' ), $link ) . "\n";
}
+ } else {
+ $out = '';
}
$this->getOutput()->addHTML( $out );
}
@@ -415,17 +476,23 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
* @todo FIXME: Wikimedia-specific policy text
*/
protected function addUsageText() {
- $this->getOutput()->addWikiMsg( 'revdelete-text' );
+ // Messages: revdelete-text-text, revdelete-text-file, logdelete-text
+ $this->getOutput()->wrapWikiMsg(
+ "<strong>$1</strong>\n$2", $this->typeLabels['text'],
+ 'revdelete-text-others'
+ );
+
if ( $this->getUser()->isAllowed( 'suppressrevision' ) ) {
$this->getOutput()->addWikiMsg( 'revdelete-suppress-text' );
}
+
if ( $this->mIsAllowed ) {
$this->getOutput()->addWikiMsg( 'revdelete-confirm' );
}
}
/**
- * @return String: HTML
+ * @return string HTML
*/
protected function buildCheckBoxes() {
$html = '<table>';
@@ -434,26 +501,42 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
if ( $list->length() == 1 ) {
$list->reset();
$bitfield = $list->current()->getBits(); // existing field
+
if ( $this->submitClicked ) {
- $bitfield = $this->extractBitfield( $this->extractBitParams(), $bitfield );
+ $bitfield = RevisionDeleter::extractBitfield( $this->extractBitParams(), $bitfield );
}
+
foreach ( $this->checks as $item ) {
+ // Messages: revdelete-hide-text, revdelete-hide-image, revdelete-hide-name,
+ // revdelete-hide-comment, revdelete-hide-user, revdelete-hide-restricted
list( $message, $name, $field ) = $item;
- $innerHTML = Xml::checkLabel( $this->msg( $message )->text(), $name, $name, $bitfield & $field );
+ $innerHTML = Xml::checkLabel(
+ $this->msg( $message )->text(),
+ $name,
+ $name,
+ $bitfield & $field
+ );
+
if ( $field == Revision::DELETED_RESTRICTED ) {
$innerHTML = "<b>$innerHTML</b>";
}
+
$line = Xml::tags( 'td', array( 'class' => 'mw-input' ), $innerHTML );
$html .= "<tr>$line</tr>\n";
}
- // Otherwise, use tri-state radios
} else {
+ // Otherwise, use tri-state radios
$html .= '<tr>';
- $html .= '<th class="mw-revdel-checkbox">' . $this->msg( 'revdelete-radio-same' )->escaped() . '</th>';
- $html .= '<th class="mw-revdel-checkbox">' . $this->msg( 'revdelete-radio-unset' )->escaped() . '</th>';
- $html .= '<th class="mw-revdel-checkbox">' . $this->msg( 'revdelete-radio-set' )->escaped() . '</th>';
+ $html .= '<th class="mw-revdel-checkbox">'
+ . $this->msg( 'revdelete-radio-same' )->escaped() . '</th>';
+ $html .= '<th class="mw-revdel-checkbox">'
+ . $this->msg( 'revdelete-radio-unset' )->escaped() . '</th>';
+ $html .= '<th class="mw-revdel-checkbox">'
+ . $this->msg( 'revdelete-radio-set' )->escaped() . '</th>';
$html .= "<th></th></tr>\n";
foreach ( $this->checks as $item ) {
+ // Messages: revdelete-hide-text, revdelete-hide-image, revdelete-hide-name,
+ // revdelete-hide-comment, revdelete-hide-user, revdelete-hide-restricted
list( $message, $name, $field ) = $item;
// If there are several items, use third state by default...
if ( $this->submitClicked ) {
@@ -474,6 +557,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
}
$html .= '</table>';
+
return $html;
}
@@ -487,30 +571,37 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$token = $this->getRequest()->getVal( 'wpEditToken' );
if ( $this->submitClicked && !$this->getUser()->matchEditToken( $token ) ) {
$this->getOutput()->addWikiMsg( 'sessionfailure' );
+
return false;
}
$bitParams = $this->extractBitParams();
- $listReason = $this->getRequest()->getText( 'wpRevDeleteReasonList', 'other' ); // from dropdown
+ // from dropdown
+ $listReason = $this->getRequest()->getText( 'wpRevDeleteReasonList', 'other' );
$comment = $listReason;
- if ( $comment != 'other' && $this->otherReason != '' ) {
- // Entry from drop down menu + additional comment
- $comment .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $this->otherReason;
- } elseif ( $comment == 'other' ) {
+ if ( $comment === 'other' ) {
$comment = $this->otherReason;
+ } elseif ( $this->otherReason !== '' ) {
+ // Entry from drop down menu + additional comment
+ $comment .= $this->msg( 'colon-separator' )->inContentLanguage()->text()
+ . $this->otherReason;
}
# Can the user set this field?
- if ( $bitParams[Revision::DELETED_RESTRICTED] == 1 && !$this->getUser()->isAllowed( 'suppressrevision' ) ) {
+ if ( $bitParams[Revision::DELETED_RESTRICTED] == 1
+ && !$this->getUser()->isAllowed( 'suppressrevision' )
+ ) {
throw new PermissionsError( 'suppressrevision' );
}
# If the save went through, go to success message...
$status = $this->save( $bitParams, $comment, $this->targetObj );
if ( $status->isGood() ) {
$this->success();
+
return true;
- # ...otherwise, bounce back to form...
} else {
+ # ...otherwise, bounce back to form...
$this->failure( $status );
}
+
return false;
}
@@ -518,16 +609,23 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
* Report that the submit operation succeeded
*/
protected function success() {
+ // Messages: revdelete-success, logdelete-success
$this->getOutput()->setPageTitle( $this->msg( 'actioncomplete' ) );
- $this->getOutput()->wrapWikiMsg( "<span class=\"success\">\n$1\n</span>", $this->typeLabels['success'] );
- $this->list->reloadFromMaster();
+ $this->getOutput()->wrapWikiMsg(
+ "<span class=\"success\">\n$1\n</span>",
+ $this->typeLabels['success']
+ );
+ $this->wasSaved = true;
+ $this->revDelList->reloadFromMaster();
$this->showForm();
}
/**
* Report that the submit operation failed
+ * @param Status $status
*/
protected function failure( $status ) {
+ // Messages: revdelete-failure, logdelete-failure
$this->getOutput()->setPageTitle( $this->msg( 'actionfailed' ) );
$this->getOutput()->addWikiText( $status->getWikiText( $this->typeLabels['failure'] ) );
$this->showForm();
@@ -551,26 +649,16 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
if ( !isset( $bitfield[Revision::DELETED_RESTRICTED] ) ) {
$bitfield[Revision::DELETED_RESTRICTED] = 0;
}
- return $bitfield;
- }
- /**
- * Put together a rev_deleted bitfield
- * @deprecated since 1.22, use RevisionDeleter::extractBitfield instead
- * @param array $bitPars extractBitParams() params
- * @param int $oldfield current bitfield
- * @return array
- */
- public static function extractBitfield( $bitPars, $oldfield ) {
- return RevisionDeleter::extractBitfield( $bitPars, $oldfield );
+ return $bitfield;
}
/**
- * Do the write operations. Simple wrapper for RevDel_*List::setVisibility().
- * @param $bitfield
- * @param $reason
- * @param $title
- * @return
+ * Do the write operations. Simple wrapper for RevDel*List::setVisibility().
+ * @param int $bitfield
+ * @param string $reason
+ * @param Title $title
+ * @return Status
*/
protected function save( $bitfield, $reason, $title ) {
return $this->getList()->setVisibility(
diff --git a/includes/specials/SpecialRunJobs.php b/includes/specials/SpecialRunJobs.php
new file mode 100644
index 00000000..d4a06eb5
--- /dev/null
+++ b/includes/specials/SpecialRunJobs.php
@@ -0,0 +1,112 @@
+<?php
+/**
+ * Implements Special:RunJobs
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Aaron Schulz
+ */
+
+/**
+ * Special page designed for running background tasks (internal use only)
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialRunJobs extends UnlistedSpecialPage {
+ public function __construct() {
+ parent::__construct( 'RunJobs' );
+ }
+
+ public function execute( $par = '' ) {
+ $this->getOutput()->disable();
+
+ if ( wfReadOnly() ) {
+ header( "HTTP/1.0 423 Locked" );
+ print 'Wiki is in read-only mode';
+
+ return;
+ } elseif ( !$this->getRequest()->wasPosted() ) {
+ header( "HTTP/1.0 400 Bad Request" );
+ print 'Request must be POSTed';
+
+ return;
+ }
+
+ $optional = array( 'maxjobs' => 0, 'maxtime' => 30, 'type' => false, 'async' => true );
+ $required = array_flip( array( 'title', 'tasks', 'signature', 'sigexpiry' ) );
+
+ $params = array_intersect_key( $this->getRequest()->getValues(), $required + $optional );
+ $missing = array_diff_key( $required, $params );
+ if ( count( $missing ) ) {
+ header( "HTTP/1.0 400 Bad Request" );
+ print 'Missing parameters: ' . implode( ', ', array_keys( $missing ) );
+
+ return;
+ }
+
+ $squery = $params;
+ unset( $squery['signature'] );
+ $cSig = self::getQuerySignature( $squery, $this->getConfig()->get( 'SecretKey' ) ); // correct signature
+ $rSig = $params['signature']; // provided signature
+
+ $verified = is_string( $rSig ) && hash_equals( $cSig, $rSig );
+ if ( !$verified || $params['sigexpiry'] < time() ) {
+ header( "HTTP/1.0 400 Bad Request" );
+ print 'Invalid or stale signature provided';
+
+ return;
+ }
+
+ // Apply any default parameter values
+ $params += $optional;
+
+ if ( $params['async'] ) {
+ // Client will usually disconnect before checking the response,
+ // but it needs to know when it is safe to disconnect. Until this
+ // reaches ignore_user_abort(), it is not safe as the jobs won't run.
+ ignore_user_abort( true ); // jobs may take a bit of time
+ header( "HTTP/1.0 202 Accepted" );
+ ob_flush();
+ flush();
+ // Once the client receives this response, it can disconnect
+ }
+
+ // Do all of the specified tasks...
+ if ( in_array( 'jobs', explode( '|', $params['tasks'] ) ) ) {
+ $runner = new JobRunner();
+ $response = $runner->run( array(
+ 'type' => $params['type'],
+ 'maxJobs' => $params['maxjobs'] ? $params['maxjobs'] : 1,
+ 'maxTime' => $params['maxtime'] ? $params['maxjobs'] : 30
+ ) );
+ if ( !$params['async'] ) {
+ print FormatJson::encode( $response, true );
+ }
+ }
+ }
+
+ /**
+ * @param array $query
+ * @param string $secretKey
+ * @return string
+ */
+ public static function getQuerySignature( array $query, $secretKey ) {
+ ksort( $query ); // stable order
+ return hash_hmac( 'sha1', wfArrayToCgi( $query ), $secretKey );
+ }
+}
diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php
index 8609c740..88ab7d82 100644
--- a/includes/specials/SpecialSearch.php
+++ b/includes/specials/SpecialSearch.php
@@ -30,25 +30,24 @@
class SpecialSearch extends SpecialPage {
/**
* Current search profile. Search profile is just a name that identifies
- * the active search tab on the search page (content, help, discussions...)
+ * the active search tab on the search page (content, discussions...)
* For users tt replaces the set of enabled namespaces from the query
* string when applicable. Extensions can add new profiles with hooks
* with custom search options just for that profile.
- * null|string
+ * @var null|string
*/
protected $profile;
- function getProfile() { return $this->profile; }
- /// Search engine
+ /** @var SearchEngine Search engine */
protected $searchEngine;
- /// Search engine type, if not default
+ /** @var string Search engine type, if not default */
protected $searchEngineType;
- /// For links
+ /** @var array For links */
protected $extraParams = array();
- /// No idea, apparently used by some other classes
+ /** @var string No idea, apparently used by some other classes */
protected $mPrefix;
/**
@@ -60,12 +59,6 @@ class SpecialSearch extends SpecialPage {
* @var array
*/
protected $namespaces;
- function getNamespaces() { return $this->namespaces; }
-
- /**
- * @var bool
- */
- protected $searchRedirects;
/**
* @var string
@@ -81,14 +74,17 @@ class SpecialSearch extends SpecialPage {
/**
* Entry point
*
- * @param string $par or null
+ * @param string $par
*/
public function execute( $par ) {
$this->setHeaders();
$this->outputHeader();
$out = $this->getOutput();
$out->allowClickjacking();
- $out->addModuleStyles( 'mediawiki.special' );
+ $out->addModuleStyles( array(
+ 'mediawiki.special', 'mediawiki.special.search', 'mediawiki.ui', 'mediawiki.ui.button',
+ 'mediawiki.ui.input',
+ ) );
// Strip underscores from title parameter; most of the time we'll want
// text form here. But don't strip underscores from actual text params!
@@ -100,13 +96,22 @@ class SpecialSearch extends SpecialPage {
$search = str_replace( "\n", " ", $request->getText( 'search', $titleParam ) );
$this->load();
+ if ( !is_null( $request->getVal( 'nsRemember' ) ) ) {
+ $this->saveNamespaces();
+ // Remove the token from the URL to prevent the user from inadvertently
+ // exposing it (e.g. by pasting it into a public wiki page) or undoing
+ // later settings changes (e.g. by reloading the page).
+ $query = $request->getValues();
+ unset( $query['title'], $query['nsRemember'] );
+ $out->redirect( $this->getPageTitle()->getFullURL( $query ) );
+ return;
+ }
$this->searchEngineType = $request->getVal( 'srbackend' );
if ( $request->getVal( 'fulltext' )
|| !is_null( $request->getVal( 'offset' ) )
- || !is_null( $request->getVal( 'searchx' ) ) )
- {
+ ) {
$this->showResults( $search );
} else {
$this->goResult( $search );
@@ -120,7 +125,7 @@ class SpecialSearch extends SpecialPage {
*/
public function load() {
$request = $this->getRequest();
- list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, 'searchlimit' );
+ list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, '' );
$this->mPrefix = $request->getVal( 'prefix', '' );
$user = $this->getUser();
@@ -160,9 +165,6 @@ class SpecialSearch extends SpecialPage {
}
}
- // Redirects defaults to true, but we don't know whether it was ticked of or just missing
- $default = $request->getBool( 'profile' ) ? 0 : 1;
- $this->searchRedirects = $request->getBool( 'redirs', $default ) ? 1 : 0;
$this->didYouMeanHtml = ''; # html of did you mean... link
$this->fulltext = $request->getVal( 'fulltext' );
$this->profile = $profile;
@@ -171,57 +173,44 @@ class SpecialSearch extends SpecialPage {
/**
* If an exact title match can be found, jump straight ahead to it.
*
- * @param $term String
+ * @param string $term
*/
public function goResult( $term ) {
$this->setupPage( $term );
# Try to go to page as entered.
- $t = Title::newFromText( $term );
+ $title = Title::newFromText( $term );
# If the string cannot be used to create a title
- if ( is_null( $t ) ) {
+ if ( is_null( $title ) ) {
$this->showResults( $term );
+
return;
}
# If there's an exact or very near match, jump right there.
- $t = SearchEngine::getNearMatch( $term );
+ $title = SearchEngine::getNearMatch( $term );
- if ( !wfRunHooks( 'SpecialSearchGo', array( &$t, &$term ) ) ) {
- # Hook requested termination
- return;
- }
+ if ( !is_null( $title ) ) {
+ $this->getOutput()->redirect( $title->getFullURL() );
- if ( !is_null( $t ) ) {
- $this->getOutput()->redirect( $t->getFullURL() );
return;
}
# No match, generate an edit URL
- $t = Title::newFromText( $term );
- if ( !is_null( $t ) ) {
- global $wgGoToEdit;
- wfRunHooks( 'SpecialSearchNogomatch', array( &$t ) );
- wfDebugLog( 'nogomatch', $t->getText(), false );
-
- # If the feature is enabled, go straight to the edit page
- if ( $wgGoToEdit ) {
- $this->getOutput()->redirect( $t->getFullURL( array( 'action' => 'edit' ) ) );
- return;
- }
+ $title = Title::newFromText( $term );
+ if ( !is_null( $title ) ) {
+ wfRunHooks( 'SpecialSearchNogomatch', array( &$title ) );
}
$this->showResults( $term );
}
/**
- * @param $term String
+ * @param string $term
*/
public function showResults( $term ) {
- global $wgDisableTextSearch, $wgSearchForwardUrl, $wgContLang, $wgScript;
- wfProfileIn( __METHOD__ );
+ global $wgContLang;
+ $profile = new ProfileSection( __METHOD__ );
$search = $this->getSearchEngine();
$search->setLimitOffset( $this->limit, $this->offset );
$search->setNamespaces( $this->namespaces );
- $search->showRedirects = $this->searchRedirects; // BC
- $search->setFeatureData( 'list-redirects', $this->searchRedirects );
$search->prefix = $this->mPrefix;
$term = $search->transformSearchTerm( $term );
@@ -231,15 +220,20 @@ class SpecialSearch extends SpecialPage {
$out = $this->getOutput();
- if ( $wgDisableTextSearch ) {
- if ( $wgSearchForwardUrl ) {
- $url = str_replace( '$1', urlencode( $term ), $wgSearchForwardUrl );
+ if ( $this->getConfig()->get( 'DisableTextSearch' ) ) {
+ $searchFowardUrl = $this->getConfig()->get( 'SearchForwardUrl' );
+ if ( $searchFowardUrl ) {
+ $url = str_replace( '$1', urlencode( $term ), $searchFowardUrl );
$out->redirect( $url );
} else {
$out->addHTML(
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', null, $this->msg( 'search-external' )->text() ) .
- Xml::element( 'p', array( 'class' => 'mw-searchdisabled' ), $this->msg( 'searchdisabled' )->text() ) .
+ Xml::element(
+ 'p',
+ array( 'class' => 'mw-searchdisabled' ),
+ $this->msg( 'searchdisabled' )->text()
+ ) .
$this->msg( 'googlesearch' )->rawParams(
htmlspecialchars( $term ),
'UTF-8',
@@ -248,19 +242,19 @@ class SpecialSearch extends SpecialPage {
Xml::closeElement( 'fieldset' )
);
}
- wfProfileOut( __METHOD__ );
+
return;
}
- $t = Title::newFromText( $term );
+ $title = Title::newFromText( $term );
+ $showSuggestion = $title === null || !$title->isKnown();
+ $search->setShowSuggestion( $showSuggestion );
// fetch search results
$rewritten = $search->replacePrefixes( $term );
$titleMatches = $search->searchTitle( $rewritten );
- if ( !( $titleMatches instanceof SearchResultTooMany ) ) {
- $textMatches = $search->searchText( $rewritten );
- }
+ $textMatches = $search->searchText( $rewritten );
$textStatus = null;
if ( $textMatches instanceof Status ) {
@@ -269,9 +263,7 @@ class SpecialSearch extends SpecialPage {
}
// did you mean... suggestions
- if ( $textMatches && !$textStatus && $textMatches->hasSuggestion() ) {
- $st = SpecialPage::getTitleFor( 'Search' );
-
+ if ( $showSuggestion && $textMatches && !$textStatus && $textMatches->hasSuggestion() ) {
# mirror Go/Search behavior of original request ..
$didYouMeanParams = array( 'search' => $textMatches->getSuggestionQuery() );
@@ -291,18 +283,18 @@ class SpecialSearch extends SpecialPage {
}
$suggestLink = Linker::linkKnown(
- $st,
+ $this->getPageTitle(),
$suggestionSnippet,
array(),
$stParams
);
- $this->didYouMeanHtml = '<div class="searchdidyoumean">' . $this->msg( 'search-suggest' )->rawParams( $suggestLink )->text() . '</div>';
+ $this->didYouMeanHtml = '<div class="searchdidyoumean">'
+ . $this->msg( 'search-suggest' )->rawParams( $suggestLink )->text() . '</div>';
}
if ( !wfRunHooks( 'SpecialSearchResultsPrepend', array( $this, $out, $term ) ) ) {
# Hook requested termination
- wfProfileOut( __METHOD__ );
return;
}
@@ -313,78 +305,62 @@ class SpecialSearch extends SpecialPage {
array(
'id' => ( $this->profile === 'advanced' ? 'powersearch' : 'search' ),
'method' => 'get',
- 'action' => $wgScript
+ 'action' => wfScript(),
)
)
);
- $out->addHtml(
- Xml::openElement( 'table', array( 'id' => 'mw-search-top-table', 'cellpadding' => 0, 'cellspacing' => 0 ) ) .
- Xml::openElement( 'tr' ) .
- Xml::openElement( 'td' ) . "\n" .
- $this->shortDialog( $term ) .
- Xml::closeElement( 'td' ) .
- Xml::closeElement( 'tr' ) .
- Xml::closeElement( 'table' )
- );
- // Sometimes the search engine knows there are too many hits
- if ( $titleMatches instanceof SearchResultTooMany ) {
- $out->wrapWikiMsg( "==$1==\n", 'toomanymatches' );
- wfProfileOut( __METHOD__ );
- return;
+ // Get number of results
+ $titleMatchesNum = $textMatchesNum = $numTitleMatches = $numTextMatches = 0;
+ if ( $titleMatches ) {
+ $titleMatchesNum = $titleMatches->numRows();
+ $numTitleMatches = $titleMatches->getTotalHits();
+ }
+ if ( $textMatches ) {
+ $textMatchesNum = $textMatches->numRows();
+ $numTextMatches = $textMatches->getTotalHits();
}
+ $num = $titleMatchesNum + $textMatchesNum;
+ $totalRes = $numTitleMatches + $numTextMatches;
+
+ $out->addHtml(
+ # This is an awful awful ID name. It's not a table, but we
+ # named it poorly from when this was a table so now we're
+ # stuck with it
+ Xml::openElement( 'div', array( 'id' => 'mw-search-top-table' ) ) .
+ $this->shortDialog( $term, $num, $totalRes ) .
+ Xml::closeElement( 'div' ) .
+ $this->formHeader( $term ) .
+ Xml::closeElement( 'form' )
+ );
$filePrefix = $wgContLang->getFormattedNsText( NS_FILE ) . ':';
if ( trim( $term ) === '' || $filePrefix === trim( $term ) ) {
- $out->addHTML( $this->formHeader( $term, 0, 0 ) );
- $out->addHtml( $this->getProfileForm( $this->profile, $term ) );
- $out->addHTML( '</form>' );
// Empty query -- straight view of search form
- wfProfileOut( __METHOD__ );
return;
}
- // Get number of results
- $titleMatchesNum = $titleMatches ? $titleMatches->numRows() : 0;
- $textMatchesNum = $textMatches ? $textMatches->numRows() : 0;
- // Total initial query matches (possible false positives)
- $num = $titleMatchesNum + $textMatchesNum;
-
- // Get total actual results (after second filtering, if any)
- $numTitleMatches = $titleMatches && !is_null( $titleMatches->getTotalHits() ) ?
- $titleMatches->getTotalHits() : $titleMatchesNum;
- $numTextMatches = $textMatches && !is_null( $textMatches->getTotalHits() ) ?
- $textMatches->getTotalHits() : $textMatchesNum;
-
- // get total number of results if backend can calculate it
- $totalRes = 0;
- if ( $titleMatches && !is_null( $titleMatches->getTotalHits() ) ) {
- $totalRes += $titleMatches->getTotalHits();
- }
- if ( $textMatches && !is_null( $textMatches->getTotalHits() ) ) {
- $totalRes += $textMatches->getTotalHits();
- }
-
- // show number of results and current offset
- $out->addHTML( $this->formHeader( $term, $num, $totalRes ) );
- $out->addHtml( $this->getProfileForm( $this->profile, $term ) );
-
- $out->addHtml( Xml::closeElement( 'form' ) );
$out->addHtml( "<div class='searchresults'>" );
// prev/next links
+ $prevnext = null;
if ( $num || $this->offset ) {
// Show the create link ahead
- $this->showCreateLink( $t );
- $prevnext = $this->getLanguage()->viewPrevNext( $this->getTitle(), $this->offset, $this->limit,
- $this->powerSearchOptions() + array( 'search' => $term ),
- max( $titleMatchesNum, $textMatchesNum ) < $this->limit
- );
- //$out->addHTML( "<p class='mw-search-pager-top'>{$prevnext}</p>\n" );
- wfRunHooks( 'SpecialSearchResults', array( $term, &$titleMatches, &$textMatches ) );
- } else {
- wfRunHooks( 'SpecialSearchNoResults', array( $term ) );
+ $this->showCreateLink( $title, $num, $titleMatches, $textMatches );
+ if ( $totalRes > $this->limit || $this->offset ) {
+ if ( $this->searchEngineType !== null ) {
+ $this->setExtraParam( 'srbackend', $this->searchEngineType );
+ }
+ $prevnext = $this->getLanguage()->viewPrevNext(
+ $this->getPageTitle(),
+ $this->offset,
+ $this->limit,
+ $this->powerSearchOptions() + array( 'search' => $term ),
+ $this->limit + $this->offset >= $totalRes
+ );
+ }
}
+ wfRunHooks( 'SpecialSearchResults', array( $term, &$titleMatches, &$textMatches ) );
$out->parserOptions()->setEditSection( false );
if ( $titleMatches ) {
@@ -399,10 +375,8 @@ class SpecialSearch extends SpecialPage {
if ( $numTextMatches > 0 && $numTitleMatches > 0 ) {
// if no title matches the heading is redundant
$out->wrapWikiMsg( "==$1==\n", 'textmatches' );
- } elseif ( $totalRes == 0 ) {
- # Don't show the 'no text matches' if we received title matches
- # $out->wrapWikiMsg( "==$1==\n", 'notextmatches' );
}
+
// show interwiki results if any
if ( $textMatches->hasInterwikiResults() ) {
$out->addHTML( $this->showInterwiki( $textMatches->getInterwikiResults(), $term ) );
@@ -417,49 +391,60 @@ class SpecialSearch extends SpecialPage {
if ( $num === 0 ) {
if ( $textStatus ) {
$out->addHTML( '<div class="error">' .
- htmlspecialchars( $textStatus->getWikiText( 'search-error' ) ) . '</div>' );
+ $textStatus->getMessage( 'search-error' ) . '</div>' );
} else {
$out->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>",
array( 'search-nonefound', wfEscapeWikiText( $term ) ) );
- $this->showCreateLink( $t );
+ $this->showCreateLink( $title, $num, $titleMatches, $textMatches );
}
}
$out->addHtml( "</div>" );
- if ( $num || $this->offset ) {
+ if ( $prevnext ) {
$out->addHTML( "<p class='mw-search-pager-bottom'>{$prevnext}</p>\n" );
}
- wfRunHooks( 'SpecialSearchResultsAppend', array( $this, $out, $term ) );
- wfProfileOut( __METHOD__ );
}
/**
- * @param $t Title
+ * @param Title $title
+ * @param int $num The number of search results found
+ * @param null|SearchResultSet $titleMatches Results from title search
+ * @param null|SearchResultSet $textMatches Results from text search
*/
- protected function showCreateLink( $t ) {
+ protected function showCreateLink( $title, $num, $titleMatches, $textMatches ) {
// show direct page/create link if applicable
// Check DBkey !== '' in case of fragment link only.
- if ( is_null( $t ) || $t->getDBkey() === '' ) {
+ if ( is_null( $title ) || $title->getDBkey() === ''
+ || ( $titleMatches !== null && $titleMatches->searchContainedSyntax() )
+ || ( $textMatches !== null && $textMatches->searchContainedSyntax() )
+ ) {
// invalid title
// preserve the paragraph for margins etc...
$this->getOutput()->addHtml( '<p></p>' );
+
return;
}
- if ( $t->isKnown() ) {
+ $linkClass = 'mw-search-createlink';
+ if ( $title->isKnown() ) {
$messageName = 'searchmenu-exists';
- } elseif ( $t->userCan( 'create', $this->getUser() ) ) {
+ $linkClass = 'mw-search-exists';
+ } elseif ( $title->quickUserCan( 'create', $this->getUser() ) ) {
$messageName = 'searchmenu-new';
} else {
$messageName = 'searchmenu-new-nocreate';
}
- $params = array( $messageName, wfEscapeWikiText( $t->getPrefixedText() ) );
- wfRunHooks( 'SpecialSearchCreateLink', array( $t, &$params ) );
+ $params = array(
+ $messageName,
+ wfEscapeWikiText( $title->getPrefixedText() ),
+ Message::numParam( $num )
+ );
+ wfRunHooks( 'SpecialSearchCreateLink', array( $title, &$params ) );
// Extensions using the hook might still return an empty $messageName
if ( $messageName ) {
- $this->getOutput()->wrapWikiMsg( "<p class=\"mw-search-createlink\">\n$1</p>", $params );
+ $this->getOutput()->wrapWikiMsg( "<p class=\"$linkClass\">\n$1</p>", $params );
} else {
// preserve the paragraph for margins etc...
$this->getOutput()->addHtml( '<p></p>' );
@@ -467,7 +452,7 @@ class SpecialSearch extends SpecialPage {
}
/**
- * @param $term string
+ * @param string $term
*/
protected function setupPage( $term ) {
# Should advanced UI be used?
@@ -475,9 +460,10 @@ class SpecialSearch extends SpecialPage {
$out = $this->getOutput();
if ( strval( $term ) !== '' ) {
$out->setPageTitle( $this->msg( 'searchresults' ) );
- $out->setHTMLTitle( $this->msg( 'pagetitle' )->rawParams(
- $this->msg( 'searchresults-title' )->rawParams( $term )->text()
- ) );
+ $out->setHTMLTitle( $this->msg( 'pagetitle' )
+ ->rawParams( $this->msg( 'searchresults-title' )->rawParams( $term )->text() )
+ ->inContentLanguage()->text()
+ );
}
// add javascript specific to special:search
$out->addModules( 'mediawiki.special.search' );
@@ -487,8 +473,8 @@ class SpecialSearch extends SpecialPage {
* Extract "power search" namespace settings from the request object,
* returning a list of index numbers to search.
*
- * @param $request WebRequest
- * @return Array
+ * @param WebRequest $request
+ * @return array
*/
protected function powerSearch( &$request ) {
$arr = array();
@@ -504,11 +490,10 @@ class SpecialSearch extends SpecialPage {
/**
* Reconstruct the 'power search' options for links
*
- * @return Array
+ * @return array
*/
protected function powerSearchOptions() {
$opt = array();
- $opt['redirs'] = $this->searchRedirects ? 1 : 0;
if ( $this->profile !== 'advanced' ) {
$opt['profile'] = $this->profile;
} else {
@@ -516,28 +501,58 @@ class SpecialSearch extends SpecialPage {
$opt['ns' . $n] = 1;
}
}
+
return $opt + $this->extraParams;
}
/**
+ * Save namespace preferences when we're supposed to
+ *
+ * @return bool Whether we wrote something
+ */
+ protected function saveNamespaces() {
+ $user = $this->getUser();
+ $request = $this->getRequest();
+
+ if ( $user->isLoggedIn() &&
+ $user->matchEditToken(
+ $request->getVal( 'nsRemember' ),
+ 'searchnamespace',
+ $request
+ )
+ ) {
+ // Reset namespace preferences: namespaces are not searched
+ // when they're not mentioned in the URL parameters.
+ foreach ( MWNamespace::getValidNamespaces() as $n ) {
+ $user->setOption( 'searchNs' . $n, false );
+ }
+ // The request parameters include all the namespaces to be searched.
+ // Even if they're the same as an existing profile, they're not eaten.
+ foreach ( $this->namespaces as $n ) {
+ $user->setOption( 'searchNs' . $n, true );
+ }
+
+ $user->saveSettings();
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
* Show whole set of results
*
- * @param $matches SearchResultSet
+ * @param SearchResultSet $matches
*
* @return string
*/
protected function showMatches( &$matches ) {
global $wgContLang;
- wfProfileIn( __METHOD__ );
+ $profile = new ProfileSection( __METHOD__ );
$terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
- $out = "";
- $infoLine = $matches->getInfo();
- if ( !is_null( $infoLine ) ) {
- $out .= "\n<!-- {$infoLine} -->\n";
- }
- $out .= "<ul class='mw-search-results'>\n";
+ $out = "<ul class='mw-search-results'>\n";
$result = $matches->next();
while ( $result ) {
$out .= $this->showHit( $result, $terms );
@@ -547,27 +562,26 @@ class SpecialSearch extends SpecialPage {
// convert the whole thing to desired language variant
$out = $wgContLang->convert( $out );
- wfProfileOut( __METHOD__ );
+
return $out;
}
/**
* Format a single hit result
*
- * @param $result SearchResult
- * @param array $terms terms to highlight
+ * @param SearchResult $result
+ * @param array $terms Terms to highlight
*
* @return string
*/
protected function showHit( $result, $terms ) {
- wfProfileIn( __METHOD__ );
+ $profile = new ProfileSection( __METHOD__ );
if ( $result->isBrokenTitle() ) {
- wfProfileOut( __METHOD__ );
- return "<!-- Broken link in search result -->\n";
+ return '';
}
- $t = $result->getTitle();
+ $title = $result->getTitle();
$titleSnippet = $result->getTitleSnippet( $terms );
@@ -575,10 +589,10 @@ class SpecialSearch extends SpecialPage {
$titleSnippet = null;
}
- $link_t = clone $t;
+ $link_t = clone $title;
wfRunHooks( 'ShowSearchHitTitle',
- array( &$link_t, &$titleSnippet, $result, $terms, $this ) );
+ array( &$link_t, &$titleSnippet, $result, $terms, $this ) );
$link = Linker::linkKnown(
$link_t,
@@ -588,8 +602,7 @@ class SpecialSearch extends SpecialPage {
//If page content is not readable, just return the title.
//This is not quite safe, but better than showing excerpts from non-readable pages
//Note that hiding the entry entirely would screw up paging.
- if ( !$t->userCan( 'read', $this->getUser() ) ) {
- wfProfileOut( __METHOD__ );
+ if ( !$title->userCan( 'read', $this->getUser() ) ) {
return "<li>{$link}</li>\n";
}
@@ -597,8 +610,7 @@ class SpecialSearch extends SpecialPage {
// The least confusing at this point is to drop the result.
// You may get less results, but... oh well. :P
if ( $result->isMissingRevision() ) {
- wfProfileOut( __METHOD__ );
- return "<!-- missing page " . htmlspecialchars( $t->getPrefixedText() ) . "-->\n";
+ return '';
}
// format redirects / relevant sections
@@ -637,16 +649,6 @@ class SpecialSearch extends SpecialPage {
$lang = $this->getLanguage();
- // format score
- if ( is_null( $result->getScore() ) ) {
- // Search engine doesn't report scoring info
- $score = '';
- } else {
- $percent = sprintf( '%2.1f', $result->getScore() * 100 );
- $score = $this->msg( 'search-result-score' )->numParams( $percent )->text()
- . ' - ';
- }
-
// format description
$byteSize = $result->getByteSize();
$wordCount = $result->getWordCount();
@@ -654,8 +656,8 @@ class SpecialSearch extends SpecialPage {
$size = $this->msg( 'search-result-size', $lang->formatSize( $byteSize ) )
->numParams( $wordCount )->escaped();
- if ( $t->getNamespace() == NS_CATEGORY ) {
- $cat = Category::newFromTitle( $t );
+ if ( $title->getNamespace() == NS_CATEGORY ) {
+ $cat = Category::newFromTitle( $title );
$size = $this->msg( 'search-result-category-size' )
->numParams( $cat->getPageCount(), $cat->getSubcatCount(), $cat->getFileCount() )
->escaped();
@@ -663,35 +665,19 @@ class SpecialSearch extends SpecialPage {
$date = $lang->userTimeAndDate( $timestamp, $this->getUser() );
- // link to related articles if supported
- $related = '';
- if ( $result->hasRelated() ) {
- $st = SpecialPage::getTitleFor( 'Search' );
- $stParams = array_merge(
- $this->powerSearchOptions(),
- array(
- 'search' => $this->msg( 'searchrelated' )->inContentLanguage()->text() .
- ':' . $t->getPrefixedText(),
- 'fulltext' => $this->msg( 'search' )->text()
- )
- );
-
- $related = ' -- ' . Linker::linkKnown(
- $st,
- $this->msg( 'search-relatedarticle' )->text(),
- array(),
- $stParams
- );
- }
-
+ $fileMatch = '';
// Include a thumbnail for media files...
- if ( $t->getNamespace() == NS_FILE ) {
- $img = wfFindFile( $t );
+ if ( $title->getNamespace() == NS_FILE ) {
+ $img = $result->getFile();
+ $img = $img ?: wfFindFile( $title );
+ if ( $result->isFileMatch() ) {
+ $fileMatch = "<span class='searchalttitle'>" .
+ $this->msg( 'search-file-match' )->escaped() . "</span>";
+ }
if ( $img ) {
$thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) );
if ( $thumb ) {
$desc = $this->msg( 'parentheses' )->rawParams( $img->getShortDesc() )->escaped();
- wfProfileOut( __METHOD__ );
// Float doesn't seem to interact well with the bullets.
// Table messes up vertical alignment of the bullets.
// Bullets are therefore disabled (didn't look great anyway).
@@ -702,9 +688,9 @@ class SpecialSearch extends SpecialPage {
$thumb->toHtml( array( 'desc-link' => true ) ) .
'</td>' .
'<td style="vertical-align: top;">' .
- $link .
+ "{$link} {$redirect} {$section} {$fileMatch}" .
$extract .
- "<div class='mw-search-result-data'>{$score}{$desc} - {$date}{$related}</div>" .
+ "<div class='mw-search-result-data'>{$desc} - {$date}</div>" .
'</td>' .
'</tr>' .
'</table>' .
@@ -715,33 +701,33 @@ class SpecialSearch extends SpecialPage {
$html = null;
+ $score = '';
if ( wfRunHooks( 'ShowSearchHit', array(
$this, $result, $terms,
&$link, &$redirect, &$section, &$extract,
&$score, &$size, &$date, &$related,
&$html
) ) ) {
- $html = "<li><div class='mw-search-result-heading'>{$link} {$redirect} {$section}</div> {$extract}\n" .
- "<div class='mw-search-result-data'>{$score}{$size} - {$date}{$related}</div>" .
+ $html = "<li><div class='mw-search-result-heading'>" .
+ "{$link} {$redirect} {$section} {$fileMatch}</div> {$extract}\n" .
+ "<div class='mw-search-result-data'>{$size} - {$date}</div>" .
"</li>\n";
}
- wfProfileOut( __METHOD__ );
return $html;
}
/**
* Show results from other wikis
*
- * @param $matches SearchResultSet
- * @param $query String
+ * @param SearchResultSet|array $matches
+ * @param string $query
*
* @return string
*/
- protected function showInterwiki( &$matches, $query ) {
+ protected function showInterwiki( $matches, $query ) {
global $wgContLang;
- wfProfileIn( __METHOD__ );
- $terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
+ $profile = new ProfileSection( __METHOD__ );
$out = "<div id='mw-search-interwiki'><div id='mw-search-interwiki-caption'>" .
$this->msg( 'search-interwiki-caption' )->text() . "</div>\n";
@@ -749,7 +735,8 @@ class SpecialSearch extends SpecialPage {
// work out custom project captions
$customCaptions = array();
- $customLines = explode( "\n", $this->msg( 'search-interwiki-custom' )->text() ); // format per line <iwprefix>:<caption>
+ // format per line <iwprefix>:<caption>
+ $customLines = explode( "\n", $this->msg( 'search-interwiki-custom' )->text() );
foreach ( $customLines as $line ) {
$parts = explode( ":", $line, 2 );
if ( count( $parts ) == 2 ) { // validate line
@@ -757,57 +744,62 @@ class SpecialSearch extends SpecialPage {
}
}
- $prev = null;
- $result = $matches->next();
- while ( $result ) {
- $out .= $this->showInterwikiHit( $result, $prev, $terms, $query, $customCaptions );
- $prev = $result->getInterwikiPrefix();
- $result = $matches->next();
+ if ( !is_array( $matches ) ) {
+ $matches = array( $matches );
}
- // TODO: should support paging in a non-confusing way (not sure how though, maybe via ajax)..
+
+ foreach ( $matches as $set ) {
+ $prev = null;
+ $result = $set->next();
+ while ( $result ) {
+ $out .= $this->showInterwikiHit( $result, $prev, $query, $customCaptions );
+ $prev = $result->getInterwikiPrefix();
+ $result = $set->next();
+ }
+ }
+
+ // @todo Should support paging in a non-confusing way (not sure how though, maybe via ajax)..
$out .= "</ul></div>\n";
// convert the whole thing to desired language variant
$out = $wgContLang->convert( $out );
- wfProfileOut( __METHOD__ );
+
return $out;
}
/**
* Show single interwiki link
*
- * @param $result SearchResult
- * @param $lastInterwiki String
- * @param $terms Array
- * @param $query String
- * @param array $customCaptions iw prefix -> caption
+ * @param SearchResult $result
+ * @param string $lastInterwiki
+ * @param string $query
+ * @param array $customCaptions Interwiki prefix -> caption
*
* @return string
*/
- protected function showInterwikiHit( $result, $lastInterwiki, $terms, $query, $customCaptions ) {
- wfProfileIn( __METHOD__ );
+ protected function showInterwikiHit( $result, $lastInterwiki, $query, $customCaptions ) {
+ $profile = new ProfileSection( __METHOD__ );
if ( $result->isBrokenTitle() ) {
- wfProfileOut( __METHOD__ );
- return "<!-- Broken link in search result -->\n";
+ return '';
}
- $t = $result->getTitle();
+ $title = $result->getTitle();
- $titleSnippet = $result->getTitleSnippet( $terms );
+ $titleSnippet = $result->getTitleSnippet();
if ( $titleSnippet == '' ) {
$titleSnippet = null;
}
$link = Linker::linkKnown(
- $t,
+ $title,
$titleSnippet
);
// format redirect if any
$redirectTitle = $result->getRedirectTitle();
- $redirectText = $result->getRedirectSnippet( $terms );
+ $redirectText = $result->getRedirectSnippet();
$redirect = '';
if ( !is_null( $redirectTitle ) ) {
if ( $redirectText == '' ) {
@@ -822,18 +814,18 @@ class SpecialSearch extends SpecialPage {
$out = "";
// display project name
- if ( is_null( $lastInterwiki ) || $lastInterwiki != $t->getInterwiki() ) {
- if ( array_key_exists( $t->getInterwiki(), $customCaptions ) ) {
+ if ( is_null( $lastInterwiki ) || $lastInterwiki != $title->getInterwiki() ) {
+ if ( array_key_exists( $title->getInterwiki(), $customCaptions ) ) {
// captions from 'search-interwiki-custom'
- $caption = $customCaptions[$t->getInterwiki()];
+ $caption = $customCaptions[$title->getInterwiki()];
} else {
// default is to show the hostname of the other wiki which might suck
// if there are many wikis on one hostname
- $parsed = wfParseUrl( $t->getFullURL() );
+ $parsed = wfParseUrl( $title->getFullURL() );
$caption = $this->msg( 'search-interwiki-default', $parsed['host'] )->text();
}
// "more results" link (special page stuff could be localized, but we might not know target lang)
- $searchTitle = Title::newFromText( $t->getInterwiki() . ":Special:Search" );
+ $searchTitle = Title::newFromText( $title->getInterwiki() . ":Special:Search" );
$searchLink = Linker::linkKnown(
$searchTitle,
$this->msg( 'search-interwiki-more' )->text(),
@@ -848,36 +840,16 @@ class SpecialSearch extends SpecialPage {
}
$out .= "<li>{$link} {$redirect}</li>\n";
- wfProfileOut( __METHOD__ );
- return $out;
- }
- /**
- * @param $profile
- * @param $term
- * @return String
- */
- protected function getProfileForm( $profile, $term ) {
- // Hidden stuff
- $opts = array();
- $opts['redirs'] = $this->searchRedirects;
- $opts['profile'] = $this->profile;
-
- if ( $profile === 'advanced' ) {
- return $this->powerSearchBox( $term, $opts );
- } else {
- $form = '';
- wfRunHooks( 'SpecialSearchProfileForm', array( $this, &$form, $profile, $term, $opts ) );
- return $form;
- }
+ return $out;
}
/**
* Generates the power search box at [[Special:Search]]
*
- * @param string $term search term
- * @param $opts array
- * @return String: HTML form
+ * @param string $term Search term
+ * @param array $opts
+ * @return string HTML form
*/
protected function powerSearchBox( $term, $opts ) {
global $wgContLang;
@@ -896,9 +868,7 @@ class SpecialSearch extends SpecialPage {
}
$rows[$subject] .=
- Xml::openElement(
- 'td', array( 'style' => 'white-space: nowrap' )
- ) .
+ Xml::openElement( 'td' ) .
Xml::checkLabel(
$name,
"ns{$namespace}",
@@ -929,30 +899,41 @@ class SpecialSearch extends SpecialPage {
$showSections = array( 'namespaceTables' => $namespaceTables );
- // Show redirects check only if backend supports it
- if ( $this->getSearchEngine()->supports( 'list-redirects' ) ) {
- $showSections['redirects'] =
- Xml::checkLabel( $this->msg( 'powersearch-redir' )->text(), 'redirs', 'redirs', $this->searchRedirects );
- }
-
wfRunHooks( 'SpecialSearchPowerBox', array( &$showSections, $term, $opts ) );
$hidden = '';
- unset( $opts['redirs'] );
foreach ( $opts as $key => $value ) {
$hidden .= Html::hidden( $key, $value );
}
+
+ # Stuff to feed saveNamespaces()
+ $remember = '';
+ $user = $this->getUser();
+ if ( $user->isLoggedIn() ) {
+ $remember .= Xml::checkLabel(
+ wfMessage( 'powersearch-remember' )->text(),
+ 'nsRemember',
+ 'mw-search-powersearch-remember',
+ false,
+ // The token goes here rather than in a hidden field so it
+ // is only sent when necessary (not every form submission).
+ array( 'value' => $user->getEditToken(
+ 'searchnamespace',
+ $this->getRequest()
+ ) )
+ );
+ }
+
// Return final output
- return Xml::openElement(
- 'fieldset',
- array( 'id' => 'mw-searchoptions', 'style' => 'margin:0em;' )
- ) .
+ return Xml::openElement( 'fieldset', array( 'id' => 'mw-searchoptions' ) ) .
Xml::element( 'legend', null, $this->msg( 'powersearch-legend' )->text() ) .
Xml::tags( 'h4', null, $this->msg( 'powersearch-ns' )->parse() ) .
- Html::element( 'div', array( 'id' => 'mw-search-togglebox' ) ) .
+ Xml::element( 'div', array( 'id' => 'mw-search-togglebox' ), '', false ) .
Xml::element( 'div', array( 'class' => 'divider' ), '', false ) .
implode( Xml::element( 'div', array( 'class' => 'divider' ), '', false ), $showSections ) .
$hidden .
+ Xml::element( 'div', array( 'class' => 'divider' ), '', false ) .
+ $remember .
Xml::closeElement( 'fieldset' );
}
@@ -977,14 +958,6 @@ class SpecialSearch extends SpecialPage {
'tooltip' => 'searchprofile-images-tooltip',
'namespaces' => array( NS_FILE ),
),
- 'help' => array(
- 'message' => 'searchprofile-project',
- 'tooltip' => 'searchprofile-project-tooltip',
- 'namespaces' => SearchEngine::helpNamespaces(),
- 'namespace-messages' => SearchEngine::namespacesAsText(
- SearchEngine::helpNamespaces()
- ),
- ),
'all' => array(
'message' => 'searchprofile-everything',
'tooltip' => 'searchprofile-everything-tooltip',
@@ -1010,12 +983,10 @@ class SpecialSearch extends SpecialPage {
}
/**
- * @param $term
- * @param $resultsShown
- * @param $totalNum
+ * @param string $term
* @return string
*/
- protected function formHeader( $term, $resultsShown, $totalNum ) {
+ protected function formHeader( $term ) {
$out = Xml::openElement( 'div', array( 'class' => 'mw-search-formheader' ) );
$bareterm = $term;
@@ -1054,69 +1025,74 @@ class SpecialSearch extends SpecialPage {
}
$out .= Xml::closeElement( 'ul' );
$out .= Xml::closeElement( 'div' );
-
- // Results-info
- if ( $resultsShown > 0 ) {
- if ( $totalNum > 0 ) {
- $top = $this->msg( 'showingresultsheader' )
- ->numParams( $this->offset + 1, $this->offset + $resultsShown, $totalNum )
- ->params( wfEscapeWikiText( $term ) )
- ->numParams( $resultsShown )
- ->parse();
- } elseif ( $resultsShown >= $this->limit ) {
- $top = $this->msg( 'showingresults' )
- ->numParams( $this->limit, $this->offset + 1 )
- ->parse();
- } else {
- $top = $this->msg( 'showingresultsnum' )
- ->numParams( $this->limit, $this->offset + 1, $resultsShown )
- ->parse();
- }
- $out .= Xml::tags( 'div', array( 'class' => 'results-info' ),
- Xml::tags( 'ul', null, Xml::tags( 'li', null, $top ) )
- );
- }
-
$out .= Xml::element( 'div', array( 'style' => 'clear:both' ), '', false );
$out .= Xml::closeElement( 'div' );
+ // Hidden stuff
+ $opts = array();
+ $opts['profile'] = $this->profile;
+
+ if ( $this->profile === 'advanced' ) {
+ $out .= $this->powerSearchBox( $term, $opts );
+ } else {
+ $form = '';
+ wfRunHooks( 'SpecialSearchProfileForm', array( $this, &$form, $this->profile, $term, $opts ) );
+ $out .= $form;
+ }
+
return $out;
}
/**
- * @param $term string
+ * @param string $term
+ * @param int $resultsShown
+ * @param int $totalNum
* @return string
*/
- protected function shortDialog( $term ) {
- $out = Html::hidden( 'title', $this->getTitle()->getPrefixedText() );
+ protected function shortDialog( $term, $resultsShown, $totalNum ) {
+ $out = Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() );
$out .= Html::hidden( 'profile', $this->profile ) . "\n";
// Term box
$out .= Html::input( 'search', $term, 'search', array(
'id' => $this->profile === 'advanced' ? 'powerSearchText' : 'searchText',
'size' => '50',
- 'autofocus'
+ 'autofocus',
+ 'class' => 'mw-ui-input mw-ui-input-inline',
) ) . "\n";
$out .= Html::hidden( 'fulltext', 'Search' ) . "\n";
- $out .= Xml::submitButton( $this->msg( 'searchbutton' )->text() ) . "\n";
+ $out .= Xml::submitButton(
+ $this->msg( 'searchbutton' )->text(),
+ array( 'class' => array( 'mw-ui-button', 'mw-ui-progressive' ) )
+ ) . "\n";
+
+ // Results-info
+ if ( $totalNum > 0 && $this->offset < $totalNum ) {
+ $top = $this->msg( 'search-showingresults' )
+ ->numParams( $this->offset + 1, $this->offset + $resultsShown, $totalNum )
+ ->numParams( $resultsShown )
+ ->parse();
+ $out .= Xml::tags( 'div', array( 'class' => 'results-info' ), $top ) .
+ Xml::element( 'div', array( 'style' => 'clear:both' ), '', false );
+ }
+
return $out . $this->didYouMeanHtml;
}
/**
* Make a search link with some target namespaces
*
- * @param $term String
- * @param array $namespaces ignored
- * @param string $label link's text
- * @param string $tooltip link's tooltip
- * @param array $params query string parameters
- * @return String: HTML fragment
+ * @param string $term
+ * @param array $namespaces Ignored
+ * @param string $label Link's text
+ * @param string $tooltip Link's tooltip
+ * @param array $params Query string parameters
+ * @return string HTML fragment
*/
protected function makeSearchLink( $term, $namespaces, $label, $tooltip, $params = array() ) {
$opt = $params;
foreach ( $namespaces as $n ) {
$opt['ns' . $n] = 1;
}
- $opt['redirs'] = $this->searchRedirects;
$stParams = array_merge(
array(
@@ -1129,7 +1105,7 @@ class SpecialSearch extends SpecialPage {
return Xml::element(
'a',
array(
- 'href' => $this->getTitle()->getLocalURL( $stParams ),
+ 'href' => $this->getPageTitle()->getLocalURL( $stParams ),
'title' => $tooltip
),
$label
@@ -1139,33 +1115,35 @@ class SpecialSearch extends SpecialPage {
/**
* Check if query starts with image: prefix
*
- * @param string $term the string to check
- * @return Boolean
+ * @param string $term The string to check
+ * @return bool
*/
protected function startsWithImage( $term ) {
global $wgContLang;
- $p = explode( ':', $term );
- if ( count( $p ) > 1 ) {
- return $wgContLang->getNsIndex( $p[0] ) == NS_FILE;
+ $parts = explode( ':', $term );
+ if ( count( $parts ) > 1 ) {
+ return $wgContLang->getNsIndex( $parts[0] ) == NS_FILE;
}
+
return false;
}
/**
* Check if query starts with all: prefix
*
- * @param string $term the string to check
- * @return Boolean
+ * @param string $term The string to check
+ * @return bool
*/
protected function startsWithAll( $term ) {
$allkeyword = $this->msg( 'searchall' )->inContentLanguage()->text();
- $p = explode( ':', $term );
- if ( count( $p ) > 1 ) {
- return $p[0] == $allkeyword;
+ $parts = explode( ':', $term );
+ if ( count( $parts ) > 1 ) {
+ return $parts[0] == $allkeyword;
}
+
return false;
}
@@ -1179,17 +1157,34 @@ class SpecialSearch extends SpecialPage {
$this->searchEngine = $this->searchEngineType ?
SearchEngine::create( $this->searchEngineType ) : SearchEngine::create();
}
+
return $this->searchEngine;
}
/**
+ * Current search profile.
+ * @return null|string
+ */
+ function getProfile() {
+ return $this->profile;
+ }
+
+ /**
+ * Current namespaces.
+ * @return array
+ */
+ function getNamespaces() {
+ return $this->namespaces;
+ }
+
+ /**
* Users of hook SpecialSearchSetupEngine can use this to
* add more params to links to not lose selection when
* user navigates search results.
* @since 1.18
*
- * @param $key
- * @param $value
+ * @param string $key
+ * @param mixed $value
*/
public function setExtraParam( $key, $value ) {
$this->extraParams[$key] = $value;
diff --git a/includes/specials/SpecialShortpages.php b/includes/specials/SpecialShortpages.php
index 9b50875a..782d9a17 100644
--- a/includes/specials/SpecialShortpages.php
+++ b/includes/specials/SpecialShortpages.php
@@ -40,12 +40,15 @@ class ShortPagesPage extends QueryPage {
function getQueryInfo() {
return array(
'tables' => array( 'page' ),
- 'fields' => array( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_len' ),
- 'conds' => array( 'page_namespace' =>
- MWNamespace::getContentNamespaces(),
- 'page_is_redirect' => 0 ),
+ 'fields' => array(
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_len'
+ ),
+ 'conds' => array(
+ 'page_namespace' => MWNamespace::getContentNamespaces(),
+ 'page_is_redirect' => 0
+ ),
'options' => array( 'USE INDEX' => 'page_redirect_namespace_len' )
);
}
@@ -55,7 +58,7 @@ class ShortPagesPage extends QueryPage {
}
/**
- * @param $db DatabaseBase
+ * @param DatabaseBase $db
* @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {
@@ -111,8 +114,8 @@ class ShortPagesPage extends QueryPage {
$size = $this->msg( 'nbytes' )->numParams( $result->value )->escaped();
return $exists
- ? "${hlinkInParentheses} {$dm}{$plink} {$dm}[{$size}]"
- : "<del>${hlinkInParentheses} {$dm}{$plink} {$dm}[{$size}]</del>";
+ ? "${hlinkInParentheses} {$dm}{$plink} {$dm}[{$size}]"
+ : "<del>${hlinkInParentheses} {$dm}{$plink} {$dm}[{$size}]</del>";
}
protected function getGroupName() {
diff --git a/includes/specials/SpecialSpecialpages.php b/includes/specials/SpecialSpecialpages.php
index 47c89d0d..eff06f46 100644
--- a/includes/specials/SpecialSpecialpages.php
+++ b/includes/specials/SpecialSpecialpages.php
@@ -49,8 +49,6 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
}
private function getPageGroups() {
- global $wgSortSpecialPages;
-
$pages = SpecialPageFactory::getUsablePages( $this->getUser() );
if ( !count( $pages ) ) {
@@ -68,7 +66,7 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
$groups[$group] = array();
}
$groups[$group][$page->getDescription()] = array(
- $page->getTitle(),
+ $page->getPageTitle(),
$page->isRestricted(),
$page->isCached()
);
@@ -76,10 +74,8 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
}
/** Sort */
- if ( $wgSortSpecialPages ) {
- foreach ( $groups as $group => $sortedPages ) {
- ksort( $groups[$group] );
- }
+ foreach ( $groups as $group => $sortedPages ) {
+ ksort( $groups[$group] );
}
/** Always move "other" to end */
@@ -103,9 +99,15 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
$middle = ceil( $total / 2 );
$count = 0;
- $out->wrapWikiMsg( "<h2 class=\"mw-specialpagesgroup\" id=\"mw-specialpagesgroup-$group\">$1</h2>\n", "specialpages-group-$group" );
+ $out->wrapWikiMsg(
+ "<h2 class=\"mw-specialpagesgroup\" id=\"mw-specialpagesgroup-$group\">$1</h2>\n",
+ "specialpages-group-$group"
+ );
$out->addHTML(
- Html::openElement( 'table', array( 'style' => 'width:100%;', 'class' => 'mw-specialpages-table' ) ) . "\n" .
+ Html::openElement(
+ 'table',
+ array( 'style' => 'width:100%;', 'class' => 'mw-specialpages-table' )
+ ) . "\n" .
Html::openElement( 'tr' ) . "\n" .
Html::openElement( 'td', array( 'style' => 'width:30%;vertical-align:top' ) ) . "\n" .
Html::openElement( 'ul' ) . "\n"
@@ -124,7 +126,11 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
}
$link = Linker::linkKnown( $title, htmlspecialchars( $desc ) );
- $out->addHTML( Html::rawElement( 'li', array( 'class' => implode( ' ', $pageClasses ) ), $link ) . "\n" );
+ $out->addHTML( Html::rawElement(
+ 'li',
+ array( 'class' => implode( ' ', $pageClasses ) ),
+ $link
+ ) . "\n" );
# Split up the larger groups
$count++;
@@ -144,6 +150,7 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
}
if ( $includesRestrictedPages || $includesCachedPages ) {
+ $out->wrapWikiMsg( "<h2 class=\"mw-specialpages-note-top\">$1</h2>", 'specialpages-note-top' );
$out->wrapWikiMsg( "<div class=\"mw-specialpages-notes\">\n$1\n</div>", 'specialpages-note' );
}
}
diff --git a/includes/specials/SpecialStatistics.php b/includes/specials/SpecialStatistics.php
index f26d1a60..f0e360e8 100644
--- a/includes/specials/SpecialStatistics.php
+++ b/includes/specials/SpecialStatistics.php
@@ -28,16 +28,18 @@
* @ingroup SpecialPage
*/
class SpecialStatistics extends SpecialPage {
-
private $views, $edits, $good, $images, $total, $users,
- $activeUsers = 0;
+ $activeUsers = 0;
public function __construct() {
parent::__construct( 'Statistics' );
}
public function execute( $par ) {
- global $wgMemc, $wgDisableCounters, $wgMiserMode;
+ global $wgMemc;
+
+ $disableCounters = $this->getConfig()->get( 'DisableCounters' );
+ $miserMode = $this->getConfig()->get( 'MiserMode' );
$this->setHeaders();
$this->getOutput()->addModuleStyles( 'mediawiki.special' );
@@ -53,12 +55,12 @@ class SpecialStatistics extends SpecialPage {
# Staticic - views
$viewsStats = '';
- if ( !$wgDisableCounters ) {
+ if ( !$disableCounters ) {
$viewsStats = $this->getViewsStats();
}
# Set active user count
- if ( !$wgMiserMode ) {
+ if ( !$miserMode ) {
$key = wfMemcKey( 'sitestats', 'activeusers-updated' );
// Re-calculate the count if the last tally is old...
if ( !$wgMemc->get( $key ) ) {
@@ -84,7 +86,7 @@ class SpecialStatistics extends SpecialPage {
$text .= $viewsStats;
# Statistic - popular pages
- if ( !$wgDisableCounters && !$wgMiserMode ) {
+ if ( !$disableCounters && !$miserMode ) {
$text .= $this->getMostViewedPages();
}
@@ -107,14 +109,16 @@ class SpecialStatistics extends SpecialPage {
/**
* Format a row
- * @param $text String: description of the row
- * @param $number Float: a statistical number
- * @param $trExtraParams Array: params to table row, see Html::elememt
- * @param $descMsg String: message key
+ * @param string $text Description of the row
+ * @param float $number A statistical number
+ * @param array $trExtraParams Params to table row, see Html::elememt
+ * @param string $descMsg Message key
* @param array|string $descMsgParam Message parameters
- * @return string table row in HTML format
+ * @return string Table row in HTML format
*/
- private function formatRow( $text, $number, $trExtraParams = array(), $descMsg = '', $descMsgParam = '' ) {
+ private function formatRow( $text, $number, $trExtraParams = array(),
+ $descMsg = '', $descMsgParam = ''
+ ) {
if ( $descMsg ) {
$msg = $this->msg( $descMsg, $descMsgParam );
if ( $msg->exists() ) {
@@ -123,6 +127,7 @@ class SpecialStatistics extends SpecialPage {
" $descriptionText" );
}
}
+
return Html::rawElement( 'tr', $trExtraParams,
Html::rawElement( 'td', array(), $text ) .
Html::rawElement( 'td', array( 'class' => 'mw-statistics-numbers' ), $number )
@@ -139,55 +144,59 @@ class SpecialStatistics extends SpecialPage {
Xml::tags( 'th', array( 'colspan' => '2' ), $this->msg( 'statistics-header-pages' )->parse() ) .
Xml::closeElement( 'tr' ) .
$this->formatRow( Linker::linkKnown( SpecialPage::getTitleFor( 'Allpages' ),
- $this->msg( 'statistics-articles' )->parse() ),
- $this->getLanguage()->formatNum( $this->good ),
- array( 'class' => 'mw-statistics-articles' ) ) .
+ $this->msg( 'statistics-articles' )->parse() ),
+ $this->getLanguage()->formatNum( $this->good ),
+ array( 'class' => 'mw-statistics-articles' ) ) .
$this->formatRow( $this->msg( 'statistics-pages' )->parse(),
- $this->getLanguage()->formatNum( $this->total ),
- array( 'class' => 'mw-statistics-pages' ),
- 'statistics-pages-desc' ) .
- $this->formatRow( Linker::linkKnown( SpecialPage::getTitleFor( 'Listfiles' ),
- $this->msg( 'statistics-files' )->parse() ),
- $this->getLanguage()->formatNum( $this->images ),
- array( 'class' => 'mw-statistics-files' ) );
+ $this->getLanguage()->formatNum( $this->total ),
+ array( 'class' => 'mw-statistics-pages' ),
+ 'statistics-pages-desc' ) .
+ $this->formatRow( Linker::linkKnown( SpecialPage::getTitleFor( 'MediaStatistics' ),
+ $this->msg( 'statistics-files' )->parse() ),
+ $this->getLanguage()->formatNum( $this->images ),
+ array( 'class' => 'mw-statistics-files' ) );
}
+
private function getEditStats() {
return Xml::openElement( 'tr' ) .
Xml::tags( 'th', array( 'colspan' => '2' ), $this->msg( 'statistics-header-edits' )->parse() ) .
Xml::closeElement( 'tr' ) .
- $this->formatRow( $this->msg( 'statistics-edits' )->parse(),
- $this->getLanguage()->formatNum( $this->edits ),
- array( 'class' => 'mw-statistics-edits' ) ) .
- $this->formatRow( $this->msg( 'statistics-edits-average' )->parse(),
- $this->getLanguage()->formatNum( sprintf( '%.2f', $this->total ? $this->edits / $this->total : 0 ) ),
- array( 'class' => 'mw-statistics-edits-average' ) );
+ $this->formatRow( $this->msg( 'statistics-edits' )->parse(),
+ $this->getLanguage()->formatNum( $this->edits ),
+ array( 'class' => 'mw-statistics-edits' )
+ ) .
+ $this->formatRow( $this->msg( 'statistics-edits-average' )->parse(),
+ $this->getLanguage()
+ ->formatNum( sprintf( '%.2f', $this->total ? $this->edits / $this->total : 0 ) ),
+ array( 'class' => 'mw-statistics-edits-average' )
+ );
}
private function getUserStats() {
- global $wgActiveUserDays;
return Xml::openElement( 'tr' ) .
Xml::tags( 'th', array( 'colspan' => '2' ), $this->msg( 'statistics-header-users' )->parse() ) .
Xml::closeElement( 'tr' ) .
- $this->formatRow( $this->msg( 'statistics-users' )->parse(),
- $this->getLanguage()->formatNum( $this->users ),
- array( 'class' => 'mw-statistics-users' ) ) .
- $this->formatRow( $this->msg( 'statistics-users-active' )->parse() . ' ' .
- Linker::linkKnown(
- SpecialPage::getTitleFor( 'Activeusers' ),
- $this->msg( 'listgrouprights-members' )->escaped()
- ),
- $this->getLanguage()->formatNum( $this->activeUsers ),
- array( 'class' => 'mw-statistics-users-active' ),
- 'statistics-users-active-desc',
- $this->getLanguage()->formatNum( $wgActiveUserDays ) );
+ $this->formatRow( $this->msg( 'statistics-users' )->parse(),
+ $this->getLanguage()->formatNum( $this->users ),
+ array( 'class' => 'mw-statistics-users' )
+ ) .
+ $this->formatRow( $this->msg( 'statistics-users-active' )->parse() . ' ' .
+ Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Activeusers' ),
+ $this->msg( 'listgrouprights-members' )->escaped()
+ ),
+ $this->getLanguage()->formatNum( $this->activeUsers ),
+ array( 'class' => 'mw-statistics-users-active' ),
+ 'statistics-users-active-desc',
+ $this->getLanguage()->formatNum( $this->getConfig()->get( 'ActiveUserDays' ) )
+ );
}
private function getGroupStats() {
- global $wgGroupPermissions, $wgImplicitGroups;
$text = '';
- foreach ( $wgGroupPermissions as $group => $permissions ) {
+ foreach ( $this->getConfig()->get( 'GroupPermissions' ) as $group => $permissions ) {
# Skip generic * and implicit groups
- if ( in_array( $group, $wgImplicitGroups ) || $group == '*' ) {
+ if ( in_array( $group, $this->getConfig()->get( 'ImplicitGroups' ) ) || $group == '*' ) {
continue;
}
$groupname = htmlspecialchars( $group );
@@ -224,6 +233,7 @@ class SpecialStatistics extends SpecialPage {
$this->getLanguage()->formatNum( $countUsers ),
array( 'class' => 'statistics-group-' . Sanitizer::escapeClass( $group ) . $classZero ) );
}
+
return $text;
}
@@ -244,36 +254,43 @@ class SpecialStatistics extends SpecialPage {
$text = '';
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select(
- 'page',
- array(
- 'page_namespace',
- 'page_title',
- 'page_counter',
- ),
- array(
- 'page_is_redirect' => 0,
- 'page_counter > 0',
- ),
- __METHOD__,
- array(
- 'ORDER BY' => 'page_counter DESC',
- 'LIMIT' => 10,
- )
+ 'page',
+ array(
+ 'page_namespace',
+ 'page_title',
+ 'page_counter',
+ ),
+ array(
+ 'page_is_redirect' => 0,
+ 'page_counter > 0',
+ ),
+ __METHOD__,
+ array(
+ 'ORDER BY' => 'page_counter DESC',
+ 'LIMIT' => 10,
+ )
+ );
+
+ if ( $res->numRows() > 0 ) {
+ $text .= Xml::openElement( 'tr' );
+ $text .= Xml::tags(
+ 'th',
+ array( 'colspan' => '2' ),
+ $this->msg( 'statistics-mostpopular' )->parse()
);
- if ( $res->numRows() > 0 ) {
- $text .= Xml::openElement( 'tr' );
- $text .= Xml::tags( 'th', array( 'colspan' => '2' ), $this->msg( 'statistics-mostpopular' )->parse() );
- $text .= Xml::closeElement( 'tr' );
- foreach ( $res as $row ) {
- $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
- if ( $title instanceof Title ) {
- $text .= $this->formatRow( Linker::link( $title ),
- $this->getLanguage()->formatNum( $row->page_counter ) );
-
- }
+ $text .= Xml::closeElement( 'tr' );
+
+ foreach ( $res as $row ) {
+ $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
+
+ if ( $title instanceof Title ) {
+ $text .= $this->formatRow( Linker::link( $title ),
+ $this->getLanguage()->formatNum( $row->page_counter ) );
}
- $res->free();
}
+ $res->free();
+ }
+
return $text;
}
@@ -301,7 +318,11 @@ class SpecialStatistics extends SpecialPage {
$name = $this->msg( $key )->parse();
$number = htmlspecialchars( $value );
- $return .= $this->formatRow( $name, $this->getLanguage()->formatNum( $number ), array( 'class' => 'mw-statistics-hook', 'id' => 'mw-' . $key ) );
+ $return .= $this->formatRow(
+ $name,
+ $this->getLanguage()->formatNum( $number ),
+ array( 'class' => 'mw-statistics-hook', 'id' => 'mw-' . $key )
+ );
}
} else {
// Create the legacy header only once
@@ -310,7 +331,8 @@ class SpecialStatistics extends SpecialPage {
}
// Recursively remap the legacy structure
- $return .= $this->getOtherStats( array( 'statistics-header-hooks' => array( $header => $items ) ) );
+ $return .= $this->getOtherStats( array( 'statistics-header-hooks' =>
+ array( $header => $items ) ) );
}
}
diff --git a/includes/specials/SpecialTags.php b/includes/specials/SpecialTags.php
index 077e7cbc..b7627285 100644
--- a/includes/specials/SpecialTags.php
+++ b/includes/specials/SpecialTags.php
@@ -46,11 +46,11 @@ class SpecialTags extends SpecialPage {
// Write the headers
$html = Xml::tags( 'tr', null, Xml::tags( 'th', null, $this->msg( 'tags-tag' )->parse() ) .
- Xml::tags( 'th', null, $this->msg( 'tags-display-header' )->parse() ) .
- Xml::tags( 'th', null, $this->msg( 'tags-description-header' )->parse() ) .
- Xml::tags( 'th', null, $this->msg( 'tags-active-header' )->parse() ) .
- Xml::tags( 'th', null, $this->msg( 'tags-hitcount-header' )->parse() )
- );
+ Xml::tags( 'th', null, $this->msg( 'tags-display-header' )->parse() ) .
+ Xml::tags( 'th', null, $this->msg( 'tags-description-header' )->parse() ) .
+ Xml::tags( 'th', null, $this->msg( 'tags-active-header' )->parse() ) .
+ Xml::tags( 'th', null, $this->msg( 'tags-hitcount-header' )->parse() )
+ );
// Used in #doTagRow()
$this->definedTags = array_fill_keys( ChangeTags::listDefinedTags(), true );
diff --git a/includes/specials/SpecialTrackingCategories.php b/includes/specials/SpecialTrackingCategories.php
new file mode 100644
index 00000000..552031f1
--- /dev/null
+++ b/includes/specials/SpecialTrackingCategories.php
@@ -0,0 +1,148 @@
+<?php
+/**
+ * Implements Special:TrackingCategories
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * A special page that displays list of tracking categories
+ * Tracking categories allow pages with certain characteristics to be tracked.
+ * It works by adding any such page to a category automatically.
+ * Category is specified by the tracking category's system message.
+ *
+ * @ingroup SpecialPage
+ * @since 1.23
+ */
+
+class SpecialTrackingCategories extends SpecialPage {
+ function __construct() {
+ parent::__construct( 'TrackingCategories' );
+ }
+
+ function execute( $par ) {
+ $this->setHeaders();
+ $this->outputHeader();
+ $this->getOutput()->allowClickjacking();
+ $this->getOutput()->addHTML(
+ Html::openElement( 'table', array( 'class' => 'mw-datatable',
+ 'id' => 'mw-trackingcategories-table' ) ) . "\n" .
+ "<thead><tr>
+ <th>" .
+ $this->msg( 'trackingcategories-msg' )->escaped() . "
+ </th>
+ <th>" .
+ $this->msg( 'trackingcategories-name' )->escaped() .
+ "</th>
+ <th>" .
+ $this->msg( 'trackingcategories-desc' )->escaped() . "
+ </th>
+ </tr></thead>"
+ );
+
+ foreach ( $this->getConfig()->get( 'TrackingCategories' ) as $catMsg ) {
+ /*
+ * Check if the tracking category varies by namespace
+ * Otherwise only pages in the current namespace will be displayed
+ * If it does vary, show pages considering all namespaces
+ */
+ $msgObj = $this->msg( $catMsg )->inContentLanguage();
+ $allMsgs = array();
+ $catDesc = $catMsg . '-desc';
+ $catMsgTitle = Title::makeTitleSafe( NS_MEDIAWIKI, $catMsg );
+ if ( !$catMsgTitle ) {
+ continue;
+ }
+ $catMsgTitleText = Linker::link(
+ $catMsgTitle,
+ htmlspecialchars( $catMsg )
+ );
+
+ // Match things like {{NAMESPACE}} and {{NAMESPACENUMBER}}.
+ // False positives are ok, this is just an efficiency shortcut
+ if ( strpos( $msgObj->plain(), '{{' ) !== false ) {
+ $ns = MWNamespace::getValidNamespaces();
+ foreach ( $ns as $namesp ) {
+ $tempTitle = Title::makeTitleSafe( $namesp, $catMsg );
+ if ( !$tempTitle ) {
+ continue;
+ }
+ $catName = $msgObj->title( $tempTitle )->text();
+ # Allow tracking categories to be disabled by setting them to "-"
+ if ( $catName !== '-' ) {
+ $catTitle = Title::makeTitleSafe( NS_CATEGORY, $catName );
+ if ( $catTitle ) {
+ $catTitleText = Linker::link(
+ $catTitle,
+ htmlspecialchars( $catName )
+ );
+ $allMsgs[] = $catTitleText;
+ }
+ }
+ }
+ } else {
+ $catName = $msgObj->text();
+ # Allow tracking categories to be disabled by setting them to "-"
+ if ( $catName !== '-' ) {
+ $catTitle = Title::makeTitleSafe( NS_CATEGORY, $catName );
+ if ( $catTitle ) {
+ $catTitleText = Linker::link(
+ $catTitle,
+ htmlspecialchars( $catName )
+ );
+ $allMsgs[] = $catTitleText;
+ }
+ }
+ }
+
+ # Extra message, when no category was found
+ if ( !count( $allMsgs ) ) {
+ $allMsgs[] = $this->msg( 'trackingcategories-disabled' )->parse();
+ }
+
+ /*
+ * Show category description if it exists as a system message
+ * as category-name-desc
+ */
+ $descMsg = $this->msg( $catDesc );
+ if ( $descMsg->isBlank() ) {
+ $descMsg = $this->msg( 'trackingcategories-nodesc' );
+ }
+
+ $this->getOutput()->addHTML(
+ Html::openElement( 'tr' ) .
+ Html::openElement( 'td', array( 'class' => 'mw-trackingcategories-name' ) ) .
+ $this->getLanguage()->commaList( array_unique( $allMsgs ) ) .
+ Html::closeElement( 'td' ) .
+ Html::openElement( 'td', array( 'class' => 'mw-trackingcategories-msg' ) ) .
+ $catMsgTitleText .
+ Html::closeElement( 'td' ) .
+ Html::openElement( 'td', array( 'class' => 'mw-trackingcategories-desc' ) ) .
+ $descMsg->parse() .
+ Html::closeElement( 'td' ) .
+ Html::closeElement( 'tr' )
+ );
+ }
+ $this->getOutput()->addHTML( Html::closeElement( 'table' ) );
+ }
+
+ protected function getGroupName() {
+ return 'pages';
+ }
+}
diff --git a/includes/specials/SpecialUnblock.php b/includes/specials/SpecialUnblock.php
index ca93b6d1..244b8894 100644
--- a/includes/specials/SpecialUnblock.php
+++ b/includes/specials/SpecialUnblock.php
@@ -42,6 +42,11 @@ class SpecialUnblock extends SpecialPage {
list( $this->target, $this->type ) = SpecialBlock::getTargetAndType( $par, $this->getRequest() );
$this->block = Block::newFromTarget( $this->target );
+ if ( $this->target instanceof User ) {
+ # Set the 'relevant user' in the skin, so it displays links like Contributions,
+ # User logs, UserRights, etc.
+ $this->getSkin()->setRelevantUser( $this->target );
+ }
$this->setHeaders();
$this->outputHeader();
@@ -58,8 +63,10 @@ class SpecialUnblock extends SpecialPage {
if ( $form->show() ) {
switch ( $this->type ) {
- case Block::TYPE_USER:
case Block::TYPE_IP:
+ $out->addWikiMsg( 'unblocked-ip', wfEscapeWikiText( $this->target ) );
+ break;
+ case Block::TYPE_USER:
$out->addWikiMsg( 'unblocked', wfEscapeWikiText( $this->target ) );
break;
case Block::TYPE_RANGE:
@@ -77,14 +84,14 @@ class SpecialUnblock extends SpecialPage {
$fields = array(
'Target' => array(
'type' => 'text',
- 'label-message' => 'ipadressorusername',
- 'tabindex' => '1',
+ 'label-message' => 'ipaddressorusername',
+ 'autofocus' => true,
'size' => '45',
'required' => true,
),
'Name' => array(
'type' => 'info',
- 'label-message' => 'ipadressorusername',
+ 'label-message' => 'ipaddressorusername',
),
'Reason' => array(
'type' => 'text',
@@ -102,13 +109,18 @@ class SpecialUnblock extends SpecialPage {
if ( $type == Block::TYPE_AUTO && $this->type == Block::TYPE_IP ) {
$fields['Target']['default'] = $this->target;
unset( $fields['Name'] );
-
} else {
$fields['Target']['default'] = $target;
$fields['Target']['type'] = 'hidden';
switch ( $type ) {
- case Block::TYPE_USER:
case Block::TYPE_IP:
+ $fields['Name']['default'] = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Contributions', $target->getName() ),
+ $target->getName()
+ );
+ $fields['Name']['raw'] = true;
+ break;
+ case Block::TYPE_USER:
$fields['Name']['default'] = Linker::link(
$target->getUserPage(),
$target->getName()
@@ -127,12 +139,15 @@ class SpecialUnblock extends SpecialPage {
$fields['Target']['default'] = "#{$this->target}";
break;
}
+ // target is hidden, so the reason is the first element
+ $fields['Target']['autofocus'] = false;
+ $fields['Reason']['autofocus'] = true;
}
-
} else {
$fields['Target']['default'] = $this->target;
unset( $fields['Name'] );
}
+
return $fields;
}
@@ -140,7 +155,7 @@ class SpecialUnblock extends SpecialPage {
* Submit callback for an HTMLForm object
* @param array $data
* @param HTMLForm $form
- * @return Array( Array(message key, parameters)
+ * @return array|bool Array(message key, parameters)
*/
public static function processUIUnblock( array $data, HTMLForm $form ) {
return self::processUnblock( $data, $form->getContext() );
@@ -149,10 +164,10 @@ class SpecialUnblock extends SpecialPage {
/**
* Process the form
*
- * @param $data Array
- * @param $context IContextSource
+ * @param array $data
+ * @param IContextSource $context
* @throws ErrorPageError
- * @return Array( Array(message key, parameters) ) on failure, True on success
+ * @return array|bool Array(message key, parameters) on failure, True on success
*/
public static function processUnblock( array $data, IContextSource $context ) {
$performer = $context->getUser();
@@ -176,6 +191,7 @@ class SpecialUnblock extends SpecialPage {
list( $target, $type ) = SpecialBlock::getTargetAndType( $target );
if ( $block->getType() == Block::TYPE_RANGE && $type == Block::TYPE_IP ) {
$range = $block->getTarget();
+
return array( array( 'ipb_blocked_as_range', $target, $range ) );
}
diff --git a/includes/specials/SpecialUncategorizedimages.php b/includes/specials/SpecialUncategorizedimages.php
index 3bfcedec..9060f53a 100644
--- a/includes/specials/SpecialUncategorizedimages.php
+++ b/includes/specials/SpecialUncategorizedimages.php
@@ -26,10 +26,9 @@
* Special page lists images which haven't been categorised
*
* @ingroup SpecialPage
+ * @todo FIXME: Use an instance of UncategorizedPagesPage or something
*/
-// @todo FIXME: Use an instance of UncategorizedPagesPage or something
class UncategorizedImagesPage extends ImageQueryPage {
-
function __construct( $name = 'Uncategorizedimages' ) {
parent::__construct( $name );
}
@@ -50,13 +49,13 @@ class UncategorizedImagesPage extends ImageQueryPage {
return array(
'tables' => array( 'page', 'categorylinks' ),
'fields' => array( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_title' ),
+ 'title' => 'page_title',
+ 'value' => 'page_title' ),
'conds' => array( 'cl_from IS NULL',
- 'page_namespace' => NS_FILE,
- 'page_is_redirect' => 0 ),
+ 'page_namespace' => NS_FILE,
+ 'page_is_redirect' => 0 ),
'join_conds' => array( 'categorylinks' => array(
- 'LEFT JOIN', 'cl_from=page_id' ) )
+ 'LEFT JOIN', 'cl_from=page_id' ) )
);
}
diff --git a/includes/specials/SpecialUncategorizedpages.php b/includes/specials/SpecialUncategorizedpages.php
index 8bc9e489..8251d5b3 100644
--- a/includes/specials/SpecialUncategorizedpages.php
+++ b/includes/specials/SpecialUncategorizedpages.php
@@ -25,8 +25,8 @@
* A special page looking for page without any category.
*
* @ingroup SpecialPage
+ * @todo FIXME: Make $requestedNamespace selectable, unify all subclasses into one
*/
-// @todo FIXME: Make $requestedNamespace selectable, unify all subclasses into one
class UncategorizedPagesPage extends PageQueryPage {
protected $requestedNamespace = false;
@@ -49,16 +49,23 @@ class UncategorizedPagesPage extends PageQueryPage {
function getQueryInfo() {
return array(
'tables' => array( 'page', 'categorylinks' ),
- 'fields' => array( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_title' ),
+ 'fields' => array(
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_title'
+ ),
// default for page_namespace is all content namespaces (if requestedNamespace is false)
// otherwise, page_namespace is requestedNamespace
- 'conds' => array( 'cl_from IS NULL',
- 'page_namespace' => ( $this->requestedNamespace !== false ? $this->requestedNamespace : MWNamespace::getContentNamespaces() ),
- 'page_is_redirect' => 0 ),
- 'join_conds' => array( 'categorylinks' => array(
- 'LEFT JOIN', 'cl_from = page_id' ) )
+ 'conds' => array(
+ 'cl_from IS NULL',
+ 'page_namespace' => $this->requestedNamespace !== false
+ ? $this->requestedNamespace
+ : MWNamespace::getContentNamespaces(),
+ 'page_is_redirect' => 0
+ ),
+ 'join_conds' => array(
+ 'categorylinks' => array( 'LEFT JOIN', 'cl_from = page_id' )
+ )
);
}
@@ -68,6 +75,7 @@ class UncategorizedPagesPage extends PageQueryPage {
if ( $this->requestedNamespace === false && count( MWNamespace::getContentNamespaces() ) > 1 ) {
return array( 'page_namespace', 'page_title' );
}
+
return array( 'page_title' );
}
diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php
index d4aed113..c3e871b8 100644
--- a/includes/specials/SpecialUndelete.php
+++ b/includes/specials/SpecialUndelete.php
@@ -27,26 +27,28 @@
* @ingroup SpecialPage
*/
class PageArchive {
- /**
- * @var Title
- */
+ /** @var Title */
protected $title;
- /**
- * @var Status
- */
+ /** @var Status */
protected $fileStatus;
- /**
- * @var Status
- */
+ /** @var Status */
protected $revisionStatus;
- function __construct( $title ) {
+ /** @var Config */
+ protected $config;
+
+ function __construct( $title, Config $config = null ) {
if ( is_null( $title ) ) {
throw new MWException( __METHOD__ . ' given a null title.' );
}
$this->title = $title;
+ if ( $config === null ) {
+ wfDebug( __METHOD__ . ' did not have a Config object passed to it' );
+ $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
+ }
+ $this->config = $config;
}
/**
@@ -58,6 +60,7 @@ class PageArchive {
*/
public static function listAllPages() {
$dbr = wfGetDB( DB_SLAVE );
+
return self::listPages( $dbr, '' );
}
@@ -96,7 +99,7 @@ class PageArchive {
* @return bool|ResultWrapper
*/
protected static function listPages( $dbr, $condition ) {
- return $dbr->resultObject( $dbr->select(
+ return $dbr->select(
array( 'archive' ),
array(
'ar_namespace',
@@ -110,7 +113,7 @@ class PageArchive {
'ORDER BY' => array( 'ar_namespace', 'ar_title' ),
'LIMIT' => 100,
)
- ) );
+ );
}
/**
@@ -120,28 +123,42 @@ class PageArchive {
* @return ResultWrapper
*/
function listRevisions() {
- global $wgContentHandlerUseDB;
-
$dbr = wfGetDB( DB_SLAVE );
+ $tables = array( 'archive' );
+
$fields = array(
'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1',
);
- if ( $wgContentHandlerUseDB ) {
+ if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
$fields[] = 'ar_content_format';
$fields[] = 'ar_content_model';
}
- $res = $dbr->select( 'archive',
+ $conds = array( 'ar_namespace' => $this->title->getNamespace(),
+ 'ar_title' => $this->title->getDBkey() );
+
+ $options = array( 'ORDER BY' => 'ar_timestamp DESC' );
+
+ $join_conds = array();
+
+ ChangeTags::modifyDisplayQuery(
+ $tables,
$fields,
- array( 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey() ),
- __METHOD__,
- array( 'ORDER BY' => 'ar_timestamp DESC' ) );
+ $conds,
+ $join_conds,
+ $options
+ );
- return $dbr->resultObject( $res );
+ return $dbr->select( $tables,
+ $fields,
+ $conds,
+ __METHOD__,
+ $options,
+ $join_conds
+ );
}
/**
@@ -158,15 +175,13 @@ class PageArchive {
}
$dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select(
+ return $dbr->select(
'filearchive',
ArchivedFile::selectFields(),
array( 'fa_name' => $this->title->getDBkey() ),
__METHOD__,
array( 'ORDER BY' => 'fa_timestamp DESC' )
);
-
- return $dbr->resultObject( $res );
}
/**
@@ -177,8 +192,6 @@ class PageArchive {
* @return Revision|null
*/
function getRevision( $timestamp ) {
- global $wgContentHandlerUseDB;
-
$dbr = wfGetDB( DB_SLAVE );
$fields = array(
@@ -196,7 +209,7 @@ class PageArchive {
'ar_sha1',
);
- if ( $wgContentHandlerUseDB ) {
+ if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
$fields[] = 'ar_content_format';
$fields[] = 'ar_content_model';
}
@@ -318,7 +331,7 @@ class PageArchive {
/**
* Quick check if any archived revisions are present for the page.
*
- * @return boolean
+ * @return bool
*/
function isDeleted() {
$dbr = wfGetDB( DB_SLAVE );
@@ -336,16 +349,18 @@ class PageArchive {
* Once restored, the items will be removed from the archive tables.
* The deletion log will be updated with an undeletion notice.
*
- * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete.
+ * @param array $timestamps Pass an empty array to restore all revisions,
+ * otherwise list the ones to undelete.
* @param string $comment
* @param array $fileVersions
* @param bool $unsuppress
* @param User $user User performing the action, or null to use $wgUser
- *
- * @return array(number of file revisions restored, number of image revisions restored, log message)
- * on success, false on failure
+ * @return array(number of file revisions restored, number of image revisions
+ * restored, log message) on success, false on failure.
*/
- function undelete( $timestamps, $comment = '', $fileVersions = array(), $unsuppress = false, User $user = null ) {
+ function undelete( $timestamps, $comment = '', $fileVersions = array(),
+ $unsuppress = false, User $user = null
+ ) {
// If both the set of text revisions and file revisions are empty,
// restore everything. Otherwise, just restore the requested items.
$restoreAll = empty( $timestamps ) && empty( $fileVersions );
@@ -388,6 +403,7 @@ class PageArchive {
->inContentLanguage()->text();
} else {
wfDebug( "Undelete: nothing undeleted...\n" );
+
return false;
}
@@ -418,15 +434,14 @@ class PageArchive {
* to the cur/old tables. If the page currently exists, all revisions will
* be stuffed into old, otherwise the most recent will go into cur.
*
- * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete.
+ * @param array $timestamps Pass an empty array to restore all revisions,
+ * otherwise list the ones to undelete.
* @param bool $unsuppress Remove all ar_deleted/fa_deleted restrictions of seletected revs
* @param string $comment
* @throws ReadOnlyError
- * @return Status Object containing the number of revisions restored on success
+ * @return Status Status object containing the number of revisions restored on success
*/
private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) {
- global $wgContentHandlerUseDB;
-
if ( wfReadOnly() ) {
throw new ReadOnlyError();
}
@@ -475,15 +490,12 @@ class PageArchive {
$previousTimestamp = 0;
}
- if ( $restoreAll ) {
- $oldones = '1 = 1'; # All revisions...
- } else {
- $oldts = implode( ',',
- array_map( array( &$dbw, 'addQuotes' ),
- array_map( array( &$dbw, 'timestamp' ),
- $timestamps ) ) );
-
- $oldones = "ar_timestamp IN ( {$oldts} )";
+ $oldWhere = array(
+ 'ar_namespace' => $this->title->getNamespace(),
+ 'ar_title' => $this->title->getDBkey(),
+ );
+ if ( !$restoreAll ) {
+ $oldWhere['ar_timestamp'] = array_map( array( &$dbw, 'timestamp' ), $timestamps );
}
$fields = array(
@@ -502,7 +514,7 @@ class PageArchive {
'ar_sha1'
);
- if ( $wgContentHandlerUseDB ) {
+ if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
$fields[] = 'ar_content_format';
$fields[] = 'ar_content_model';
}
@@ -512,27 +524,25 @@ class PageArchive {
*/
$result = $dbw->select( 'archive',
$fields,
- /* WHERE */ array(
- 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey(),
- $oldones ),
+ $oldWhere,
__METHOD__,
/* options */ array( 'ORDER BY' => 'ar_timestamp' )
);
- $ret = $dbw->resultObject( $result );
- $rev_count = $dbw->numRows( $result );
+ $rev_count = $result->numRows();
if ( !$rev_count ) {
wfDebug( __METHOD__ . ": no revisions to restore\n" );
$status = Status::newGood( 0 );
$status->warning( "undelete-no-results" );
+
return $status;
}
- $ret->seek( $rev_count - 1 ); // move to last
- $row = $ret->fetchObject(); // get newest archived rev
- $ret->seek( 0 ); // move back
+ $result->seek( $rev_count - 1 ); // move to last
+ $row = $result->fetchObject(); // get newest archived rev
+ $oldPageId = (int)$row->ar_page_id; // pass this to ArticleUndelete hook
+ $result->seek( 0 ); // move back
// grab the content to check consistency with global state before restoring the page.
$revision = Revision::newFromArchiveRow( $row,
@@ -574,7 +584,7 @@ class PageArchive {
$revision = null;
$restored = 0;
- foreach ( $ret as $row ) {
+ foreach ( $result as $row ) {
// Check for key dupes due to shitty archive integrity.
if ( $row->ar_rev_id ) {
$exists = $dbw->selectField( 'revision', '1',
@@ -599,10 +609,7 @@ class PageArchive {
}
# Now that it's safely stored, take it out of the archive
$dbw->delete( 'archive',
- /* WHERE */ array(
- 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey(),
- $oldones ),
+ $oldWhere,
__METHOD__ );
// Was anything restored at all?
@@ -617,10 +624,14 @@ class PageArchive {
if ( $created || $wasnew ) {
// Update site stats, link tables, etc
$user = User::newFromName( $revision->getRawUserText(), false );
- $article->doEditUpdates( $revision, $user, array( 'created' => $created, 'oldcountable' => $oldcountable ) );
+ $article->doEditUpdates(
+ $revision,
+ $user,
+ array( 'created' => $created, 'oldcountable' => $oldcountable )
+ );
}
- wfRunHooks( 'ArticleUndelete', array( &$this->title, $created, $comment ) );
+ wfRunHooks( 'ArticleUndelete', array( &$this->title, $created, $comment, $oldPageId ) );
if ( $this->title->getNamespace() == NS_FILE ) {
$update = new HTMLCacheUpdate( $this->title, 'imagelinks' );
@@ -652,13 +663,20 @@ class PageArchive {
* @ingroup SpecialPage
*/
class SpecialUndelete extends SpecialPage {
- var $mAction, $mTarget, $mTimestamp, $mRestore, $mInvert, $mFilename;
- var $mTargetTimestamp, $mAllowed, $mCanView, $mComment, $mToken;
-
- /**
- * @var Title
- */
- var $mTargetObj;
+ private $mAction;
+ private $mTarget;
+ private $mTimestamp;
+ private $mRestore;
+ private $mInvert;
+ private $mFilename;
+ private $mTargetTimestamp;
+ private $mAllowed;
+ private $mCanView;
+ private $mComment;
+ private $mToken;
+
+ /** @var Title */
+ private $mTargetObj;
function __construct() {
parent::__construct( 'Undelete', 'deletedhistory' );
@@ -697,10 +715,10 @@ class SpecialUndelete extends SpecialPage {
$this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && $user->isAllowed( 'suppressrevision' );
$this->mToken = $request->getVal( 'token' );
- if ( $user->isAllowed( 'undelete' ) && !$user->isBlocked() ) {
+ if ( $this->isAllowed( 'undelete' ) && !$user->isBlocked() ) {
$this->mAllowed = true; // user can restore
$this->mCanView = true; // user can view content
- } elseif ( $user->isAllowed( 'deletedtext' ) ) {
+ } elseif ( $this->isAllowed( 'deletedtext' ) ) {
$this->mAllowed = false; // user cannot restore
$this->mCanView = true; // user can view content
$this->mRestore = false;
@@ -729,14 +747,35 @@ class SpecialUndelete extends SpecialPage {
}
}
+ /**
+ * Checks whether a user is allowed the permission for the
+ * specific title if one is set.
+ *
+ * @param string $permission
+ * @param User $user
+ * @return bool
+ */
+ private function isAllowed( $permission, User $user = null ) {
+ $user = $user ? : $this->getUser();
+ if ( $this->mTargetObj !== null ) {
+ return $this->mTargetObj->userCan( $permission, $user );
+ } else {
+ return $user->isAllowed( $permission );
+ }
+ }
+
+ function userCanExecute( User $user ) {
+ return $this->isAllowed( $this->mRestriction, $user );
+ }
+
function execute( $par ) {
- $this->checkPermissions();
$user = $this->getUser();
$this->setHeaders();
$this->outputHeader();
$this->loadRequest( $par );
+ $this->checkPermissions(); // Needs to be after mTargetObj is set
$out = $this->getOutput();
@@ -747,6 +786,7 @@ class SpecialUndelete extends SpecialPage {
if ( $user->isAllowed( 'browsearchive' ) ) {
$this->showSearchForm();
}
+
return;
}
@@ -784,14 +824,12 @@ class SpecialUndelete extends SpecialPage {
}
function showSearchForm() {
- global $wgScript;
-
$out = $this->getOutput();
$out->setPageTitle( $this->msg( 'undelete-search-title' ) );
$out->addHTML(
- Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) .
+ Xml::openElement( 'form', array( 'method' => 'get', 'action' => wfScript() ) ) .
Xml::fieldset( $this->msg( 'undelete-search-box' )->text() ) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
+ Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) .
Html::rawElement(
'label',
array( 'for' => 'prefix' ),
@@ -826,12 +864,13 @@ class SpecialUndelete extends SpecialPage {
if ( $result->numRows() == 0 ) {
$out->addWikiMsg( 'undelete-no-results' );
+
return false;
}
$out->addWikiMsg( 'undeletepagetext', $this->getLanguage()->formatNum( $result->numRows() ) );
- $undelete = $this->getTitle();
+ $undelete = $this->getPageTitle();
$out->addHTML( "<ul>\n" );
foreach ( $result as $row ) {
$title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
@@ -868,7 +907,7 @@ class SpecialUndelete extends SpecialPage {
return;
}
- $archive = new PageArchive( $this->mTargetObj );
+ $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
if ( !wfRunHooks( 'UndeleteForm::showRevision', array( &$archive, $this->mTargetObj ) ) ) {
return;
}
@@ -879,6 +918,7 @@ class SpecialUndelete extends SpecialPage {
if ( !$rev ) {
$out->addWikiMsg( 'undeleterevision-missing' );
+
return;
}
@@ -886,14 +926,17 @@ class SpecialUndelete extends SpecialPage {
if ( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
$out->wrapWikiMsg(
"<div class='mw-warning plainlinks'>\n$1\n</div>\n",
- 'rev-deleted-text-permission'
+ $rev->isDeleted( Revision::DELETED_RESTRICTED ) ?
+ 'rev-suppressed-text-permission' : 'rev-deleted-text-permission'
);
+
return;
}
$out->wrapWikiMsg(
"<div class='mw-warning plainlinks'>\n$1\n</div>\n",
- 'rev-deleted-text-view'
+ $rev->isDeleted( Revision::DELETED_RESTRICTED ) ?
+ 'rev-suppressed-text-view' : 'rev-deleted-text-view'
);
$out->addHTML( '<br />' );
// and we are allowed to see...
@@ -914,7 +957,7 @@ class SpecialUndelete extends SpecialPage {
}
$link = Linker::linkKnown(
- $this->getTitle( $this->mTargetObj->getPrefixedDBkey() ),
+ $this->getPageTitle( $this->mTargetObj->getPrefixedDBkey() ),
htmlspecialchars( $this->mTargetObj->getPrefixedText() )
);
@@ -997,7 +1040,7 @@ class SpecialUndelete extends SpecialPage {
'style' => 'clear: both' ) ) .
Xml::openElement( 'form', array(
'method' => 'post',
- 'action' => $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) ) ) ) .
+ 'action' => $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) ) ) ) .
Xml::element( 'input', array(
'type' => 'hidden',
'name' => 'target',
@@ -1032,26 +1075,19 @@ class SpecialUndelete extends SpecialPage {
$diffEngine = $currentRev->getContentHandler()->createDifferenceEngine( $diffContext );
$diffEngine->showDiffStyle();
- $this->getOutput()->addHTML( "<div>" .
- "<table style='width: 98%;' cellpadding='0' cellspacing='4' class='diff'>" .
- "<col class='diff-marker' />" .
- "<col class='diff-content' />" .
- "<col class='diff-marker' />" .
- "<col class='diff-content' />" .
- "<tr>" .
- "<td colspan='2' style='width: 50%; text-align: center' class='diff-otitle'>" .
- $this->diffHeader( $previousRev, 'o' ) .
- "</td>\n" .
- "<td colspan='2' style='width: 50%; text-align: center' class='diff-ntitle'>" .
- $this->diffHeader( $currentRev, 'n' ) .
- "</td>\n" .
- "</tr>" .
- $diffEngine->generateContentDiffBody(
- $previousRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ),
- $currentRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ) ) .
- "</table>" .
- "</div>\n"
+
+ $formattedDiff = $diffEngine->generateContentDiffBody(
+ $previousRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ),
+ $currentRev->getContent( Revision::FOR_THIS_USER, $this->getUser() )
);
+
+ $formattedDiff = $diffEngine->addHeader(
+ $formattedDiff,
+ $this->diffHeader( $previousRev, 'o' ),
+ $this->diffHeader( $currentRev, 'n' )
+ );
+
+ $this->getOutput()->addHTML( "<div>$formattedDiff</div>\n" );
}
/**
@@ -1063,7 +1099,7 @@ class SpecialUndelete extends SpecialPage {
$isDeleted = !( $rev->getId() && $rev->getTitle() );
if ( $isDeleted ) {
/// @todo FIXME: $rev->getTitle() is null for deleted revs...?
- $targetPage = $this->getTitle();
+ $targetPage = $this->getPageTitle();
$targetQuery = array(
'target' => $this->mTargetObj->getPrefixedText(),
'timestamp' => wfTimestamp( TS_MW, $rev->getTimestamp() )
@@ -1083,6 +1119,18 @@ class SpecialUndelete extends SpecialPage {
$rdel = " $rdel";
}
+ $minor = $rev->isMinor() ? ChangesList::flag( 'minor' ) : '';
+
+ $tags = wfGetDB( DB_SLAVE )->selectField(
+ 'tag_summary',
+ 'ts_tags',
+ array( 'ts_rev_id' => $rev->getId() ),
+ __METHOD__
+ );
+ $tagSummary = ChangeTags::formatSummaryRow( $tags, 'deleteddiff' );
+
+ // FIXME This is reimplementing DifferenceEngine#getRevisionHeader
+ // and partially #showDiffPage, but worse
return '<div id="mw-diff-' . $prefix . 'title1"><strong>' .
Linker::link(
$targetPage,
@@ -1100,12 +1148,16 @@ class SpecialUndelete extends SpecialPage {
Linker::revUserTools( $rev ) . '<br />' .
'</div>' .
'<div id="mw-diff-' . $prefix . 'title3">' .
- Linker::revComment( $rev ) . $rdel . '<br />' .
+ $minor . Linker::revComment( $rev ) . $rdel . '<br />' .
+ '</div>' .
+ '<div id="mw-diff-' . $prefix . 'title5">' .
+ $tagSummary[0] . '<br />' .
'</div>';
}
/**
* Show a form confirming whether a tokenless user really wants to see a file
+ * @param string $key
*/
private function showFileConfirmationForm( $key ) {
$out = $this->getOutput();
@@ -1119,7 +1171,7 @@ class SpecialUndelete extends SpecialPage {
$out->addHTML(
Xml::openElement( 'form', array(
'method' => 'POST',
- 'action' => $this->getTitle()->getLocalURL( array(
+ 'action' => $this->getPageTitle()->getLocalURL( array(
'target' => $this->mTarget,
'file' => $key,
'token' => $user->getEditToken( $key ),
@@ -1133,6 +1185,7 @@ class SpecialUndelete extends SpecialPage {
/**
* Show a deleted file version requested by the visitor.
+ * @param string $key
*/
private function showFile( $key ) {
$this->getOutput()->disable();
@@ -1161,7 +1214,7 @@ class SpecialUndelete extends SpecialPage {
array( 'undeletepagetitle', wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) )
);
- $archive = new PageArchive( $this->mTargetObj );
+ $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
wfRunHooks( 'UndeleteForm::showHistory', array( &$archive, $this->mTargetObj ) );
/*
$text = $archive->getLastRevisionText();
@@ -1207,7 +1260,7 @@ class SpecialUndelete extends SpecialPage {
}
if ( $this->mAllowed ) {
- $action = $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) );
+ $action = $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) );
# Start the form here
$top = Xml::openElement(
'form',
@@ -1243,32 +1296,42 @@ class SpecialUndelete extends SpecialPage {
$unsuppressBox = '';
}
- $table =
- Xml::fieldset( $this->msg( 'undelete-fieldset-title' )->text() ) .
- Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) .
- "<tr>
- <td colspan='2' class='mw-undelete-extrahelp'>" .
- $this->msg( 'undeleteextrahelp' )->parseAsBlock() .
- "</td>
- </tr>
- <tr>
- <td class='mw-label'>" .
- Xml::label( $this->msg( 'undeletecomment' )->text(), 'wpComment' ) .
- "</td>
- <td class='mw-input'>" .
- Xml::input( 'wpComment', 50, $this->mComment, array( 'id' => 'wpComment', 'autofocus' => true ) ) .
- "</td>
- </tr>
- <tr>
- <td>&#160;</td>
- <td class='mw-submit'>" .
- Xml::submitButton( $this->msg( 'undeletebtn' )->text(), array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) ) . ' ' .
- Xml::submitButton( $this->msg( 'undeleteinvert' )->text(), array( 'name' => 'invert', 'id' => 'mw-undelete-invert' ) ) .
- "</td>
- </tr>" .
- $unsuppressBox .
- Xml::closeElement( 'table' ) .
- Xml::closeElement( 'fieldset' );
+ $table = Xml::fieldset( $this->msg( 'undelete-fieldset-title' )->text() ) .
+ Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) .
+ "<tr>
+ <td colspan='2' class='mw-undelete-extrahelp'>" .
+ $this->msg( 'undeleteextrahelp' )->parseAsBlock() .
+ "</td>
+ </tr>
+ <tr>
+ <td class='mw-label'>" .
+ Xml::label( $this->msg( 'undeletecomment' )->text(), 'wpComment' ) .
+ "</td>
+ <td class='mw-input'>" .
+ Xml::input(
+ 'wpComment',
+ 50,
+ $this->mComment,
+ array( 'id' => 'wpComment', 'autofocus' => true )
+ ) .
+ "</td>
+ </tr>
+ <tr>
+ <td>&#160;</td>
+ <td class='mw-submit'>" .
+ Xml::submitButton(
+ $this->msg( 'undeletebtn' )->text(),
+ array( 'name' => 'restore', 'id' => 'mw-undelete-submit' )
+ ) . ' ' .
+ Xml::submitButton(
+ $this->msg( 'undeleteinvert' )->text(),
+ array( 'name' => 'invert', 'id' => 'mw-undelete-invert' )
+ ) .
+ "</td>
+ </tr>" .
+ $unsuppressBox .
+ Xml::closeElement( 'table' ) .
+ Xml::closeElement( 'fieldset' );
$out->addHTML( $table );
}
@@ -1338,7 +1401,7 @@ class SpecialUndelete extends SpecialPage {
// Build page & diff links...
$user = $this->getUser();
if ( $this->mCanView ) {
- $titleObj = $this->getTitle();
+ $titleObj = $this->getPageTitle();
# Last link
if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
$pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
@@ -1367,6 +1430,9 @@ class SpecialUndelete extends SpecialPage {
// User links
$userLink = Linker::revUserTools( $rev );
+ // Minor edit
+ $minor = $rev->isMinor() ? ChangesList::flag( 'minor' ) : '';
+
// Revision text size
$size = $row->ar_len;
if ( !is_null( $size ) ) {
@@ -1376,14 +1442,31 @@ class SpecialUndelete extends SpecialPage {
// Edit summary
$comment = Linker::revComment( $rev );
+ // Tags
+ $attribs = array();
+ list( $tagSummary, $classes ) = ChangeTags::formatSummaryRow( $row->ts_tags, 'deletedhistory' );
+ if ( $classes ) {
+ $attribs['class'] = implode( ' ', $classes );
+ }
+
// Revision delete links
$revdlink = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj );
- $revisionRow = $this->msg( 'undelete-revisionrow' )
- ->rawParams( $checkBox, $revdlink, $last, $pageLink, $userLink, $revTextSize, $comment )
+ $revisionRow = $this->msg( 'undelete-revision-row' )
+ ->rawParams(
+ $checkBox,
+ $revdlink,
+ $last,
+ $pageLink,
+ $userLink,
+ $minor,
+ $revTextSize,
+ $comment,
+ $tagSummary
+ )
->escaped();
- return "<li>$revisionRow</li>";
+ return Xml::tags( 'li', $attribs, $revisionRow ) . "\n";
}
private function formatFileRow( $row ) {
@@ -1391,12 +1474,14 @@ class SpecialUndelete extends SpecialPage {
$ts = wfTimestamp( TS_MW, $row->fa_timestamp );
$user = $this->getUser();
- if ( $this->mAllowed && $row->fa_storage_key ) {
- $checkBox = Xml::check( 'fileid' . $row->fa_id );
+ $checkBox = '';
+ if ( $this->mCanView && $row->fa_storage_key ) {
+ if ( $this->mAllowed ) {
+ $checkBox = Xml::check( 'fileid' . $row->fa_id );
+ }
$key = urlencode( $row->fa_storage_key );
- $pageLink = $this->getFileLink( $file, $this->getTitle(), $ts, $key );
+ $pageLink = $this->getFileLink( $file, $this->getPageTitle(), $ts, $key );
} else {
- $checkBox = '';
$pageLink = $this->getLanguage()->userTimeAndDate( $ts, $user );
}
$userLink = $this->getFileUser( $file );
@@ -1408,8 +1493,8 @@ class SpecialUndelete extends SpecialPage {
$comment = $this->getFileComment( $file );
// Add show/hide deletion links if available
- $canHide = $user->isAllowed( 'deleterevision' );
- if ( $canHide || ( $file->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) ) {
+ $canHide = $this->isAllowed( 'deleterevision' );
+ if ( $canHide || ( $file->getVisibility() && $this->isAllowed( 'deletedhistory' ) ) ) {
if ( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
// Revision was hidden from sysops
$revdlink = Linker::revDeleteLinkDisabled( $canHide );
@@ -1468,7 +1553,7 @@ class SpecialUndelete extends SpecialPage {
* @param File|ArchivedFile $file
* @param Title $titleObj
* @param string $ts A timestamp
- * @param string $key a storage key
+ * @param string $key A storage key
*
* @return string HTML fragment
*/
@@ -1543,9 +1628,7 @@ class SpecialUndelete extends SpecialPage {
}
function undelete() {
- global $wgUploadMaintenance;
-
- if ( $wgUploadMaintenance && $this->mTargetObj->getNamespace() == NS_FILE ) {
+ if ( $this->getConfig()->get( 'UploadMaintenance' ) && $this->mTargetObj->getNamespace() == NS_FILE ) {
throw new ErrorPageError( 'undelete-error', 'filedelete-maintenance' );
}
@@ -1554,7 +1637,7 @@ class SpecialUndelete extends SpecialPage {
}
$out = $this->getOutput();
- $archive = new PageArchive( $this->mTargetObj );
+ $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
wfRunHooks( 'UndeleteForm::undelete', array( &$archive, $this->mTargetObj ) );
$ok = $archive->undelete(
$this->mTargetTimestamp,
@@ -1580,13 +1663,23 @@ class SpecialUndelete extends SpecialPage {
// Show revision undeletion warnings and errors
$status = $archive->getRevisionStatus();
if ( $status && !$status->isGood() ) {
- $out->addWikiText( '<div class="error">' . $status->getWikiText( 'cannotundelete', 'cannotundelete' ) . '</div>' );
+ $out->addWikiText( '<div class="error">' .
+ $status->getWikiText(
+ 'cannotundelete',
+ 'cannotundelete'
+ ) . '</div>'
+ );
}
// Show file undeletion warnings and errors
$status = $archive->getFileStatus();
if ( $status && !$status->isGood() ) {
- $out->addWikiText( '<div class="error">' . $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) . '</div>' );
+ $out->addWikiText( '<div class="error">' .
+ $status->getWikiText(
+ 'undelete-error-short',
+ 'undelete-error-long'
+ ) . '</div>'
+ );
}
}
diff --git a/includes/specials/SpecialUnlockdb.php b/includes/specials/SpecialUnlockdb.php
index 35141d80..a8b97d78 100644
--- a/includes/specials/SpecialUnlockdb.php
+++ b/includes/specials/SpecialUnlockdb.php
@@ -37,11 +37,9 @@ class SpecialUnlockdb extends FormSpecialPage {
}
public function checkExecutePermissions( User $user ) {
- global $wgReadOnlyFile;
-
parent::checkExecutePermissions( $user );
# If the lock file isn't writable, we can do sweet bugger all
- if ( !file_exists( $wgReadOnlyFile ) ) {
+ if ( !file_exists( $this->getConfig()->get( 'ReadOnlyFile' ) ) ) {
throw new ErrorPageError( 'lockdb', 'databasenotlocked' );
}
}
@@ -62,20 +60,19 @@ class SpecialUnlockdb extends FormSpecialPage {
}
public function onSubmit( array $data ) {
- global $wgReadOnlyFile;
-
if ( !$data['Confirm'] ) {
return Status::newFatal( 'locknoconfirm' );
}
+ $readOnlyFile = $this->getConfig()->get( 'ReadOnlyFile' );
wfSuppressWarnings();
- $res = unlink( $wgReadOnlyFile );
+ $res = unlink( $readOnlyFile );
wfRestoreWarnings();
if ( $res ) {
return Status::newGood();
} else {
- return Status::newFatal( 'filedeleteerror', $wgReadOnlyFile );
+ return Status::newFatal( 'filedeleteerror', $readOnlyFile );
}
}
diff --git a/includes/specials/SpecialUnusedcategories.php b/includes/specials/SpecialUnusedcategories.php
index b686a5b5..713823bb 100644
--- a/includes/specials/SpecialUnusedcategories.php
+++ b/includes/specials/SpecialUnusedcategories.php
@@ -25,15 +25,14 @@
* @ingroup SpecialPage
*/
class UnusedCategoriesPage extends QueryPage {
+ function __construct( $name = 'Unusedcategories' ) {
+ parent::__construct( $name );
+ }
function isExpensive() {
return true;
}
- function __construct( $name = 'Unusedcategories' ) {
- parent::__construct( $name );
- }
-
function getPageHeader() {
return $this->msg( 'unusedcategoriestext' )->parseAsBlock();
}
@@ -41,14 +40,17 @@ class UnusedCategoriesPage extends QueryPage {
function getQueryInfo() {
return array(
'tables' => array( 'page', 'categorylinks' ),
- 'fields' => array( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_title' ),
- 'conds' => array( 'cl_from IS NULL',
- 'page_namespace' => NS_CATEGORY,
- 'page_is_redirect' => 0 ),
- 'join_conds' => array( 'categorylinks' => array(
- 'LEFT JOIN', 'cl_to = page_title' ) )
+ 'fields' => array(
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_title'
+ ),
+ 'conds' => array(
+ 'cl_from IS NULL',
+ 'page_namespace' => NS_CATEGORY,
+ 'page_is_redirect' => 0
+ ),
+ 'join_conds' => array( 'categorylinks' => array( 'LEFT JOIN', 'cl_to = page_title' ) )
);
}
@@ -67,6 +69,7 @@ class UnusedCategoriesPage extends QueryPage {
*/
function formatResult( $skin, $result ) {
$title = Title::makeTitle( NS_CATEGORY, $result->title );
+
return Linker::link( $title, htmlspecialchars( $title->getText() ) );
}
diff --git a/includes/specials/SpecialUnusedimages.php b/includes/specials/SpecialUnusedimages.php
index d332db75..36ec09ec 100644
--- a/includes/specials/SpecialUnusedimages.php
+++ b/includes/specials/SpecialUnusedimages.php
@@ -44,31 +44,32 @@ class UnusedimagesPage extends ImageQueryPage {
}
function getQueryInfo() {
- global $wgCountCategorizedImagesAsUsed;
$retval = array(
'tables' => array( 'image', 'imagelinks' ),
- 'fields' => array( 'namespace' => NS_FILE,
- 'title' => 'img_name',
- 'value' => 'img_timestamp',
- 'img_user', 'img_user_text',
- 'img_description' ),
+ 'fields' => array(
+ 'namespace' => NS_FILE,
+ 'title' => 'img_name',
+ 'value' => 'img_timestamp',
+ 'img_user', 'img_user_text',
+ 'img_description'
+ ),
'conds' => array( 'il_to IS NULL' ),
- 'join_conds' => array( 'imagelinks' => array(
- 'LEFT JOIN', 'il_to = img_name' ) )
+ 'join_conds' => array( 'imagelinks' => array( 'LEFT JOIN', 'il_to = img_name' ) )
);
- if ( $wgCountCategorizedImagesAsUsed ) {
+ if ( $this->getConfig()->get( 'CountCategorizedImagesAsUsed' ) ) {
// Order is significant
$retval['tables'] = array( 'image', 'page', 'categorylinks',
- 'imagelinks' );
+ 'imagelinks' );
$retval['conds']['page_namespace'] = NS_FILE;
$retval['conds'][] = 'cl_from IS NULL';
$retval['conds'][] = 'img_name = page_title';
$retval['join_conds']['categorylinks'] = array(
- 'LEFT JOIN', 'cl_from = page_id' );
+ 'LEFT JOIN', 'cl_from = page_id' );
$retval['join_conds']['imagelinks'] = array(
- 'LEFT JOIN', 'il_to = page_title' );
+ 'LEFT JOIN', 'il_to = page_title' );
}
+
return $retval;
}
diff --git a/includes/specials/SpecialUnusedtemplates.php b/includes/specials/SpecialUnusedtemplates.php
index 1dc9f420..0c2b8707 100644
--- a/includes/specials/SpecialUnusedtemplates.php
+++ b/includes/specials/SpecialUnusedtemplates.php
@@ -30,7 +30,6 @@
* @ingroup SpecialPage
*/
class UnusedtemplatesPage extends QueryPage {
-
function __construct( $name = 'Unusedtemplates' ) {
parent::__construct( $name );
}
@@ -50,12 +49,16 @@ class UnusedtemplatesPage extends QueryPage {
function getQueryInfo() {
return array(
'tables' => array( 'page', 'templatelinks' ),
- 'fields' => array( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_title' ),
- 'conds' => array( 'page_namespace' => NS_TEMPLATE,
- 'tl_from IS NULL',
- 'page_is_redirect' => 0 ),
+ 'fields' => array(
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_title'
+ ),
+ 'conds' => array(
+ 'page_namespace' => NS_TEMPLATE,
+ 'tl_from IS NULL',
+ 'page_is_redirect' => 0
+ ),
'join_conds' => array( 'templatelinks' => array(
'LEFT JOIN', array( 'tl_title = page_title',
'tl_namespace = page_namespace' ) ) )
@@ -79,6 +82,7 @@ class UnusedtemplatesPage extends QueryPage {
SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() ),
$this->msg( 'unusedtemplateswlh' )->escaped()
);
+
return $this->getLanguage()->specialList( $pageLink, $wlhLink );
}
diff --git a/includes/specials/SpecialUnwatchedpages.php b/includes/specials/SpecialUnwatchedpages.php
index 954e3ffe..bb07c197 100644
--- a/includes/specials/SpecialUnwatchedpages.php
+++ b/includes/specials/SpecialUnwatchedpages.php
@@ -46,13 +46,16 @@ class UnwatchedpagesPage extends QueryPage {
function getQueryInfo() {
return array(
'tables' => array( 'page', 'watchlist' ),
- 'fields' => array( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_namespace' ),
- 'conds' => array( 'wl_title IS NULL',
- 'page_is_redirect' => 0,
- "page_namespace != '" . NS_MEDIAWIKI .
- "'" ),
+ 'fields' => array(
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_namespace'
+ ),
+ 'conds' => array(
+ 'wl_title IS NULL',
+ 'page_is_redirect' => 0,
+ "page_namespace != '" . NS_MEDIAWIKI . "'"
+ ),
'join_conds' => array( 'watchlist' => array(
'LEFT JOIN', array( 'wl_title = page_title',
'wl_namespace = page_namespace' ) ) )
@@ -68,6 +71,15 @@ class UnwatchedpagesPage extends QueryPage {
}
/**
+ * Add the JS
+ * @param string|null $par
+ */
+ public function execute( $par ) {
+ parent::execute( $par );
+ $this->getOutput()->addModules( 'mediawiki.special.unwatchedPages' );
+ }
+
+ /**
* @param Skin $skin
* @param object $result Result row
* @return string
@@ -88,7 +100,7 @@ class UnwatchedpagesPage extends QueryPage {
$wlink = Linker::linkKnown(
$nt,
$this->msg( 'watch' )->escaped(),
- array(),
+ array( 'class' => 'mw-watch-link' ),
array( 'action' => 'watch', 'token' => $token )
);
diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php
index 09facf4f..55d09dd6 100644
--- a/includes/specials/SpecialUpload.php
+++ b/includes/specials/SpecialUpload.php
@@ -32,51 +32,57 @@ class SpecialUpload extends SpecialPage {
/**
* Constructor : initialise object
* Get data POSTed through the form and assign them to the object
- * @param $request WebRequest : data posted.
+ * @param WebRequest $request Data posted.
*/
public function __construct( $request = null ) {
parent::__construct( 'Upload', 'upload' );
}
/** Misc variables **/
- public $mRequest; // The WebRequest or FauxRequest this form is supposed to handle
+
+ /** @var WebRequest|FauxRequest The request this form is supposed to handle */
+ public $mRequest;
public $mSourceType;
- /**
- * @var UploadBase
- */
+ /** @var UploadBase */
public $mUpload;
- /**
- * @var LocalFile
- */
+ /** @var LocalFile */
public $mLocalFile;
public $mUploadClicked;
/** User input variables from the "description" section **/
- public $mDesiredDestName; // The requested target file name
+
+ /** @var string The requested target file name */
+ public $mDesiredDestName;
public $mComment;
public $mLicense;
/** User input variables from the root section **/
+
public $mIgnoreWarning;
- public $mWatchThis;
+ public $mWatchthis;
public $mCopyrightStatus;
public $mCopyrightSource;
/** Hidden variables **/
+
public $mDestWarningAck;
- public $mForReUpload; // The user followed an "overwrite this file" link
- public $mCancelUpload; // The user clicked "Cancel and return to upload form" button
+
+ /** @var bool The user followed an "overwrite this file" link */
+ public $mForReUpload;
+
+ /** @var bool The user clicked "Cancel and return to upload form" button */
+ public $mCancelUpload;
public $mTokenOk;
- public $mUploadSuccessful = false; // Subclasses can use this to determine whether a file was uploaded
+
+ /** @var bool Subclasses can use this to determine whether a file was uploaded */
+ public $mUploadSuccessful = false;
/** Text injection points for hooks not using HTMLForm **/
public $uploadFormTextTop;
public $uploadFormTextAfterSummary;
- public $mWatchthis;
-
/**
* Initialize instance variables from request and create an Upload handler
*/
@@ -127,8 +133,8 @@ class SpecialUpload extends SpecialPage {
* Handle permission checking elsewhere in order to be able to show
* custom error messages.
*
- * @param $user User object
- * @return Boolean
+ * @param User $user
+ * @return bool
*/
public function userCanExecute( User $user ) {
return UploadBase::isEnabled() && parent::userCanExecute( $user );
@@ -136,6 +142,7 @@ class SpecialUpload extends SpecialPage {
/**
* Special page entry point
+ * @param string $par
*/
public function execute( $par ) {
$this->setHeaders();
@@ -180,7 +187,8 @@ class SpecialUpload extends SpecialPage {
} else {
# Backwards compatibility hook
if ( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) ) {
- wfDebug( "Hook 'UploadForm:initial' broke output of the upload form" );
+ wfDebug( "Hook 'UploadForm:initial' broke output of the upload form\n" );
+
return;
}
$this->showUploadForm( $this->getUploadForm() );
@@ -195,7 +203,7 @@ class SpecialUpload extends SpecialPage {
/**
* Show the main upload form
*
- * @param $form Mixed: an HTMLForm instance or HTML string to show
+ * @param HTMLForm|string $form An HTMLForm instance or HTML string to show
*/
protected function showUploadForm( $form ) {
# Add links if file was previously deleted
@@ -208,21 +216,20 @@ class SpecialUpload extends SpecialPage {
} else {
$this->getOutput()->addHTML( $form );
}
-
}
/**
* Get an UploadForm instance with title and text properly set.
*
* @param string $message HTML string to add to the form
- * @param string $sessionKey session key in case this is a stashed upload
- * @param $hideIgnoreWarning Boolean: whether to hide "ignore warning" check box
+ * @param string $sessionKey Session key in case this is a stashed upload
+ * @param bool $hideIgnoreWarning Whether to hide "ignore warning" check box
* @return UploadForm
*/
protected function getUploadForm( $message = '', $sessionKey = '', $hideIgnoreWarning = false ) {
# Initialize form
$context = new DerivativeContext( $this->getContext() );
- $context->setTitle( $this->getTitle() ); // Remove subpage
+ $context->setTitle( $this->getPageTitle() ); // Remove subpage
$form = new UploadForm( array(
'watch' => $this->getWatchCheck(),
'forreupload' => $this->mForReUpload,
@@ -317,23 +324,24 @@ class SpecialUpload extends SpecialPage {
$form->setSubmitText( $this->msg( 'upload-tryagain' )->escaped() );
$this->showUploadForm( $form );
}
+
/**
* Stashes the upload, shows the main form, but adds a "continue anyway button".
* Also checks whether there are actually warnings to display.
*
- * @param $warnings Array
- * @return boolean true if warnings were displayed, false if there are no
- * warnings and it should continue processing
+ * @param array $warnings
+ * @return bool True if warnings were displayed, false if there are no
+ * warnings and it should continue processing
*/
protected function showUploadWarning( $warnings ) {
# If there are no warnings, or warnings we can ignore, return early.
# mDestWarningAck is set when some javascript has shown the warning
# to the user. mForReUpload is set when the user clicks the "upload a
# new version" link.
- if ( !$warnings || ( count( $warnings ) == 1 &&
- isset( $warnings['exists'] ) &&
- ( $this->mDestWarningAck || $this->mForReUpload ) ) )
- {
+ if ( !$warnings || ( count( $warnings ) == 1
+ && isset( $warnings['exists'] )
+ && ( $this->mDestWarningAck || $this->mForReUpload ) )
+ ) {
return false;
}
@@ -350,9 +358,14 @@ class SpecialUpload extends SpecialPage {
} elseif ( $warning == 'duplicate' ) {
$msg = $this->getDupeWarning( $args );
} elseif ( $warning == 'duplicate-archive' ) {
- $msg = "\t<li>" . $this->msg( 'file-deleted-duplicate',
- Title::makeTitle( NS_FILE, $args )->getPrefixedText() )->parse()
- . "</li>\n";
+ if ( $args === '' ) {
+ $msg = "\t<li>" . $this->msg( 'file-deleted-duplicate-notitle' )->parse()
+ . "</li>\n";
+ } else {
+ $msg = "\t<li>" . $this->msg( 'file-deleted-duplicate',
+ Title::makeTitle( NS_FILE, $args )->getPrefixedText() )->parse()
+ . "</li>\n";
+ }
} else {
if ( $args === true ) {
$args = array();
@@ -397,6 +410,7 @@ class SpecialUpload extends SpecialPage {
$status = $this->mUpload->fetchFile();
if ( !$status->isOK() ) {
$this->showUploadError( $this->getOutput()->parse( $status->getWikiText() ) );
+
return;
}
@@ -414,6 +428,7 @@ class SpecialUpload extends SpecialPage {
$details = $this->mUpload->verifyUpload();
if ( $details['status'] != UploadBase::OK ) {
$this->processVerificationError( $details );
+
return;
}
@@ -422,6 +437,7 @@ class SpecialUpload extends SpecialPage {
if ( $permErrors !== true ) {
$code = array_shift( $permErrors[0] );
$this->showRecoverableUploadError( $this->msg( $code, $permErrors[0] )->parse() );
+
return;
}
@@ -442,9 +458,17 @@ class SpecialUpload extends SpecialPage {
} else {
$pageText = false;
}
- $status = $this->mUpload->performUpload( $this->mComment, $pageText, $this->mWatchthis, $this->getUser() );
+
+ $status = $this->mUpload->performUpload(
+ $this->mComment,
+ $pageText,
+ $this->mWatchthis,
+ $this->getUser()
+ );
+
if ( !$status->isGood() ) {
$this->showUploadError( $this->getOutput()->parse( $status->getWikiText() ) );
+
return;
}
@@ -456,13 +480,16 @@ class SpecialUpload extends SpecialPage {
/**
* Get the initial image page text based on a comment and optional file status information
- * @param $comment string
- * @param $license string
- * @param $copyStatus string
- * @param $source string
+ * @param string $comment
+ * @param string $license
+ * @param string $copyStatus
+ * @param string $source
* @return string
+ * @todo Use Config obj instead of globals
*/
- public static function getInitialPageText( $comment = '', $license = '', $copyStatus = '', $source = '' ) {
+ public static function getInitialPageText( $comment = '', $license = '',
+ $copyStatus = '', $source = ''
+ ) {
global $wgUseCopyrightUpload, $wgForceUIMsgAsContentMsg;
$msg = array();
@@ -496,6 +523,7 @@ class SpecialUpload extends SpecialPage {
$pageText = $comment;
}
}
+
return $pageText;
}
@@ -509,7 +537,7 @@ class SpecialUpload extends SpecialPage {
*
* Note that the page target can be changed *on the form*, so our check
* state can get out of sync.
- * @return Bool|String
+ * @return bool|string
*/
protected function getWatchCheck() {
if ( $this->getUser()->getOption( 'watchdefault' ) ) {
@@ -517,11 +545,17 @@ class SpecialUpload extends SpecialPage {
return true;
}
+ $desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
+ if ( $desiredTitleObj instanceof Title && $this->getUser()->isWatched( $desiredTitleObj ) ) {
+ // Already watched, don't change that
+ return true;
+ }
+
$local = wfLocalFile( $this->mDesiredDestName );
if ( $local && $local->exists() ) {
// We're uploading a new version of an existing file.
// No creation, so don't watch it if we're not already.
- return $this->getUser()->isWatched( $local->getTitle() );
+ return false;
} else {
// New page should get watched if that's our option.
return $this->getUser()->getOption( 'watchcreations' );
@@ -531,12 +565,10 @@ class SpecialUpload extends SpecialPage {
/**
* Provides output to the user for a result of UploadBase::verifyUpload
*
- * @param array $details result of UploadBase::verifyUpload
+ * @param array $details Result of UploadBase::verifyUpload
* @throws MWException
*/
protected function processVerificationError( $details ) {
- global $wgFileExtensions;
-
switch ( $details['status'] ) {
/** Statuses that only require name changing **/
@@ -571,7 +603,7 @@ class SpecialUpload extends SpecialPage {
} else {
$msg->params( $details['finalExt'] );
}
- $extensions = array_unique( $wgFileExtensions );
+ $extensions = array_unique( $this->getConfig()->get( 'FileExtensions' ) );
$msg->params( $this->getLanguage()->commaList( $extensions ),
count( $extensions ) );
@@ -610,7 +642,7 @@ class SpecialUpload extends SpecialPage {
/**
* Remove a temporarily kept file stashed by saveTempUploadedFile().
*
- * @return Boolean: success
+ * @return bool Success
*/
protected function unsaveUploadedFile() {
if ( !( $this->mUpload instanceof UploadFromStash ) ) {
@@ -619,6 +651,7 @@ class SpecialUpload extends SpecialPage {
$success = $this->mUpload->unsaveUploadedFile();
if ( !$success ) {
$this->getOutput()->showFileDeleteError( $this->mUpload->getTempPath() );
+
return false;
} else {
return true;
@@ -631,8 +664,8 @@ class SpecialUpload extends SpecialPage {
* Formats a result of UploadBase::getExistsWarning as HTML
* This check is static and can be done pre-upload via AJAX
*
- * @param array $exists the result of UploadBase::getExistsWarning
- * @return String: empty string if there is no warning or an HTML fragment
+ * @param array $exists The result of UploadBase::getExistsWarning
+ * @return string Empty string if there is no warning or an HTML fragment
*/
public static function getExistsWarning( $exists ) {
if ( !$exists ) {
@@ -683,7 +716,7 @@ class SpecialUpload extends SpecialPage {
/**
* Construct a warning and a gallery from an array of duplicate files.
- * @param $dupes array
+ * @param array $dupes
* @return string
*/
public function getDupeWarning( $dupes ) {
@@ -691,12 +724,12 @@ class SpecialUpload extends SpecialPage {
return '';
}
- $gallery = ImageGalleryBase::factory();
- $gallery->setContext( $this->getContext() );
+ $gallery = ImageGalleryBase::factory( false, $this->getContext() );
$gallery->setShowBytes( false );
foreach ( $dupes as $file ) {
$gallery->add( $file->getTitle() );
}
+
return '<li>' .
wfMessage( 'file-exists-duplicate' )->numParams( count( $dupes ) )->parse() .
$gallery->toHtml() . "</li>\n";
@@ -705,6 +738,18 @@ class SpecialUpload extends SpecialPage {
protected function getGroupName() {
return 'media';
}
+
+ /**
+ * Should we rotate images in the preview on Special:Upload.
+ *
+ * This controls js: mw.config.get( 'wgFileCanRotate' )
+ *
+ * @todo What about non-BitmapHandler handled files?
+ */
+ static public function rotationEnabled() {
+ $bitmapHandler = new BitmapHandler();
+ return $bitmapHandler->autoRotateEnabled();
+ }
}
/**
@@ -731,8 +776,7 @@ class UploadForm extends HTMLForm {
public function __construct( array $options = array(), IContextSource $context = null ) {
$this->mWatch = !empty( $options['watch'] );
$this->mForReUpload = !empty( $options['forreupload'] );
- $this->mSessionKey = isset( $options['sessionkey'] )
- ? $options['sessionkey'] : '';
+ $this->mSessionKey = isset( $options['sessionkey'] ) ? $options['sessionkey'] : '';
$this->mHideIgnoreWarning = !empty( $options['hideignorewarning'] );
$this->mDestWarningAck = !empty( $options['destwarningack'] );
$this->mDestFile = isset( $options['destfile'] ) ? $options['destfile'] : '';
@@ -754,6 +798,18 @@ class UploadForm extends HTMLForm {
wfRunHooks( 'UploadFormInitDescriptor', array( &$descriptor ) );
parent::__construct( $descriptor, $context, 'upload' );
+ # Add a link to edit MediaWik:Licenses
+ if ( $this->getUser()->isAllowed( 'editinterface' ) ) {
+ $licensesLink = Linker::link(
+ Title::makeTitle( NS_MEDIAWIKI, 'Licenses' ),
+ $this->msg( 'licenses-edit' )->escaped(),
+ array(),
+ array( 'action' => 'edit' )
+ );
+ $editLicenses = '<p class="mw-upload-editlicenses">' . $licensesLink . '</p>';
+ $this->addFooterText( $editLicenses, 'description' );
+ }
+
# Set some form properties
$this->setSubmitText( $this->msg( 'uploadbtn' )->text() );
$this->setSubmitName( 'wpUpload' );
@@ -768,18 +824,15 @@ class UploadForm extends HTMLForm {
$this->mSourceIds[] = $field['id'];
}
}
-
}
/**
* Get the descriptor of the fieldset that contains the file source
* selection. The section is 'source'
*
- * @return Array: descriptor array
+ * @return array Descriptor array
*/
protected function getSourceSection() {
- global $wgCopyUploadsFromSpecialUpload;
-
if ( $this->mSessionKey ) {
return array(
'SessionKey' => array(
@@ -794,8 +847,8 @@ class UploadForm extends HTMLForm {
}
$canUploadByUrl = UploadFromUrl::isEnabled()
- && UploadFromUrl::isAllowed( $this->getUser() )
- && $wgCopyUploadsFromSpecialUpload;
+ && ( UploadFromUrl::isAllowed( $this->getUser() ) === true )
+ && $this->getConfig()->get( 'CopyUploadsFromSpecialUpload' );
$radio = $canUploadByUrl;
$selectedSourceType = strtolower( $this->getRequest()->getText( 'wpSourceType', 'File' ) );
@@ -812,7 +865,7 @@ class UploadForm extends HTMLForm {
$this->mMaxUploadSize['file'] = UploadBase::getMaxUploadSize( 'file' );
# Limit to upload_max_filesize unless we are running under HipHop and
# that setting doesn't exist
- if ( !wfIsHipHop() ) {
+ if ( !wfIsHHVM() ) {
$this->mMaxUploadSize['file'] = min( $this->mMaxUploadSize['file'],
wfShorthandToInteger( ini_get( 'upload_max_filesize' ) ),
wfShorthandToInteger( ini_get( 'post_max_size' ) )
@@ -824,12 +877,13 @@ class UploadForm extends HTMLForm {
'section' => 'source',
'type' => 'file',
'id' => 'wpUploadFile',
+ 'radio-id' => 'wpSourceTypeFile',
'label-message' => 'sourcefilename',
'upload-type' => 'File',
'radio' => &$radio,
'help' => $this->msg( 'upload-maxfilesize',
- $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['file'] ) )
- ->parse() .
+ $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['file'] )
+ )->parse() .
$this->msg( 'word-separator' )->escaped() .
$this->msg( 'upload_source_file' )->escaped(),
'checked' => $selectedSourceType == 'file',
@@ -841,12 +895,13 @@ class UploadForm extends HTMLForm {
'class' => 'UploadSourceField',
'section' => 'source',
'id' => 'wpUploadFileURL',
+ 'radio-id' => 'wpSourceTypeurl',
'label-message' => 'sourceurl',
'upload-type' => 'url',
'radio' => &$radio,
'help' => $this->msg( 'upload-maxfilesize',
- $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['url'] ) )
- ->parse() .
+ $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['url'] )
+ )->parse() .
$this->msg( 'word-separator' )->escaped() .
$this->msg( 'upload_source_url' )->escaped(),
'checked' => $selectedSourceType == 'url',
@@ -860,41 +915,57 @@ class UploadForm extends HTMLForm {
'default' => $this->getExtensionsMessage(),
'raw' => true,
);
+
return $descriptor;
}
/**
* Get the messages indicating which extensions are preferred and prohibitted.
*
- * @return String: HTML string containing the message
+ * @return string HTML string containing the message
*/
protected function getExtensionsMessage() {
# Print a list of allowed file extensions, if so configured. We ignore
# MIME type here, it's incomprehensible to most people and too long.
- global $wgCheckFileExtensions, $wgStrictFileExtensions,
- $wgFileExtensions, $wgFileBlacklist;
+ $config = $this->getConfig();
- if ( $wgCheckFileExtensions ) {
- if ( $wgStrictFileExtensions ) {
+ if ( $config->get( 'CheckFileExtensions' ) ) {
+ if ( $config->get( 'StrictFileExtensions' ) ) {
# Everything not permitted is banned
$extensionsList =
'<div id="mw-upload-permitted">' .
- $this->msg( 'upload-permitted', $this->getContext()->getLanguage()->commaList( array_unique( $wgFileExtensions ) ) )->parseAsBlock() .
+ $this->msg(
+ 'upload-permitted',
+ $this->getContext()->getLanguage()->commaList(
+ array_unique( $config->get( 'FileExtensions' ) )
+ )
+ )->parseAsBlock() .
"</div>\n";
} else {
# We have to list both preferred and prohibited
$extensionsList =
'<div id="mw-upload-preferred">' .
- $this->msg( 'upload-preferred', $this->getContext()->getLanguage()->commaList( array_unique( $wgFileExtensions ) ) )->parseAsBlock() .
+ $this->msg(
+ 'upload-preferred',
+ $this->getContext()->getLanguage()->commaList(
+ array_unique( $config->get( 'FileExtensions' ) )
+ )
+ )->parseAsBlock() .
"</div>\n" .
'<div id="mw-upload-prohibited">' .
- $this->msg( 'upload-prohibited', $this->getContext()->getLanguage()->commaList( array_unique( $wgFileBlacklist ) ) )->parseAsBlock() .
+ $this->msg(
+ 'upload-prohibited',
+ $this->getContext()->getLanguage()->commaList(
+ array_unique( $config->get( 'FileBlacklist' ) )
+ )
+ )->parseAsBlock() .
"</div>\n";
}
} else {
# Everything is permitted.
$extensionsList = '';
}
+
return $extensionsList;
}
@@ -902,9 +973,10 @@ class UploadForm extends HTMLForm {
* Get the descriptor of the fieldset that contains the file description
* input. The section is 'description'
*
- * @return Array: descriptor array
+ * @return array Descriptor array
*/
protected function getDescriptionSection() {
+ $config = $this->getConfig();
if ( $this->mSessionKey ) {
$stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
try {
@@ -977,8 +1049,7 @@ class UploadForm extends HTMLForm {
);
}
- global $wgUseCopyrightUpload;
- if ( $wgUseCopyrightUpload ) {
+ if ( $config->get( 'UseCopyrightUpload' ) ) {
$descriptor['UploadCopyStatus'] = array(
'type' => 'text',
'section' => 'description',
@@ -1000,7 +1071,7 @@ class UploadForm extends HTMLForm {
* Get the descriptor of the fieldset that contains the upload options,
* such as "watch this file". The section is 'options'
*
- * @return Array: descriptor array
+ * @return array Descriptor array
*/
protected function getOptionsSection() {
$user = $this->getUser();
@@ -1011,7 +1082,7 @@ class UploadForm extends HTMLForm {
'id' => 'wpWatchthis',
'label-message' => 'watchthisupload',
'section' => 'options',
- 'default' => $user->getOption( 'watchcreations' ),
+ 'default' => $this->mWatch,
)
);
}
@@ -1053,10 +1124,11 @@ class UploadForm extends HTMLForm {
* Add upload JS to the OutputPage
*/
protected function addUploadJS() {
- global $wgUseAjax, $wgAjaxUploadDestCheck, $wgAjaxLicensePreview, $wgEnableAPI, $wgStrictFileExtensions;
+ $config = $this->getConfig();
- $useAjaxDestCheck = $wgUseAjax && $wgAjaxUploadDestCheck;
- $useAjaxLicensePreview = $wgUseAjax && $wgAjaxLicensePreview && $wgEnableAPI;
+ $useAjaxDestCheck = $config->get( 'UseAjax' ) && $config->get( 'AjaxUploadDestCheck' );
+ $useAjaxLicensePreview = $config->get( 'UseAjax' ) &&
+ $config->get( 'AjaxLicensePreview' ) && $config->get( 'EnableAPI' );
$this->mMaxUploadSize['*'] = UploadBase::getMaxUploadSize();
$scriptVars = array(
@@ -1067,7 +1139,7 @@ class UploadForm extends HTMLForm {
// the wpDestFile textbox
$this->mDestFile === '',
'wgUploadSourceIds' => $this->mSourceIds,
- 'wgStrictFileExtensions' => $wgStrictFileExtensions,
+ 'wgStrictFileExtensions' => $config->get( 'StrictFileExtensions' ),
'wgCapitalizeUploads' => MWNamespace::isCapitalized( NS_FILE ),
'wgMaxUploadSize' => $this->mMaxUploadSize,
);
@@ -1077,20 +1149,18 @@ class UploadForm extends HTMLForm {
$out->addModules( array(
'mediawiki.action.edit', // For <charinsert> support
- 'mediawiki.legacy.upload', // Old form stuff...
- 'mediawiki.special.upload', // Newer extras for thumbnail preview.
+ 'mediawiki.special.upload', // Extras for thumbnail and license preview.
) );
}
/**
* Empty function; submission is handled elsewhere.
*
- * @return bool false
+ * @return bool False
*/
function trySubmit() {
return false;
}
-
}
/**
@@ -1099,7 +1169,7 @@ class UploadForm extends HTMLForm {
class UploadSourceField extends HTMLTextField {
/**
- * @param $cellAttributes array
+ * @param array $cellAttributes
* @return string
*/
function getLabelHtml( $cellAttributes = array() ) {
@@ -1107,15 +1177,25 @@ class UploadSourceField extends HTMLTextField {
$label = Html::rawElement( 'label', array( 'for' => $id ), $this->mLabel );
if ( !empty( $this->mParams['radio'] ) ) {
+ if ( isset( $this->mParams['radio-id'] ) ) {
+ $radioId = $this->mParams['radio-id'];
+ } else {
+ // Old way. For the benefit of extensions that do not define
+ // the 'radio-id' key.
+ $radioId = 'wpSourceType' . $this->mParams['upload-type'];
+ }
+
$attribs = array(
'name' => 'wpSourceType',
'type' => 'radio',
- 'id' => $id,
+ 'id' => $radioId,
'value' => $this->mParams['upload-type'],
);
+
if ( !empty( $this->mParams['checked'] ) ) {
$attribs['checked'] = 'checked';
}
+
$label .= Html::element( 'input', $attribs );
}
diff --git a/includes/specials/SpecialUploadStash.php b/includes/specials/SpecialUploadStash.php
index 1373df1a..ddb435d9 100644
--- a/includes/specials/SpecialUploadStash.php
+++ b/includes/specials/SpecialUploadStash.php
@@ -57,8 +57,9 @@ class SpecialUploadStash extends UnlistedSpecialPage {
/**
* Execute page -- can output a file directly or show a listing of them.
*
- * @param string $subPage subpage, e.g. in http://example.com/wiki/Special:UploadStash/foo.jpg, the "foo.jpg" part
- * @return Boolean: success
+ * @param string $subPage Subpage, e.g. in
+ * http://example.com/wiki/Special:UploadStash/foo.jpg, the "foo.jpg" part
+ * @return bool Success
*/
public function execute( $subPage ) {
$this->checkPermissions();
@@ -66,6 +67,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
if ( $subPage === null || $subPage === '' ) {
return $this->showUploads();
}
+
return $this->showUpload( $subPage );
}
@@ -73,7 +75,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
* If file available in stash, cats it out to the client as a simple HTTP response.
* n.b. Most sanity checking done in UploadStashLocalFile, so this is straightforward.
*
- * @param string $key the key of a particular requested file
+ * @param string $key The key of a particular requested file
* @throws HttpError
* @return bool
*/
@@ -99,7 +101,8 @@ class SpecialUploadStash extends UnlistedSpecialPage {
$message = $e->getMessage();
} catch ( SpecialUploadStashTooLargeException $e ) {
$code = 500;
- $message = 'Cannot serve a file larger than ' . self::MAX_SERVE_BYTES . ' bytes. ' . $e->getMessage();
+ $message = 'Cannot serve a file larger than ' . self::MAX_SERVE_BYTES .
+ ' bytes. ' . $e->getMessage();
} catch ( Exception $e ) {
$code = 500;
$message = $e->getMessage();
@@ -136,10 +139,11 @@ class SpecialUploadStash extends UnlistedSpecialPage {
$handler = $file->getHandler();
if ( $handler ) {
$params = $handler->parseParamString( $paramString );
+
return array( 'file' => $file, 'type' => $type, 'params' => $params );
} else {
throw new UploadStashBadPathException( 'No handler found for ' .
- "mime {$file->getMimeType()} of file {$file->getPath()}" );
+ "mime {$file->getMimeType()} of file {$file->getPath()}" );
}
}
@@ -149,20 +153,19 @@ class SpecialUploadStash extends UnlistedSpecialPage {
/**
* Get a thumbnail for file, either generated locally or remotely, and stream it out
*
- * @param $file
- * @param $params array
+ * @param File $file
+ * @param array $params
*
- * @return boolean success
+ * @return bool Success
*/
private function outputThumbFromStash( $file, $params ) {
-
- // this global, if it exists, points to a "scaler", as you might find in the Wikimedia Foundation cluster. See outputRemoteScaledThumb()
- // this is part of our horrible NFS-based system, we create a file on a mount point here, but fetch the scaled file from somewhere else that
- // happens to share it over NFS
- global $wgUploadStashScalerBaseUrl;
-
$flags = 0;
- if ( $wgUploadStashScalerBaseUrl ) {
+ // this config option, if it exists, points to a "scaler", as you might find in
+ // the Wikimedia Foundation cluster. See outputRemoteScaledThumb(). This
+ // is part of our horrible NFS-based system, we create a file on a mount
+ // point here, but fetch the scaled file from somewhere else that
+ // happens to share it over NFS.
+ if ( $this->getConfig()->get( 'UploadStashScalerBaseUrl' ) ) {
$this->outputRemoteScaledThumb( $file, $params, $flags );
} else {
$this->outputLocallyScaledThumb( $file, $params, $flags );
@@ -170,16 +173,15 @@ class SpecialUploadStash extends UnlistedSpecialPage {
}
/**
- * Scale a file (probably with a locally installed imagemagick, or similar) and output it to STDOUT.
- * @param $file File
+ * Scale a file (probably with a locally installed imagemagick, or similar)
+ * and output it to STDOUT.
+ * @param File $file
* @param array $params Scaling parameters ( e.g. array( width => '50' ) );
* @param int $flags Scaling flags ( see File:: constants )
- * @throws MWException
- * @throws UploadStashFileNotFoundException
- * @return boolean success
+ * @throws MWException|UploadStashFileNotFoundException
+ * @return bool Success
*/
private function outputLocallyScaledThumb( $file, $params, $flags ) {
-
// n.b. this is stupid, we insist on re-transforming the file every time we are invoked. We rely
// on HTTP caching to ensure this doesn't happen.
@@ -195,8 +197,8 @@ class SpecialUploadStash extends UnlistedSpecialPage {
throw new UploadStashFileNotFoundException( "no local path for scaled item" );
}
- // now we should construct a File, so we can get mime and other such info in a standard way
- // n.b. mimetype may be different from original (ogx original -> jpeg thumb)
+ // now we should construct a File, so we can get MIME and other such info in a standard way
+ // n.b. MIME type may be different from original (ogx original -> jpeg thumb)
$thumbFile = new UnregisteredLocalFile( false,
$this->stash->repo, $thumbnailImage->getStoragePath(), false );
if ( !$thumbFile ) {
@@ -204,28 +206,32 @@ class SpecialUploadStash extends UnlistedSpecialPage {
}
return $this->outputLocalFile( $thumbFile );
-
}
/**
- * Scale a file with a remote "scaler", as exists on the Wikimedia Foundation cluster, and output it to STDOUT.
- * Note: unlike the usual thumbnail process, the web client never sees the cluster URL; we do the whole HTTP transaction to the scaler ourselves
- * and cat the results out.
- * Note: We rely on NFS to have propagated the file contents to the scaler. However, we do not rely on the thumbnail being created in NFS and then
- * propagated back to our filesystem. Instead we take the results of the HTTP request instead.
- * Note: no caching is being done here, although we are instructing the client to cache it forever.
- * @param $file: File object
- * @param $params: scaling parameters ( e.g. array( width => '50' ) );
- * @param $flags: scaling flags ( see File:: constants )
+ * Scale a file with a remote "scaler", as exists on the Wikimedia Foundation
+ * cluster, and output it to STDOUT.
+ * Note: Unlike the usual thumbnail process, the web client never sees the
+ * cluster URL; we do the whole HTTP transaction to the scaler ourselves
+ * and cat the results out.
+ * Note: We rely on NFS to have propagated the file contents to the scaler.
+ * However, we do not rely on the thumbnail being created in NFS and then
+ * propagated back to our filesystem. Instead we take the results of the
+ * HTTP request instead.
+ * Note: No caching is being done here, although we are instructing the
+ * client to cache it forever.
+ *
+ * @param File $file
+ * @param array $params Scaling parameters ( e.g. array( width => '50' ) );
+ * @param int $flags Scaling flags ( see File:: constants )
* @throws MWException
- * @return boolean success
+ * @return bool Success
*/
private function outputRemoteScaledThumb( $file, $params, $flags ) {
-
- // this global probably looks something like 'http://upload.wikimedia.org/wikipedia/test/thumb/temp'
- // do not use trailing slash
- global $wgUploadStashScalerBaseUrl;
- $scalerBaseUrl = $wgUploadStashScalerBaseUrl;
+ // This option probably looks something like
+ // 'http://upload.wikimedia.org/wikipedia/test/thumb/temp'. Do not use
+ // trailing slash.
+ $scalerBaseUrl = $this->getConfig()->get( 'UploadStashScalerBaseUrl' );
if ( preg_match( '/^\/\//', $scalerBaseUrl ) ) {
// this is apparently a protocol-relative URL, which makes no sense in this context,
@@ -248,16 +254,17 @@ class SpecialUploadStash extends UnlistedSpecialPage {
);
$req = MWHttpRequest::factory( $scalerThumbUrl, $httpOptions );
$status = $req->execute();
- if ( ! $status->isOK() ) {
+ if ( !$status->isOK() ) {
$errors = $status->getErrorsArray();
$errorStr = "Fetching thumbnail failed: " . print_r( $errors, 1 );
$errorStr .= "\nurl = $scalerThumbUrl\n";
throw new MWException( $errorStr );
}
$contentType = $req->getResponseHeader( "content-type" );
- if ( ! $contentType ) {
+ if ( !$contentType ) {
throw new MWException( "Missing content-type header" );
}
+
return $this->outputContents( $req->getContent(), $contentType );
}
@@ -265,7 +272,8 @@ class SpecialUploadStash extends UnlistedSpecialPage {
* Output HTTP response for file
* Side effect: writes HTTP response to STDOUT.
*
- * @param $file File object with a local path (e.g. UnregisteredLocalFile, LocalFile. Oddly these don't share an ancestor!)
+ * @param File $file File object with a local path (e.g. UnregisteredLocalFile,
+ * LocalFile. Oddly these don't share an ancestor!)
* @throws SpecialUploadStashTooLargeException
* @return bool
*/
@@ -273,6 +281,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
if ( $file->getSize() > self::MAX_SERVE_BYTES ) {
throw new SpecialUploadStashTooLargeException();
}
+
return $file->getRepo()->streamFile( $file->getPath(),
array( 'Content-Transfer-Encoding: binary',
'Expires: Sun, 17-Jan-2038 19:14:07 GMT' )
@@ -282,8 +291,8 @@ class SpecialUploadStash extends UnlistedSpecialPage {
/**
* Output HTTP response of raw content
* Side effect: writes HTTP response to STDOUT.
- * @param string $content content
- * @param string $contentType mime type
+ * @param string $content Content
+ * @param string $contentType MIME type
* @throws SpecialUploadStashTooLargeException
* @return bool
*/
@@ -294,15 +303,18 @@ class SpecialUploadStash extends UnlistedSpecialPage {
}
self::outputFileHeaders( $contentType, $size );
print $content;
+
return true;
}
/**
* Output headers for streaming
- * XXX unsure about encoding as binary; if we received from HTTP perhaps we should use that encoding, concatted with semicolon to mimeType as it usually is.
+ * @todo Unsure about encoding as binary; if we received from HTTP perhaps
+ * we should use that encoding, concatenated with semicolon to `$contentType` as it
+ * usually is.
* Side effect: preps PHP to write headers to STDOUT.
- * @param string $contentType : string suitable for content-type header
- * @param string $size: length in bytes
+ * @param string $contentType String suitable for content-type header
+ * @param string $size Length in bytes
*/
private static function outputFileHeaders( $contentType, $size ) {
header( "Content-Type: $contentType", true );
@@ -324,11 +336,13 @@ class SpecialUploadStash extends UnlistedSpecialPage {
public static function tryClearStashedUploads( $formData ) {
if ( isset( $formData['Clear'] ) ) {
$stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
- wfDebug( "stash has: " . print_r( $stash->listFiles(), true ) );
- if ( ! $stash->clear() ) {
+ wfDebug( 'stash has: ' . print_r( $stash->listFiles(), true ) . "\n" );
+
+ if ( !$stash->clear() ) {
return Status::newFatal( 'uploadstash-errclear' );
}
}
+
return Status::newGood();
}
@@ -346,7 +360,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
// this design is extremely dubious, but supposedly HTMLForm is our standard now?
$context = new DerivativeContext( $this->getContext() );
- $context->setTitle( $this->getTitle() ); // Remove subpage
+ $context->setTitle( $this->getPageTitle() ); // Remove subpage
$form = new HTMLForm( array(
'Clear' => array(
'type' => 'hidden',
@@ -362,7 +376,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
// show the files + form, if there are any, or just say there are none
$refreshHtml = Html::element( 'a',
- array( 'href' => $this->getTitle()->getLocalURL() ),
+ array( 'href' => $this->getPageTitle()->getLocalURL() ),
$this->msg( 'uploadstash-refresh' )->text() );
$files = $this->stash->listFiles();
if ( $files && count( $files ) ) {
@@ -372,7 +386,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
// TODO: Use Linker::link or even construct the list in plain wikitext
$fileListItemsHtml .= Html::rawElement( 'li', array(),
Html::element( 'a', array( 'href' =>
- $this->getTitle( "file/$file" )->getLocalURL() ), $file )
+ $this->getPageTitle( "file/$file" )->getLocalURL() ), $file )
);
}
$this->getOutput()->addHtml( Html::rawElement( 'ul', array(), $fileListItemsHtml ) );
@@ -390,4 +404,5 @@ class SpecialUploadStash extends UnlistedSpecialPage {
}
}
-class SpecialUploadStashTooLargeException extends MWException {};
+class SpecialUploadStashTooLargeException extends MWException {
+}
diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php
index 5ac3e654..6de7c90d 100644
--- a/includes/specials/SpecialUserlogin.php
+++ b/includes/specials/SpecialUserlogin.php
@@ -27,7 +27,6 @@
* @ingroup SpecialPage
*/
class LoginForm extends SpecialPage {
-
const SUCCESS = 0;
const NO_NAME = 1;
const ILLEGAL = 2;
@@ -42,26 +41,64 @@ class LoginForm extends SpecialPage {
const USER_BLOCKED = 11;
const NEED_TOKEN = 12;
const WRONG_TOKEN = 13;
+ const USER_MIGRATED = 14;
- var $mUsername, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted;
- var $mAction, $mCreateaccount, $mCreateaccountMail;
- var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage;
- var $mSkipCookieCheck, $mReturnToQuery, $mToken, $mStickHTTPS;
- var $mType, $mReason, $mRealName;
- var $mAbortLoginErrorMsg = null;
+ /**
+ * Valid error and warning messages
+ *
+ * Special:Userlogin can show an error or warning message on the form when
+ * coming from another page. This is done via the ?error= or ?warning= GET
+ * parameters.
+ *
+ * This array is the list of valid message keys. All other values will be
+ * ignored.
+ *
+ * @since 1.24
+ * @var string[]
+ */
+ public static $validErrorMessages = array(
+ 'exception-nologin-text',
+ 'watchlistanontext',
+ 'changeemail-no-info',
+ 'resetpass-no-info',
+ 'confirmemail_needlogin',
+ 'prefsnologintext2',
+ );
+
+ public $mAbortLoginErrorMsg = null;
+
+ protected $mUsername;
+ protected $mPassword;
+ protected $mRetype;
+ protected $mReturnTo;
+ protected $mCookieCheck;
+ protected $mPosted;
+ protected $mAction;
+ protected $mCreateaccount;
+ protected $mCreateaccountMail;
+ protected $mLoginattempt;
+ protected $mRemember;
+ protected $mEmail;
+ protected $mDomain;
+ protected $mLanguage;
+ protected $mSkipCookieCheck;
+ protected $mReturnToQuery;
+ protected $mToken;
+ protected $mStickHTTPS;
+ protected $mType;
+ protected $mReason;
+ protected $mRealName;
+ protected $mEntryError = '';
+ protected $mEntryErrorType = 'error';
+
+ private $mTempPasswordUsed;
private $mLoaded = false;
private $mSecureLoginUrl;
- /**
- * @ var WebRequest
- */
+ /** @var WebRequest */
private $mOverrideRequest = null;
- /**
- * Effective request; set at the beginning of load
- *
- * @var WebRequest $mRequest
- */
+ /** @var WebRequest Effective request; set at the beginning of load */
private $mRequest = null;
/**
@@ -100,19 +137,53 @@ class LoginForm extends SpecialPage {
$this->mCookieCheck = $request->getVal( 'wpCookieCheck' );
$this->mPosted = $request->wasPosted();
$this->mCreateaccountMail = $request->getCheck( 'wpCreateaccountMail' )
- && $wgEnableEmail;
+ && $wgEnableEmail;
$this->mCreateaccount = $request->getCheck( 'wpCreateaccount' ) && !$this->mCreateaccountMail;
$this->mLoginattempt = $request->getCheck( 'wpLoginattempt' );
$this->mAction = $request->getVal( 'action' );
$this->mRemember = $request->getCheck( 'wpRemember' );
$this->mFromHTTP = $request->getBool( 'fromhttp', false );
- $this->mStickHTTPS = ( !$this->mFromHTTP && $request->detectProtocol() === 'https' ) || $request->getBool( 'wpForceHttps', false );
+ $this->mStickHTTPS = ( !$this->mFromHTTP && $request->getProtocol() === 'https' )
+ || $request->getBool( 'wpForceHttps', false );
$this->mLanguage = $request->getText( 'uselang' );
$this->mSkipCookieCheck = $request->getCheck( 'wpSkipCookieCheck' );
- $this->mToken = ( $this->mType == 'signup' ) ? $request->getVal( 'wpCreateaccountToken' ) : $request->getVal( 'wpLoginToken' );
+ $this->mToken = $this->mType == 'signup'
+ ? $request->getVal( 'wpCreateaccountToken' )
+ : $request->getVal( 'wpLoginToken' );
$this->mReturnTo = $request->getVal( 'returnto', '' );
$this->mReturnToQuery = $request->getVal( 'returntoquery', '' );
+ // Show an error or warning passed on from a previous page
+ $entryError = $this->msg( $request->getVal( 'error', '' ) );
+ $entryWarning = $this->msg( $request->getVal( 'warning', '' ) );
+ // bc: provide login link as a parameter for messages where the translation
+ // was not updated
+ $loginreqlink = Linker::linkKnown(
+ $this->getPageTitle(),
+ $this->msg( 'loginreqlink' )->escaped(),
+ array(),
+ array(
+ 'returnto' => $this->mReturnTo,
+ 'returntoquery' => $this->mReturnToQuery,
+ 'uselang' => $this->mLanguage,
+ 'fromhttp' => $this->mFromHTTP ? '1' : '0',
+ )
+ );
+
+ // Only show valid error or warning messages.
+ if ( $entryError->exists()
+ && in_array( $entryError->getKey(), self::$validErrorMessages )
+ ) {
+ $this->mEntryErrorType = 'error';
+ $this->mEntryError = $entryError->rawParams( $loginreqlink )->escaped();
+
+ } elseif ( $entryWarning->exists()
+ && in_array( $entryWarning->getKey(), self::$validErrorMessages )
+ ) {
+ $this->mEntryErrorType = 'warning';
+ $this->mEntryError = $entryWarning->rawParams( $loginreqlink )->escaped();
+ }
+
if ( $wgEnableEmail ) {
$this->mEmail = $request->getText( 'wpEmail' );
} else {
@@ -133,9 +204,10 @@ class LoginForm extends SpecialPage {
# 2. Do not return to PasswordReset after a successful password change
# but goto Wiki start page (Main_Page) instead ( bug 33997 )
$returnToTitle = Title::newFromText( $this->mReturnTo );
- if ( is_object( $returnToTitle ) && (
- $returnToTitle->isSpecial( 'Userlogout' )
- || $returnToTitle->isSpecial( 'PasswordReset' ) ) ) {
+ if ( is_object( $returnToTitle )
+ && ( $returnToTitle->isSpecial( 'Userlogout' )
+ || $returnToTitle->isSpecial( 'PasswordReset' ) )
+ ) {
$this->mReturnTo = '';
$this->mReturnToQuery = '';
}
@@ -149,8 +221,8 @@ class LoginForm extends SpecialPage {
}
}
- /*
- * @param $subPage string|null
+ /**
+ * @param string|null $subPage
*/
public function execute( $subPage ) {
if ( session_id() == '' ) {
@@ -166,21 +238,43 @@ class LoginForm extends SpecialPage {
}
$this->setHeaders();
+ // In the case where the user is already logged in, and was redirected to the login form from a
+ // page that requires login, do not show the login page. The use case scenario for this is when
+ // a user opens a large number of tabs, is redirected to the login page on all of them, and then
+ // logs in on one, expecting all the others to work properly.
+ //
+ // However, do show the form if it was visited intentionally (no 'returnto' is present). People
+ // who often switch between several accounts have grown accustomed to this behavior.
+ if (
+ $this->mType !== 'signup' &&
+ !$this->mPosted &&
+ $this->getUser()->isLoggedIn() &&
+ ( $this->mReturnTo !== '' || $this->mReturnToQuery !== '' )
+ ) {
+ $this->successfulLogin();
+ }
+
// If logging in and not on HTTPS, either redirect to it or offer a link.
global $wgSecureLogin;
- if ( WebRequest::detectProtocol() !== 'https' ) {
+ if ( $this->mRequest->getProtocol() !== 'https' ) {
$title = $this->getFullTitle();
$query = array(
- 'returnto' => $this->mReturnTo,
- 'returntoquery' => $this->mReturnToQuery,
+ 'returnto' => $this->mReturnTo !== '' ? $this->mReturnTo : null,
+ 'returntoquery' => $this->mReturnToQuery !== '' ?
+ $this->mReturnToQuery : null,
'title' => null,
+ ( $this->mEntryErrorType === 'error' ? 'error' : 'warning' ) => $this->mEntryError,
) + $this->mRequest->getQueryValues();
$url = $title->getFullURL( $query, false, PROTO_HTTPS );
- if ( $wgSecureLogin && wfCanIPUseHTTPS( $this->getRequest()->getIP() ) ) {
+ if ( $wgSecureLogin
+ && wfCanIPUseHTTPS( $this->getRequest()->getIP() )
+ && !$this->mFromHTTP ) // Avoid infinite redirect
+ {
$url = wfAppendQuery( $url, 'fromhttp=1' );
$this->getOutput()->redirect( $url );
// Since we only do this redir to change proto, always vary
$this->getOutput()->addVaryHeader( 'X-Forwarded-Proto' );
+
return;
} else {
// A wiki without HTTPS login support should set $wgServer to
@@ -194,20 +288,24 @@ class LoginForm extends SpecialPage {
if ( !is_null( $this->mCookieCheck ) ) {
$this->onCookieRedirectCheck( $this->mCookieCheck );
+
return;
} elseif ( $this->mPosted ) {
if ( $this->mCreateaccount ) {
$this->addNewAccount();
+
return;
} elseif ( $this->mCreateaccountMail ) {
$this->addNewAccountMailPassword();
+
return;
} elseif ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) {
$this->processLogin();
+
return;
}
}
- $this->mainLoginForm( '' );
+ $this->mainLoginForm( $this->mEntryError, $this->mEntryErrorType );
}
/**
@@ -216,13 +314,15 @@ class LoginForm extends SpecialPage {
function addNewAccountMailPassword() {
if ( $this->mEmail == '' ) {
$this->mainLoginForm( $this->msg( 'noemailcreate' )->escaped() );
+
return;
}
- $status = $this->addNewaccountInternal();
+ $status = $this->addNewAccountInternal();
if ( !$status->isGood() ) {
$error = $status->getMessage();
$this->mainLoginForm( $error->toString() );
+
return;
}
@@ -259,6 +359,7 @@ class LoginForm extends SpecialPage {
if ( !$status->isGood() ) {
$error = $status->getMessage();
$this->mainLoginForm( $error->toString() );
+
return false;
}
@@ -318,10 +419,11 @@ class LoginForm extends SpecialPage {
# Confirm that the account was created
$out->setPageTitle( $this->msg( 'accountcreated' ) );
$out->addWikiMsg( 'accountcreatedtext', $u->getName() );
- $out->addReturnTo( $this->getTitle() );
+ $out->addReturnTo( $this->getPageTitle() );
wfRunHooks( 'AddNewAccount', array( $u, false ) );
$u->addNewUserLogEntry( 'create2', $this->mReason );
}
+
return true;
}
@@ -364,6 +466,7 @@ class LoginForm extends SpecialPage {
# Request forgery checks.
if ( !self::getCreateaccountToken() ) {
self::setCreateaccountToken();
+
return Status::newFatal( 'nocookiesfornew' );
}
@@ -385,14 +488,20 @@ class LoginForm extends SpecialPage {
} elseif ( $creationBlock instanceof Block ) {
// Throws an ErrorPageError.
$this->userBlockedMessage( $creationBlock );
+
// This should never be reached.
return false;
}
# Include checks that will include GlobalBlocking (Bug 38333)
- $permErrors = $this->getTitle()->getUserPermissionsErrors( 'createaccount', $currentUser, true );
+ $permErrors = $this->getPageTitle()->getUserPermissionsErrors(
+ 'createaccount',
+ $currentUser,
+ true
+ );
+
if ( count( $permErrors ) ) {
- throw new PermissionsError( 'createaccount', $permErrors );
+ throw new PermissionsError( 'createaccount', $permErrors );
}
$ip = $this->getRequest()->getIP();
@@ -400,9 +509,20 @@ class LoginForm extends SpecialPage {
return Status::newFatal( 'sorbs_create_account_reason' );
}
+ // Normalize the name so that silly things don't cause "invalid username"
+ // errors. User::newFromName does some rather strict checking, rejecting
+ // e.g. leading/trailing/multiple spaces. But first we need to reject
+ // usernames that would be treated as titles with a fragment part.
+ if ( strpos( $this->mUsername, '#' ) !== false ) {
+ return Status::newFatal( 'noname' );
+ }
+ $title = Title::makeTitleSafe( NS_USER, $this->mUsername );
+ if ( !is_object( $title ) ) {
+ return Status::newFatal( 'noname' );
+ }
+
# Now create a dummy user ($u) and check if it is valid
- $name = trim( $this->mUsername );
- $u = User::newFromName( $name, 'creatable' );
+ $u = User::newFromName( $title->getText(), 'creatable' );
if ( !is_object( $u ) ) {
return Status::newFatal( 'noname' );
} elseif ( 0 != $u->idForName() ) {
@@ -424,6 +544,7 @@ class LoginForm extends SpecialPage {
if ( !is_array( $valid ) ) {
$valid = array( $valid, $wgMinimalPasswordLength );
}
+
return call_user_func_array( 'Status::newFatal', $valid );
}
}
@@ -444,17 +565,30 @@ class LoginForm extends SpecialPage {
$u->setRealName( $this->mRealName );
$abortError = '';
- if ( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError ) ) ) {
+ $abortStatus = null;
+ if ( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError, &$abortStatus ) ) ) {
// Hook point to add extra creation throttles and blocks
wfDebug( "LoginForm::addNewAccountInternal: a hook blocked creation\n" );
- $abortError = new RawMessage( $abortError );
- $abortError->text();
- return Status::newFatal( $abortError );
+ if ( $abortStatus === null ) {
+ // Report back the old string as a raw message status.
+ // This will report the error back as 'createaccount-hook-aborted'
+ // with the given string as the message.
+ // To return a different error code, return a Status object.
+ $abortError = new Message( 'createaccount-hook-aborted', array( $abortError ) );
+ $abortError->text();
+
+ return Status::newFatal( $abortError );
+ } else {
+ // For MediaWiki 1.23+ and updated hooks, return the Status object
+ // returned from the hook.
+ return $abortStatus;
+ }
}
// Hook point to check for exempt from account creation throttle
if ( !wfRunHooks( 'ExemptFromAccountCreationThrottle', array( $ip ) ) ) {
- wfDebug( "LoginForm::exemptFromAccountCreationThrottle: a hook allowed account creation w/o throttle\n" );
+ wfDebug( "LoginForm::exemptFromAccountCreationThrottle: a hook " .
+ "allowed account creation w/o throttle\n" );
} else {
if ( ( $wgAccountCreationThrottle && $currentUser->isPingLimitable() ) ) {
$key = wfMemcKey( 'acctcreate', 'ip', $ip );
@@ -474,6 +608,7 @@ class LoginForm extends SpecialPage {
}
self::clearCreateaccountToken();
+
return $this->initUser( $u, false );
}
@@ -481,9 +616,9 @@ class LoginForm extends SpecialPage {
* Actually add a user to the database.
* Give it a User object that has been initialised with a name.
*
- * @param $u User object.
- * @param $autocreate boolean -- true if this is an autocreation via auth plugin
- * @return Status object, with the User object in the value member on success
+ * @param User $u
+ * @param bool $autocreate True if this is an autocreation via auth plugin
+ * @return Status Status object, with the User object in the value member on success
* @private
*/
function initUser( $u, $autocreate ) {
@@ -504,12 +639,14 @@ class LoginForm extends SpecialPage {
$wgAuth->initUser( $u, $autocreate );
- $u->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 );
$u->saveSettings();
- # Update user count
+ // Update user count
DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 0, 0, 0, 1 ) );
+ // Watch user's userpage and talk page
+ $u->addWatch( $u->getUserPage(), WatchedItem::IGNORE_USER_RIGHTS );
+
return Status::newGood( $u );
}
@@ -538,6 +675,7 @@ class LoginForm extends SpecialPage {
// If the user doesn't have a login token yet, set one.
if ( !self::getLoginToken() ) {
self::setLoginToken();
+
return self::NEED_TOKEN;
}
// If the user didn't pass a login token, tell them we need one
@@ -563,10 +701,19 @@ class LoginForm extends SpecialPage {
// will effectively be using stale data.
if ( $this->getUser()->getName() === $this->mUsername ) {
wfDebug( __METHOD__ . ": already logged in as {$this->mUsername}\n" );
+
return self::SUCCESS;
}
$u = User::newFromName( $this->mUsername );
+
+ // Give extensions a way to indicate the username has been updated,
+ // rather than telling the user the account doesn't exist.
+ if ( !wfRunHooks( 'LoginUserMigrated', array( $u, &$msg ) ) ) {
+ $this->mAbortLoginErrorMsg = $msg;
+ return self::USER_MIGRATED;
+ }
+
if ( !( $u instanceof User ) || !User::isUsableName( $u->getName() ) ) {
return self::ILLEGAL;
}
@@ -588,6 +735,7 @@ class LoginForm extends SpecialPage {
$msg = null;
if ( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$msg ) ) ) {
$this->mAbortLoginErrorMsg = $msg;
+
return $abort;
}
@@ -618,6 +766,8 @@ class LoginForm extends SpecialPage {
// At this point we just return an appropriate code/ indicating
// that the UI should show a password reset form; bot inter-
// faces etc will probably just fail cleanly here.
+ $this->mAbortLoginErrorMsg = 'resetpass-temp-emailed';
+ $this->mTempPasswordUsed = true;
$retval = self::RESET_PASS;
} else {
$retval = ( $this->mPassword == '' ) ? self::EMPTY_PASS : self::WRONG_PASS;
@@ -625,6 +775,10 @@ class LoginForm extends SpecialPage {
} elseif ( $wgBlockDisablesLogin && $u->isBlocked() ) {
// If we've enabled it, make it so that a blocked user cannot login
$retval = self::USER_BLOCKED;
+ } elseif ( $u->getPasswordExpired() == 'hard' ) {
+ // Force reset now, without logging in
+ $retval = self::RESET_PASS;
+ $this->mAbortLoginErrorMsg = 'resetpass-expired';
} else {
$wgAuth->updateUser( $u );
$wgUser = $u;
@@ -646,6 +800,7 @@ class LoginForm extends SpecialPage {
$retval = self::SUCCESS;
}
wfRunHooks( 'LoginAuthenticateAudit', array( $u, $this->mPassword, $retval ) );
+
return $retval;
}
@@ -653,7 +808,7 @@ class LoginForm extends SpecialPage {
* Increment the login attempt throttle hit count for the (username,current IP)
* tuple unless the throttle was already reached.
* @param string $username The user name
- * @return Bool|Integer The integer hit count or True if it is already at the limit
+ * @return bool|int The integer hit count or True if it is already at the limit
*/
public static function incLoginThrottle( $username ) {
global $wgPasswordAttemptThrottle, $wgMemc, $wgRequest;
@@ -695,26 +850,32 @@ class LoginForm extends SpecialPage {
* Attempt to automatically create a user on login. Only succeeds if there
* is an external authentication method which allows it.
*
- * @param $user User
+ * @param User $user
*
- * @return integer Status code
+ * @return int Status code
*/
function attemptAutoCreate( $user ) {
global $wgAuth;
if ( $this->getUser()->isBlockedFromCreateAccount() ) {
wfDebug( __METHOD__ . ": user is blocked from account creation\n" );
+
return self::CREATE_BLOCKED;
}
+
if ( !$wgAuth->autoCreate() ) {
return self::NOT_EXISTS;
}
+
if ( !$wgAuth->userExists( $user->getName() ) ) {
wfDebug( __METHOD__ . ": user does not exist\n" );
+
return self::NOT_EXISTS;
}
+
if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) {
wfDebug( __METHOD__ . ": \$wgAuth->authenticate() returned false, aborting\n" );
+
return self::WRONG_PLUGIN_PASS;
}
@@ -723,6 +884,7 @@ class LoginForm extends SpecialPage {
// Hook point to add extra creation throttles and blocks
wfDebug( "LoginForm::attemptAutoCreate: a hook blocked creation: $abortError\n" );
$this->mAbortLoginErrorMsg = $abortError;
+
return self::ABORTED;
}
@@ -732,6 +894,7 @@ class LoginForm extends SpecialPage {
if ( !$status->isOK() ) {
$errors = $status->getErrorsByType( 'error' );
$this->mAbortLoginErrorMsg = $errors[0]['message'];
+
return self::ABORTED;
}
@@ -739,27 +902,23 @@ class LoginForm extends SpecialPage {
}
function processLogin() {
- global $wgMemc, $wgLang, $wgSecureLogin, $wgPasswordAttemptThrottle;
+ global $wgMemc, $wgLang, $wgSecureLogin, $wgPasswordAttemptThrottle,
+ $wgInvalidPasswordReset;
switch ( $this->authenticateUserData() ) {
case self::SUCCESS:
# We've verified now, update the real record
$user = $this->getUser();
- if ( (bool)$this->mRemember != $user->getBoolOption( 'rememberpassword' ) ) {
- $user->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 );
- $user->saveSettings();
- } else {
- $user->invalidateCache();
- }
+ $user->invalidateCache();
if ( $user->requiresHTTPS() ) {
$this->mStickHTTPS = true;
}
if ( $wgSecureLogin && !$this->mStickHTTPS ) {
- $user->setCookies( null, false );
+ $user->setCookies( $this->mRequest, false, $this->mRemember );
} else {
- $user->setCookies();
+ $user->setCookies( $this->mRequest, null, $this->mRemember );
}
self::clearLoginToken();
@@ -778,7 +937,18 @@ class LoginForm extends SpecialPage {
$this->getContext()->setLanguage( $userLang );
// Reset SessionID on Successful login (bug 40995)
$this->renewSessionId();
- $this->successfulLogin();
+ if ( $this->getUser()->getPasswordExpired() == 'soft' ) {
+ $this->resetLoginForm( $this->msg( 'resetpass-expired-soft' ) );
+ } elseif ( $wgInvalidPasswordReset
+ && !$user->isValidPassword( $this->mPassword )
+ ) {
+ $status = $user->checkPasswordValidity( $this->mPassword );
+ $this->resetLoginForm(
+ $status->getMessage( 'resetpass-validity-soft' )
+ );
+ } else {
+ $this->successfulLogin();
+ }
} else {
$this->cookieRedirectCheck( 'login' );
}
@@ -822,7 +992,7 @@ class LoginForm extends SpecialPage {
break;
case self::RESET_PASS:
$error = $this->mAbortLoginErrorMsg ?: 'resetpass_announce';
- $this->resetLoginForm( $this->msg( $error )->text() );
+ $this->resetLoginForm( $this->msg( $error ) );
break;
case self::CREATE_BLOCKED:
$this->userBlockedMessage( $this->getUser()->isBlockedFromCreateAccount() );
@@ -830,8 +1000,8 @@ class LoginForm extends SpecialPage {
case self::THROTTLED:
$error = $this->mAbortLoginErrorMsg ?: 'login-throttled';
$this->mainLoginForm( $this->msg( $error )
- ->params ( $this->getLanguage()->formatDuration( $wgPasswordAttemptThrottle['seconds'] ) )
- ->text()
+ ->params( $this->getLanguage()->formatDuration( $wgPasswordAttemptThrottle['seconds'] ) )
+ ->text()
);
break;
case self::USER_BLOCKED:
@@ -840,7 +1010,17 @@ class LoginForm extends SpecialPage {
break;
case self::ABORTED:
$error = $this->mAbortLoginErrorMsg ?: 'login-abort-generic';
- $this->mainLoginForm( $this->msg( $error )->text() );
+ $this->mainLoginForm( $this->msg( $error,
+ wfEscapeWikiText( $this->mUsername ) )->text() );
+ break;
+ case self::USER_MIGRATED:
+ $error = $this->mAbortLoginErrorMsg ?: 'login-migrated-generic';
+ $params = array();
+ if ( is_array( $error ) ) {
+ $error = array_shift( $this->mAbortLoginErrorMsg );
+ $params = $this->mAbortLoginErrorMsg;
+ }
+ $this->mainLoginForm( $this->msg( $error, $params )->text() );
break;
default:
throw new MWException( 'Unhandled case value' );
@@ -848,24 +1028,34 @@ class LoginForm extends SpecialPage {
}
/**
- * @param $error string
+ * Show the Special:ChangePassword form, with custom message
+ * @param Message $msg
*/
- function resetLoginForm( $error ) {
- $this->getOutput()->addHTML( Xml::element( 'p', array( 'class' => 'error' ), $error ) );
+ protected function resetLoginForm( Message $msg ) {
+ // Allow hooks to explain this password reset in more detail
+ wfRunHooks( 'LoginPasswordResetMessage', array( &$msg, $this->mUsername ) );
$reset = new SpecialChangePassword();
- $reset->setContext( $this->getContext() );
+ $derivative = new DerivativeContext( $this->getContext() );
+ $derivative->setTitle( $reset->getPageTitle() );
+ $reset->setContext( $derivative );
+ if ( !$this->mTempPasswordUsed ) {
+ $reset->setOldPasswordMessage( 'oldpassword' );
+ }
+ $reset->setChangeMessage( $msg );
$reset->execute( null );
}
/**
- * @param $u User object
- * @param $throttle Boolean
- * @param string $emailTitle message name of email title
- * @param string $emailText message name of email text
- * @return Status object
+ * @param User $u
+ * @param bool $throttle
+ * @param string $emailTitle Message name of email title
+ * @param string $emailText Message name of email text
+ * @return Status
*/
- function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle', $emailText = 'passwordremindertext' ) {
- global $wgCanonicalServer, $wgScript, $wgNewPasswordExpiry;
+ function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle',
+ $emailText = 'passwordremindertext'
+ ) {
+ global $wgNewPasswordExpiry;
if ( $u->getEmail() == '' ) {
return Status::newFatal( 'noemail', $u->getName() );
@@ -882,7 +1072,11 @@ class LoginForm extends SpecialPage {
$u->setNewpassword( $np, $throttle );
$u->saveSettings();
$userLanguage = $u->getOption( 'language' );
- $m = $this->msg( $emailText, $ip, $u->getName(), $np, '<' . $wgCanonicalServer . $wgScript . '>',
+
+ $mainPage = Title::newMainPage();
+ $mainPageUrl = $mainPage->getCanonicalURL();
+
+ $m = $this->msg( $emailText, $ip, $u->getName(), $np, '<' . $mainPageUrl . '>',
round( $wgNewPasswordExpiry / 86400 ) )->inLanguage( $userLanguage )->text();
$result = $u->sendMail( $this->msg( $emailTitle )->inLanguage( $userLanguage )->text(), $m );
@@ -906,7 +1100,7 @@ class LoginForm extends SpecialPage {
wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) );
if ( $injected_html !== '' ) {
- $this->displaySuccessfulAction( $this->msg( 'loginsuccesstitle' ),
+ $this->displaySuccessfulAction( 'success', $this->msg( 'loginsuccesstitle' ),
'loginsuccess', $injected_html );
} else {
$this->executeReturnTo( 'successredirect' );
@@ -934,18 +1128,22 @@ class LoginForm extends SpecialPage {
*/
wfRunHooks( 'BeforeWelcomeCreation', array( &$welcome_creation_msg, &$injected_html ) );
- $this->displaySuccessfulAction( $this->msg( 'welcomeuser', $this->getUser()->getName() ),
- $welcome_creation_msg, $injected_html );
+ $this->displaySuccessfulAction(
+ 'signup',
+ $this->msg( 'welcomeuser', $this->getUser()->getName() ),
+ $welcome_creation_msg, $injected_html
+ );
}
/**
- * Display an "successful action" page.
+ * Display a "successful action" page.
*
- * @param string|Message $title page's title
- * @param $msgname string
- * @param $injected_html string
+ * @param string $type Condition of return to; see `executeReturnTo`
+ * @param string|Message $title Page's title
+ * @param string $msgname
+ * @param string $injected_html
*/
- private function displaySuccessfulAction( $title, $msgname, $injected_html ) {
+ private function displaySuccessfulAction( $type, $title, $msgname, $injected_html ) {
$out = $this->getOutput();
$out->setPageTitle( $title );
if ( $msgname ) {
@@ -954,7 +1152,7 @@ class LoginForm extends SpecialPage {
$out->addHTML( $injected_html );
- $this->executeReturnTo( 'success' );
+ $this->executeReturnTo( $type );
}
/**
@@ -962,7 +1160,7 @@ class LoginForm extends SpecialPage {
* there is a block on them or their IP which prevents account creation. Note that
* User::isBlockedFromCreateAccount(), which gets this block, ignores the 'hardblock'
* setting on blocks (bug 13611).
- * @param $block Block the block causing this error
+ * @param Block $block The block causing this error
* @throws ErrorPageError
*/
function userBlockedMessage( Block $block ) {
@@ -973,14 +1171,23 @@ class LoginForm extends SpecialPage {
# haven't bothered to log out before trying to create an account to
# evade it, but we'll leave that to their guilty conscience to figure
# out.
+ $errorParams = array(
+ $block->getTarget(),
+ $block->mReason ? $block->mReason : $this->msg( 'blockednoreason' )->text(),
+ $block->getByName()
+ );
+
+ if ( $block->getType() === Block::TYPE_RANGE ) {
+ $errorMessage = 'cantcreateaccount-range-text';
+ $errorParams[] = $this->getRequest()->getIP();
+ } else {
+ $errorMessage = 'cantcreateaccount-text';
+ }
+
throw new ErrorPageError(
'cantcreateaccounttitle',
- 'cantcreateaccount-text',
- array(
- $block->getTarget(),
- $block->mReason ? $block->mReason : $this->msg( 'blockednoreason' )->text(),
- $block->getByName()
- )
+ $errorMessage,
+ $errorParams
);
}
@@ -989,8 +1196,9 @@ class LoginForm extends SpecialPage {
* Extensions can use this to reuse the "return to" logic after
* inject steps (such as redirection) into the login process.
*
- * @param $type string, one of the following:
+ * @param string $type One of the following:
* - error: display a return to link ignoring $wgRedirectOnLogin
+ * - signup: display a return to link using $wgRedirectOnLogin if needed
* - success: display a return to link using $wgRedirectOnLogin if needed
* - successredirect: send an HTTP redirect using $wgRedirectOnLogin if needed
* @param string $returnTo
@@ -1010,8 +1218,9 @@ class LoginForm extends SpecialPage {
/**
* Add a "return to" link or redirect to it.
*
- * @param $type string, one of the following:
+ * @param string $type One of the following:
* - error: display a return to link ignoring $wgRedirectOnLogin
+ * - signup: display a return to link using $wgRedirectOnLogin if needed
* - success: display a return to link using $wgRedirectOnLogin if needed
* - successredirect: send an HTTP redirect using $wgRedirectOnLogin if needed
*/
@@ -1026,6 +1235,9 @@ class LoginForm extends SpecialPage {
$returnToQuery = wfCgiToArray( $this->mReturnToQuery );
}
+ // Allow modification of redirect behavior
+ wfRunHooks( 'PostLoginRedirect', array( &$returnTo, &$returnToQuery, &$type ) );
+
$returnToTitle = Title::newFromText( $returnTo );
if ( !$returnToTitle ) {
$returnToTitle = Title::newMainPage();
@@ -1051,6 +1263,8 @@ class LoginForm extends SpecialPage {
}
/**
+ * @param string $msg
+ * @param string $msgtype
* @private
*/
function mainLoginForm( $msg, $msgtype = 'error' ) {
@@ -1059,7 +1273,7 @@ class LoginForm extends SpecialPage {
global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration;
global $wgSecureLogin, $wgPasswordResetRoutes;
- $titleObj = $this->getTitle();
+ $titleObj = $this->getPageTitle();
$user = $this->getUser();
$out = $this->getOutput();
@@ -1072,6 +1286,7 @@ class LoginForm extends SpecialPage {
throw new PermissionsError( 'createaccount', $permErrors );
} elseif ( $user->isBlockedFromCreateAccount() ) {
$this->userBlockedMessage( $user->isBlockedFromCreateAccount() );
+
return;
} elseif ( wfReadOnly() ) {
throw new ReadOnlyError;
@@ -1087,33 +1302,47 @@ class LoginForm extends SpecialPage {
}
}
- if ( $this->mType == 'signup' ) {
- $template = new UsercreateTemplate();
+ // Generic styles and scripts for both login and signup form
+ $out->addModuleStyles( array(
+ 'mediawiki.ui',
+ 'mediawiki.ui.button',
+ 'mediawiki.ui.checkbox',
+ 'mediawiki.ui.input',
+ 'mediawiki.special.userlogin.common.styles'
+ ) );
+ $out->addModules( array(
+ 'mediawiki.special.userlogin.common.js'
+ ) );
- $out->addModuleStyles( array(
- 'mediawiki.ui',
- 'mediawiki.special.createaccount'
- ) );
+ if ( $this->mType == 'signup' ) {
// XXX hack pending RL or JS parse() support for complex content messages
// https://bugzilla.wikimedia.org/show_bug.cgi?id=25349
$out->addJsConfigVars( 'wgCreateacctImgcaptchaHelp',
$this->msg( 'createacct-imgcaptcha-help' )->parse() );
+
+ // Additional styles and scripts for signup form
$out->addModules( array(
- 'mediawiki.special.createaccount.js'
+ 'mediawiki.special.userlogin.signup.js'
) );
+ $out->addModuleStyles( array(
+ 'mediawiki.special.userlogin.signup.styles'
+ ) );
+
+ $template = new UsercreateTemplate();
+
// Must match number of benefits defined in messages
$template->set( 'benefitCount', 3 );
$q = 'action=submitlogin&type=signup';
$linkq = 'type=login';
} else {
- $template = new UserloginTemplate();
-
+ // Additional styles for login form
$out->addModuleStyles( array(
- 'mediawiki.ui',
- 'mediawiki.special.userlogin'
+ 'mediawiki.special.userlogin.login.styles'
) );
+ $template = new UserloginTemplate();
+
$q = 'action=submitlogin&type=login';
$linkq = 'type=signup';
}
@@ -1167,7 +1396,7 @@ class LoginForm extends SpecialPage {
$template->set( 'resetlink', $resetLink );
$template->set( 'canremember', ( $wgCookieExpiration > 0 ) );
$template->set( 'usereason', $user->isLoggedIn() );
- $template->set( 'remember', $user->getOption( 'rememberpassword' ) || $this->mRemember );
+ $template->set( 'remember', $this->mRemember );
$template->set( 'cansecurelogin', ( $wgSecureLogin === true ) );
$template->set( 'stickhttps', (int)$this->mStickHTTPS );
$template->set( 'loggedin', $user->isLoggedIn() );
@@ -1196,7 +1425,7 @@ class LoginForm extends SpecialPage {
$template->set( 'secureLoginUrl', $this->mSecureLoginUrl );
// Use loginend-https for HTTPS requests if it's not blank, loginend otherwise
// Ditto for signupend. New forms use neither.
- $usingHTTPS = WebRequest::detectProtocol() == 'https';
+ $usingHTTPS = $this->mRequest->getProtocol() == 'https';
$loginendHTTPS = $this->msg( 'loginend-https' );
$signupendHTTPS = $this->msg( 'signupend-https' );
if ( $usingHTTPS && !$loginendHTTPS->isBlank() ) {
@@ -1226,7 +1455,7 @@ class LoginForm extends SpecialPage {
* Whether the login/create account form should display a link to the
* other form (in addition to whatever the skin provides).
*
- * @param $user User
+ * @param User $user
* @return bool
*/
private function showCreateOrLoginLink( &$user ) {
@@ -1251,15 +1480,17 @@ class LoginForm extends SpecialPage {
*/
function hasSessionCookie() {
global $wgDisableCookieCheck;
+
return $wgDisableCookieCheck ? true : $this->getRequest()->checkSessionCookie();
}
/**
* Get the login token from the current session
- * @return Mixed
+ * @return mixed
*/
public static function getLoginToken() {
global $wgRequest;
+
return $wgRequest->getSessionData( 'wsLoginToken' );
}
@@ -1268,7 +1499,7 @@ class LoginForm extends SpecialPage {
*/
public static function setLoginToken() {
global $wgRequest;
- // Generate a token directly instead of using $user->editToken()
+ // Generate a token directly instead of using $user->getEditToken()
// because the latter reuses $_SESSION['wsEditToken']
$wgRequest->setSessionData( 'wsLoginToken', MWCryptRand::generateHex( 32 ) );
}
@@ -1283,10 +1514,11 @@ class LoginForm extends SpecialPage {
/**
* Get the createaccount token from the current session
- * @return Mixed
+ * @return mixed
*/
public static function getCreateaccountToken() {
global $wgRequest;
+
return $wgRequest->getSessionData( 'wsCreateaccountToken' );
}
@@ -1319,6 +1551,7 @@ class LoginForm extends SpecialPage {
}
/**
+ * @param string $type
* @private
*/
function cookieRedirectCheck( $type ) {
@@ -1334,6 +1567,7 @@ class LoginForm extends SpecialPage {
}
/**
+ * @param string $type
* @private
*/
function onCookieRedirectCheck( $type ) {
@@ -1369,6 +1603,7 @@ class LoginForm extends SpecialPage {
$links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) );
}
}
+
return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams(
$this->getLanguage()->pipeList( $links ) )->escaped() : '';
} else {
@@ -1403,7 +1638,7 @@ class LoginForm extends SpecialPage {
$attr['lang'] = $attr['hreflang'] = $targetLanguage->getHtmlCode();
return Linker::linkKnown(
- $this->getTitle(),
+ $this->getPageTitle(),
htmlspecialchars( $text ),
$attr,
$query
diff --git a/includes/specials/SpecialUserlogout.php b/includes/specials/SpecialUserlogout.php
index d957e875..d65ac852 100644
--- a/includes/specials/SpecialUserlogout.php
+++ b/includes/specials/SpecialUserlogout.php
@@ -27,7 +27,6 @@
* @ingroup SpecialPage
*/
class SpecialUserlogout extends UnlistedSpecialPage {
-
function __construct() {
parent::__construct( 'Userlogout' );
}
diff --git a/includes/specials/SpecialUserrights.php b/includes/specials/SpecialUserrights.php
index 4501736f..cefdad07 100644
--- a/includes/specials/SpecialUserrights.php
+++ b/includes/specials/SpecialUserrights.php
@@ -55,6 +55,7 @@ class UserrightsPage extends SpecialPage {
if ( $user->getId() == 0 ) {
return false;
}
+
return !empty( $available['add'] )
|| !empty( $available['remove'] )
|| ( ( $this->isself || !$checkIfSelf ) &&
@@ -66,7 +67,7 @@ class UserrightsPage extends SpecialPage {
* Manage forms to be shown according to posted data.
* Depending on the submit button used, call a form or a save function.
*
- * @param $par Mixed: string if any subpage provided, else null
+ * @param string|null $par String if any subpage provided, else null
* @throws UserBlockedError|PermissionsError
*/
public function execute( $par ) {
@@ -118,6 +119,7 @@ class UserrightsPage extends SpecialPage {
$out = $this->getOutput();
$out->wrapWikiMsg( "<div class=\"successbox\">\n$1\n</div>", 'userrights-removed-self' );
$out->returnToMain();
+
return;
}
@@ -148,12 +150,18 @@ class UserrightsPage extends SpecialPage {
$status = $this->fetchUser( $this->mTarget );
if ( !$status->isOK() ) {
$this->getOutput()->addWikiText( $status->getWikiText() );
+
return;
}
$targetUser = $status->value;
+ if ( $targetUser instanceof User ) { // UserRightsProxy doesn't have this method (bug 61252)
+ $targetUser->clearInstanceCache(); // bug 38989
+ }
- if ( $request->getVal( 'conflictcheck-originalgroups' ) !== implode( ',', $targetUser->getGroups() ) ) {
+ if ( $request->getVal( 'conflictcheck-originalgroups' )
+ !== implode( ',', $targetUser->getGroups() )
+ ) {
$out->addWikiMsg( 'userrights-conflict' );
} else {
$this->saveUserGroups(
@@ -163,6 +171,7 @@ class UserrightsPage extends SpecialPage {
);
$out->redirect( $this->getSuccessURL() );
+
return;
}
}
@@ -174,15 +183,15 @@ class UserrightsPage extends SpecialPage {
}
function getSuccessURL() {
- return $this->getTitle( $this->mTarget )->getFullURL( array( 'success' => 1 ) );
+ return $this->getPageTitle( $this->mTarget )->getFullURL( array( 'success' => 1 ) );
}
/**
* Save user groups changes in the database.
* Data comes from the editUserGroupsForm() form function
*
- * @param string $username username to apply changes to.
- * @param string $reason reason for group change
+ * @param string $username Username to apply changes to.
+ * @param string $reason Reason for group change
* @param User|UserRightsProxy $user Target user object.
* @return null
*/
@@ -209,11 +218,11 @@ class UserrightsPage extends SpecialPage {
/**
* Save user groups changes in the database.
*
- * @param $user User object
- * @param array $add of groups to add
- * @param array $remove of groups to remove
- * @param string $reason reason for group change
- * @return Array: Tuple of added, then removed groups
+ * @param User $user
+ * @param array $add Array of groups to add
+ * @param array $remove Array of groups to remove
+ * @param string $reason Reason for group change
+ * @return array Tuple of added, then removed groups
*/
function doSaveUserGroups( $user, $add, $remove, $reason = '' ) {
global $wgAuth;
@@ -256,18 +265,23 @@ class UserrightsPage extends SpecialPage {
// update groups in external authentication database
$wgAuth->updateExternalDBGroups( $user, $add, $remove );
- wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) );
- wfDebug( 'newGroups: ' . print_r( $newGroups, true ) );
+ wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) . "\n" );
+ wfDebug( 'newGroups: ' . print_r( $newGroups, true ) . "\n" );
wfRunHooks( 'UserRights', array( &$user, $add, $remove ) );
if ( $newGroups != $oldGroups ) {
$this->addLogEntry( $user, $oldGroups, $newGroups, $reason );
}
+
return array( $add, $remove );
}
/**
* Add a rights log entry for an action.
+ * @param User $user
+ * @param array $oldGroups
+ * @param array $newGroups
+ * @param array $reason
*/
function addLogEntry( $user, $oldGroups, $newGroups, $reason ) {
$logEntry = new ManualLogEntry( 'rights', 'rights' );
@@ -284,12 +298,13 @@ class UserrightsPage extends SpecialPage {
/**
* Edit user groups membership
- * @param string $username name of the user.
+ * @param string $username Name of the user.
*/
function editUserGroupsForm( $username ) {
$status = $this->fetchUser( $username );
if ( !$status->isOK() ) {
$this->getOutput()->addWikiText( $status->getWikiText() );
+
return;
} else {
$user = $status->value;
@@ -310,12 +325,10 @@ class UserrightsPage extends SpecialPage {
*
* Side effects: error output for invalid access
* @param string $username
- * @return Status object
+ * @return Status
*/
public function fetchUser( $username ) {
- global $wgUserrightsInterwikiDelimiter;
-
- $parts = explode( $wgUserrightsInterwikiDelimiter, $username );
+ $parts = explode( $this->getConfig()->get( 'UserrightsInterwikiDelimiter' ), $username );
if ( count( $parts ) < 2 ) {
$name = trim( $username );
$database = '';
@@ -384,8 +397,8 @@ class UserrightsPage extends SpecialPage {
/**
* Make a list of group names to be stored as parameter for log entries
*
- * @deprecated in 1.21; use LogFormatter instead.
- * @param $ids array
+ * @deprecated since 1.21; use LogFormatter instead.
+ * @param array $ids
* @return string
*/
function makeGroupNameListForLog( $ids ) {
@@ -402,12 +415,26 @@ class UserrightsPage extends SpecialPage {
* Output a form to allow searching for a user
*/
function switchForm() {
- global $wgScript;
$this->getOutput()->addHTML(
- Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'name' => 'uluser', 'id' => 'mw-userrights-form1' ) ) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
+ Html::openElement(
+ 'form',
+ array(
+ 'method' => 'get',
+ 'action' => wfScript(),
+ 'name' => 'uluser',
+ 'id' => 'mw-userrights-form1'
+ )
+ ) .
+ Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) .
Xml::fieldset( $this->msg( 'userrights-lookup-user' )->text() ) .
- Xml::inputLabel( $this->msg( 'userrights-user-editname' )->text(), 'user', 'username', 30, str_replace( '_', ' ', $this->mTarget ), array( 'autofocus' => true ) ) . ' ' .
+ Xml::inputLabel(
+ $this->msg( 'userrights-user-editname' )->text(),
+ 'user',
+ 'username',
+ 30,
+ str_replace( '_', ' ', $this->mTarget ),
+ array( 'autofocus' => true )
+ ) . ' ' .
Xml::submitButton( $this->msg( 'editusergroup' )->text() ) .
Html::closeElement( 'fieldset' ) .
Html::closeElement( 'form' ) . "\n"
@@ -419,8 +446,8 @@ class UserrightsPage extends SpecialPage {
* form will be able to manipulate based on the current user's system
* permissions.
*
- * @param array $groups list of groups the given user is in
- * @return Array: Tuple of addable, then removable groups
+ * @param array $groups List of groups the given user is in
+ * @return array Tuple of addable, then removable groups
*/
protected function splitGroups( $groups ) {
list( $addable, $removable, $addself, $removeself ) = array_values( $this->changeableGroups() );
@@ -440,8 +467,8 @@ class UserrightsPage extends SpecialPage {
/**
* Show the form to edit group memberships.
*
- * @param $user User or UserRightsProxy you're editing
- * @param $groups Array: Array of groups the user is in
+ * @param User|UserRightsProxy $user User or UserRightsProxy you're editing
+ * @param array $groups Array of groups the user is in
*/
protected function showEditUserGroupsForm( $user, $groups ) {
$list = array();
@@ -476,30 +503,48 @@ class UserrightsPage extends SpecialPage {
$grouplist = $this->msg( 'userrights-groupsmember', $count, $user->getName() )->parse();
$grouplist = '<p>' . $grouplist . ' ' . $displayedList . "</p>\n";
}
+
$count = count( $autoList );
if ( $count > 0 ) {
- $autogrouplistintro = $this->msg( 'userrights-groupsmember-auto', $count, $user->getName() )->parse();
+ $autogrouplistintro = $this->msg( 'userrights-groupsmember-auto', $count, $user->getName() )
+ ->parse();
$grouplist .= '<p>' . $autogrouplistintro . ' ' . $displayedAutolist . "</p>\n";
}
$userToolLinks = Linker::userToolLinks(
- $user->getId(),
- $user->getName(),
- false, /* default for redContribsWhenNoEdits */
- Linker::TOOL_LINKS_EMAIL /* Add "send e-mail" link */
+ $user->getId(),
+ $user->getName(),
+ false, /* default for redContribsWhenNoEdits */
+ Linker::TOOL_LINKS_EMAIL /* Add "send e-mail" link */
);
$this->getOutput()->addHTML(
- Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL(), 'name' => 'editGroup', 'id' => 'mw-userrights-form2' ) ) .
+ Xml::openElement(
+ 'form',
+ array(
+ 'method' => 'post',
+ 'action' => $this->getPageTitle()->getLocalURL(),
+ 'name' => 'editGroup',
+ 'id' => 'mw-userrights-form2'
+ )
+ ) .
Html::hidden( 'user', $this->mTarget ) .
Html::hidden( 'wpEditToken', $this->getUser()->getEditToken( $this->mTarget ) ) .
- Html::hidden( 'conflictcheck-originalgroups', implode( ',', $user->getGroups() ) ) . // Conflict detection
+ Html::hidden(
+ 'conflictcheck-originalgroups',
+ implode( ',', $user->getGroups() )
+ ) . // Conflict detection
Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', array(), $this->msg( 'userrights-editusergroup', $user->getName() )->text() ) .
- $this->msg( 'editinguser' )->params( wfEscapeWikiText( $user->getName() ) )->rawParams( $userToolLinks )->parse() .
+ Xml::element(
+ 'legend',
+ array(),
+ $this->msg( 'userrights-editusergroup', $user->getName() )->text()
+ ) .
+ $this->msg( 'editinguser' )->params( wfEscapeWikiText( $user->getName() ) )
+ ->rawParams( $userToolLinks )->parse() .
$this->msg( 'userrights-groups-help', $user->getName() )->parse() .
$grouplist .
- Xml::tags( 'p', null, $this->groupCheckboxes( $groups, $user ) ) .
+ $this->groupCheckboxes( $groups, $user ) .
Xml::openElement( 'table', array( 'id' => 'mw-userrights-table-outer' ) ) .
"<tr>
<td class='mw-label'>" .
@@ -514,7 +559,9 @@ class UserrightsPage extends SpecialPage {
<td></td>
<td class='mw-submit'>" .
Xml::submitButton( $this->msg( 'saveusergroups' )->text(),
- array( 'name' => 'saveusergroups' ) + Linker::tooltipAndAccesskeyAttribs( 'userrights-set' ) ) .
+ array( 'name' => 'saveusergroups' ) +
+ Linker::tooltipAndAccesskeyAttribs( 'userrights-set' )
+ ) .
"</td>
</tr>" .
Xml::closeElement( 'table' ) . "\n" .
@@ -526,7 +573,7 @@ class UserrightsPage extends SpecialPage {
/**
* Format a link to a group description page
*
- * @param $group string
+ * @param string $group
* @return string
*/
private static function buildGroupLink( $group ) {
@@ -536,7 +583,7 @@ class UserrightsPage extends SpecialPage {
/**
* Format a link to a group member description page
*
- * @param $group string
+ * @param string $group
* @return string
*/
private static function buildGroupMemberLink( $group ) {
@@ -555,8 +602,8 @@ class UserrightsPage extends SpecialPage {
* Adds a table with checkboxes where you can select what groups to add/remove
*
* @todo Just pass the username string?
- * @param array $usergroups groups the user belongs to
- * @param $user User a user object
+ * @param array $usergroups Groups the user belongs to
+ * @param User $user
* @return string XHTML table element with checkboxes
*/
private function groupCheckboxes( $usergroups, $user ) {
@@ -599,8 +646,13 @@ class UserrightsPage extends SpecialPage {
continue;
}
// Messages: userrights-changeable-col, userrights-unchangeable-col
- $ret .= Xml::element( 'th', null, $this->msg( 'userrights-' . $name . '-col', count( $column ) )->text() );
+ $ret .= Xml::element(
+ 'th',
+ null,
+ $this->msg( 'userrights-' . $name . '-col', count( $column ) )->text()
+ );
}
+
$ret .= "</tr>\n<tr>\n";
foreach ( $columns as $column ) {
if ( $column === array() ) {
@@ -631,28 +683,41 @@ class UserrightsPage extends SpecialPage {
}
/**
- * @param $group String: the name of the group to check
+ * @param string $group The name of the group to check
* @return bool Can we remove the group?
*/
private function canRemove( $group ) {
// $this->changeableGroups()['remove'] doesn't work, of course. Thanks, PHP.
$groups = $this->changeableGroups();
- return in_array( $group, $groups['remove'] ) || ( $this->isself && in_array( $group, $groups['remove-self'] ) );
+
+ return in_array(
+ $group,
+ $groups['remove'] ) || ( $this->isself && in_array( $group, $groups['remove-self'] )
+ );
}
/**
- * @param string $group the name of the group to check
+ * @param string $group The name of the group to check
* @return bool Can we add the group?
*/
private function canAdd( $group ) {
$groups = $this->changeableGroups();
- return in_array( $group, $groups['add'] ) || ( $this->isself && in_array( $group, $groups['add-self'] ) );
+
+ return in_array(
+ $group,
+ $groups['add'] ) || ( $this->isself && in_array( $group, $groups['add-self'] )
+ );
}
/**
* Returns $this->getUser()->changeableGroups()
*
- * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ), 'add-self' => array( addablegroups to self ), 'remove-self' => array( removable groups from self ) )
+ * @return array Array(
+ * 'add' => array( addablegroups ),
+ * 'remove' => array( removablegroups ),
+ * 'add-self' => array( addablegroups to self ),
+ * 'remove-self' => array( removable groups from self )
+ * )
*/
function changeableGroups() {
return $this->getUser()->changeableGroups();
@@ -661,8 +726,8 @@ class UserrightsPage extends SpecialPage {
/**
* Show a rights log fragment for the specified user
*
- * @param $user User to show log for
- * @param $output OutputPage to use
+ * @param User $user User to show log for
+ * @param OutputPage $output OutputPage to use
*/
protected function showLogFragment( $user, $output ) {
$rightsLogPage = new LogPage( 'rights' );
diff --git a/includes/specials/SpecialVersion.php b/includes/specials/SpecialVersion.php
index 5ba785f5..cb3fc118 100644
--- a/includes/specials/SpecialVersion.php
+++ b/includes/specials/SpecialVersion.php
@@ -29,9 +29,13 @@
* @ingroup SpecialPage
*/
class SpecialVersion extends SpecialPage {
-
protected $firstExtOpened = false;
+ /**
+ * Stores the current rev id/SHA hash of MediaWiki core
+ */
+ protected $coreId = '';
+
protected static $extensionTypes = false;
protected static $viewvcUrls = array(
@@ -46,43 +50,95 @@ class SpecialVersion extends SpecialPage {
/**
* main()
+ * @param string|null $par
*/
public function execute( $par ) {
- global $wgSpecialVersionShowHooks, $IP;
+ global $IP, $wgExtensionCredits;
$this->setHeaders();
$this->outputHeader();
$out = $this->getOutput();
$out->allowClickjacking();
- if ( $par !== 'Credits' ) {
- $text =
- $this->getMediaWikiCredits() .
- $this->softwareInformation() .
- $this->getEntryPointInfo() .
- $this->getExtensionCredits();
- if ( $wgSpecialVersionShowHooks ) {
- $text .= $this->getWgHooks();
+ // Explode the sub page information into useful bits
+ $parts = explode( '/', (string)$par );
+ $extNode = null;
+ if ( isset( $parts[1] ) ) {
+ $extName = str_replace( '_', ' ', $parts[1] );
+ // Find it!
+ foreach ( $wgExtensionCredits as $group => $extensions ) {
+ foreach ( $extensions as $ext ) {
+ if ( isset( $ext['name'] ) && ( $ext['name'] === $extName ) ) {
+ $extNode = &$ext;
+ break 2;
+ }
+ }
}
-
- $out->addWikiText( $text );
- $out->addHTML( $this->IPInfo() );
-
- if ( $this->getRequest()->getVal( 'easteregg' ) ) {
- // TODO: put something interesting here
+ if ( !$extNode ) {
+ $out->setStatusCode( 404 );
}
} else {
- // Credits sub page
-
- // Header
- $out->addHTML( wfMessage( 'version-credits-summary' )->parseAsBlock() );
+ $extName = 'MediaWiki';
+ }
- $wikiText = file_get_contents( $IP . '/CREDITS' );
+ // Now figure out what to do
+ switch ( strtolower( $parts[0] ) ) {
+ case 'credits':
+ $wikiText = '{{int:version-credits-not-found}}';
+ if ( $extName === 'MediaWiki' ) {
+ $wikiText = file_get_contents( $IP . '/CREDITS' );
+ } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
+ $file = $this->getExtAuthorsFileName( dirname( $extNode['path'] ) );
+ if ( $file ) {
+ $wikiText = file_get_contents( $file );
+ if ( substr( $file, -4 ) === '.txt' ) {
+ $wikiText = Html::element( 'pre', array(), $wikiText );
+ }
+ }
+ }
- // Take everything from the first section onwards, to remove the (not localized) header
- $wikiText = substr( $wikiText, strpos( $wikiText, '==' ) );
+ $out->setPageTitle( $this->msg( 'version-credits-title', $extName ) );
+ $out->addWikiText( $wikiText );
+ break;
+
+ case 'license':
+ $wikiText = '{{int:version-license-not-found}}';
+ if ( $extName === 'MediaWiki' ) {
+ $wikiText = file_get_contents( $IP . '/COPYING' );
+ } elseif ( ( $extNode !== null ) && isset( $extNode['path'] ) ) {
+ $file = $this->getExtLicenseFileName( dirname( $extNode['path'] ) );
+ if ( $file ) {
+ $wikiText = file_get_contents( $file );
+ if ( !isset( $extNode['license-name'] ) ) {
+ // If the developer did not explicitly set license-name they probably
+ // are unaware that we're now sucking this file in and thus it's probably
+ // not wikitext friendly.
+ $wikiText = "<pre>$wikiText</pre>";
+ }
+ }
+ }
- $out->addWikiText( $wikiText );
+ $out->setPageTitle( $this->msg( 'version-license-title', $extName ) );
+ $out->addWikiText( $wikiText );
+ break;
+
+ default:
+ $out->addModules( 'mediawiki.special.version' );
+ $out->addWikiText(
+ $this->getMediaWikiCredits() .
+ $this->softwareInformation() .
+ $this->getEntryPointInfo()
+ );
+ $out->addHtml(
+ $this->getSkinCredits() .
+ $this->getExtensionCredits() .
+ $this->getParserTags() .
+ $this->getParserFunctionHooks()
+ );
+ $out->addWikiText( $this->getWgHooks() );
+ $out->addHTML( $this->IPInfo() );
+
+ break;
}
}
@@ -92,7 +148,11 @@ class SpecialVersion extends SpecialPage {
* @return string
*/
private static function getMediaWikiCredits() {
- $ret = Xml::element( 'h2', array( 'id' => 'mw-version-license' ), wfMessage( 'version-license' )->text() );
+ $ret = Xml::element(
+ 'h2',
+ array( 'id' => 'mw-version-license' ),
+ wfMessage( 'version-license' )->text()
+ );
// This text is always left-to-right.
$ret .= '<div class="plainlinks">';
@@ -107,18 +167,21 @@ class SpecialVersion extends SpecialPage {
/**
* Get the "MediaWiki is copyright 2001-20xx by lots of cool guys" text
*
- * @return String
+ * @return string
*/
public static function getCopyrightAndAuthorList() {
global $wgLang;
if ( defined( 'MEDIAWIKI_INSTALL' ) ) {
- $othersLink = '[//www.mediawiki.org/wiki/Special:Version/Credits ' . wfMessage( 'version-poweredby-others' )->text() . ']';
+ $othersLink = '[//www.mediawiki.org/wiki/Special:Version/Credits ' .
+ wfMessage( 'version-poweredby-others' )->text() . ']';
} else {
- $othersLink = '[[Special:Version/Credits|' . wfMessage( 'version-poweredby-others' )->text() . ']]';
+ $othersLink = '[[Special:Version/Credits|' .
+ wfMessage( 'version-poweredby-others' )->text() . ']]';
}
- $translatorsLink = '[//translatewiki.net/wiki/Translating:MediaWiki/Credits ' . wfMessage( 'version-poweredby-translators' )->text() . ']';
+ $translatorsLink = '[//translatewiki.net/wiki/Translating:MediaWiki/Credits ' .
+ wfMessage( 'version-poweredby-translators' )->text() . ']';
$authorList = array(
'Magnus Manske', 'Brion Vibber', 'Lee Daniel Crocker',
@@ -141,7 +204,7 @@ class SpecialVersion extends SpecialPage {
*
* @return string
*/
- static function softwareInformation() {
+ public static function softwareInformation() {
$dbr = wfGetDB( DB_SLAVE );
// Put the software in an array of form 'name' => 'version'. All messages should
@@ -149,13 +212,21 @@ class SpecialVersion extends SpecialPage {
// wikimarkup can be used.
$software = array();
$software['[https://www.mediawiki.org/ MediaWiki]'] = self::getVersionLinked();
- $software['[http://www.php.net/ PHP]'] = phpversion() . " (" . PHP_SAPI . ")";
+ if ( wfIsHHVM() ) {
+ $software['[http://hhvm.com/ HHVM]'] = HHVM_VERSION . " (" . PHP_SAPI . ")";
+ } else {
+ $software['[https://php.net/ PHP]'] = PHP_VERSION . " (" . PHP_SAPI . ")";
+ }
$software[$dbr->getSoftwareLink()] = $dbr->getServerInfo();
// Allow a hook to add/remove items.
wfRunHooks( 'SoftwareInfo', array( &$software ) );
- $out = Xml::element( 'h2', array( 'id' => 'mw-version-software' ), wfMessage( 'version-software' )->text() ) .
+ $out = Xml::element(
+ 'h2',
+ array( 'id' => 'mw-version-software' ),
+ wfMessage( 'version-software' )->text()
+ ) .
Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-software' ) ) .
"<tr>
<th>" . wfMessage( 'version-software-product' )->text() . "</th>
@@ -175,7 +246,7 @@ class SpecialVersion extends SpecialPage {
/**
* Return a string of the MediaWiki version with SVN revision if available.
*
- * @param $flags String
+ * @param string $flags
* @return mixed
*/
public static function getVersion( $flags = '' ) {
@@ -205,6 +276,7 @@ class SpecialVersion extends SpecialPage {
}
wfProfileOut( __METHOD__ );
+
return $version;
}
@@ -233,11 +305,12 @@ class SpecialVersion extends SpecialPage {
}
wfProfileOut( __METHOD__ );
+
return $v;
}
/**
- * @return string wgVersion + a link to subversion revision of svn BASE
+ * @return string Global wgVersion + a link to subversion revision of svn BASE
*/
private static function getVersionLinkedSvn() {
global $IP;
@@ -273,12 +346,14 @@ class SpecialVersion extends SpecialPage {
preg_match( "/^(\d+\.\d+)/", $wgVersion, $versionParts );
$versionUrl = "https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
}
+
return "[$versionUrl $wgVersion]";
}
/**
* @since 1.22 Returns the HEAD date in addition to the sha1 and link
- * @return bool|string wgVersion + HEAD sha1 stripped to the first 7 chars with link and date, or false on failure
+ * @return bool|string Global wgVersion + HEAD sha1 stripped to the first 7 chars
+ * with link and date, or false on failure
*/
private static function getVersionLinkedGit() {
global $IP, $wgLang;
@@ -298,7 +373,7 @@ class SpecialVersion extends SpecialPage {
$gitHeadCommitDate = $gitInfo->getHeadCommitDate();
if ( $gitHeadCommitDate ) {
- $shortSHA1 .= "<br/>" . $wgLang->timeanddate( $gitHeadCommitDate, true );
+ $shortSHA1 .= Html::element( 'br' ) . $wgLang->timeanddate( $gitHeadCommitDate, true );
}
return self::getwgVersionLinked() . " $shortSHA1";
@@ -308,9 +383,7 @@ class SpecialVersion extends SpecialPage {
* Returns an array with the base extension types.
* Type is stored as array key, the message as array value.
*
- * TODO: ideally this would return all extension types, including
- * those added by SpecialVersionExtensionTypes. This is not possible
- * since this hook is passing along $this though.
+ * TODO: ideally this would return all extension types.
*
* @since 1.17
*
@@ -340,35 +413,39 @@ class SpecialVersion extends SpecialPage {
*
* @since 1.17
*
- * @param $type String
+ * @param string $type
*
* @return string
*/
public static function getExtensionTypeName( $type ) {
$types = self::getExtensionTypes();
+
return isset( $types[$type] ) ? $types[$type] : $types['other'];
}
/**
- * Generate wikitext showing extensions name, URL, author and description.
+ * Generate wikitext showing the name, URL, author and description of each extension.
*
- * @return String: Wikitext
+ * @return string Wikitext
*/
- function getExtensionCredits() {
- global $wgExtensionCredits, $wgExtensionFunctions, $wgParser;
+ public function getExtensionCredits() {
+ global $wgExtensionCredits;
- if ( !count( $wgExtensionCredits ) && !count( $wgExtensionFunctions ) ) {
+ if (
+ count( $wgExtensionCredits ) === 0 ||
+ // Skins are displayed separately, see getSkinCredits()
+ ( count( $wgExtensionCredits ) === 1 && isset( $wgExtensionCredits['skin'] ) )
+ ) {
return '';
}
$extensionTypes = self::getExtensionTypes();
- /**
- * @deprecated as of 1.17, use hook ExtensionTypes instead.
- */
- wfRunHooks( 'SpecialVersionExtensionTypes', array( &$this, &$extensionTypes ) );
-
- $out = Xml::element( 'h2', array( 'id' => 'mw-version-ext' ), $this->msg( 'version-extensions' )->text() ) .
+ $out = Xml::element(
+ 'h2',
+ array( 'id' => 'mw-version-ext' ),
+ $this->msg( 'version-extensions' )->text()
+ ) .
Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-ext' ) );
// Make sure the 'other' type is set to an array.
@@ -383,9 +460,11 @@ class SpecialVersion extends SpecialPage {
}
}
+ $this->firstExtOpened = false;
// Loop through the extension categories to display their extensions in the list.
foreach ( $extensionTypes as $type => $message ) {
- if ( $type != 'other' ) {
+ // Skins have a separate section
+ if ( $type !== 'other' && $type !== 'skin' ) {
$out .= $this->getExtensionCategory( $type, $message );
}
}
@@ -393,24 +472,89 @@ class SpecialVersion extends SpecialPage {
// We want the 'other' type to be last in the list.
$out .= $this->getExtensionCategory( 'other', $extensionTypes['other'] );
+ $out .= Xml::closeElement( 'table' );
+
+ return $out;
+ }
+
+ /**
+ * Generate wikitext showing the name, URL, author and description of each skin.
+ *
+ * @return string Wikitext
+ */
+ public function getSkinCredits() {
+ global $wgExtensionCredits;
+ if ( !isset( $wgExtensionCredits['skin'] ) || count( $wgExtensionCredits['skin'] ) === 0 ) {
+ return '';
+ }
+
+ $out = Xml::element(
+ 'h2',
+ array( 'id' => 'mw-version-skin' ),
+ $this->msg( 'version-skins' )->text()
+ ) .
+ Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-skin' ) );
+
+ $this->firstExtOpened = false;
+ $out .= $this->getExtensionCategory( 'skin', null );
+
+ $out .= Xml::closeElement( 'table' );
+
+ return $out;
+ }
+
+ /**
+ * Obtains a list of installed parser tags and the associated H2 header
+ *
+ * @return string HTML output
+ */
+ protected function getParserTags() {
+ global $wgParser;
+
$tags = $wgParser->getTags();
- $cnt = count( $tags );
- if ( $cnt ) {
- for ( $i = 0; $i < $cnt; ++$i ) {
- $tags[$i] = "&lt;{$tags[$i]}&gt;";
- }
- $out .= $this->openExtType( $this->msg( 'version-parser-extensiontags' )->text(), 'parser-tags' );
- $out .= '<tr><td colspan="4">' . $this->listToText( $tags ) . "</td></tr>\n";
+ if ( count( $tags ) ) {
+ $out = Html::rawElement(
+ 'h2',
+ array( 'class' => 'mw-headline' ),
+ Linker::makeExternalLink(
+ '//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
+ $this->msg( 'version-parser-extensiontags' )->parse(),
+ false /* msg()->parse() already escapes */
+ )
+ );
+
+ array_walk( $tags, function ( &$value ) {
+ $value = '&lt;' . htmlspecialchars( $value ) . '&gt;';
+ } );
+ $out .= $this->listToText( $tags );
+ } else {
+ $out = '';
}
+ return $out;
+ }
+
+ /**
+ * Obtains a list of installed parser function hooks and the associated H2 header
+ *
+ * @return string HTML output
+ */
+ protected function getParserFunctionHooks() {
+ global $wgParser;
+
$fhooks = $wgParser->getFunctionHooks();
if ( count( $fhooks ) ) {
- $out .= $this->openExtType( $this->msg( 'version-parser-function-hooks' )->text(), 'parser-function-hooks' );
- $out .= '<tr><td colspan="4">' . $this->listToText( $fhooks ) . "</td></tr>\n";
- }
+ $out = Html::rawElement( 'h2', array( 'class' => 'mw-headline' ), Linker::makeExternalLink(
+ '//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
+ $this->msg( 'version-parser-function-hooks' )->parse(),
+ false /* msg()->parse() already escapes */
+ ) );
- $out .= Xml::closeElement( 'table' );
+ $out .= $this->listToText( $fhooks );
+ } else {
+ $out = '';
+ }
return $out;
}
@@ -420,8 +564,8 @@ class SpecialVersion extends SpecialPage {
*
* @since 1.17
*
- * @param $type String
- * @param $message String
+ * @param string $type
+ * @param string $message
*
* @return string
*/
@@ -445,11 +589,11 @@ class SpecialVersion extends SpecialPage {
/**
* Callback to sort extensions by type.
- * @param $a array
- * @param $b array
+ * @param array $a
+ * @param array $b
* @return int
*/
- function compare( $a, $b ) {
+ public function compare( $a, $b ) {
if ( $a['name'] === $b['name'] ) {
return 0;
} else {
@@ -460,63 +604,160 @@ class SpecialVersion extends SpecialPage {
}
/**
- * Creates and formats the credits for a single extension and returns this.
+ * Creates and formats a version line for a single extension.
*
- * @param $extension Array
+ * Information for five columns will be created. Parameters required in the
+ * $extension array for part rendering are indicated in ()
+ * - The name of (name), and URL link to (url), the extension
+ * - Official version number (version) and if available version control system
+ * revision (path), link, and date
+ * - If available the short name of the license (license-name) and a linke
+ * to ((LICENSE)|(COPYING))(\.txt)? if it exists.
+ * - Description of extension (descriptionmsg or description)
+ * - List of authors (author) and link to a ((AUTHORS)|(CREDITS))(\.txt)? file if it exists
*
- * @return string
+ * @param array $extension
+ *
+ * @return string Raw HTML
*/
- function getCreditsForExtension( array $extension ) {
- global $wgLang;
+ public function getCreditsForExtension( array $extension ) {
+ $out = $this->getOutput();
- $name = isset( $extension['name'] ) ? $extension['name'] : '[no name]';
+ // We must obtain the information for all the bits and pieces!
+ // ... such as extension names and links
+ if ( isset( $extension['namemsg'] ) ) {
+ // Localized name of extension
+ $extensionName = $this->msg( $extension['namemsg'] )->text();
+ } elseif ( isset( $extension['name'] ) ) {
+ // Non localized version
+ $extensionName = $extension['name'];
+ } else {
+ $extensionName = $this->msg( 'version-no-ext-name' )->text();
+ }
- $vcsText = false;
+ if ( isset( $extension['url'] ) ) {
+ $extensionNameLink = Linker::makeExternalLink(
+ $extension['url'],
+ $extensionName,
+ true,
+ '',
+ array( 'class' => 'mw-version-ext-name' )
+ );
+ } else {
+ $extensionNameLink = $extensionName;
+ }
+
+ // ... and the version information
+ // If the extension path is set we will check that directory for GIT and SVN
+ // metadata in an attempt to extract date and vcs commit metadata.
+ $canonicalVersion = '&ndash;';
+ $extensionPath = null;
+ $vcsVersion = null;
+ $vcsLink = null;
+ $vcsDate = null;
+
+ if ( isset( $extension['version'] ) ) {
+ $canonicalVersion = $out->parseInline( $extension['version'] );
+ }
if ( isset( $extension['path'] ) ) {
- $gitInfo = new GitInfo( dirname( $extension['path'] ) );
- $gitHeadSHA1 = $gitInfo->getHeadSHA1();
- if ( $gitHeadSHA1 !== false ) {
- $vcsText = '(' . substr( $gitHeadSHA1, 0, 7 ) . ')';
- $gitViewerUrl = $gitInfo->getHeadViewUrl();
- if ( $gitViewerUrl !== false ) {
- $vcsText = "[$gitViewerUrl $vcsText]";
+ global $IP;
+ $extensionPath = dirname( $extension['path'] );
+ if ( $this->coreId == '' ) {
+ wfDebug( 'Looking up core head id' );
+ $coreHeadSHA1 = self::getGitHeadSha1( $IP );
+ if ( $coreHeadSHA1 ) {
+ $this->coreId = $coreHeadSHA1;
+ } else {
+ $svnInfo = self::getSvnInfo( $IP );
+ if ( $svnInfo !== false ) {
+ $this->coreId = $svnInfo['checkout-rev'];
+ }
}
- $gitHeadCommitDate = $gitInfo->getHeadCommitDate();
- if ( $gitHeadCommitDate ) {
- $vcsText .= "<br/>" . $wgLang->timeanddate( $gitHeadCommitDate, true );
+ }
+ $cache = wfGetCache( CACHE_ANYTHING );
+ $memcKey = wfMemcKey( 'specialversion-ext-version-text', $extension['path'], $this->coreId );
+ list( $vcsVersion, $vcsLink, $vcsDate ) = $cache->get( $memcKey );
+
+ if ( !$vcsVersion ) {
+ wfDebug( "Getting VCS info for extension $extensionName" );
+ $gitInfo = new GitInfo( $extensionPath );
+ $vcsVersion = $gitInfo->getHeadSHA1();
+ if ( $vcsVersion !== false ) {
+ $vcsVersion = substr( $vcsVersion, 0, 7 );
+ $vcsLink = $gitInfo->getHeadViewUrl();
+ $vcsDate = $gitInfo->getHeadCommitDate();
+ } else {
+ $svnInfo = self::getSvnInfo( $extensionPath );
+ if ( $svnInfo !== false ) {
+ $vcsVersion = $this->msg( 'version-svn-revision', $svnInfo['checkout-rev'] )->text();
+ $vcsLink = isset( $svnInfo['viewvc-url'] ) ? $svnInfo['viewvc-url'] : '';
+ }
}
+ $cache->set( $memcKey, array( $vcsVersion, $vcsLink, $vcsDate ), 60 * 60 * 24 );
} else {
- $svnInfo = self::getSvnInfo( dirname( $extension['path'] ) );
- # Make subversion text/link.
- if ( $svnInfo !== false ) {
- $directoryRev = isset( $svnInfo['directory-rev'] ) ? $svnInfo['directory-rev'] : null;
- $vcsText = $this->msg( 'version-svn-revision', $directoryRev, $svnInfo['checkout-rev'] )->text();
- $vcsText = isset( $svnInfo['viewvc-url'] ) ? '[' . $svnInfo['viewvc-url'] . " $vcsText]" : $vcsText;
- }
+ wfDebug( "Pulled VCS info for extension $extensionName from cache" );
}
}
- # Make main link (or just the name if there is no URL).
- if ( isset( $extension['url'] ) ) {
- $mainLink = "[{$extension['url']} $name]";
- } else {
- $mainLink = $name;
- }
+ $versionString = Html::rawElement(
+ 'span',
+ array( 'class' => 'mw-version-ext-version' ),
+ $canonicalVersion
+ );
- if ( isset( $extension['version'] ) ) {
- $versionText = '<span class="mw-version-ext-version">' .
- $this->msg( 'version-version', $extension['version'] )->text() .
- '</span>';
- } else {
- $versionText = '';
+ if ( $vcsVersion ) {
+ if ( $vcsLink ) {
+ $vcsVerString = Linker::makeExternalLink(
+ $vcsLink,
+ $this->msg( 'version-version', $vcsVersion ),
+ true,
+ '',
+ array( 'class' => 'mw-version-ext-vcs-version' )
+ );
+ } else {
+ $vcsVerString = Html::element( 'span',
+ array( 'class' => 'mw-version-ext-vcs-version' ),
+ "({$vcsVersion})"
+ );
+ }
+ $versionString .= " {$vcsVerString}";
+
+ if ( $vcsDate ) {
+ $vcsTimeString = Html::element( 'span',
+ array( 'class' => 'mw-version-ext-vcs-timestamp' ),
+ $this->getLanguage()->timeanddate( $vcsDate, true )
+ );
+ $versionString .= " {$vcsTimeString}";
+ }
+ $versionString = Html::rawElement( 'span',
+ array( 'class' => 'mw-version-ext-meta-version' ),
+ $versionString
+ );
}
- # Make description text.
- $description = isset( $extension['description'] ) ? $extension['description'] : '';
+ // ... and license information; if a license file exists we
+ // will link to it
+ $licenseLink = '';
+ if ( isset( $extension['license-name'] ) ) {
+ $licenseLink = Linker::link(
+ $this->getPageTitle( 'License/' . $extensionName ),
+ $out->parseInline( $extension['license-name'] ),
+ array( 'class' => 'mw-version-ext-license' )
+ );
+ } elseif ( $this->getExtLicenseFileName( $extensionPath ) ) {
+ $licenseLink = Linker::link(
+ $this->getPageTitle( 'License/' . $extensionName ),
+ $this->msg( 'version-ext-license' ),
+ array( 'class' => 'mw-version-ext-license' )
+ );
+ }
+ // ... and generate the description; which can be a parameterized l10n message
+ // in the form array( <msgname>, <parameter>, <parameter>... ) or just a straight
+ // up string
if ( isset( $extension['descriptionmsg'] ) ) {
- # Look for a localized description.
+ // Localized description of extension
$descriptionMsg = $extension['descriptionmsg'];
if ( is_array( $descriptionMsg ) ) {
@@ -527,65 +768,80 @@ class SpecialVersion extends SpecialPage {
} else {
$description = $this->msg( $descriptionMsg )->text();
}
- }
-
- if ( $vcsText !== false ) {
- $extNameVer = "<tr>
- <td><em>$mainLink $versionText</em></td>
- <td><em>$vcsText</em></td>";
+ } elseif ( isset( $extension['description'] ) ) {
+ // Non localized version
+ $description = $extension['description'];
} else {
- $extNameVer = "<tr>
- <td colspan=\"2\"><em>$mainLink $versionText</em></td>";
+ $description = '';
}
+ $description = $out->parseInline( $description );
- $author = isset( $extension['author'] ) ? $extension['author'] : array();
- $extDescAuthor = "<td>$description</td>
- <td>" . $this->listAuthors( $author, false ) . "</td>
- </tr>\n";
+ // ... now get the authors for this extension
+ $authors = isset( $extension['author'] ) ? $extension['author'] : array();
+ $authors = $this->listAuthors( $authors, $extensionName, $extensionPath );
- return $extNameVer . $extDescAuthor;
+ // Finally! Create the table
+ $html = Html::openElement( 'tr', array(
+ 'class' => 'mw-version-ext',
+ 'id' => "mw-version-ext-{$extensionName}"
+ )
+ );
+
+ $html .= Html::rawElement( 'td', array(), $extensionNameLink );
+ $html .= Html::rawElement( 'td', array(), $versionString );
+ $html .= Html::rawElement( 'td', array(), $licenseLink );
+ $html .= Html::rawElement( 'td', array( 'class' => 'mw-version-ext-description' ), $description );
+ $html .= Html::rawElement( 'td', array( 'class' => 'mw-version-ext-authors' ), $authors );
+
+ $html .= Html::closeElement( 'td' );
+
+ return $html;
}
/**
* Generate wikitext showing hooks in $wgHooks.
*
- * @return String: wikitext
+ * @return string Wikitext
*/
private function getWgHooks() {
- global $wgHooks;
+ global $wgSpecialVersionShowHooks, $wgHooks;
- if ( count( $wgHooks ) ) {
+ if ( $wgSpecialVersionShowHooks && count( $wgHooks ) ) {
$myWgHooks = $wgHooks;
ksort( $myWgHooks );
- $ret = Xml::element( 'h2', array( 'id' => 'mw-version-hooks' ), $this->msg( 'version-hooks' )->text() ) .
- Xml::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-hooks' ) ) .
- "<tr>
- <th>" . $this->msg( 'version-hook-name' )->text() . "</th>
- <th>" . $this->msg( 'version-hook-subscribedby' )->text() . "</th>
- </tr>\n";
+ $ret = array();
+ $ret[] = '== {{int:version-hooks}} ==';
+ $ret[] = Html::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-hooks' ) );
+ $ret[] = Html::openElement( 'tr' );
+ $ret[] = Html::element( 'th', array(), $this->msg( 'version-hook-name' )->text() );
+ $ret[] = Html::element( 'th', array(), $this->msg( 'version-hook-subscribedby' )->text() );
+ $ret[] = Html::closeElement( 'tr' );
foreach ( $myWgHooks as $hook => $hooks ) {
- $ret .= "<tr>
- <td>$hook</td>
- <td>" . $this->listToText( $hooks ) . "</td>
- </tr>\n";
+ $ret[] = Html::openElement( 'tr' );
+ $ret[] = Html::element( 'td', array(), $hook );
+ $ret[] = Html::element( 'td', array(), $this->listToText( $hooks ) );
+ $ret[] = Html::closeElement( 'tr' );
}
- $ret .= Xml::closeElement( 'table' );
- return $ret;
+ $ret[] = Html::closeElement( 'table' );
+
+ return implode( "\n", $ret );
} else {
return '';
}
}
- private function openExtType( $text, $name = null ) {
- $opt = array( 'colspan' => 4 );
+ private function openExtType( $text = null, $name = null ) {
$out = '';
+ $opt = array( 'colspan' => 5 );
if ( $this->firstExtOpened ) {
// Insert a spacing line
- $out .= '<tr class="sv-space">' . Html::element( 'td', $opt ) . "</tr>\n";
+ $out .= Html::rawElement( 'tr', array( 'class' => 'sv-space' ),
+ Html::element( 'td', $opt )
+ );
}
$this->firstExtOpened = true;
@@ -593,7 +849,27 @@ class SpecialVersion extends SpecialPage {
$opt['id'] = "sv-$name";
}
- $out .= "<tr>" . Xml::element( 'th', $opt, $text ) . "</tr>\n";
+ if ( $text !== null ) {
+ $out .= Html::rawElement( 'tr', array(),
+ Html::element( 'th', $opt, $text )
+ );
+ }
+
+ $firstHeadingMsg = ( $name === 'credits-skin' )
+ ? 'version-skin-colheader-name'
+ : 'version-ext-colheader-name';
+ $out .= Html::openElement( 'tr' );
+ $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
+ $this->msg( $firstHeadingMsg )->text() );
+ $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
+ $this->msg( 'version-ext-colheader-version' )->text() );
+ $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
+ $this->msg( 'version-ext-colheader-license' )->text() );
+ $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
+ $this->msg( 'version-ext-colheader-description' )->text() );
+ $out .= Html::element( 'th', array( 'class' => 'mw-version-ext-col-label' ),
+ $this->msg( 'version-ext-colheader-credits' )->text() );
+ $out .= Html::closeElement( 'tr' );
return $out;
}
@@ -601,64 +877,156 @@ class SpecialVersion extends SpecialPage {
/**
* Get information about client's IP address.
*
- * @return String: HTML fragment
+ * @return string HTML fragment
*/
private function IPInfo() {
$ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) );
+
return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>";
}
/**
* Return a formatted unsorted list of authors
*
- * @param $authors mixed: string or array of strings
- * @return String: HTML fragment
+ * 'And Others'
+ * If an item in the $authors array is '...' it is assumed to indicate an
+ * 'and others' string which will then be linked to an ((AUTHORS)|(CREDITS))(\.txt)?
+ * file if it exists in $dir.
+ *
+ * Similarly an entry ending with ' ...]' is assumed to be a link to an
+ * 'and others' page.
+ *
+ * If no '...' string variant is found, but an authors file is found an
+ * 'and others' will be added to the end of the credits.
+ *
+ * @param string|array $authors
+ * @param string $extName Name of the extension for link creation
+ * @param string $extDir Path to the extension root directory
+ *
+ * @return string HTML fragment
*/
- function listAuthors( $authors ) {
+ public function listAuthors( $authors, $extName, $extDir ) {
+ $hasOthers = false;
+
$list = array();
foreach ( (array)$authors as $item ) {
if ( $item == '...' ) {
- $list[] = $this->msg( 'version-poweredby-others' )->text();
+ $hasOthers = true;
+
+ if ( $this->getExtAuthorsFileName( $extDir ) ) {
+ $text = Linker::link(
+ $this->getPageTitle( "Credits/$extName" ),
+ $this->msg( 'version-poweredby-others' )->text()
+ );
+ } else {
+ $text = $this->msg( 'version-poweredby-others' )->text();
+ }
+ $list[] = $text;
} elseif ( substr( $item, -5 ) == ' ...]' ) {
- $list[] = substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]";
+ $hasOthers = true;
+ $list[] = $this->getOutput()->parseInline(
+ substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]"
+ );
} else {
- $list[] = $item;
+ $list[] = $this->getOutput()->parseInline( $item );
}
}
+
+ if ( !$hasOthers && $this->getExtAuthorsFileName( $extDir ) ) {
+ $list[] = $text = Linker::link(
+ $this->getPageTitle( "Credits/$extName" ),
+ $this->msg( 'version-poweredby-others' )->text()
+ );
+ }
+
return $this->listToText( $list, false );
}
/**
- * Convert an array of items into a list for display.
+ * Obtains the full path of an extensions authors or credits file if
+ * one exists.
+ *
+ * @param string $extDir Path to the extensions root directory
+ *
+ * @since 1.23
*
- * @param array $list of elements to display
- * @param $sort Boolean: whether to sort the items in $list
+ * @return bool|string False if no such file exists, otherwise returns
+ * a path to it.
+ */
+ public static function getExtAuthorsFileName( $extDir ) {
+ if ( !$extDir ) {
+ return false;
+ }
+
+ foreach ( scandir( $extDir ) as $file ) {
+ $fullPath = $extDir . DIRECTORY_SEPARATOR . $file;
+ if ( preg_match( '/^((AUTHORS)|(CREDITS))(\.txt)?$/', $file ) &&
+ is_readable( $fullPath ) &&
+ is_file( $fullPath )
+ ) {
+ return $fullPath;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Obtains the full path of an extensions copying or license file if
+ * one exists.
+ *
+ * @param string $extDir Path to the extensions root directory
*
- * @return String
+ * @since 1.23
+ *
+ * @return bool|string False if no such file exists, otherwise returns
+ * a path to it.
*/
- function listToText( $list, $sort = true ) {
- $cnt = count( $list );
+ public static function getExtLicenseFileName( $extDir ) {
+ if ( !$extDir ) {
+ return false;
+ }
- if ( $cnt == 1 ) {
- // Enforce always returning a string
- return (string)self::arrayToString( $list[0] );
- } elseif ( $cnt == 0 ) {
- return '';
- } else {
- if ( $sort ) {
- sort( $list );
+ foreach ( scandir( $extDir ) as $file ) {
+ $fullPath = $extDir . DIRECTORY_SEPARATOR . $file;
+ if ( preg_match( '/^((COPYING)|(LICENSE))(\.txt)?$/', $file ) &&
+ is_readable( $fullPath ) &&
+ is_file( $fullPath )
+ ) {
+ return $fullPath;
}
- return $this->getLanguage()->listToText( array_map( array( __CLASS__, 'arrayToString' ), $list ) );
}
+
+ return false;
+ }
+
+ /**
+ * Convert an array of items into a list for display.
+ *
+ * @param array $list List of elements to display
+ * @param bool $sort Whether to sort the items in $list
+ *
+ * @return string
+ */
+ public function listToText( $list, $sort = true ) {
+ if ( !count( $list ) ) {
+ return '';
+ }
+ if ( $sort ) {
+ sort( $list );
+ }
+
+ return $this->getLanguage()
+ ->listToText( array_map( array( __CLASS__, 'arrayToString' ), $list ) );
}
/**
* Convert an array or object to a string for display.
*
- * @param $list Mixed: will convert an array to string if given and return
- * the paramater unaltered otherwise
+ * @param mixed $list Will convert an array to string if given and return
+ * the paramater unaltered otherwise
*
- * @return Mixed
+ * @return mixed
*/
public static function arrayToString( $list ) {
if ( is_array( $list ) && count( $list ) == 1 ) {
@@ -666,6 +1034,7 @@ class SpecialVersion extends SpecialPage {
}
if ( is_object( $list ) ) {
$class = wfMessage( 'parentheses' )->params( get_class( $list ) )->escaped();
+
return $class;
} elseif ( !is_array( $list ) ) {
return $list;
@@ -675,6 +1044,7 @@ class SpecialVersion extends SpecialPage {
} else {
$class = $list[0];
}
+
return wfMessage( 'parentheses' )->params( "$class, {$list[1]}" )->escaped();
}
}
@@ -692,7 +1062,7 @@ class SpecialVersion extends SpecialPage {
* url The subversion URL of the directory
* repo-url The base URL of the repository
* viewvc-url A ViewVC URL pointing to the checked-out revision
- * @param $dir string
+ * @param string $dir
* @return array|bool
*/
public static function getSvnInfo( $dir ) {
@@ -765,9 +1135,9 @@ class SpecialVersion extends SpecialPage {
/**
* Retrieve the revision number of a Subversion working directory.
*
- * @param string $dir directory of the svn checkout
+ * @param string $dir Directory of the svn checkout
*
- * @return Integer: revision number as int
+ * @return int Revision number
*/
public static function getSvnRevision( $dir ) {
$info = self::getSvnInfo( $dir );
@@ -782,15 +1152,25 @@ class SpecialVersion extends SpecialPage {
}
/**
- * @param string $dir directory of the git checkout
- * @return bool|String sha1 of commit HEAD points to
+ * @param string $dir Directory of the git checkout
+ * @return bool|string Sha1 of commit HEAD points to
*/
public static function getGitHeadSha1( $dir ) {
$repo = new GitInfo( $dir );
+
return $repo->getHeadSHA1();
}
/**
+ * @param string $dir Directory of the git checkout
+ * @return bool|string Branch currently checked out
+ */
+ public static function getGitCurrentBranch( $dir ) {
+ $repo = new GitInfo( $dir );
+ return $repo->getCurrentBranch();
+ }
+
+ /**
* Get the list of entry points and their URLs
* @return string Wikitext
*/
@@ -810,7 +1190,11 @@ class SpecialVersion extends SpecialPage {
'dir' => $language->getDir(),
'lang' => $language->getCode()
);
- $out = Html::element( 'h2', array( 'id' => 'mw-version-entrypoints' ), $this->msg( 'version-entrypoints' )->text() ) .
+ $out = Html::element(
+ 'h2',
+ array( 'id' => 'mw-version-entrypoints' ),
+ $this->msg( 'version-entrypoints' )->text()
+ ) .
Html::openElement( 'table',
array(
'class' => 'wikitable plainlinks',
@@ -820,8 +1204,16 @@ class SpecialVersion extends SpecialPage {
)
) .
Html::openElement( 'tr' ) .
- Html::element( 'th', $thAttribures, $this->msg( 'version-entrypoints-header-entrypoint' )->text() ) .
- Html::element( 'th', $thAttribures, $this->msg( 'version-entrypoints-header-url' )->text() ) .
+ Html::element(
+ 'th',
+ $thAttribures,
+ $this->msg( 'version-entrypoints-header-entrypoint' )->text()
+ ) .
+ Html::element(
+ 'th',
+ $thAttribures,
+ $this->msg( 'version-entrypoints-header-url' )->text()
+ ) .
Html::closeElement( 'tr' );
foreach ( $entryPoints as $message => $value ) {
@@ -835,11 +1227,11 @@ class SpecialVersion extends SpecialPage {
}
$out .= Html::closeElement( 'table' );
+
return $out;
}
protected function getGroupName() {
return 'wiki';
}
-
}
diff --git a/includes/specials/SpecialWantedcategories.php b/includes/specials/SpecialWantedcategories.php
index d2ffdb94..b8c0bb28 100644
--- a/includes/specials/SpecialWantedcategories.php
+++ b/includes/specials/SpecialWantedcategories.php
@@ -29,6 +29,7 @@
* @ingroup SpecialPage
*/
class WantedCategoriesPage extends WantedQueryPage {
+ private $currentCategoryCounts;
function __construct( $name = 'Wantedcategories' ) {
parent::__construct( $name );
@@ -37,9 +38,11 @@ class WantedCategoriesPage extends WantedQueryPage {
function getQueryInfo() {
return array(
'tables' => array( 'categorylinks', 'page' ),
- 'fields' => array( 'namespace' => NS_CATEGORY,
- 'title' => 'cl_to',
- 'value' => 'COUNT(*)' ),
+ 'fields' => array(
+ 'namespace' => NS_CATEGORY,
+ 'title' => 'cl_to',
+ 'value' => 'COUNT(*)'
+ ),
'conds' => array( 'page_title IS NULL' ),
'options' => array( 'GROUP BY' => 'cl_to' ),
'join_conds' => array( 'page' => array( 'LEFT JOIN',
@@ -48,6 +51,37 @@ class WantedCategoriesPage extends WantedQueryPage {
);
}
+ function preprocessResults( $db, $res ) {
+ parent::preprocessResults( $db, $res );
+
+ $this->currentCategoryCounts = array();
+
+ if ( !$res->numRows() || !$this->isCached() ) {
+ return;
+ }
+
+ // Fetch (hopefully) up-to-date numbers of pages in each category.
+ // This should be fast enough as we limit the list to a reasonable length.
+
+ $allCategories = array();
+ foreach ( $res as $row ) {
+ $allCategories[] = $row->title;
+ }
+
+ $categoryRes = $db->select(
+ 'category',
+ array( 'cat_title', 'cat_pages' ),
+ array( 'cat_title' => $allCategories ),
+ __METHOD__
+ );
+ foreach ( $categoryRes as $row ) {
+ $this->currentCategoryCounts[$row->cat_title] = intval( $row->cat_pages );
+ }
+
+ // Back to start for display
+ $res->seek( 0 );
+ }
+
/**
* @param Skin $skin
* @param object $result Result row
@@ -59,17 +93,37 @@ class WantedCategoriesPage extends WantedQueryPage {
$nt = Title::makeTitle( $result->namespace, $result->title );
$text = htmlspecialchars( $wgContLang->convert( $nt->getText() ) );
- $plink = $this->isCached() ?
- Linker::link( $nt, $text ) :
- Linker::link(
+ if ( !$this->isCached() ) {
+ // We can assume the freshest data
+ $plink = Linker::link(
$nt,
$text,
array(),
array(),
array( 'broken' )
);
+ $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
+ } else {
+ $plink = Linker::link( $nt, $text );
+
+ $currentValue = isset( $this->currentCategoryCounts[$result->title] )
+ ? $this->currentCategoryCounts[$result->title]
+ : 0;
+
+ // If the category has been created or emptied since the list was refreshed, strike it
+ if ( $nt->isKnown() || $currentValue === 0 ) {
+ $plink = "<del>$plink</del>";
+ }
+
+ // Show the current number of category entries if it changed
+ if ( $currentValue !== $result->value ) {
+ $nlinks = $this->msg( 'nmemberschanged' )
+ ->numParams( $result->value, $currentValue )->escaped();
+ } else {
+ $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
+ }
+ }
- $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
return $this->getLanguage()->specialList( $plink, $nlinks );
}
diff --git a/includes/specials/SpecialWantedfiles.php b/includes/specials/SpecialWantedfiles.php
index b5c1fdbe..937a503c 100644
--- a/includes/specials/SpecialWantedfiles.php
+++ b/includes/specials/SpecialWantedfiles.php
@@ -49,40 +49,97 @@ class WantedFilesPage extends WantedQueryPage {
$category = false;
}
+ $noForeign = '';
+ if ( !$this->likelyToHaveFalsePositives() ) {
+ // Additional messages for grep:
+ // wantedfiletext-cat-noforeign, wantedfiletext-nocat
+ $noForeign = '-noforeign';
+ }
+
if ( $category ) {
return $this
- ->msg( 'wantedfiletext-cat' )
+ ->msg( 'wantedfiletext-cat' . $noForeign )
->params( $category->getFullText() )
->parseAsBlock();
} else {
return $this
- ->msg( 'wantedfiletext-nocat' )
+ ->msg( 'wantedfiletext-nocat' . $noForeign )
->parseAsBlock();
}
}
/**
+ * Whether foreign repos are likely to cause false positives
+ *
+ * In its own function to allow subclasses to override.
+ * @see SpecialWantedFilesGUOverride in GlobalUsage extension.
+ * @since 1.24
+ */
+ protected function likelyToHaveFalsePositives() {
+ return RepoGroup::singleton()->hasForeignRepos();
+ }
+
+ /**
* KLUGE: The results may contain false positives for files
* that exist e.g. in a shared repo. Setting this at least
* keeps them from showing up as redlinks in the output, even
* if it doesn't fix the real problem (bug 6220).
+ *
+ * @note could also have existing links here from broken file
+ * redirects.
* @return bool
*/
function forceExistenceCheck() {
return true;
}
+ /**
+ * Does the file exist?
+ *
+ * Use wfFindFile so we still think file namespace pages without
+ * files are missing, but valid file redirects and foreign files are ok.
+ *
+ * @return boolean
+ */
+ protected function existenceCheck( Title $title ) {
+ return (bool) wfFindFile( $title );
+ }
+
function getQueryInfo() {
return array(
- 'tables' => array( 'imagelinks', 'image' ),
- 'fields' => array( 'namespace' => NS_FILE,
- 'title' => 'il_to',
- 'value' => 'COUNT(*)' ),
- 'conds' => array( 'img_name IS NULL' ),
+ 'tables' => array(
+ 'imagelinks',
+ 'page',
+ 'redirect',
+ 'img1' => 'image',
+ 'img2' => 'image',
+ ),
+ 'fields' => array(
+ 'namespace' => NS_FILE,
+ 'title' => 'il_to',
+ 'value' => 'COUNT(*)'
+ ),
+ 'conds' => array(
+ 'img1.img_name' => null,
+ // We also need to exclude file redirects
+ 'img2.img_name' => null,
+ ),
'options' => array( 'GROUP BY' => 'il_to' ),
- 'join_conds' => array( 'image' =>
- array( 'LEFT JOIN',
- array( 'il_to = img_name' )
+ 'join_conds' => array(
+ 'img1' => array( 'LEFT JOIN',
+ 'il_to = img1.img_name'
+ ),
+ 'page' => array( 'LEFT JOIN', array(
+ 'il_to = page_title',
+ 'page_namespace' => NS_FILE,
+ ) ),
+ 'redirect' => array( 'LEFT JOIN', array(
+ 'page_id = rd_from',
+ 'rd_namespace' => NS_FILE,
+ 'rd_interwiki' => ''
+ ) ),
+ 'img2' => array( 'LEFT JOIN',
+ 'rd_title = img2.img_name'
)
)
);
diff --git a/includes/specials/SpecialWantedpages.php b/includes/specials/SpecialWantedpages.php
index acec4ea4..38f1808f 100644
--- a/includes/specials/SpecialWantedpages.php
+++ b/includes/specials/SpecialWantedpages.php
@@ -54,8 +54,7 @@ class WantedPagesPage extends WantedQueryPage {
}
function getQueryInfo() {
- global $wgWantedPagesThreshold;
- $count = $wgWantedPagesThreshold - 1;
+ $count = $this->getConfig()->get( 'WantedPagesThreshold' ) - 1;
$query = array(
'tables' => array(
'pagelinks',
@@ -69,8 +68,7 @@ class WantedPagesPage extends WantedQueryPage {
),
'conds' => array(
'pg1.page_namespace IS NULL',
- "pl_namespace NOT IN ( '" . NS_USER .
- "', '" . NS_USER_TALK . "' )",
+ "pl_namespace NOT IN ( '" . NS_USER . "', '" . NS_USER_TALK . "' )",
"pg2.page_namespace != '" . NS_MEDIAWIKI . "'"
),
'options' => array(
@@ -89,6 +87,7 @@ class WantedPagesPage extends WantedQueryPage {
);
// Replacement for the WantedPages::getSQL hook
wfRunHooks( 'WantedPages::getQueryInfo', array( &$this, &$query ) );
+
return $query;
}
diff --git a/includes/specials/SpecialWantedtemplates.php b/includes/specials/SpecialWantedtemplates.php
index d13fa031..a4b9dd84 100644
--- a/includes/specials/SpecialWantedtemplates.php
+++ b/includes/specials/SpecialWantedtemplates.php
@@ -32,7 +32,6 @@
* @ingroup SpecialPage
*/
class WantedTemplatesPage extends WantedQueryPage {
-
function __construct( $name = 'Wantedtemplates' ) {
parent::__construct( $name );
}
@@ -40,16 +39,19 @@ class WantedTemplatesPage extends WantedQueryPage {
function getQueryInfo() {
return array(
'tables' => array( 'templatelinks', 'page' ),
- 'fields' => array( 'namespace' => 'tl_namespace',
- 'title' => 'tl_title',
- 'value' => 'COUNT(*)' ),
- 'conds' => array( 'page_title IS NULL',
- 'tl_namespace' => NS_TEMPLATE ),
- 'options' => array(
- 'GROUP BY' => array( 'tl_namespace', 'tl_title' ) ),
+ 'fields' => array(
+ 'namespace' => 'tl_namespace',
+ 'title' => 'tl_title',
+ 'value' => 'COUNT(*)'
+ ),
+ 'conds' => array(
+ 'page_title IS NULL',
+ 'tl_namespace' => NS_TEMPLATE
+ ),
+ 'options' => array( 'GROUP BY' => array( 'tl_namespace', 'tl_title' ) ),
'join_conds' => array( 'page' => array( 'LEFT JOIN',
- array( 'page_namespace = tl_namespace',
- 'page_title = tl_title' ) ) )
+ array( 'page_namespace = tl_namespace',
+ 'page_title = tl_title' ) ) )
);
}
diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php
index 55dc6efd..8f2f86b9 100644
--- a/includes/specials/SpecialWatchlist.php
+++ b/includes/specials/SpecialWatchlist.php
@@ -18,192 +18,190 @@
* http://www.gnu.org/copyleft/gpl.html
*
* @file
- * @ingroup SpecialPage Watchlist
+ * @ingroup SpecialPage
*/
-class SpecialWatchlist extends SpecialPage {
- protected $customFilters;
- /**
- * Constructor
- */
+/**
+ * A special page that lists last changes made to the wiki,
+ * limited to user-defined list of titles.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialWatchlist extends ChangesListSpecialPage {
public function __construct( $page = 'Watchlist', $restriction = 'viewmywatchlist' ) {
parent::__construct( $page, $restriction );
}
/**
- * Execute
- * @param $par Parameter passed to the page
+ * Main execution point
+ *
+ * @param string $subpage
*/
- function execute( $par ) {
- global $wgRCShowWatchingUsers, $wgEnotifWatchlist, $wgShowUpdatedMarker;
+ function execute( $subpage ) {
+ // Anons don't get a watchlist
+ $this->requireLogin( 'watchlistanontext' );
- $user = $this->getUser();
$output = $this->getOutput();
+ $request = $this->getRequest();
+
+ $mode = SpecialEditWatchlist::getMode( $request, $subpage );
+ if ( $mode !== false ) {
+ if ( $mode === SpecialEditWatchlist::EDIT_RAW ) {
+ $title = SpecialPage::getTitleFor( 'EditWatchlist', 'raw' );
+ } elseif ( $mode === SpecialEditWatchlist::EDIT_CLEAR ) {
+ $title = SpecialPage::getTitleFor( 'EditWatchlist', 'clear' );
+ } else {
+ $title = SpecialPage::getTitleFor( 'EditWatchlist' );
+ }
+
+ $output->redirect( $title->getLocalURL() );
- # 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(),
- array(),
- array( 'returnto' => $this->getTitle()->getPrefixedText() )
- );
- $output->addHTML( $this->msg( 'watchlistanontext' )->rawParams( $llink )->parse() );
return;
}
- // Check permissions
$this->checkPermissions();
- // Add feed links
- $wlToken = $user->getTokenFromOption( 'watchlisttoken' );
- if ( $wlToken ) {
- $this->addFeedLinks( array( 'action' => 'feedwatchlist', 'allrev' => 'allrev',
- 'wlowner' => $user->getName(), 'wltoken' => $wlToken ) );
+ $user = $this->getUser();
+ $opts = $this->getOptions();
+
+ $config = $this->getConfig();
+ if ( ( $config->get( 'EnotifWatchlist' ) || $config->get( 'ShowUpdatedMarker' ) )
+ && $request->getVal( 'reset' )
+ && $request->wasPosted()
+ ) {
+ $user->clearAllNotifications();
+ $output->redirect( $this->getPageTitle()->getFullURL( $opts->getChangedValues() ) );
+
+ return;
}
- $this->setHeaders();
- $this->outputHeader();
+ parent::execute( $subpage );
+ }
- $output->addSubtitle( $this->msg( 'watchlistfor2', $user->getName()
- )->rawParams( SpecialEditWatchlist::buildTools( null ) ) );
+ /**
+ * Return an array of subpages beginning with $search that this special page will accept.
+ *
+ * @param string $search Prefix to search for
+ * @param int $limit Maximum number of results to return
+ * @return string[] Matching subpages
+ */
+ public function prefixSearchSubpages( $search, $limit = 10 ) {
+ // See also SpecialEditWatchlist::prefixSearchSubpages
+ return self::prefixSearchArray(
+ $search,
+ $limit,
+ array(
+ 'clear',
+ 'edit',
+ 'raw',
+ )
+ );
+ }
- $request = $this->getRequest();
+ /**
+ * Get a FormOptions object containing the default options
+ *
+ * @return FormOptions
+ */
+ public function getDefaultOptions() {
+ $opts = parent::getDefaultOptions();
+ $user = $this->getUser();
- $mode = SpecialEditWatchlist::getMode( $request, $par );
- if ( $mode !== false ) {
- # TODO: localise?
- switch ( $mode ) {
- case SpecialEditWatchlist::EDIT_CLEAR:
- $mode = 'clear';
- break;
- case SpecialEditWatchlist::EDIT_RAW:
- $mode = 'raw';
- break;
- default:
- $mode = null;
- }
- $title = SpecialPage::getTitleFor( 'EditWatchlist', $mode );
- $output->redirect( $title->getLocalURL() );
- return;
- }
+ $opts->add( 'days', $user->getOption( 'watchlistdays' ), FormOptions::FLOAT );
- $dbr = wfGetDB( DB_SLAVE, 'watchlist' );
+ $opts->add( 'hideminor', $user->getBoolOption( 'watchlisthideminor' ) );
+ $opts->add( 'hidebots', $user->getBoolOption( 'watchlisthidebots' ) );
+ $opts->add( 'hideanons', $user->getBoolOption( 'watchlisthideanons' ) );
+ $opts->add( 'hideliu', $user->getBoolOption( 'watchlisthideliu' ) );
+ $opts->add( 'hidepatrolled', $user->getBoolOption( 'watchlisthidepatrolled' ) );
+ $opts->add( 'hidemyself', $user->getBoolOption( 'watchlisthideown' ) );
- $nitems = $this->countItems( $dbr );
- if ( $nitems == 0 ) {
- $output->addWikiMsg( 'nowatchlist' );
- return;
- }
+ $opts->add( 'extended', $user->getBoolOption( 'extendwatchlist' ) );
- // @todo use FormOptions!
- $defaults = array(
- /* float */ 'days' => floatval( $user->getOption( 'watchlistdays' ) ),
- /* bool */ 'hideMinor' => (int)$user->getBoolOption( 'watchlisthideminor' ),
- /* bool */ 'hideBots' => (int)$user->getBoolOption( 'watchlisthidebots' ),
- /* bool */ 'hideAnons' => (int)$user->getBoolOption( 'watchlisthideanons' ),
- /* bool */ 'hideLiu' => (int)$user->getBoolOption( 'watchlisthideliu' ),
- /* bool */ 'hidePatrolled' => (int)$user->getBoolOption( 'watchlisthidepatrolled' ),
- /* bool */ 'hideOwn' => (int)$user->getBoolOption( 'watchlisthideown' ),
- /* bool */ 'extended' => (int)$user->getBoolOption( 'extendwatchlist' ),
- /* ? */ 'namespace' => '', //means all
- /* ? */ 'invert' => false,
- /* bool */ 'associated' => false,
- );
- $this->customFilters = array();
- wfRunHooks( 'SpecialWatchlistFilters', array( $this, &$this->customFilters ) );
- foreach ( $this->customFilters as $key => $params ) {
- $defaults[$key] = $params['default'];
- }
+ return $opts;
+ }
- # Extract variables from the request, falling back to user preferences or
- # other default values if these don't exist
- $values = array();
- $values['days'] = floatval( $request->getVal( 'days', $defaults['days'] ) );
- $values['hideMinor'] = (int)$request->getBool( 'hideMinor', $defaults['hideMinor'] );
- $values['hideBots'] = (int)$request->getBool( 'hideBots', $defaults['hideBots'] );
- $values['hideAnons'] = (int)$request->getBool( 'hideAnons', $defaults['hideAnons'] );
- $values['hideLiu'] = (int)$request->getBool( 'hideLiu', $defaults['hideLiu'] );
- $values['hideOwn'] = (int)$request->getBool( 'hideOwn', $defaults['hideOwn'] );
- $values['hidePatrolled'] = (int)$request->getBool( 'hidePatrolled', $defaults['hidePatrolled'] );
- $values['extended'] = (int)$request->getBool( 'extended', $defaults['extended'] );
- foreach ( $this->customFilters as $key => $params ) {
- $values[$key] = (int)$request->getBool( $key, $defaults[$key] );
+ /**
+ * Get custom show/hide filters
+ *
+ * @return array Map of filter URL param names to properties (msg/default)
+ */
+ protected function getCustomFilters() {
+ if ( $this->customFilters === null ) {
+ $this->customFilters = parent::getCustomFilters();
+ wfRunHooks( 'SpecialWatchlistFilters', array( $this, &$this->customFilters ), '1.23' );
}
- # Get namespace value, if supplied, and prepare a WHERE fragment
- $nameSpace = $request->getIntOrNull( 'namespace' );
- $invert = $request->getBool( 'invert' );
- $associated = $request->getBool( 'associated' );
- if ( !is_null( $nameSpace ) ) {
- $eq_op = $invert ? '!=' : '=';
- $bool_op = $invert ? 'AND' : 'OR';
- $nameSpace = intval( $nameSpace ); // paranioa
- if ( !$associated ) {
- $nameSpaceClause = "rc_namespace $eq_op $nameSpace";
- } else {
- $associatedNS = MWNamespace::getAssociated( $nameSpace );
- $nameSpaceClause =
- "rc_namespace $eq_op $nameSpace " .
- $bool_op .
- " rc_namespace $eq_op $associatedNS";
+ return $this->customFilters;
+ }
+
+ /**
+ * Fetch values for a FormOptions object from the WebRequest associated with this instance.
+ *
+ * Maps old pre-1.23 request parameters Watchlist used to use (different from Recentchanges' ones)
+ * to the current ones.
+ *
+ * @param FormOptions $opts
+ * @return FormOptions
+ */
+ protected function fetchOptionsFromRequest( $opts ) {
+ static $compatibilityMap = array(
+ 'hideMinor' => 'hideminor',
+ 'hideBots' => 'hidebots',
+ 'hideAnons' => 'hideanons',
+ 'hideLiu' => 'hideliu',
+ 'hidePatrolled' => 'hidepatrolled',
+ 'hideOwn' => 'hidemyself',
+ );
+
+ $params = $this->getRequest()->getValues();
+ foreach ( $compatibilityMap as $from => $to ) {
+ if ( isset( $params[$from] ) ) {
+ $params[$to] = $params[$from];
+ unset( $params[$from] );
}
- } else {
- $nameSpace = '';
- $nameSpaceClause = '';
- }
- $values['namespace'] = $nameSpace;
- $values['invert'] = $invert;
- $values['associated'] = $associated;
-
- // Dump everything here
- $nondefaults = array();
- foreach ( $defaults as $name => $defValue ) {
- wfAppendToArrayIfNotDefault( $name, $values[$name], $defaults, $nondefaults );
}
- if ( ( $wgEnotifWatchlist || $wgShowUpdatedMarker ) && $request->getVal( 'reset' ) &&
- $request->wasPosted() )
- {
- $user->clearAllNotifications();
- $output->redirect( $this->getTitle()->getFullURL( $nondefaults ) );
- return;
- }
+ // Not the prettiest way to achieve this… FormOptions internally depends on data sanitization
+ // methods defined on WebRequest and removing this dependency would cause some code duplication.
+ $request = new DerivativeRequest( $this->getRequest(), $params );
+ $opts->fetchValuesFromRequest( $request );
- # Possible where conditions
- $conds = array();
+ return $opts;
+ }
- if ( $values['days'] > 0 ) {
- $conds[] = 'rc_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( time() - intval( $values['days'] * 86400 ) ) );
- }
+ /**
+ * Return an array of conditions depending of options set in $opts
+ *
+ * @param FormOptions $opts
+ * @return array
+ */
+ public function buildMainQueryConds( FormOptions $opts ) {
+ $dbr = $this->getDB();
+ $conds = parent::buildMainQueryConds( $opts );
- # Toggles
- if ( $values['hideOwn'] ) {
- $conds[] = 'rc_user != ' . $user->getId();
- }
- if ( $values['hideBots'] ) {
- $conds[] = 'rc_bot = 0';
- }
- if ( $values['hideMinor'] ) {
- $conds[] = 'rc_minor = 0';
- }
- if ( $values['hideLiu'] ) {
- $conds[] = 'rc_user = 0';
- }
- if ( $values['hideAnons'] ) {
- $conds[] = 'rc_user != 0';
- }
- if ( $user->useRCPatrol() && $values['hidePatrolled'] ) {
- $conds[] = 'rc_patrolled != 1';
- }
- if ( $nameSpaceClause ) {
- $conds[] = $nameSpaceClause;
+ // Calculate cutoff
+ if ( $opts['days'] > 0 ) {
+ $conds[] = 'rc_timestamp > ' .
+ $dbr->addQuotes( $dbr->timestamp( time() - intval( $opts['days'] * 86400 ) ) );
}
+ return $conds;
+ }
+
+ /**
+ * Process the query
+ *
+ * @param array $conds
+ * @param FormOptions $opts
+ * @return bool|ResultWrapper Result or false (for Recentchangeslinked only)
+ */
+ public function doMainQuery( $conds, $opts ) {
+ $dbr = $this->getDB();
+ $user = $this->getUser();
+
# Toggle watchlist content (all recent edits or just the latest)
- if ( $values['extended'] ) {
+ if ( $opts['extended'] ) {
$limitWatchlist = $user->getIntOption( 'wllimit' );
$usePage = false;
} else {
@@ -211,10 +209,6 @@ class SpecialWatchlist extends SpecialPage {
$nonRevisionTypes = array( RC_LOG );
wfRunHooks( 'SpecialWatchlistGetNonRevisionTypes', array( &$nonRevisionTypes ) );
if ( $nonRevisionTypes ) {
- if ( count( $nonRevisionTypes ) === 1 ) {
- // if only one use an equality instead of IN condition
- $nonRevisionTypes = reset( $nonRevisionTypes );
- }
$conds[] = $dbr->makeList(
array(
'rc_this_oldid=page_latest',
@@ -227,51 +221,9 @@ class SpecialWatchlist extends SpecialPage {
$usePage = true;
}
- # Show a message about slave lag, if applicable
- $lag = wfGetLB()->safeGetLag( $dbr );
- if ( $lag > 0 ) {
- $output->showLagWarning( $lag );
- }
-
- # Create output
- $form = '';
-
- # Show watchlist header
- $form .= "<p>";
- $form .= $this->msg( 'watchlist-details' )->numParams( $nitems )->parse() . "\n";
- if ( $wgEnotifWatchlist && $user->getOption( 'enotifwatchlistpages' ) ) {
- $form .= $this->msg( 'wlheader-enotif' )->parse() . "\n";
- }
- if ( $wgShowUpdatedMarker ) {
- $form .= $this->msg( 'wlheader-showupdated' )->parse() . "\n";
- }
- $form .= "</p>";
-
- if ( $wgShowUpdatedMarker ) {
- $form .= Xml::openElement( 'form', array( 'method' => 'post',
- 'action' => $this->getTitle()->getLocalURL(),
- 'id' => 'mw-watchlist-resetbutton' ) ) . "\n" .
- Xml::submitButton( $this->msg( 'enotif_reset' )->text(), array( 'name' => 'dummy' ) ) . "\n" .
- Html::hidden( 'reset', 'all' ) . "\n";
- foreach ( $nondefaults as $key => $value ) {
- $form .= Html::hidden( $key, $value ) . "\n";
- }
- $form .= Xml::closeElement( 'form' ) . "\n";
- }
-
- $form .= Xml::openElement( 'form', array(
- 'method' => 'post',
- 'action' => $this->getTitle()->getLocalURL(),
- 'id' => 'mw-watchlist-form'
- ) );
- $form .= Xml::fieldset(
- $this->msg( 'watchlist-options' )->text(),
- false,
- array( 'id' => 'mw-watchlist-options' )
- );
-
$tables = array( 'recentchanges', 'watchlist' );
$fields = RecentChange::selectFields();
+ $query_options = array( 'ORDER BY' => 'rc_timestamp DESC' );
$join_conds = array(
'watchlist' => array(
'INNER JOIN',
@@ -282,12 +234,12 @@ class SpecialWatchlist extends SpecialPage {
),
),
);
- $options = array( 'ORDER BY' => 'rc_timestamp DESC' );
- if ( $wgShowUpdatedMarker ) {
+
+ if ( $this->getConfig()->get( 'ShowUpdatedMarker' ) ) {
$fields[] = 'wl_notificationtimestamp';
}
if ( $limitWatchlist ) {
- $options['LIMIT'] = $limitWatchlist;
+ $query_options['LIMIT'] = $limitWatchlist;
}
$rollbacker = $user->isAllowed( 'rollback' );
@@ -303,7 +255,7 @@ class SpecialWatchlist extends SpecialPage {
// the necessary rights.
if ( !$user->isAllowed( 'deletedhistory' ) ) {
$bitmask = LogPage::DELETED_ACTION;
- } elseif ( !$user->isAllowed( 'suppressrevision' ) ) {
+ } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
$bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
} else {
$bitmask = 0;
@@ -315,44 +267,176 @@ class SpecialWatchlist extends SpecialPage {
), LIST_OR );
}
- ChangeTags::modifyDisplayQuery( $tables, $fields, $conds, $join_conds, $options, '' );
- wfRunHooks( 'SpecialWatchlistQuery', array( &$conds, &$tables, &$join_conds, &$fields, $values ) );
+ ChangeTags::modifyDisplayQuery(
+ $tables,
+ $fields,
+ $conds,
+ $join_conds,
+ $query_options,
+ ''
+ );
+
+ $this->runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds, $opts );
+
+ return $dbr->select(
+ $tables,
+ $fields,
+ $conds,
+ __METHOD__,
+ $query_options,
+ $join_conds
+ );
+ }
+
+ protected function runMainQueryHook( &$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts ) {
+ return parent::runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds, $opts )
+ && wfRunHooks(
+ 'SpecialWatchlistQuery',
+ array( &$conds, &$tables, &$join_conds, &$fields, $opts ),
+ '1.23'
+ );
+ }
+
+ /**
+ * Return a DatabaseBase object for reading
+ *
+ * @return DatabaseBase
+ */
+ protected function getDB() {
+ return wfGetDB( DB_SLAVE, 'watchlist' );
+ }
+
+ /**
+ * Output feed links.
+ */
+ public function outputFeedLinks() {
+ $user = $this->getUser();
+ $wlToken = $user->getTokenFromOption( 'watchlisttoken' );
+ if ( $wlToken ) {
+ $this->addFeedLinks( array(
+ 'action' => 'feedwatchlist',
+ 'allrev' => 1,
+ 'wlowner' => $user->getName(),
+ 'wltoken' => $wlToken,
+ ) );
+ }
+ }
+
+ /**
+ * Build and output the actual changes list.
+ *
+ * @param ResultWrapper $rows Database rows
+ * @param FormOptions $opts
+ */
+ public function outputChangesList( $rows, $opts ) {
+ $dbr = $this->getDB();
+ $user = $this->getUser();
+ $output = $this->getOutput();
+
+ # Show a message about slave lag, if applicable
+ $lag = wfGetLB()->safeGetLag( $dbr );
+ if ( $lag > 0 ) {
+ $output->showLagWarning( $lag );
+ }
+
+ # If no rows to display, show message before try to render the list
+ if ( $rows->numRows() == 0 ) {
+ $output->wrapWikiMsg(
+ "<div class='mw-changeslist-empty'>\n$1\n</div>", 'recentchanges-noresult'
+ );
+ return;
+ }
+
+ $dbr->dataSeek( $rows, 0 );
+
+ $list = ChangesList::newFromContext( $this->getContext() );
+ $list->setWatchlistDivs();
+ $list->initChangesListRows( $rows );
+ $dbr->dataSeek( $rows, 0 );
+
+ $s = $list->beginRecentChangesList();
+ $counter = 1;
+ foreach ( $rows as $obj ) {
+ # Make RC entry
+ $rc = RecentChange::newFromRow( $obj );
+ $rc->counter = $counter++;
+
+ if ( $this->getConfig()->get( 'ShowUpdatedMarker' ) ) {
+ $updated = $obj->wl_notificationtimestamp;
+ } else {
+ $updated = false;
+ }
+
+ if ( $this->getConfig()->get( 'RCShowWatchingUsers' ) && $user->getOption( 'shownumberswatching' ) ) {
+ $rc->numberofWatchingusers = $dbr->selectField( 'watchlist',
+ 'COUNT(*)',
+ array(
+ 'wl_namespace' => $obj->rc_namespace,
+ 'wl_title' => $obj->rc_title,
+ ),
+ __METHOD__ );
+ } else {
+ $rc->numberofWatchingusers = 0;
+ }
+
+ $changeLine = $list->recentChangesLine( $rc, $updated, $counter );
+ if ( $changeLine !== false ) {
+ $s .= $changeLine;
+ }
+ }
+ $s .= $list->endRecentChangesList();
+
+ $output->addHTML( $s );
+ }
+
+ /**
+ * Set the text to be displayed above the changes
+ *
+ * @param FormOptions $opts
+ * @param int $numRows Number of rows in the result to show after this header
+ */
+ public function doHeader( $opts, $numRows ) {
+ $user = $this->getUser();
- $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $join_conds );
- $numRows = $res->numRows();
+ $this->getOutput()->addSubtitle(
+ $this->msg( 'watchlistfor2', $user->getName() )
+ ->rawParams( SpecialEditWatchlist::buildTools( null ) )
+ );
- /* Start bottom header */
+ $this->setTopText( $opts );
$lang = $this->getLanguage();
$wlInfo = '';
- if ( $values['days'] > 0 ) {
+ if ( $opts['days'] > 0 ) {
$timestamp = wfTimestampNow();
- $wlInfo = $this->msg( 'wlnote' )->numParams( $numRows, round( $values['days'] * 24 ) )->params(
- $lang->userDate( $timestamp, $user ), $lang->userTime( $timestamp, $user ) )->parse() . "<br />\n";
+ $wlInfo = $this->msg( 'wlnote' )->numParams( $numRows, round( $opts['days'] * 24 ) )->params(
+ $lang->userDate( $timestamp, $user ), $lang->userTime( $timestamp, $user )
+ )->parse() . "<br />\n";
}
- $cutofflinks = $this->cutoffLinks( $values['days'], $nondefaults ) . "<br />\n";
+ $nondefaults = $opts->getChangedValues();
+ $cutofflinks = $this->cutoffLinks( $opts['days'], $nondefaults ) . "<br />\n";
# Spit out some control panel links
$filters = array(
- 'hideMinor' => 'rcshowhideminor',
- 'hideBots' => 'rcshowhidebots',
- 'hideAnons' => 'rcshowhideanons',
- 'hideLiu' => 'rcshowhideliu',
- 'hideOwn' => 'rcshowhidemine',
- 'hidePatrolled' => 'rcshowhidepatr'
+ 'hideminor' => 'rcshowhideminor',
+ 'hidebots' => 'rcshowhidebots',
+ 'hideanons' => 'rcshowhideanons',
+ 'hideliu' => 'rcshowhideliu',
+ 'hidemyself' => 'rcshowhidemine',
+ 'hidepatrolled' => 'rcshowhidepatr'
);
- foreach ( $this->customFilters as $key => $params ) {
+ foreach ( $this->getCustomFilters() as $key => $params ) {
$filters[$key] = $params['msg'];
}
// Disable some if needed
if ( !$user->useNPPatrol() ) {
- unset( $filters['hidePatrolled'] );
+ unset( $filters['hidepatrolled'] );
}
$links = array();
foreach ( $filters as $name => $msg ) {
- $links[] = $this->showHideLink( $nondefaults, $msg, $name, $values[$name] );
+ $links[] = $this->showHideLink( $nondefaults, $msg, $name, $opts[$name] );
}
$hiddenFields = $nondefaults;
@@ -360,6 +444,9 @@ class SpecialWatchlist extends SpecialPage {
unset( $hiddenFields['invert'] );
unset( $hiddenFields['associated'] );
+ # Create output
+ $form = '';
+
# Namespace filter and put the whole form together.
$form .= $wlInfo;
$form .= $cutofflinks;
@@ -367,7 +454,7 @@ class SpecialWatchlist extends SpecialPage {
$form .= "<hr />\n<p>";
$form .= Html::namespaceSelector(
array(
- 'selected' => $nameSpace,
+ 'selected' => $opts['namespace'],
'all' => '',
'label' => $this->msg( 'namespace' )->text()
), array(
@@ -380,14 +467,14 @@ class SpecialWatchlist extends SpecialPage {
$this->msg( 'invert' )->text(),
'invert',
'nsinvert',
- $invert,
+ $opts['invert'],
array( 'title' => $this->msg( 'tooltip-invert' )->text() )
) . '&#160;';
$form .= Xml::checkLabel(
$this->msg( 'namespace_association' )->text(),
'associated',
- 'associated',
- $associated,
+ 'nsassociated',
+ $opts['associated'],
array( 'title' => $this->msg( 'tooltip-namespace_association' )->text() )
) . '&#160;';
$form .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . "</p>\n";
@@ -396,82 +483,77 @@ class SpecialWatchlist extends SpecialPage {
}
$form .= Xml::closeElement( 'fieldset' ) . "\n";
$form .= Xml::closeElement( 'form' ) . "\n";
- $output->addHTML( $form );
-
- # If there's nothing to show, stop here
- if ( $numRows == 0 ) {
- $output->wrapWikiMsg(
- "<div class='mw-changeslist-empty'>\n$1\n</div>", 'recentchanges-noresult'
- );
- return;
- }
+ $this->getOutput()->addHTML( $form );
- /* End bottom header */
-
- /* Do link batch query */
- $linkBatch = new LinkBatch;
- foreach ( $res as $row ) {
- $userNameUnderscored = str_replace( ' ', '_', $row->rc_user_text );
- if ( $row->rc_user != 0 ) {
- $linkBatch->add( NS_USER, $userNameUnderscored );
- }
- $linkBatch->add( NS_USER_TALK, $userNameUnderscored );
+ $this->setBottomText( $opts );
+ }
- $linkBatch->add( $row->rc_namespace, $row->rc_title );
- }
- $linkBatch->execute();
- $dbr->dataSeek( $res, 0 );
+ function setTopText( FormOptions $opts ) {
+ $nondefaults = $opts->getChangedValues();
+ $form = "";
+ $user = $this->getUser();
- $list = ChangesList::newFromContext( $this->getContext() );
- $list->setWatchlistDivs();
+ $dbr = $this->getDB();
+ $numItems = $this->countItems( $dbr );
+ $showUpdatedMarker = $this->getConfig()->get( 'ShowUpdatedMarker' );
- $s = $list->beginRecentChangesList();
- $counter = 1;
- foreach ( $res as $obj ) {
- # Make RC entry
- $rc = RecentChange::newFromRow( $obj );
- $rc->counter = $counter++;
-
- if ( $wgShowUpdatedMarker ) {
- $updated = $obj->wl_notificationtimestamp;
- } else {
- $updated = false;
+ // Show watchlist header
+ $form .= "<p>";
+ if ( $numItems == 0 ) {
+ $form .= $this->msg( 'nowatchlist' )->parse() . "\n";
+ } else {
+ $form .= $this->msg( 'watchlist-details' )->numParams( $numItems )->parse() . "\n";
+ if ( $this->getConfig()->get( 'EnotifWatchlist' ) && $user->getOption( 'enotifwatchlistpages' ) ) {
+ $form .= $this->msg( 'wlheader-enotif' )->parse() . "\n";
}
-
- if ( $wgRCShowWatchingUsers && $user->getOption( 'shownumberswatching' ) ) {
- $rc->numberofWatchingusers = $dbr->selectField( 'watchlist',
- 'COUNT(*)',
- array(
- 'wl_namespace' => $obj->rc_namespace,
- 'wl_title' => $obj->rc_title,
- ),
- __METHOD__ );
- } else {
- $rc->numberofWatchingusers = 0;
+ if ( $showUpdatedMarker ) {
+ $form .= $this->msg( 'wlheader-showupdated' )->parse() . "\n";
}
+ }
+ $form .= "</p>";
- $changeLine = $list->recentChangesLine( $rc, $updated, $counter );
- if ( $changeLine !== false ) {
- $s .= $changeLine;
+ if ( $numItems > 0 && $showUpdatedMarker ) {
+ $form .= Xml::openElement( 'form', array( 'method' => 'post',
+ 'action' => $this->getPageTitle()->getLocalURL(),
+ 'id' => 'mw-watchlist-resetbutton' ) ) . "\n" .
+ Xml::submitButton( $this->msg( 'enotif_reset' )->text(), array( 'name' => 'dummy' ) ) . "\n" .
+ Html::hidden( 'reset', 'all' ) . "\n";
+ foreach ( $nondefaults as $key => $value ) {
+ $form .= Html::hidden( $key, $value ) . "\n";
}
+ $form .= Xml::closeElement( 'form' ) . "\n";
}
- $s .= $list->endRecentChangesList();
- $output->addHTML( $s );
+ $form .= Xml::openElement( 'form', array(
+ 'method' => 'post',
+ 'action' => $this->getPageTitle()->getLocalURL(),
+ 'id' => 'mw-watchlist-form'
+ ) );
+ $form .= Xml::fieldset(
+ $this->msg( 'watchlist-options' )->text(),
+ false,
+ array( 'id' => 'mw-watchlist-options' )
+ );
+
+ $form .= SpecialRecentChanges::makeLegend( $this->getContext() );
+
+ $this->getOutput()->addHTML( $form );
}
protected function showHideLink( $options, $message, $name, $value ) {
$label = $this->msg( $value ? 'show' : 'hide' )->escaped();
$options[$name] = 1 - (int)$value;
- return $this->msg( $message )->rawParams( Linker::linkKnown( $this->getTitle(), $label, array(), $options ) )->escaped();
+ return $this->msg( $message )
+ ->rawParams( Linker::linkKnown( $this->getPageTitle(), $label, array(), $options ) )
+ ->escaped();
}
protected function hoursLink( $h, $options = array() ) {
$options['days'] = ( $h / 24.0 );
return Linker::linkKnown(
- $this->getTitle(),
+ $this->getPageTitle(),
$this->getLanguage()->formatNum( $h ),
array(),
$options
@@ -480,10 +562,11 @@ class SpecialWatchlist extends SpecialPage {
protected function daysLink( $d, $options = array() ) {
$options['days'] = $d;
- $message = ( $d ? $this->getLanguage()->formatNum( $d ) : $this->msg( 'watchlistall2' )->escaped() );
+ $message = $d ? $this->getLanguage()->formatNum( $d )
+ : $this->msg( 'watchlistall2' )->escaped();
return Linker::linkKnown(
- $this->getTitle(),
+ $this->getPageTitle(),
$message,
array(),
$options
@@ -508,6 +591,7 @@ class SpecialWatchlist extends SpecialPage {
foreach ( $days as $d ) {
$days[$i++] = $this->daysLink( $d, $options );
}
+
return $this->msg( 'wlshowlast' )->rawParams(
$this->getLanguage()->pipeList( $hours ),
$this->getLanguage()->pipeList( $days ),
@@ -518,19 +602,15 @@ class SpecialWatchlist extends SpecialPage {
* Count the number of items on a user's watchlist
*
* @param DatabaseBase $dbr A database connection
- * @return Integer
+ * @return int
*/
protected function countItems( $dbr ) {
# Fetch the raw count
- $res = $dbr->select( 'watchlist', array( 'count' => 'COUNT(*)' ),
+ $rows = $dbr->select( 'watchlist', array( 'count' => 'COUNT(*)' ),
array( 'wl_user' => $this->getUser()->getId() ), __METHOD__ );
- $row = $dbr->fetchObject( $res );
+ $row = $dbr->fetchObject( $rows );
$count = $row->count;
return floor( $count / 2 );
}
-
- protected function getGroupName() {
- return 'changes';
- }
}
diff --git a/includes/specials/SpecialWhatlinkshere.php b/includes/specials/SpecialWhatlinkshere.php
index 05c7dd5f..7dc6da1f 100644
--- a/includes/specials/SpecialWhatlinkshere.php
+++ b/includes/specials/SpecialWhatlinkshere.php
@@ -26,18 +26,13 @@
*
* @ingroup SpecialPage
*/
-class SpecialWhatLinksHere extends SpecialPage {
-
- /**
- * @var FormOptions
- */
+class SpecialWhatLinksHere extends IncludableSpecialPage {
+ /** @var FormOptions */
protected $opts;
protected $selfTitle;
- /**
- * @var Title
- */
+ /** @var Title */
protected $target;
protected $limits = array( 20, 50, 100, 250, 500 );
@@ -47,7 +42,6 @@ class SpecialWhatLinksHere extends SpecialPage {
}
function execute( $par ) {
- global $wgQueryPageDefaultLimit;
$out = $this->getOutput();
$this->setHeaders();
@@ -57,7 +51,7 @@ class SpecialWhatLinksHere extends SpecialPage {
$opts->add( 'target', '' );
$opts->add( 'namespace', '', FormOptions::INTNULL );
- $opts->add( 'limit', $wgQueryPageDefaultLimit );
+ $opts->add( 'limit', $this->getConfig()->get( 'QueryPageDefaultLimit' ) );
$opts->add( 'from', 0 );
$opts->add( 'back', 0 );
$opts->add( 'hideredirs', false );
@@ -69,7 +63,7 @@ class SpecialWhatLinksHere extends SpecialPage {
$opts->validateIntBounds( 'limit', 0, 5000 );
// Give precedence to subpage syntax
- if ( isset( $par ) ) {
+ if ( $par !== null ) {
$opts->setValue( 'target', $par );
}
@@ -79,18 +73,23 @@ class SpecialWhatLinksHere extends SpecialPage {
$this->target = Title::newFromURL( $opts->getValue( 'target' ) );
if ( !$this->target ) {
$out->addHTML( $this->whatlinkshereForm() );
+
return;
}
$this->getSkin()->setRelevantTitle( $this->target );
- $this->selfTitle = $this->getTitle( $this->target->getPrefixedDBkey() );
+ $this->selfTitle = $this->getPageTitle( $this->target->getPrefixedDBkey() );
$out->setPageTitle( $this->msg( 'whatlinkshere-title', $this->target->getPrefixedText() ) );
$out->addBacklinkSubtitle( $this->target );
-
- $this->showIndirectLinks( 0, $this->target, $opts->getValue( 'limit' ),
- $opts->getValue( 'from' ), $opts->getValue( 'back' ) );
+ $this->showIndirectLinks(
+ 0,
+ $this->target,
+ $opts->getValue( 'limit' ),
+ $opts->getValue( 'from' ),
+ $opts->getValue( 'back' )
+ );
}
/**
@@ -101,10 +100,8 @@ class SpecialWhatLinksHere extends SpecialPage {
* @param int $back Display from this article ID at backwards scrolling (default: 0)
*/
function showIndirectLinks( $level, $target, $limit, $from = 0, $back = 0 ) {
- global $wgMaxRedirectLinksRetrieved;
$out = $this->getOutput();
$dbr = wfGetDB( DB_SLAVE );
- $options = array();
$hidelinks = $this->opts->getValue( 'hidelinks' );
$hideredirs = $this->opts->getValue( 'hideredirs' );
@@ -113,95 +110,108 @@ class SpecialWhatLinksHere extends SpecialPage {
$fetchlinks = ( !$hidelinks || !$hideredirs );
- // Make the query
- $plConds = array(
- 'page_id=pl_from',
+ // Build query conds in concert for all three tables...
+ $conds['pagelinks'] = array(
'pl_namespace' => $target->getNamespace(),
'pl_title' => $target->getDBkey(),
);
- if ( $hideredirs ) {
- $plConds['rd_from'] = null;
- } elseif ( $hidelinks ) {
- $plConds[] = 'rd_from is NOT NULL';
- }
-
- $tlConds = array(
- 'page_id=tl_from',
+ $conds['templatelinks'] = array(
'tl_namespace' => $target->getNamespace(),
'tl_title' => $target->getDBkey(),
);
-
- $ilConds = array(
- 'page_id=il_from',
+ $conds['imagelinks'] = array(
'il_to' => $target->getDBkey(),
);
+ $useLinkNamespaceDBFields = $this->getConfig()->get( 'UseLinkNamespaceDBFields' );
$namespace = $this->opts->getValue( 'namespace' );
if ( is_int( $namespace ) ) {
- $plConds['page_namespace'] = $namespace;
- $tlConds['page_namespace'] = $namespace;
- $ilConds['page_namespace'] = $namespace;
+ if ( $useLinkNamespaceDBFields ) {
+ $conds['pagelinks']['pl_from_namespace'] = $namespace;
+ $conds['templatelinks']['tl_from_namespace'] = $namespace;
+ $conds['imagelinks']['il_from_namespace'] = $namespace;
+ } else {
+ $conds['pagelinks']['page_namespace'] = $namespace;
+ $conds['templatelinks']['page_namespace'] = $namespace;
+ $conds['imagelinks']['page_namespace'] = $namespace;
+ }
}
if ( $from ) {
- $tlConds[] = "tl_from >= $from";
- $plConds[] = "pl_from >= $from";
- $ilConds[] = "il_from >= $from";
+ $conds['templatelinks'][] = "tl_from >= $from";
+ $conds['pagelinks'][] = "pl_from >= $from";
+ $conds['imagelinks'][] = "il_from >= $from";
}
- // Read an extra row as an at-end check
- $queryLimit = $limit + 1;
-
- // Enforce join order, sometimes namespace selector may
- // trigger filesorts which are far less efficient than scanning many entries
- $options[] = 'STRAIGHT_JOIN';
-
- $options['LIMIT'] = $queryLimit;
- $fields = array( 'page_id', 'page_namespace', 'page_title', 'rd_from' );
+ if ( $hideredirs ) {
+ $conds['pagelinks']['rd_from'] = null;
+ } elseif ( $hidelinks ) {
+ $conds['pagelinks'][] = 'rd_from is NOT NULL';
+ }
- $joinConds = array( 'redirect' => array( 'LEFT JOIN', array(
- 'rd_from = page_id',
- 'rd_namespace' => $target->getNamespace(),
- 'rd_title' => $target->getDBkey(),
- 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL'
- )));
+ $queryFunc = function ( $dbr, $table, $fromCol ) use ( $conds, $target, $limit, $useLinkNamespaceDBFields ) {
+ // Read an extra row as an at-end check
+ $queryLimit = $limit + 1;
+ $on = array(
+ "rd_from = $fromCol",
+ 'rd_title' => $target->getDBkey(),
+ 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL'
+ );
+ if ( $useLinkNamespaceDBFields ) { // migration check
+ $on['rd_namespace'] = $target->getNamespace();
+ }
+ // Inner LIMIT is 2X in case of stale backlinks with wrong namespaces
+ $subQuery = $dbr->selectSqlText(
+ array( $table, 'page', 'redirect' ),
+ array( $fromCol, 'rd_from' ),
+ $conds[$table],
+ __CLASS__ . '::showIndirectLinks',
+ array( 'ORDER BY' => $fromCol, 'LIMIT' => 2 * $queryLimit ),
+ array(
+ 'page' => array( 'INNER JOIN', "$fromCol = page_id" ),
+ 'redirect' => array( 'LEFT JOIN', $on )
+ )
+ );
+ return $dbr->select(
+ array( 'page', 'temp_backlink_range' => "($subQuery)" ),
+ array( 'page_id', 'page_namespace', 'page_title', 'rd_from' ),
+ array(),
+ __CLASS__ . '::showIndirectLinks',
+ array( 'ORDER BY' => 'page_id', 'LIMIT' => $queryLimit ),
+ array( 'page' => array( 'INNER JOIN', "$fromCol = page_id" ) )
+ );
+ };
if ( $fetchlinks ) {
- $options['ORDER BY'] = 'pl_from';
- $plRes = $dbr->select( array( 'pagelinks', 'page', 'redirect' ), $fields,
- $plConds, __METHOD__, $options,
- $joinConds
- );
+ $plRes = $queryFunc( $dbr, 'pagelinks', 'pl_from' );
}
if ( !$hidetrans ) {
- $options['ORDER BY'] = 'tl_from';
- $tlRes = $dbr->select( array( 'templatelinks', 'page', 'redirect' ), $fields,
- $tlConds, __METHOD__, $options,
- $joinConds
- );
+ $tlRes = $queryFunc( $dbr, 'templatelinks', 'tl_from' );
}
if ( !$hideimages ) {
- $options['ORDER BY'] = 'il_from';
- $ilRes = $dbr->select( array( 'imagelinks', 'page', 'redirect' ), $fields,
- $ilConds, __METHOD__, $options,
- $joinConds
- );
+ $ilRes = $queryFunc( $dbr, 'imagelinks', 'il_from' );
}
- if ( ( !$fetchlinks || !$plRes->numRows() ) && ( $hidetrans || !$tlRes->numRows() ) && ( $hideimages || !$ilRes->numRows() ) ) {
+ if ( ( !$fetchlinks || !$plRes->numRows() )
+ && ( $hidetrans || !$tlRes->numRows() )
+ && ( $hideimages || !$ilRes->numRows() )
+ ) {
if ( 0 == $level ) {
- $out->addHTML( $this->whatlinkshereForm() );
-
- // Show filters only if there are links
- if ( $hidelinks || $hidetrans || $hideredirs || $hideimages ) {
- $out->addHTML( $this->getFilterPanel() );
+ if ( !$this->including() ) {
+ $out->addHTML( $this->whatlinkshereForm() );
+
+ // Show filters only if there are links
+ if ( $hidelinks || $hidetrans || $hideredirs || $hideimages ) {
+ $out->addHTML( $this->getFilterPanel() );
+ }
+ $errMsg = is_int( $namespace ) ? 'nolinkshere-ns' : 'nolinkshere';
+ $out->addWikiMsg( $errMsg, $this->target->getPrefixedText() );
+ $out->setStatusCode( 404 );
}
-
- $errMsg = is_int( $namespace ) ? 'nolinkshere-ns' : 'nolinkshere';
- $out->addWikiMsg( $errMsg, $this->target->getPrefixedText() );
}
+
return;
}
@@ -250,31 +260,34 @@ class SpecialWhatLinksHere extends SpecialPage {
$prevId = $from;
if ( $level == 0 ) {
- $out->addHTML( $this->whatlinkshereForm() );
- $out->addHTML( $this->getFilterPanel() );
- $out->addWikiMsg( 'linkshere', $this->target->getPrefixedText() );
+ if ( !$this->including() ) {
+ $out->addHTML( $this->whatlinkshereForm() );
+ $out->addHTML( $this->getFilterPanel() );
+ $out->addWikiMsg( 'linkshere', $this->target->getPrefixedText() );
- $prevnext = $this->getPrevNext( $prevId, $nextId );
- $out->addHTML( $prevnext );
+ $prevnext = $this->getPrevNext( $prevId, $nextId );
+ $out->addHTML( $prevnext );
+ }
}
-
$out->addHTML( $this->listStart( $level ) );
foreach ( $rows as $row ) {
$nt = Title::makeTitle( $row->page_namespace, $row->page_title );
if ( $row->rd_from && $level < 2 ) {
- $out->addHTML( $this->listItem( $row, $nt, true ) );
- $this->showIndirectLinks( $level + 1, $nt, $wgMaxRedirectLinksRetrieved );
+ $out->addHTML( $this->listItem( $row, $nt, $target, true ) );
+ $this->showIndirectLinks( $level + 1, $nt, $this->getConfig()->get( 'MaxRedirectLinksRetrieved' ) );
$out->addHTML( Xml::closeElement( 'li' ) );
} else {
- $out->addHTML( $this->listItem( $row, $nt ) );
+ $out->addHTML( $this->listItem( $row, $nt, $target ) );
}
}
$out->addHTML( $this->listEnd() );
if ( $level == 0 ) {
- $out->addHTML( $prevnext );
+ if ( !$this->including() ) {
+ $out->addHTML( $prevnext );
+ }
}
}
@@ -282,7 +295,7 @@ class SpecialWhatLinksHere extends SpecialPage {
return Xml::openElement( 'ul', ( $level ? array() : array( 'id' => 'mw-whatlinkshere-list' ) ) );
}
- protected function listItem( $row, $nt, $notClose = false ) {
+ protected function listItem( $row, $nt, $target, $notClose = false ) {
$dirmark = $this->getLanguage()->getDirMark();
# local message cache
@@ -322,13 +335,19 @@ class SpecialWhatLinksHere extends SpecialPage {
$props[] = $msgcache['isimage'];
}
+ wfRunHooks( 'WhatLinksHereProps', array( $row, $nt, $target, &$props ) );
+
if ( count( $props ) ) {
- $propsText = $this->msg( 'parentheses' )->rawParams( implode( $msgcache['semicolon-separator'], $props ) )->escaped();
+ $propsText = $this->msg( 'parentheses' )
+ ->rawParams( implode( $msgcache['semicolon-separator'], $props ) )->escaped();
}
# Space for utilities links, with a what-links-here link provided
$wlhLink = $this->wlhLink( $nt, $msgcache['whatlinkshere-links'] );
- $wlh = Xml::wrapClass( $this->msg( 'parentheses' )->rawParams( $wlhLink )->escaped(), 'mw-whatlinkshere-tools' );
+ $wlh = Xml::wrapClass(
+ $this->msg( 'parentheses' )->rawParams( $wlhLink )->escaped(),
+ 'mw-whatlinkshere-tools'
+ );
return $notClose ?
Xml::openElement( 'li' ) . "$link $propsText $dirmark $wlh\n" :
@@ -342,7 +361,7 @@ class SpecialWhatLinksHere extends SpecialPage {
protected function wlhLink( Title $target, $text ) {
static $title = null;
if ( $title === null ) {
- $title = $this->getTitle();
+ $title = $this->getPageTitle();
}
return Linker::linkKnown(
@@ -393,8 +412,6 @@ class SpecialWhatLinksHere extends SpecialPage {
}
function whatlinkshereForm() {
- global $wgScript;
-
// We get nicer value from the title object
$this->opts->consumeValue( 'target' );
// Reset these for new requests
@@ -404,10 +421,10 @@ class SpecialWhatLinksHere extends SpecialPage {
$namespace = $this->opts->consumeValue( 'namespace' );
# Build up the form
- $f = Xml::openElement( 'form', array( 'action' => $wgScript ) );
+ $f = Xml::openElement( 'form', array( 'action' => wfScript() ) );
# Values that should not be forgotten
- $f .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() );
+ $f .= Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() );
foreach ( $this->opts->getUnconsumedValues() as $name => $value ) {
$f .= Html::hidden( $name, $value );
}
@@ -416,7 +433,7 @@ class SpecialWhatLinksHere extends SpecialPage {
# Target input
$f .= Xml::inputLabel( $this->msg( 'whatlinkshere-page' )->text(), 'target',
- 'mw-whatlinkshere-target', 40, $target );
+ 'mw-whatlinkshere-target', 40, $target );
$f .= ' ';
@@ -462,7 +479,8 @@ class SpecialWhatLinksHere extends SpecialPage {
$types[] = 'hideimages';
}
- // Combined message keys: 'whatlinkshere-hideredirs', 'whatlinkshere-hidetrans', 'whatlinkshere-hidelinks', 'whatlinkshere-hideimages'
+ // Combined message keys: 'whatlinkshere-hideredirs', 'whatlinkshere-hidetrans',
+ // 'whatlinkshere-hidelinks', 'whatlinkshere-hideimages'
// To be sure they will be found by grep
foreach ( $types as $type ) {
$chosen = $this->opts->getValue( $type );
@@ -471,7 +489,11 @@ class SpecialWhatLinksHere extends SpecialPage {
$links[] = $this->msg( "whatlinkshere-{$type}" )->rawParams(
$this->makeSelfLink( $msg, array_merge( $changed, $overrides ) ) )->escaped();
}
- return Xml::fieldset( $this->msg( 'whatlinkshere-filters' )->text(), $this->getLanguage()->pipeList( $links ) );
+
+ return Xml::fieldset(
+ $this->msg( 'whatlinkshere-filters' )->text(),
+ $this->getLanguage()->pipeList( $links )
+ );
}
protected function getGroupName() {
diff --git a/includes/specials/SpecialWithoutinterwiki.php b/includes/specials/SpecialWithoutinterwiki.php
index 9d23499f..bd014613 100644
--- a/includes/specials/SpecialWithoutinterwiki.php
+++ b/includes/specials/SpecialWithoutinterwiki.php
@@ -41,21 +41,25 @@ class WithoutInterwikiPage extends PageQueryPage {
}
function getPageHeader() {
- global $wgScript;
-
# Do not show useless input form if special page is cached
if ( $this->isCached() ) {
return '';
}
$prefix = $this->prefix;
- $t = $this->getTitle();
+ $t = $this->getPageTitle();
- return Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . "\n" .
+ return Html::openElement( 'form', array( 'method' => 'get', 'action' => wfScript() ) ) . "\n" .
Html::openElement( 'fieldset' ) . "\n" .
Html::element( 'legend', null, $this->msg( 'withoutinterwiki-legend' )->text() ) . "\n" .
Html::hidden( 'title', $t->getPrefixedText() ) . "\n" .
- Xml::inputLabel( $this->msg( 'allpagesprefix' )->text(), 'prefix', 'wiprefix', 20, $prefix ) . "\n" .
+ Xml::inputLabel(
+ $this->msg( 'allpagesprefix' )->text(),
+ 'prefix',
+ 'wiprefix',
+ 20,
+ $prefix
+ ) . "\n" .
Xml::submitButton( $this->msg( 'withoutinterwiki-submit' )->text() ) . "\n" .
Html::closeElement( 'fieldset' ) . "\n" .
Html::closeElement( 'form' );
@@ -80,19 +84,23 @@ class WithoutInterwikiPage extends PageQueryPage {
function getQueryInfo() {
$query = array(
'tables' => array( 'page', 'langlinks' ),
- 'fields' => array( 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_title' ),
- 'conds' => array( 'll_title IS NULL',
- 'page_namespace' => MWNamespace::getContentNamespaces(),
- 'page_is_redirect' => 0 ),
- 'join_conds' => array( 'langlinks' => array(
- 'LEFT JOIN', 'll_from = page_id' ) )
+ 'fields' => array(
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_title'
+ ),
+ 'conds' => array(
+ 'll_title IS NULL',
+ 'page_namespace' => MWNamespace::getContentNamespaces(),
+ 'page_is_redirect' => 0
+ ),
+ 'join_conds' => array( 'langlinks' => array( 'LEFT JOIN', 'll_from = page_id' ) )
);
if ( $this->prefix ) {
$dbr = wfGetDB( DB_SLAVE );
$query['conds'][] = 'page_title ' . $dbr->buildLike( $this->prefix, $dbr->anyString() );
}
+
return $query;
}