summaryrefslogtreecommitdiff
path: root/includes/specials
diff options
context:
space:
mode:
Diffstat (limited to 'includes/specials')
-rw-r--r--includes/specials/SpecialActiveusers.php29
-rw-r--r--includes/specials/SpecialAllMessages.php18
-rw-r--r--includes/specials/SpecialAllPages.php83
-rw-r--r--includes/specials/SpecialAncientpages.php6
-rw-r--r--includes/specials/SpecialBlock.php39
-rw-r--r--includes/specials/SpecialBlockList.php77
-rw-r--r--includes/specials/SpecialBrokenRedirects.php4
-rw-r--r--includes/specials/SpecialChangeContentModel.php223
-rw-r--r--includes/specials/SpecialChangeEmail.php13
-rw-r--r--includes/specials/SpecialChangePassword.php3
-rw-r--r--includes/specials/SpecialComparePages.php3
-rw-r--r--includes/specials/SpecialConfirmemail.php8
-rw-r--r--includes/specials/SpecialContributions.php8
-rw-r--r--includes/specials/SpecialDeletedContributions.php8
-rw-r--r--includes/specials/SpecialDiff.php8
-rw-r--r--includes/specials/SpecialDoubleRedirects.php5
-rw-r--r--includes/specials/SpecialEditTags.php20
-rw-r--r--includes/specials/SpecialEditWatchlist.php7
-rw-r--r--includes/specials/SpecialEmailuser.php7
-rw-r--r--includes/specials/SpecialExport.php207
-rw-r--r--includes/specials/SpecialFewestrevisions.php4
-rw-r--r--includes/specials/SpecialFileDuplicateSearch.php6
-rw-r--r--includes/specials/SpecialFilepath.php11
-rw-r--r--includes/specials/SpecialImport.php144
-rw-r--r--includes/specials/SpecialJavaScriptTest.php108
-rw-r--r--includes/specials/SpecialLinkSearch.php68
-rw-r--r--includes/specials/SpecialListDuplicatedFiles.php4
-rw-r--r--includes/specials/SpecialListfiles.php14
-rw-r--r--includes/specials/SpecialListredirects.php4
-rw-r--r--includes/specials/SpecialListusers.php16
-rw-r--r--includes/specials/SpecialLockdb.php4
-rw-r--r--includes/specials/SpecialMIMEsearch.php7
-rw-r--r--includes/specials/SpecialMediaStatistics.php12
-rw-r--r--includes/specials/SpecialMergeHistory.php2
-rw-r--r--includes/specials/SpecialMostcategories.php4
-rw-r--r--includes/specials/SpecialMostinterwikis.php4
-rw-r--r--includes/specials/SpecialMostlinked.php4
-rw-r--r--includes/specials/SpecialMostlinkedcategories.php2
-rw-r--r--includes/specials/SpecialMovepage.php338
-rw-r--r--includes/specials/SpecialMyLanguage.php26
-rw-r--r--includes/specials/SpecialMyRedirectPages.php55
-rw-r--r--includes/specials/SpecialNewpages.php4
-rw-r--r--includes/specials/SpecialPageLanguage.php7
-rw-r--r--includes/specials/SpecialPagesWithProp.php8
-rw-r--r--includes/specials/SpecialPasswordReset.php2
-rw-r--r--includes/specials/SpecialPermanentLink.php8
-rw-r--r--includes/specials/SpecialPreferences.php9
-rw-r--r--includes/specials/SpecialProtectedtitles.php12
-rw-r--r--includes/specials/SpecialRandomInCategory.php16
-rw-r--r--includes/specials/SpecialRandompage.php20
-rw-r--r--includes/specials/SpecialRecentchanges.php23
-rw-r--r--includes/specials/SpecialRecentchangeslinked.php1
-rw-r--r--includes/specials/SpecialResetTokens.php5
-rw-r--r--includes/specials/SpecialRevisiondelete.php36
-rw-r--r--includes/specials/SpecialRunJobs.php15
-rw-r--r--includes/specials/SpecialSearch.php152
-rw-r--r--includes/specials/SpecialShortpages.php2
-rw-r--r--includes/specials/SpecialSpecialpages.php27
-rw-r--r--includes/specials/SpecialStatistics.php43
-rw-r--r--includes/specials/SpecialTags.php97
-rw-r--r--includes/specials/SpecialUndelete.php15
-rw-r--r--includes/specials/SpecialUnlockdb.php4
-rw-r--r--includes/specials/SpecialUnusedcategories.php4
-rw-r--r--includes/specials/SpecialUnusedtemplates.php4
-rw-r--r--includes/specials/SpecialUnwatchedpages.php4
-rw-r--r--includes/specials/SpecialUpload.php14
-rw-r--r--includes/specials/SpecialUploadStash.php4
-rw-r--r--includes/specials/SpecialUserlogin.php115
-rw-r--r--includes/specials/SpecialUserrights.php8
-rw-r--r--includes/specials/SpecialVersion.php76
-rw-r--r--includes/specials/SpecialWantedfiles.php2
-rw-r--r--includes/specials/SpecialWantedtemplates.php5
-rw-r--r--includes/specials/SpecialWatchlist.php1
-rw-r--r--includes/specials/SpecialWhatlinkshere.php53
74 files changed, 1526 insertions, 883 deletions
diff --git a/includes/specials/SpecialActiveusers.php b/includes/specials/SpecialActiveusers.php
index 5e2ee1c2..047e9413 100644
--- a/includes/specials/SpecialActiveusers.php
+++ b/includes/specials/SpecialActiveusers.php
@@ -267,21 +267,24 @@ class SpecialActiveUsers extends SpecialPage {
$out->wrapWikiMsg( "<div class='mw-activeusers-intro'>\n$1\n</div>",
array( 'activeusers-intro', $this->getLanguage()->formatNum( $days ) ) );
- // Get the timestamp of the last cache update
+ // Mention the level of cache staleness...
$dbr = wfGetDB( DB_SLAVE, 'recentchanges' );
- $cTime = $dbr->selectField( 'querycache_info',
- 'qci_timestamp',
- array( 'qci_type' => 'activeusers' )
- );
-
- $secondsOld = $cTime
- ? time() - wfTimestamp( TS_UNIX, $cTime )
- : $days * 86400; // fully stale :)
-
- if ( $secondsOld > 0 ) {
- // Mention the level of staleness
- $out->addWikiMsg( 'cachedspecial-viewing-cached-ttl',
+ $rcMax = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)' );
+ if ( $rcMax ) {
+ $cTime = $dbr->selectField( 'querycache_info',
+ 'qci_timestamp',
+ array( 'qci_type' => 'activeusers' )
+ );
+ if ( $cTime ) {
+ $secondsOld = wfTimestamp( TS_UNIX, $rcMax ) - wfTimestamp( TS_UNIX, $cTime );
+ } else {
+ $rcMin = $dbr->selectField( 'recentchanges', 'MIN(rc_timestamp)' );
+ $secondsOld = time() - wfTimestamp( TS_UNIX, $rcMin );
+ }
+ if ( $secondsOld > 0 ) {
+ $out->addWikiMsg( 'cachedspecial-viewing-cached-ttl',
$this->getLanguage()->formatDuration( $secondsOld ) );
+ }
}
$up = new ActiveUsersPager( $this->getContext(), null, $par );
diff --git a/includes/specials/SpecialAllMessages.php b/includes/specials/SpecialAllMessages.php
index 7b596bb0..762658c5 100644
--- a/includes/specials/SpecialAllMessages.php
+++ b/includes/specials/SpecialAllMessages.php
@@ -29,7 +29,7 @@
*/
class SpecialAllMessages extends SpecialPage {
/**
- * @var AllmessagesTablePager
+ * @var AllMessagesTablePager
*/
protected $table;
@@ -61,7 +61,7 @@ class SpecialAllMessages extends SpecialPage {
$out->addModuleStyles( 'mediawiki.special' );
$this->addHelpLink( 'Help:System message' );
- $this->table = new AllmessagesTablePager(
+ $this->table = new AllMessagesTablePager(
$this,
array(),
wfGetLangObj( $request->getVal( 'lang', $par ) )
@@ -130,7 +130,7 @@ class AllMessagesTablePager extends TablePager {
if ( $prefix !== null ) {
$this->displayPrefix = $prefix->getDBkey();
- $this->prefix = '/^' . preg_quote( $this->displayPrefix ) . '/i';
+ $this->prefix = '/^' . preg_quote( $this->displayPrefix, '/' ) . '/i';
} else {
$this->displayPrefix = false;
$this->prefix = false;
@@ -206,7 +206,7 @@ class AllMessagesTablePager extends TablePager {
Xml::label( $this->msg( 'table_pager_limit_label' )->text(), 'mw-table_pager_limit_label' ) .
'</td>
<td class="mw-input">' .
- $this->getLimitSelect() .
+ $this->getLimitSelect( array( 'id' => 'mw-table_pager_limit_label' ) ) .
'</td>
<tr>
<td></td>
@@ -392,10 +392,10 @@ class AllMessagesTablePager extends TablePager {
);
}
- return $title . ' '
- . $this->msg( 'parentheses' )->rawParams( $talk )->escaped()
- . ' '
- . $this->msg( 'parentheses' )->rawParams( $translation )->escaped();
+ return $title . ' ' .
+ $this->msg( 'parentheses' )->rawParams( $talk )->escaped() .
+ ' ' .
+ $this->msg( 'parentheses' )->rawParams( $translation )->escaped();
case 'am_default' :
case 'am_actual' :
@@ -445,7 +445,7 @@ class AllMessagesTablePager extends TablePager {
} elseif ( $field === 'am_title' ) {
return array( 'class' => $field );
} else {
- return array( 'lang' => $this->langcode, 'dir' => $this->lang->getDir(), 'class' => $field );
+ return array( 'lang' => wfBCP47( $this->langcode ), 'dir' => $this->lang->getDir(), 'class' => $field );
}
}
diff --git a/includes/specials/SpecialAllPages.php b/includes/specials/SpecialAllPages.php
index 74b1f7bb..c4a67c0c 100644
--- a/includes/specials/SpecialAllPages.php
+++ b/includes/specials/SpecialAllPages.php
@@ -25,6 +25,7 @@
* Implements Special:Allpages
*
* @ingroup SpecialPage
+ * @todo Rewrite using IndexPager
*/
class SpecialAllPages extends IncludableSpecialPage {
@@ -179,6 +180,7 @@ class SpecialAllPages extends IncludableSpecialPage {
$toList = $this->getNamespaceKeyAndText( $namespace, $to );
$namespaces = $this->getContext()->getLanguage()->getNamespaces();
$n = 0;
+ $prevTitle = null;
if ( !$fromList || !$toList ) {
$out = $this->msg( 'allpagesbadtitle' )->parseAsBlock();
@@ -191,15 +193,13 @@ class SpecialAllPages extends IncludableSpecialPage {
list( , $toKey, $to ) = $toList;
$dbr = wfGetDB( DB_SLAVE );
- $conds = array(
- 'page_namespace' => $namespace,
- 'page_title >= ' . $dbr->addQuotes( $fromKey )
- );
-
+ $filterConds = array( 'page_namespace' => $namespace );
if ( $hideredirects ) {
- $conds['page_is_redirect'] = 0;
+ $filterConds['page_is_redirect'] = 0;
}
+ $conds = $filterConds;
+ $conds[] = 'page_title >= ' . $dbr->addQuotes( $fromKey );
if ( $toKey !== "" ) {
$conds[] = 'page_title <= ' . $dbr->addQuotes( $toKey );
}
@@ -234,6 +234,35 @@ class SpecialAllPages extends IncludableSpecialPage {
} else {
$out = '';
}
+
+ if ( $fromKey !== '' && !$this->including() ) {
+ # Get the first title from previous chunk
+ $prevConds = $filterConds;
+ $prevConds[] = 'page_title < ' . $dbr->addQuotes( $fromKey );
+ $prevKey = $dbr->selectField(
+ 'page',
+ 'page_title',
+ $prevConds,
+ __METHOD__,
+ array( 'ORDER BY' => 'page_title DESC', 'OFFSET' => $this->maxPerPage - 1 )
+ );
+
+ if ( $prevKey === false ) {
+ # The previous chunk is not complete, need to link to the very first title
+ # available in the database
+ $prevKey = $dbr->selectField(
+ 'page',
+ 'page_title',
+ $prevConds,
+ __METHOD__,
+ array( 'ORDER BY' => 'page_title' )
+ );
+ }
+
+ if ( $prevKey !== false ) {
+ $prevTitle = Title::makeTitle( $namespace, $prevKey );
+ }
+ }
}
if ( $this->including() ) {
@@ -241,44 +270,6 @@ class SpecialAllPages extends IncludableSpecialPage {
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(
@@ -287,7 +278,7 @@ class SpecialAllPages extends IncludableSpecialPage {
$bottomLinks = array();
# Do we put a previous link ?
- if ( $prevTitle && $pt = $prevTitle->getText() ) {
+ if ( $prevTitle ) {
$query = array( 'from' => $prevTitle->getText() );
if ( $namespace ) {
@@ -300,7 +291,7 @@ class SpecialAllPages extends IncludableSpecialPage {
$prevLink = Linker::linkKnown(
$self,
- $this->msg( 'prevpage', $pt )->escaped(),
+ $this->msg( 'prevpage', $prevTitle->getText() )->escaped(),
array(),
$query
);
diff --git a/includes/specials/SpecialAncientpages.php b/includes/specials/SpecialAncientpages.php
index b0830327..2da24a8e 100644
--- a/includes/specials/SpecialAncientpages.php
+++ b/includes/specials/SpecialAncientpages.php
@@ -32,7 +32,7 @@ class AncientPagesPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() {
+ public function isExpensive() {
return true;
}
@@ -40,7 +40,7 @@ class AncientPagesPage extends QueryPage {
return false;
}
- function getQueryInfo() {
+ public function getQueryInfo() {
return array(
'tables' => array( 'page', 'revision' ),
'fields' => array(
@@ -56,7 +56,7 @@ class AncientPagesPage extends QueryPage {
);
}
- function usesTimestamps() {
+ public function usesTimestamps() {
return true;
}
diff --git a/includes/specials/SpecialBlock.php b/includes/specials/SpecialBlock.php
index b80d921d..cd6cc76e 100644
--- a/includes/specials/SpecialBlock.php
+++ b/includes/specials/SpecialBlock.php
@@ -97,7 +97,6 @@ class SpecialBlock extends FormSpecialPage {
protected function alterForm( HTMLForm $form ) {
$form->setWrapperLegendMsg( 'blockip-legend' );
$form->setHeaderText( '' );
- $form->setSubmitCallback( array( __CLASS__, 'processUIForm' ) );
$form->setSubmitDestructive();
$msg = $this->alreadyBlocked ? 'ipb-change-block' : 'ipbsubmit';
@@ -394,8 +393,8 @@ class SpecialBlock extends FormSpecialPage {
# Link to edit the block dropdown reasons, if applicable
if ( $user->isAllowed( 'editinterface' ) ) {
- $links[] = Linker::link(
- Title::makeTitle( NS_MEDIAWIKI, 'Ipbreason-dropdown' ),
+ $links[] = Linker::linkKnown(
+ $this->msg( 'ipbreason-dropdown' )->inContentLanguage()->getTitle(),
$this->msg( 'ipb-edit-dropdown' )->escaped(),
array(),
array( 'action' => 'edit' )
@@ -597,17 +596,8 @@ class SpecialBlock extends FormSpecialPage {
}
/**
- * Submit callback for an HTMLForm object, will simply pass
- * @param array $data
- * @param HTMLForm $form
- * @return bool|string
- */
- public static function processUIForm( array $data, HTMLForm $form ) {
- return self::processForm( $data, $form->getContext() );
- }
-
- /**
- * Given the form data, actually implement a block
+ * Given the form data, actually implement a block. This is also called from ApiBlock.
+ *
* @param array $data
* @param IContextSource $context
* @return bool|string
@@ -672,8 +662,8 @@ class SpecialBlock extends FormSpecialPage {
if ( $data['HideUser'] ) {
if ( !$performer->isAllowed( 'hideuser' ) ) {
# this codepath is unreachable except by a malicious user spoofing forms,
- # or by race conditions (user has oversight and sysop, loads block form,
- # and is de-oversighted before submission); so need to fail completely
+ # or by race conditions (user has hideuser and block rights, loads block form,
+ # and loses hideuser rights before submission); so need to fail completely
# rather than just silently disable hiding
return array( 'badaccess-group0' );
}
@@ -797,7 +787,7 @@ class SpecialBlock extends FormSpecialPage {
$logParams['5::duration'] = $data['Expiry'];
$logParams['6::flags'] = self::blockLogFlags( $data, $type );
- # Make log entry, if the name is hidden, put it in the oversight log
+ # Make log entry, if the name is hidden, put it in the suppression log
$log_type = $data['HideUser'] ? 'suppress' : 'block';
$logEntry = new ManualLogEntry( $log_type, $logaction );
$logEntry->setTarget( Title::makeTitle( NS_USER, $target ) );
@@ -848,16 +838,11 @@ 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
+ * @return string Timestamp or 'infinity'
*/
public static function parseExpiryInput( $expiry ) {
- static $infinity;
- if ( $infinity == null ) {
- $infinity = wfGetDB( DB_SLAVE )->getInfinity();
- }
-
if ( wfIsInfinity( $expiry ) ) {
- $expiry = $infinity;
+ $expiry = 'infinity';
} else {
$expiry = strtotime( $expiry );
@@ -967,11 +952,11 @@ class SpecialBlock extends FormSpecialPage {
/**
* Process the form on POST submission.
* @param array $data
+ * @param HTMLForm $form
* @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
- // second parameter. See alterForm for the real function
+ public function onSubmit( array $data, HTMLForm $form = null ) {
+ return self::processForm( $data, $form->getContext() );
}
/**
diff --git a/includes/specials/SpecialBlockList.php b/includes/specials/SpecialBlockList.php
index 0ec144a2..9defaba7 100644
--- a/includes/specials/SpecialBlockList.php
+++ b/includes/specials/SpecialBlockList.php
@@ -65,6 +65,9 @@ class SpecialBlockList extends SpecialPage {
return;
}
+ # setup BlockListPager here to get the actual default Limit
+ $pager = $this->getBlockListPager();
+
# Just show the block list
$fields = array(
'Target' => array(
@@ -77,11 +80,11 @@ class SpecialBlockList extends SpecialPage {
),
'Options' => array(
'type' => 'multiselect',
- 'options' => array(
- $this->msg( 'blocklist-userblocks' )->text() => 'userblocks',
- $this->msg( 'blocklist-tempblocks' )->text() => 'tempblocks',
- $this->msg( 'blocklist-addressblocks' )->text() => 'addressblocks',
- $this->msg( 'blocklist-rangeblocks' )->text() => 'rangeblocks',
+ 'options-messages' => array(
+ 'blocklist-userblocks' => 'userblocks',
+ 'blocklist-tempblocks' => 'tempblocks',
+ 'blocklist-addressblocks' => 'addressblocks',
+ 'blocklist-rangeblocks' => 'rangeblocks',
),
'flatlist' => true,
),
@@ -96,7 +99,7 @@ class SpecialBlockList extends SpecialPage {
$lang->formatNum( 500 ) => 500,
),
'name' => 'limit',
- 'default' => 50,
+ 'default' => $pager->getLimit(),
),
);
$context = new DerivativeContext( $this->getContext() );
@@ -109,10 +112,14 @@ class SpecialBlockList extends SpecialPage {
$form->prepareForm();
$form->displayForm( '' );
- $this->showList();
+ $this->showList( $pager );
}
- function showList() {
+ /**
+ * Setup a new BlockListPager instance.
+ * @return BlockListPager
+ */
+ protected function getBlockListPager() {
$conds = array();
# Is the user allowed to see hidden blocks?
if ( !$this->getUser()->isAllowed( 'hideuser' ) ) {
@@ -163,12 +170,20 @@ class SpecialBlockList extends SpecialPage {
$conds[] = "ipb_range_end = ipb_range_start";
}
+ return new BlockListPager( $this, $conds );
+ }
+
+ /**
+ * Show the list of blocked accounts matching the actual filter.
+ * @param BlockListPager $pager The BlockListPager instance for this page
+ */
+ protected function showList( BlockListPager $pager ) {
+ $out = $this->getOutput();
+
# Check for other blocks, i.e. global/tor blocks
$otherBlockLink = array();
Hooks::run( 'OtherBlockLogLink', array( &$otherBlockLink, $this->target ) );
- $out = $this->getOutput();
-
# Show additional header for the local block only when other blocks exists.
# Not necessary in a standard installation without such extensions enabled
if ( count( $otherBlockLink ) ) {
@@ -177,7 +192,6 @@ class SpecialBlockList extends SpecialPage {
);
}
- $pager = new BlockListPager( $this, $conds );
if ( $pager->getNumRows() ) {
$out->addParserOutputContent( $pager->getFullOutput() );
} elseif ( $this->target ) {
@@ -249,7 +263,7 @@ class BlockListPager extends TablePager {
function formatValue( $name, $value ) {
static $msg = null;
if ( $msg === null ) {
- $msg = array(
+ $keys = array(
'anononlyblock',
'createaccountblock',
'noautoblockblock',
@@ -258,17 +272,22 @@ class BlockListPager extends TablePager {
'unblocklink',
'change-blocklink',
);
- $msg = array_combine( $msg, array_map( array( $this, 'msg' ), $msg ) );
+
+ foreach ( $keys as $key ) {
+ $msg[$key] = $this->msg( $key )->escaped();
+ }
}
/** @var $row object */
$row = $this->mCurrentRow;
+ $language = $this->getLanguage();
+
$formatted = '';
switch ( $name ) {
case 'ipb_timestamp':
- $formatted = $this->getLanguage()->userTimeAndDate( $value, $this->getUser() );
+ $formatted = htmlspecialchars( $language->userTimeAndDate( $value, $this->getUser() ) );
break;
case 'ipb_target':
@@ -294,7 +313,10 @@ class BlockListPager extends TablePager {
break;
case 'ipb_expiry':
- $formatted = $this->getLanguage()->formatExpiry( $value, /* User preference timezone */true );
+ $formatted = htmlspecialchars( $language->formatExpiry(
+ $value,
+ /* User preference timezone */true
+ ) );
if ( $this->getUser()->isAllowed( 'block' ) ) {
if ( $row->ipb_auto ) {
$links[] = Linker::linkKnown(
@@ -317,7 +339,7 @@ class BlockListPager extends TablePager {
'span',
array( 'class' => 'mw-blocklist-actions' ),
$this->msg( 'parentheses' )->rawParams(
- $this->getLanguage()->pipeList( $links ) )->escaped()
+ $language->pipeList( $links ) )->escaped()
);
}
break;
@@ -355,7 +377,7 @@ class BlockListPager extends TablePager {
$properties[] = $msg['blocklist-nousertalk'];
}
- $formatted = $this->getLanguage()->commaList( $properties );
+ $formatted = $language->commaList( $properties );
break;
default:
@@ -430,23 +452,14 @@ class BlockListPager extends TablePager {
$lb = new LinkBatch;
$lb->setCaller( __METHOD__ );
- $userids = array();
-
foreach ( $result as $row ) {
- $userids[] = $row->ipb_by;
-
- # Usernames and titles are in fact related by a simple substitution of space -> underscore
- # The last few lines of Title::secureAndSplit() tell the story.
- $name = str_replace( ' ', '_', $row->ipb_address );
- $lb->add( NS_USER, $name );
- $lb->add( NS_USER_TALK, $name );
- }
+ $lb->add( NS_USER, $row->ipb_address );
+ $lb->add( NS_USER_TALK, $row->ipb_address );
- $ua = UserArray::newFromIDs( $userids );
- foreach ( $ua as $user ) {
- $name = str_replace( ' ', '_', $user->getName() );
- $lb->add( NS_USER, $name );
- $lb->add( NS_USER_TALK, $name );
+ if ( isset( $row->by_user_name ) ) {
+ $lb->add( NS_USER, $row->by_user_name );
+ $lb->add( NS_USER_TALK, $row->by_user_name );
+ }
}
$lb->execute();
diff --git a/includes/specials/SpecialBrokenRedirects.php b/includes/specials/SpecialBrokenRedirects.php
index 1bbdbeab..701f75f0 100644
--- a/includes/specials/SpecialBrokenRedirects.php
+++ b/includes/specials/SpecialBrokenRedirects.php
@@ -32,7 +32,7 @@ class BrokenRedirectsPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() {
+ public function isExpensive() {
return true;
}
@@ -48,7 +48,7 @@ class BrokenRedirectsPage extends QueryPage {
return $this->msg( 'brokenredirectstext' )->parseAsBlock();
}
- function getQueryInfo() {
+ public function getQueryInfo() {
$dbr = wfGetDB( DB_SLAVE );
return array(
diff --git a/includes/specials/SpecialChangeContentModel.php b/includes/specials/SpecialChangeContentModel.php
new file mode 100644
index 00000000..cce5da5c
--- /dev/null
+++ b/includes/specials/SpecialChangeContentModel.php
@@ -0,0 +1,223 @@
+<?php
+
+class SpecialChangeContentModel extends FormSpecialPage {
+
+ public function __construct() {
+ parent::__construct( 'ChangeContentModel', 'editcontentmodel' );
+ }
+
+ /**
+ * @var Title|null
+ */
+ private $title;
+
+ /**
+ * @var Revision|bool|null
+ *
+ * A Revision object, false if no revision exists, null if not loaded yet
+ */
+ private $oldRevision;
+
+ protected function setParameter( $par ) {
+ $par = $this->getRequest()->getVal( 'pagetitle', $par );
+ $title = Title::newFromText( $par );
+ if ( $title ) {
+ $this->title = $title;
+ $this->par = $title->getPrefixedText();
+ } else {
+ $this->par = '';
+ }
+ }
+
+ protected function getDisplayFormat() {
+ return 'ooui';
+ }
+
+ protected function alterForm( HTMLForm $form ) {
+ if ( !$this->title ) {
+ $form->setMethod( 'GET' );
+ }
+ }
+
+ public function validateTitle( $title ) {
+ if ( !$title ) {
+ // No form input yet
+ return true;
+ }
+
+ // Already validated by HTMLForm, but if not, throw
+ // and exception instead of a fatal
+ $titleObj = Title::newFromTextThrow( $title );
+
+ $this->oldRevision = Revision::newFromTitle( $titleObj ) ?: false;
+
+ if ( $this->oldRevision ) {
+ $oldContent = $this->oldRevision->getContent();
+ if ( !$oldContent->getContentHandler()->supportsDirectEditing() ) {
+ return $this->msg( 'changecontentmodel-nodirectediting' )
+ ->params( ContentHandler::getLocalizedName( $oldContent->getModel() ) )
+ ->escaped();
+ }
+ }
+
+ return true;
+ }
+
+ protected function getFormFields() {
+ $that = $this;
+ $fields = array(
+ 'pagetitle' => array(
+ 'type' => 'title',
+ 'creatable' => true,
+ 'name' => 'pagetitle',
+ 'default' => $this->par,
+ 'label-message' => 'changecontentmodel-title-label',
+ 'validation-callback' => array( $this, 'validateTitle' ),
+ ),
+ );
+ if ( $this->title ) {
+ $fields['pagetitle']['readonly'] = true;
+ $fields += array(
+ 'model' => array(
+ 'type' => 'select',
+ 'name' => 'model',
+ 'options' => $this->getOptionsForTitle( $this->title ),
+ 'label-message' => 'changecontentmodel-model-label'
+ ),
+ 'reason' => array(
+ 'type' => 'text',
+ 'name' => 'reason',
+ 'validation-callback' => function( $reason ) use ( $that ) {
+ $match = EditPage::matchSummarySpamRegex( $reason );
+ if ( $match ) {
+ return $that->msg( 'spamprotectionmatch', $match )->parse();
+ }
+
+ return true;
+ },
+ 'label-message' => 'changecontentmodel-reason-label',
+ ),
+ );
+ }
+
+ return $fields;
+ }
+
+ private function getOptionsForTitle( Title $title = null ) {
+ $models = ContentHandler::getContentModels();
+ $options = array();
+ foreach ( $models as $model ) {
+ $handler = ContentHandler::getForModelID( $model );
+ if ( !$handler->supportsDirectEditing() ) {
+ continue;
+ }
+ if ( $title ) {
+ if ( $title->getContentModel() === $model ) {
+ continue;
+ }
+ if ( !$handler->canBeUsedOn( $title ) ) {
+ continue;
+ }
+ }
+ $options[ContentHandler::getLocalizedName( $model )] = $model;
+ }
+
+ return $options;
+ }
+
+ public function onSubmit( array $data ) {
+ global $wgContLang;
+
+ if ( $data['pagetitle'] === '' ) {
+ // Initial form view of special page, pass
+ return false;
+ }
+
+ // At this point, it has to be a POST request. This is enforced by HTMLForm,
+ // but lets be safe verify that.
+ if ( !$this->getRequest()->wasPosted() ) {
+ throw new RuntimeException( "Form submission was not POSTed" );
+ }
+
+ $this->title = Title::newFromText( $data['pagetitle' ] );
+ $user = $this->getUser();
+ // Check permissions and make sure the user has permission to edit the specific page
+ $errors = $this->title->getUserPermissionsErrors( 'editcontentmodel', $user );
+ $errors = wfMergeErrorArrays( $errors, $this->title->getUserPermissionsErrors( 'edit', $user ) );
+ if ( $errors ) {
+ $out = $this->getOutput();
+ $wikitext = $out->formatPermissionsErrorMessage( $errors );
+ // Hack to get our wikitext parsed
+ return Status::newFatal( new RawMessage( '$1', array( $wikitext ) ) );
+ }
+
+ $page = WikiPage::factory( $this->title );
+ if ( $this->oldRevision === null ) {
+ $this->oldRevision = $page->getRevision() ?: false;
+ }
+ $oldModel = $this->title->getContentModel();
+ if ( $this->oldRevision ) {
+ $oldContent = $this->oldRevision->getContent();
+ try {
+ $newContent = ContentHandler::makeContent(
+ $oldContent->getNativeData(), $this->title, $data['model']
+ );
+ } catch ( MWException $e ) {
+ return Status::newFatal(
+ $this->msg( 'changecontentmodel-cannot-convert' )
+ ->params(
+ $this->title->getPrefixedText(),
+ ContentHandler::getLocalizedName( $data['model'] )
+ )
+ );
+ }
+ } else {
+ // Page doesn't exist, create an empty content object
+ $newContent = ContentHandler::getForModelID( $data['model'] )->makeEmptyContent();
+ }
+ $flags = $this->oldRevision ? EDIT_UPDATE : EDIT_NEW;
+ if ( $user->isAllowed( 'bot' ) ) {
+ $flags |= EDIT_FORCE_BOT;
+ }
+
+ $log = new ManualLogEntry( 'contentmodel', 'change' );
+ $log->setPerformer( $user );
+ $log->setTarget( $this->title );
+ $log->setComment( $data['reason'] );
+ $log->setParameters( array(
+ '4::oldmodel' => $oldModel,
+ '5::newmodel' => $data['model']
+ ) );
+
+ $formatter = LogFormatter::newFromEntry( $log );
+ $formatter->setContext( RequestContext::newExtraneousContext( $this->title ) );
+ $reason = $formatter->getPlainActionText();
+ if ( $data['reason'] !== '' ) {
+ $reason .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $data['reason'];
+ }
+ # Truncate for whole multibyte characters.
+ $reason = $wgContLang->truncate( $reason, 255 );
+
+ $status = $page->doEditContent(
+ $newContent,
+ $reason,
+ $flags,
+ $this->oldRevision ? $this->oldRevision->getId() : false,
+ $user
+ );
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
+ $logid = $log->insert();
+ $log->publish( $logid );
+
+ return $status;
+ }
+
+ public function onSuccess() {
+ $out = $this->getOutput();
+ $out->setPageTitle( $this->msg( 'changecontentmodel-success-title' ) );
+ $out->addWikiMsg( 'changecontentmodel-success-text', $this->title );
+ }
+}
diff --git a/includes/specials/SpecialChangeEmail.php b/includes/specials/SpecialChangeEmail.php
index eca307d9..22df04e8 100644
--- a/includes/specials/SpecialChangeEmail.php
+++ b/includes/specials/SpecialChangeEmail.php
@@ -92,14 +92,14 @@ class SpecialChangeEmail extends FormSpecialPage {
'NewEmail' => array(
'type' => 'email',
'label-message' => 'changeemail-newemail',
+ 'autofocus' => true
),
);
if ( $this->getConfig()->get( 'RequirePasswordforEmailChange' ) ) {
$fields['Password'] = array(
'type' => 'password',
- 'label-message' => 'changeemail-password',
- 'autofocus' => true,
+ 'label-message' => 'changeemail-password'
);
}
@@ -107,7 +107,7 @@ class SpecialChangeEmail extends FormSpecialPage {
}
protected function getDisplayFormat() {
- return 'vform';
+ return 'ooui';
}
protected function alterForm( HTMLForm $form ) {
@@ -129,7 +129,8 @@ class SpecialChangeEmail extends FormSpecialPage {
public function onSuccess() {
$request = $this->getRequest();
- $titleObj = Title::newFromText( $request->getVal( 'returnto' ) );
+ $returnto = $request->getVal( 'returnto' );
+ $titleObj = $returnto !== null ? Title::newFromText( $returnto ) : null;
if ( !$titleObj instanceof Title ) {
$titleObj = Title::newMainPage();
}
@@ -159,6 +160,10 @@ class SpecialChangeEmail extends FormSpecialPage {
return Status::newFatal( 'invalidemailaddress' );
}
+ if ( $newaddr === $user->getEmail() ) {
+ return Status::newFatal( 'changeemail-nochange' );
+ }
+
$throttleCount = LoginForm::incLoginThrottle( $user->getName() );
if ( $throttleCount === true ) {
$lang = $this->getLanguage();
diff --git a/includes/specials/SpecialChangePassword.php b/includes/specials/SpecialChangePassword.php
index 168095f8..6a4347df 100644
--- a/includes/specials/SpecialChangePassword.php
+++ b/includes/specials/SpecialChangePassword.php
@@ -179,7 +179,8 @@ class SpecialChangePassword extends FormSpecialPage {
}
if ( $request->getCheck( 'wpCancel' ) ) {
- $titleObj = Title::newFromText( $request->getVal( 'returnto' ) );
+ $returnto = $request->getVal( 'returnto' );
+ $titleObj = $returnto !== null ? Title::newFromText( $returnto ) : null;
if ( !$titleObj instanceof Title ) {
$titleObj = Title::newMainPage();
}
diff --git a/includes/specials/SpecialComparePages.php b/includes/specials/SpecialComparePages.php
index da1a54cd..0f8b7291 100644
--- a/includes/specials/SpecialComparePages.php
+++ b/includes/specials/SpecialComparePages.php
@@ -50,10 +50,12 @@ class SpecialComparePages extends SpecialPage {
$this->setHeaders();
$this->outputHeader();
+ # Form (.mw-searchInput enables suggestions)
$form = new HTMLForm( array(
'Page1' => array(
'type' => 'text',
'name' => 'page1',
+ 'cssclass' => 'mw-searchInput',
'label-message' => 'compare-page1',
'size' => '40',
'section' => 'page1',
@@ -70,6 +72,7 @@ class SpecialComparePages extends SpecialPage {
'Page2' => array(
'type' => 'text',
'name' => 'page2',
+ 'cssclass' => 'mw-searchInput',
'label-message' => 'compare-page2',
'size' => '40',
'section' => 'page2',
diff --git a/includes/specials/SpecialConfirmemail.php b/includes/specials/SpecialConfirmemail.php
index 63561552..147f67e8 100644
--- a/includes/specials/SpecialConfirmemail.php
+++ b/includes/specials/SpecialConfirmemail.php
@@ -43,6 +43,10 @@ class EmailConfirmation extends UnlistedSpecialPage {
* @throws UserNotLoggedIn
*/
function execute( $code ) {
+ // Ignore things like master queries/connections on GET requests.
+ // It's very convenient to just allow formless link usage.
+ Profiler::instance()->getTransactionProfiler()->resetExpectations();
+
$this->setHeaders();
$this->checkReadOnly();
@@ -151,6 +155,10 @@ class EmailInvalidation extends UnlistedSpecialPage {
}
function execute( $code ) {
+ // Ignore things like master queries/connections on GET requests.
+ // It's very convenient to just allow formless link usage.
+ Profiler::instance()->getTransactionProfiler()->resetExpectations();
+
$this->setHeaders();
$this->checkReadOnly();
$this->checkPermissions();
diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php
index c2cd8122..9672580d 100644
--- a/includes/specials/SpecialContributions.php
+++ b/includes/specials/SpecialContributions.php
@@ -38,6 +38,7 @@ class SpecialContributions extends IncludableSpecialPage {
$this->outputHeader();
$out = $this->getOutput();
$out->addModuleStyles( 'mediawiki.special' );
+ $this->addHelpLink( 'Help:User contributions' );
$this->opts = array();
$request = $this->getRequest();
@@ -724,7 +725,6 @@ class ContribsPager extends ReverseChronologicalPager {
$limit,
$descending
);
- $pager = $this;
/*
* This hook will allow extensions to add in additional queries, so they can get their data
@@ -749,7 +749,7 @@ class ContribsPager extends ReverseChronologicalPager {
) );
Hooks::run(
'ContribsPager::reallyDoQuery',
- array( &$data, $pager, $offset, $limit, $descending )
+ array( &$data, $this, $offset, $limit, $descending )
);
$result = array();
@@ -965,14 +965,14 @@ class ContribsPager extends ReverseChronologicalPager {
* we're definitely dealing with revision data and we may proceed, if not, we'll leave it
* to extensions to subscribe to the hook to parse the row.
*/
- wfSuppressWarnings();
+ MediaWiki\suppressWarnings();
try {
$rev = new Revision( $row );
$validRevision = (bool)$rev->getId();
} catch ( Exception $e ) {
$validRevision = false;
}
- wfRestoreWarnings();
+ MediaWiki\restoreWarnings();
if ( $validRevision ) {
$classes = array();
diff --git a/includes/specials/SpecialDeletedContributions.php b/includes/specials/SpecialDeletedContributions.php
index fb85942d..44352a78 100644
--- a/includes/specials/SpecialDeletedContributions.php
+++ b/includes/specials/SpecialDeletedContributions.php
@@ -88,15 +88,13 @@ class DeletedContribsPager extends IndexPager {
* @return ResultWrapper
*/
function reallyDoQuery( $offset, $limit, $descending ) {
- $pager = $this;
-
$data = array( parent::reallyDoQuery( $offset, $limit, $descending ) );
// This hook will allow extensions to add in additional queries, nearly
// identical to ContribsPager::reallyDoQuery.
Hooks::run(
'DeletedContribsPager::reallyDoQuery',
- array( &$data, $pager, $offset, $limit, $descending )
+ array( &$data, $this, $offset, $limit, $descending )
);
$result = array();
@@ -203,14 +201,14 @@ class DeletedContribsPager extends IndexPager {
* we're definitely dealing with revision data and we may proceed, if not, we'll leave it
* to extensions to subscribe to the hook to parse the row.
*/
- wfSuppressWarnings();
+ MediaWiki\suppressWarnings();
try {
$rev = Revision::newFromArchiveRow( $row );
$validRevision = (bool)$rev->getId();
} catch ( Exception $e ) {
$validRevision = false;
}
- wfRestoreWarnings();
+ MediaWiki\restoreWarnings();
if ( $validRevision ) {
$ret = $this->formatRevisionRow( $row );
diff --git a/includes/specials/SpecialDiff.php b/includes/specials/SpecialDiff.php
index 89c1c021..8b5d31a8 100644
--- a/includes/specials/SpecialDiff.php
+++ b/includes/specials/SpecialDiff.php
@@ -37,12 +37,16 @@
* @since 1.23
*/
class SpecialDiff extends RedirectSpecialPage {
- function __construct() {
+ public function __construct() {
parent::__construct( 'Diff' );
$this->mAllowedRedirectParams = array();
}
- function getRedirect( $subpage ) {
+ /**
+ * @param string|null $subpage
+ * @return Title|bool
+ */
+ public function getRedirect( $subpage ) {
$parts = explode( '/', $subpage );
// Try to parse the values given, generating somewhat pretty URLs if possible
diff --git a/includes/specials/SpecialDoubleRedirects.php b/includes/specials/SpecialDoubleRedirects.php
index c364f70f..6d40985b 100644
--- a/includes/specials/SpecialDoubleRedirects.php
+++ b/includes/specials/SpecialDoubleRedirects.php
@@ -32,7 +32,7 @@ class DoubleRedirectsPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() {
+ public function isExpensive() {
return true;
}
@@ -99,7 +99,7 @@ class DoubleRedirectsPage extends QueryPage {
return $retval;
}
- function getQueryInfo() {
+ public function getQueryInfo() {
return $this->reallyGetQueryInfo();
}
@@ -156,7 +156,6 @@ class DoubleRedirectsPage extends QueryPage {
$this->msg( 'parentheses', $this->msg( 'editlink' )->text() )->escaped(),
array(),
array(
- 'redirect' => 'no',
'action' => 'edit'
)
);
diff --git a/includes/specials/SpecialEditTags.php b/includes/specials/SpecialEditTags.php
index f41a1f1d..d2b2e708 100644
--- a/includes/specials/SpecialEditTags.php
+++ b/includes/specials/SpecialEditTags.php
@@ -123,7 +123,7 @@ class SpecialEditTags extends UnlistedSpecialPage {
// Either submit or create our form
if ( $this->isAllowed && $this->submitClicked ) {
- $this->submit( $request );
+ $this->submit();
} else {
$this->showForm();
}
@@ -349,20 +349,18 @@ class SpecialEditTags extends UnlistedSpecialPage {
protected function getTagSelect( $selectedTags, $label ) {
$result = array();
$result[0] = Xml::label( $label, 'mw-edittags-tag-list' );
- $result[1] = Xml::openElement( 'select', array(
- 'name' => 'wpTagList[]',
- 'id' => 'mw-edittags-tag-list',
- 'multiple' => 'multiple',
- 'size' => '8',
- ) );
+
+ $select = new XmlSelect( 'wpTagList[]', 'mw-edittags-tag-list', $selectedTags );
+ $select->setAttribute( 'multiple', 'multiple' );
+ $select->setAttribute( 'size', '8' );
$tags = ChangeTags::listExplicitlyDefinedTags();
$tags = array_unique( array_merge( $tags, $selectedTags ) );
- foreach ( $tags as $tag ) {
- $result[1] .= Xml::option( $tag, $tag, in_array( $tag, $selectedTags ) );
- }
- $result[1] .= Xml::closeElement( 'select' );
+ // Values of $tags are also used as <option> labels
+ $select->addOptions( array_combine( $tags, $tags ) );
+
+ $result[1] = $select->getHTML();
return $result;
}
diff --git a/includes/specials/SpecialEditWatchlist.php b/includes/specials/SpecialEditWatchlist.php
index 910fe259..74662aec 100644
--- a/includes/specials/SpecialEditWatchlist.php
+++ b/includes/specials/SpecialEditWatchlist.php
@@ -102,7 +102,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
case self::EDIT_NORMAL:
default:
- $this->executeViewEditWatchlist();
+ $this->executeViewEditWatchlist();
break;
}
}
@@ -299,7 +299,9 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
*/
private function getWatchlist() {
$list = array();
- $dbr = wfGetDB( DB_MASTER );
+
+ $index = $this->getRequest()->wasPosted() ? DB_MASTER : DB_SLAVE;
+ $dbr = wfGetDB( $index );
$res = $dbr->select(
'watchlist',
@@ -312,6 +314,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
);
if ( $res->numRows() > 0 ) {
+ /** @var Title[] $titles */
$titles = array();
foreach ( $res as $row ) {
$title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title );
diff --git a/includes/specials/SpecialEmailuser.php b/includes/specials/SpecialEmailuser.php
index c55fa94c..92cb8bf6 100644
--- a/includes/specials/SpecialEmailuser.php
+++ b/includes/specials/SpecialEmailuser.php
@@ -356,7 +356,9 @@ class SpecialEmailUser extends UnlistedSpecialPage {
$replyTo = null;
}
- $status = UserMailer::send( $to, $mailFrom, $subject, $text, $replyTo );
+ $status = UserMailer::send( $to, $mailFrom, $subject, $text, array(
+ 'replyTo' => $replyTo,
+ ) );
if ( !$status->isGood() ) {
return $status;
@@ -367,7 +369,10 @@ class SpecialEmailUser extends UnlistedSpecialPage {
if ( $data['CCMe'] && $to != $from ) {
$cc_subject = $context->msg( 'emailccsubject' )->rawParams(
$target->getName(), $subject )->text();
+
+ // target and sender are equal, because this is the CC for the sender
Hooks::run( 'EmailUserCC', array( &$from, &$from, &$cc_subject, &$text ) );
+
$ccStatus = UserMailer::send( $from, $from, $cc_subject, $text );
$status->merge( $ccStatus );
}
diff --git a/includes/specials/SpecialExport.php b/includes/specials/SpecialExport.php
index c30d962a..39c4d771 100644
--- a/includes/specials/SpecialExport.php
+++ b/includes/specials/SpecialExport.php
@@ -30,7 +30,6 @@
*/
class SpecialExport extends SpecialPage {
private $curonly, $doExport, $pageLinkDepth, $templates;
- private $images;
public function __construct() {
parent::__construct( 'Export' );
@@ -46,7 +45,6 @@ class SpecialExport extends SpecialPage {
$this->doExport = false;
$request = $this->getRequest();
$this->templates = $request->getCheck( 'templates' );
- $this->images = $request->getCheck( 'images' ); // Doesn't do anything yet
$this->pageLinkDepth = $this->validateLinkDepth(
$request->getIntOrNull( 'pagelink-depth' )
);
@@ -188,113 +186,122 @@ class SpecialExport extends SpecialPage {
$categoryName = '';
}
- $form = Xml::openElement( 'form', array( 'method' => 'post',
- 'action' => $this->getPageTitle()->getLocalURL( 'action=submit' ) ) );
- $form .= Xml::inputLabel(
- $this->msg( 'export-addcattext' )->text(),
- 'catname',
- 'catname',
- 40,
- $categoryName
- ) . '&#160;';
- $form .= Xml::submitButton(
- $this->msg( 'export-addcat' )->text(),
- array( 'name' => 'addcat' )
- ) . '<br />';
-
+ $formDescriptor = array(
+ 'catname' => array(
+ 'type' => 'textwithbutton',
+ 'name' => 'catname',
+ 'horizontal-label' => true,
+ 'label-message' => 'export-addcattext',
+ 'default' => $categoryName,
+ 'size' => 40,
+ 'buttontype' => 'submit',
+ 'buttonname' => 'addcat',
+ 'buttondefault' => $this->msg( 'export-addcat' )->text(),
+ ),
+ );
if ( $config->get( 'ExportFromNamespaces' ) ) {
- $form .= Html::namespaceSelector(
- array(
- 'selected' => $nsindex,
- 'label' => $this->msg( 'export-addnstext' )->text()
- ), array(
+ $formDescriptor += array(
+ 'nsindex' => array(
+ 'type' => 'namespaceselectwithbutton',
+ 'default' => $nsindex,
+ 'label-message' => 'export-addnstext',
+ 'horizontal-label' => true,
'name' => 'nsindex',
'id' => 'namespace',
- 'class' => 'namespaceselector',
- )
- ) . '&#160;';
- $form .= Xml::submitButton(
- $this->msg( 'export-addns' )->text(),
- array( 'name' => 'addns' )
- ) . '<br />';
+ 'cssclass' => 'namespaceselector',
+ 'buttontype' => 'submit',
+ 'buttonname' => 'addns',
+ 'buttondefault' => $this->msg( 'export-addns' )->text(),
+ ),
+ );
}
if ( $config->get( 'ExportAllowAll' ) ) {
- $form .= Xml::checkLabel(
- $this->msg( 'exportall' )->text(),
- 'exportall',
- 'exportall',
- $request->wasPosted() ? $request->getCheck( 'exportall' ) : false
- ) . '<br />';
+ $formDescriptor += array(
+ 'exportall' => array(
+ 'type' => 'check',
+ 'label-message' => 'exportall',
+ 'name' => 'exportall',
+ 'id' => 'exportall',
+ 'default' => $request->wasPosted() ? $request->getCheck( 'exportall' ) : false,
+ ),
+ );
}
- $form .= Xml::element(
- 'textarea',
- array( 'name' => 'pages', 'cols' => 40, 'rows' => 10 ),
- $page,
- false
+ $formDescriptor += array(
+ 'textarea' => array(
+ 'class' => 'HTMLTextAreaField',
+ 'name' => 'pages',
+ 'nodata' => true,
+ 'cols' => 40,
+ 'rows' => 10,
+ 'default' => $page,
+ ),
);
- $form .= '<br />';
if ( $config->get( 'ExportAllowHistory' ) ) {
- $form .= Xml::checkLabel(
- $this->msg( 'exportcuronly' )->text(),
- 'curonly',
- 'curonly',
- $request->wasPosted() ? $request->getCheck( 'curonly' ) : true
- ) . '<br />';
+ $formDescriptor += array(
+ 'curonly' => array(
+ 'type' => 'check',
+ 'label-message' => 'exportcuronly',
+ 'name' => 'curonly',
+ 'id' => 'curonly',
+ 'default' => $request->wasPosted() ? $request->getCheck( 'curonly' ) : true,
+ ),
+ );
} else {
$out->addWikiMsg( 'exportnohistory' );
}
- $form .= Xml::checkLabel(
- $this->msg( 'export-templates' )->text(),
- 'templates',
- 'wpExportTemplates',
- $request->wasPosted() ? $request->getCheck( 'templates' ) : false
- ) . '<br />';
+ $formDescriptor += array(
+ 'templates' => array(
+ 'type' => 'check',
+ 'label-message' => 'export-templates',
+ 'name' => 'templates',
+ 'id' => 'wpExportTemplates',
+ 'default' => $request->wasPosted() ? $request->getCheck( 'templates' ) : false,
+ ),
+ );
if ( $config->get( 'ExportMaxLinkDepth' ) || $this->userCanOverrideExportDepth() ) {
- $form .= Xml::inputLabel(
- $this->msg( 'export-pagelinks' )->text(),
- 'pagelink-depth',
- 'pagelink-depth',
- 20,
- 0
- ) . '<br />';
+ $formDescriptor += array(
+ 'pagelink-depth' => array(
+ 'type' => 'text',
+ 'name' => 'pagelink-depth',
+ 'id' => 'pagelink-depth',
+ 'label-message' => 'export-pagelinks',
+ 'default' => '0',
+ 'size' => 20,
+ ),
+ );
}
- /* 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',
- 'wpDownload',
- $request->wasPosted() ? $request->getCheck( 'wpDownload' ) : true
- ) . '<br />';
+ $formDescriptor += array(
+ 'wpDownload' => array(
+ 'type' => 'check',
+ 'name' =>'wpDownload',
+ 'id' => 'wpDownload',
+ 'default' => $request->wasPosted() ? $request->getCheck( 'wpDownload' ) : true,
+ 'label-message' => 'export-download',
+ ),
+ );
if ( $config->get( 'ExportAllowListContributors' ) ) {
- $form .= Xml::checkLabel(
- $this->msg( 'exportlistauthors' )->text(),
- 'listauthors',
- 'listauthors',
- $request->wasPosted() ? $request->getCheck( 'listauthors' ) : false
- ) . '<br />';
+ $formDescriptor += array(
+ 'listauthors' => array(
+ 'type' => 'check',
+ 'label-message' => 'exportlistauthors',
+ 'default' => $request->wasPosted() ? $request->getCheck( 'listauthors' ) : false,
+ 'name' => 'listauthors',
+ 'id' => 'listauthors',
+ ),
+ );
}
- $form .= Xml::submitButton(
- $this->msg( 'export-submit' )->text(),
- Linker::tooltipAndAccesskeyAttribs( 'export' )
- );
- $form .= Xml::closeElement( 'form' );
-
- $out->addHTML( $form );
+ $htmlForm = HTMLForm::factory( 'div', $formDescriptor, $this->getContext() );
+ $htmlForm->setSubmitTextMsg( 'export-submit' );
+ $htmlForm->prepareForm()->displayForm( false );
+ $this->addHelpLink( 'Help:Export' );
}
/**
@@ -319,7 +326,6 @@ class SpecialExport extends SpecialPage {
if ( $exportall ) {
$history = WikiExporter::FULL;
} else {
-
$pageSet = array(); // Inverted index of all pages to look up
// Split up and normalize input
@@ -344,11 +350,6 @@ class SpecialExport extends SpecialPage {
$pageSet = $this->getPageLinks( $inputPages, $pageSet, $linkDepth );
}
- // Enable this when we can do something useful exporting/importing image information.
- // if( $this->images ) ) {
- // $pageSet = $this->getImages( $inputPages, $pageSet );
- // }
-
$pages = array_keys( $pageSet );
// Normalize titles to the same format and remove dupes, see bug 17374
@@ -371,9 +372,9 @@ class SpecialExport extends SpecialPage {
$buffer = WikiExporter::STREAM;
// This might take a while... :D
- wfSuppressWarnings();
+ MediaWiki\suppressWarnings();
set_time_limit( 0 );
- wfRestoreWarnings();
+ MediaWiki\restoreWarnings();
}
$exporter = new WikiExporter( $db, $history, $buffer );
@@ -535,24 +536,6 @@ class SpecialExport extends SpecialPage {
}
/**
- * Expand a list of pages to include images used in those pages.
- *
- * @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 getImages( $inputPages, $pageSet ) {
- return $this->getLinks(
- $inputPages,
- $pageSet,
- 'imagelinks',
- array( 'namespace' => NS_FILE, 'title' => 'il_to' ),
- array( 'page_id=il_from' )
- );
- }
-
- /**
* Expand a list of pages to include items used in those pages.
* @param array $inputPages Array of page titles
* @param array $pageSet
diff --git a/includes/specials/SpecialFewestrevisions.php b/includes/specials/SpecialFewestrevisions.php
index dc9d57c2..406233b4 100644
--- a/includes/specials/SpecialFewestrevisions.php
+++ b/includes/specials/SpecialFewestrevisions.php
@@ -32,7 +32,7 @@ class FewestrevisionsPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() {
+ public function isExpensive() {
return true;
}
@@ -40,7 +40,7 @@ class FewestrevisionsPage extends QueryPage {
return false;
}
- function getQueryInfo() {
+ public function getQueryInfo() {
return array(
'tables' => array( 'revision', 'page' ),
'fields' => array(
diff --git a/includes/specials/SpecialFileDuplicateSearch.php b/includes/specials/SpecialFileDuplicateSearch.php
index da79bb81..4c0c75fb 100644
--- a/includes/specials/SpecialFileDuplicateSearch.php
+++ b/includes/specials/SpecialFileDuplicateSearch.php
@@ -48,7 +48,7 @@ class FileDuplicateSearchPage extends QueryPage {
return false;
}
- function isCached() {
+ public function isCached() {
return false;
}
@@ -82,7 +82,7 @@ class FileDuplicateSearchPage extends QueryPage {
$this->getOutput()->addHtml( implode( "\n", $html ) );
}
- function getQueryInfo() {
+ public function getQueryInfo() {
return array(
'tables' => array( 'image' ),
'fields' => array(
@@ -95,7 +95,7 @@ class FileDuplicateSearchPage extends QueryPage {
);
}
- function execute( $par ) {
+ public function execute( $par ) {
$this->setHeaders();
$this->outputHeader();
diff --git a/includes/specials/SpecialFilepath.php b/includes/specials/SpecialFilepath.php
index 93232117..542589f3 100644
--- a/includes/specials/SpecialFilepath.php
+++ b/includes/specials/SpecialFilepath.php
@@ -27,13 +27,18 @@
* @ingroup SpecialPage
*/
class SpecialFilepath extends RedirectSpecialPage {
- function __construct() {
+ public function __construct() {
parent::__construct( 'Filepath' );
$this->mAllowedRedirectParams = array( 'width', 'height' );
}
- // implement by redirecting through Special:Redirect/file
- function getRedirect( $par ) {
+ /**
+ * Implement by redirecting through Special:Redirect/file.
+ *
+ * @param string|null $subpage
+ * @return Title
+ */
+ public function getRedirect( $par ) {
$file = $par ?: $this->getRequest()->getText( 'file' );
if ( $file ) {
diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php
index af869647..4cdf6ddf 100644
--- a/includes/specials/SpecialImport.php
+++ b/includes/specials/SpecialImport.php
@@ -34,6 +34,7 @@ class SpecialImport extends SpecialPage {
private $interwiki = false;
private $subproject;
private $fullInterwikiPrefix;
+ private $mapping = 'default';
private $namespace;
private $rootpage = '';
private $frompage = '';
@@ -56,6 +57,8 @@ class SpecialImport extends SpecialPage {
* @throws ReadOnlyError
*/
function execute( $par ) {
+ $this->useTransactionalTimeLimit();
+
$this->setHeaders();
$this->outputHeader();
@@ -101,26 +104,33 @@ class SpecialImport extends SpecialPage {
private function doImport() {
$isUpload = false;
$request = $this->getRequest();
- $this->namespace = $request->getIntOrNull( 'namespace' );
$this->sourceName = $request->getVal( "source" );
$this->logcomment = $request->getText( 'log-comment' );
$this->pageLinkDepth = $this->getConfig()->get( 'ExportMaxLinkDepth' ) == 0
? 0
: $request->getIntOrNull( 'pagelink-depth' );
- $this->rootpage = $request->getText( 'rootpage' );
+
+ $this->mapping = $request->getVal( 'mapping' );
+ if ( $this->mapping === 'namespace' ) {
+ $this->namespace = $request->getIntOrNull( 'namespace' );
+ } elseif ( $this->mapping === 'subpage' ) {
+ $this->rootpage = $request->getText( 'rootpage' );
+ } else {
+ $this->mapping = 'default';
+ }
$user = $this->getUser();
if ( !$user->matchEditToken( $request->getVal( 'editToken' ) ) ) {
$source = Status::newFatal( 'import-token-mismatch' );
- } elseif ( $this->sourceName == 'upload' ) {
+ } elseif ( $this->sourceName === 'upload' ) {
$isUpload = true;
if ( $user->isAllowed( 'importupload' ) ) {
$source = ImportStreamSource::newFromUpload( "xmlimport" );
} else {
throw new PermissionsError( 'importupload' );
}
- } elseif ( $this->sourceName == "interwiki" ) {
+ } elseif ( $this->sourceName === 'interwiki' ) {
if ( !$user->isAllowed( 'import' ) ) {
throw new PermissionsError( 'import' );
}
@@ -163,8 +173,7 @@ class SpecialImport extends SpecialPage {
$importer = new WikiImporter( $source->value, $this->getConfig() );
if ( !is_null( $this->namespace ) ) {
$importer->setTargetNamespace( $this->namespace );
- }
- if ( !is_null( $this->rootpage ) ) {
+ } elseif ( !is_null( $this->rootpage ) ) {
$statusRootPage = $importer->setTargetRootPage( $this->rootpage );
if ( !$statusRootPage->isGood() ) {
$out->wrapWikiMsg(
@@ -219,13 +228,88 @@ class SpecialImport extends SpecialPage {
}
}
+ private function getMappingFormPart( $sourceName ) {
+ $isSameSourceAsBefore = ( $this->sourceName === $sourceName );
+ $defaultNamespace = $this->getConfig()->get( 'ImportTargetNamespace' );
+ return "<tr>
+ <td>
+ </td>
+ <td class='mw-input'>" .
+ Xml::radioLabel(
+ $this->msg( 'import-mapping-default' )->text(),
+ 'mapping',
+ 'default',
+ // mw-import-mapping-interwiki-default, mw-import-mapping-upload-default
+ "mw-import-mapping-$sourceName-default",
+ ( $isSameSourceAsBefore ?
+ ( $this->mapping === 'default' ) :
+ is_null( $defaultNamespace ) )
+ ) .
+ "</td>
+ </tr>
+ <tr>
+ <td>
+ </td>
+ <td class='mw-input'>" .
+ Xml::radioLabel(
+ $this->msg( 'import-mapping-namespace' )->text(),
+ 'mapping',
+ 'namespace',
+ // mw-import-mapping-interwiki-namespace, mw-import-mapping-upload-namespace
+ "mw-import-mapping-$sourceName-namespace",
+ ( $isSameSourceAsBefore ?
+ ( $this->mapping === 'namespace' ) :
+ !is_null( $defaultNamespace ) )
+ ) . ' ' .
+ Html::namespaceSelector(
+ array(
+ 'selected' => ( $isSameSourceAsBefore ?
+ $this->namespace :
+ ( $defaultNamespace || '' ) ),
+ ), array(
+ 'name' => "namespace",
+ // mw-import-namespace-interwiki, mw-import-namespace-upload
+ 'id' => "mw-import-namespace-$sourceName",
+ 'class' => 'namespaceselector',
+ )
+ ) .
+ "</td>
+ </tr>
+ <tr>
+ <td>
+ </td>
+ <td class='mw-input'>" .
+ Xml::radioLabel(
+ $this->msg( 'import-mapping-subpage' )->text(),
+ 'mapping',
+ 'subpage',
+ // mw-import-mapping-interwiki-subpage, mw-import-mapping-upload-subpage
+ "mw-import-mapping-$sourceName-subpage",
+ ( $isSameSourceAsBefore ? ( $this->mapping === 'subpage' ) : '' )
+ ) . ' ' .
+ Xml::input( 'rootpage', 50,
+ ( $isSameSourceAsBefore ? $this->rootpage : '' ),
+ array(
+ // Should be "mw-import-rootpage-...", but we keep this inaccurate
+ // ID for legacy reasons
+ // mw-interwiki-rootpage-interwiki, mw-interwiki-rootpage-upload
+ 'id' => "mw-interwiki-rootpage-$sourceName",
+ 'type' => 'text'
+ )
+ ) . ' ' .
+ "</td>
+ </tr>";
+ }
+
private function showForm() {
$action = $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) );
$user = $this->getUser();
$out = $this->getOutput();
+ $this->addHelpLink( '//meta.wikimedia.org/wiki/Special:MyLanguage/Help:Import', true );
$importSources = $this->getConfig()->get( 'ImportSources' );
if ( $user->isAllowed( 'importupload' ) ) {
+ $mappingSelection = $this->getMappingFormPart( 'upload' );
$out->addHTML(
Xml::fieldset( $this->msg( 'import-upload' )->text() ) .
Xml::openElement(
@@ -255,22 +339,11 @@ class SpecialImport extends SpecialPage {
"</td>
<td class='mw-input'>" .
Xml::input( 'log-comment', 50,
- ( $this->sourceName == 'upload' ? $this->logcomment : '' ),
+ ( $this->sourceName === 'upload' ? $this->logcomment : '' ),
array( 'id' => 'mw-import-comment', 'type' => 'text' ) ) . ' ' .
"</td>
</tr>
- <tr>
- <td class='mw-label'>" .
- Xml::label(
- $this->msg( 'import-interwiki-rootpage' )->text(),
- 'mw-interwiki-rootpage-upload'
- ) .
- "</td>
- <td class='mw-input'>" .
- Xml::input( 'rootpage', 50, $this->rootpage,
- array( 'id' => 'mw-interwiki-rootpage-upload', 'type' => 'text' ) ) . ' ' .
- "</td>
- </tr>
+ $mappingSelection
<tr>
<td></td>
<td class='mw-submit'>" .
@@ -301,6 +374,7 @@ class SpecialImport extends SpecialPage {
"</td>
</tr>";
}
+ $mappingSelection = $this->getMappingFormPart( 'interwiki' );
$out->addHTML(
Xml::fieldset( $this->msg( 'importinterwiki' )->text() ) .
@@ -415,43 +489,15 @@ class SpecialImport extends SpecialPage {
$importDepth
<tr>
<td class='mw-label'>" .
- Xml::label( $this->msg( 'import-interwiki-namespace' )->text(), 'namespace' ) .
- "</td>
- <td class='mw-input'>" .
- Html::namespaceSelector(
- array(
- 'selected' => $this->namespace,
- 'all' => '',
- ), array(
- 'name' => 'namespace',
- 'id' => 'namespace',
- 'class' => 'namespaceselector',
- )
- ) .
- "</td>
- </tr>
- <tr>
- <td class='mw-label'>" .
Xml::label( $this->msg( 'import-comment' )->text(), 'mw-interwiki-comment' ) .
"</td>
<td class='mw-input'>" .
Xml::input( 'log-comment', 50,
- ( $this->sourceName == 'interwiki' ? $this->logcomment : '' ),
+ ( $this->sourceName === 'interwiki' ? $this->logcomment : '' ),
array( 'id' => 'mw-interwiki-comment', 'type' => 'text' ) ) . ' ' .
"</td>
</tr>
- <tr>
- <td class='mw-label'>" .
- Xml::label(
- $this->msg( 'import-interwiki-rootpage' )->text(),
- 'mw-interwiki-rootpage-interwiki'
- ) .
- "</td>
- <td class='mw-input'>" .
- Xml::input( 'rootpage', 50, $this->rootpage,
- array( 'id' => 'mw-interwiki-rootpage-interwiki', 'type' => 'text' ) ) . ' ' .
- "</td>
- </tr>
+ $mappingSelection
<tr>
<td>
</td>
diff --git a/includes/specials/SpecialJavaScriptTest.php b/includes/specials/SpecialJavaScriptTest.php
index ecb166a4..fbdefea4 100644
--- a/includes/specials/SpecialJavaScriptTest.php
+++ b/includes/specials/SpecialJavaScriptTest.php
@@ -44,6 +44,11 @@ class SpecialJavaScriptTest extends SpecialPage {
if ( $par === null ) {
// No framework specified
+ // If only one framework is configured, redirect to it. Otherwise display a list.
+ if ( count( self::$frameworks ) === 1 ) {
+ $out->redirect( $this->getPageTitle( self::$frameworks[0] . '/plain' )->getLocalURL() );
+ return;
+ }
$out->setStatusCode( 404 );
$out->setPageTitle( $this->msg( 'javascripttest' ) );
$out->addHTML(
@@ -74,10 +79,14 @@ class SpecialJavaScriptTest extends SpecialPage {
// no sensitive data. In order to allow TestSwarm to embed it into a test client window,
// we need to allow iframing of this page.
$out->allowClickjacking();
- $out->setSubtitle(
- $this->msg( 'javascripttest-backlink' )
- ->rawParams( Linker::linkKnown( $this->getPageTitle() ) )
- );
+ if ( count( self::$frameworks ) !== 1 ) {
+ // If there's only one framework, don't set the subtitle since it
+ // is going to redirect back to this page
+ $out->setSubtitle(
+ $this->msg( 'javascripttest-backlink' )
+ ->rawParams( Linker::linkKnown( $this->getPageTitle() ) )
+ );
+ }
// Custom actions
if ( isset( $pars[1] ) ) {
@@ -134,13 +143,15 @@ class SpecialJavaScriptTest extends SpecialPage {
}
/**
- * Wrap HTML contents in a summary container.
+ * Get summary text wrapped in a container
*
- * @param string $html HTML contents to be wrapped
* @return string HTML
*/
- private function wrapSummaryHtml( $html ) {
- return "<div id=\"mw-javascripttest-summary\">$html</div>";
+ private function getSummaryHtml() {
+ $summary = $this->msg( 'javascripttest-qunit-intro' )
+ ->params( 'https://www.mediawiki.org/wiki/Manual:JavaScript_unit_testing' )
+ ->parseAsBlock();
+ return "<div id=\"mw-javascripttest-summary\">$summary</div>";
}
/**
@@ -153,30 +164,24 @@ class SpecialJavaScriptTest extends SpecialPage {
$modules = $out->getResourceLoader()->getTestModuleNames( 'qunit' );
- $summary = $this->msg( 'javascripttest-qunit-intro' )
- ->params( 'https://www.mediawiki.org/wiki/Manual:JavaScript_unit_testing' )
- ->parseAsBlock();
-
$baseHtml = <<<HTML
<div class="mw-content-ltr">
<div id="qunit"></div>
</div>
HTML;
- $out->addHtml( $this->wrapSummaryHtml( $summary ) . $baseHtml );
+ $out->addHtml( $this->getSummaryHtml() . $baseHtml );
// The testrunner configures QUnit and essentially depends on it. However, test suites
// are reusable in environments that preload QUnit (or a compatibility interface to
// another framework). Therefore we have to load it ourselves.
- $out->addHtml( Html::inlineScript(
- ResourceLoader::makeLoaderConditionalScript(
- Xml::encodeJsCall( 'mw.loader.using', array(
- array( 'jquery.qunit', 'jquery.qunit.completenessTest' ),
- new XmlJsCode(
- 'function () {' . Xml::encodeJsCall( 'mw.loader.load', array( $modules ) ) . '}'
- )
- ) )
- )
+ $out->addHtml( ResourceLoader::makeInlineScript(
+ Xml::encodeJsCall( 'mw.loader.using', array(
+ array( 'jquery.qunit', 'jquery.qunit.completenessTest' ),
+ new XmlJsCode(
+ 'function () {' . Xml::encodeJsCall( 'mw.loader.load', array( $modules ) ) . '}'
+ )
+ ) )
) );
}
@@ -200,13 +205,27 @@ HTML;
'lang' => $this->getLanguage()->getCode(),
'skin' => $this->getSkin()->getSkinName(),
'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false',
+ 'target' => 'test',
);
$embedContext = new ResourceLoaderContext( $rl, new FauxRequest( $query ) );
$query['only'] = 'scripts';
$startupContext = new ResourceLoaderContext( $rl, new FauxRequest( $query ) );
+ $query['raw'] = true;
+
$modules = $rl->getTestModuleNames( 'qunit' );
+ // Disable autostart because we load modules asynchronously. By default, QUnit would start
+ // at domready when there are no tests loaded and also fire 'QUnit.done' which then instructs
+ // Karma to end the run before the tests even started.
+ $qunitConfig = 'QUnit.config.autostart = false;'
+ . 'if (window.__karma__) {'
+ // karma-qunit's use of autostart=false and QUnit.start conflicts with ours.
+ // Hack around this by replacing 'karma.loaded' with a no-op and call it ourselves later.
+ // See <https://github.com/karma-runner/karma-qunit/issues/27>.
+ . 'window.__karma__.loaded = function () {};'
+ . '}';
+
// The below is essentially a pure-javascript version of OutputPage::getHeadScripts.
$startup = $rl->makeModuleResponse( $startupContext, array(
'startup' => $rl->getModule( 'startup' ),
@@ -220,43 +239,54 @@ HTML;
'user.options' => $rl->getModule( 'user.options' ),
'user.tokens' => $rl->getModule( 'user.tokens' ),
) );
- $code .= Xml::encodeJsCall( 'mw.loader.load', array( $modules ) );
+ // Catch exceptions (such as "dependency missing" or "unknown module") so that we
+ // always start QUnit. Re-throw so that they are caught and reported as global exceptions
+ // by QUnit and Karma.
+ $code .= '(function () {'
+ . 'var start = window.__karma__ ? window.__karma__.start : QUnit.start;'
+ . 'try {'
+ . 'mw.loader.using( ' . Xml::encodeJsVar( $modules ) . ' ).always( start );'
+ . '} catch ( e ) { start(); throw e; }'
+ . '}());';
header( 'Content-Type: text/javascript; charset=utf-8' );
header( 'Cache-Control: private, no-cache, must-revalidate' );
header( 'Pragma: no-cache' );
+ echo $qunitConfig;
echo $startup;
- echo "\n";
- // Note: The following has to be wrapped in a script tag because the startup module also
- // writes a script tag (the one loading mediawiki.js). Script tags are synchronous, block
- // each other, and run in order. But they don't nest. The code appended after the startup
- // module runs before the added script tag is parsed and executed.
- echo Xml::encodeJsCall( 'document.write', array( Html::inlineScript( $code ) ) );
+ // The following has to be deferred via RLQ because the startup module is asynchronous.
+ echo ResourceLoader::makeLoaderConditionalScript( $code );
}
private function plainQUnit() {
$out = $this->getOutput();
$out->disable();
- $url = $this->getPageTitle( 'qunit/export' )->getFullURL( array(
- 'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false',
- ) );
-
- $styles = $out->makeResourceLoaderLink(
- 'jquery.qunit', ResourceLoaderModule::TYPE_STYLES, false
+ $styles = $out->makeResourceLoaderLink( 'jquery.qunit',
+ ResourceLoaderModule::TYPE_STYLES
);
- // Use 'raw' since this is a plain HTML page without ResourceLoader
- $scripts = $out->makeResourceLoaderLink(
- 'jquery.qunit', ResourceLoaderModule::TYPE_SCRIPTS, false, array( 'raw' => 'true' )
+
+ // Use 'raw' because QUnit loads before ResourceLoader initialises (omit mw.loader.state call)
+ // Use 'test' to ensure OutputPage doesn't use the "async" attribute because QUnit must
+ // load before qunit/export.
+ $scripts = $out->makeResourceLoaderLink( 'jquery.qunit',
+ ResourceLoaderModule::TYPE_SCRIPTS,
+ array( 'raw' => true, 'sync' => true )
);
- $head = trim( $styles['html'] . $scripts['html'] );
+ $head = implode( "\n", array_merge( $styles['html'], $scripts['html'] ) );
+ $summary = $this->getSummaryHtml();
$html = <<<HTML
<!DOCTYPE html>
<title>QUnit</title>
$head
+$summary
<div id="qunit"></div>
HTML;
+
+ $url = $this->getPageTitle( 'qunit/export' )->getFullURL( array(
+ 'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false',
+ ) );
$html .= "\n" . Html::linkedScript( $url );
header( 'Content-Type: text/html; charset=utf-8' );
diff --git a/includes/specials/SpecialLinkSearch.php b/includes/specials/SpecialLinkSearch.php
index 75ff8f30..f4748674 100644
--- a/includes/specials/SpecialLinkSearch.php
+++ b/includes/specials/SpecialLinkSearch.php
@@ -80,7 +80,7 @@ class LinkSearchPage extends QueryPage {
return false;
}
- function execute( $par ) {
+ public function execute( $par ) {
$this->initServices();
$this->setHeaders();
@@ -91,7 +91,7 @@ class LinkSearchPage extends QueryPage {
$request = $this->getRequest();
$target = $request->getVal( 'target', $par );
- $namespace = $request->getIntOrNull( 'namespace', null );
+ $namespace = $request->getIntOrNull( 'namespace' );
$protocols_list = array();
foreach ( $this->getConfig()->get( 'UrlProtocols' ) as $prot ) {
@@ -121,43 +121,41 @@ class LinkSearchPage extends QueryPage {
'<nowiki>' . $this->getLanguage()->commaList( $protocols_list ) . '</nowiki>',
count( $protocols_list )
);
- $s = Html::openElement(
- 'form',
- array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => wfScript() )
- ) . "\n" .
- Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) . "\n" .
- Html::openElement( 'fieldset' ) . "\n" .
- Html::element( 'legend', array(), $this->msg( 'linksearch' )->text() ) . "\n" .
- Xml::inputLabel(
- $this->msg( 'linksearch-pat' )->text(),
- 'target',
- 'target',
- 50,
- $target,
- array(
- // URLs are always ltr
- 'dir' => 'ltr',
- )
- ) . "\n";
-
+ $fields = array(
+ 'target' => array(
+ 'type' => 'text',
+ 'name' => 'target',
+ 'id' => 'target',
+ 'size' => 50,
+ 'label-message' => 'linksearch-pat',
+ 'default' => $target,
+ 'dir' => 'ltr',
+ )
+ );
if ( !$this->getConfig()->get( 'MiserMode' ) ) {
- $s .= Html::namespaceSelector(
- array(
- 'selected' => $namespace,
- 'all' => '',
- 'label' => $this->msg( 'linksearch-ns' )->text()
- ), array(
+ $fields += array(
+ 'namespace' => array(
+ 'type' => 'namespaceselect',
'name' => 'namespace',
+ 'label-message' => 'linksearch-ns',
+ 'default' => $namespace,
'id' => 'namespace',
- 'class' => 'namespaceselector',
- )
+ 'all' => '',
+ 'cssclass' => 'namespaceselector',
+ ),
);
}
-
- $s .= Xml::submitButton( $this->msg( 'linksearch-ok' )->text() ) . "\n" .
- Html::closeElement( 'fieldset' ) . "\n" .
- Html::closeElement( 'form' ) . "\n";
- $out->addHTML( $s );
+ $hiddenFields = array(
+ 'title' => $this->getPageTitle()->getPrefixedDBkey(),
+ );
+ $htmlForm = HTMLForm::factory( 'ooui', $fields, $this->getContext() );
+ $htmlForm->addHiddenFields( $hiddenFields );
+ $htmlForm->setSubmitTextMsg( 'linksearch-ok' );
+ $htmlForm->setWrapperLegendMsg( 'linksearch' );
+ $htmlForm->setAction( wfScript() );
+ $htmlForm->setMethod( 'get' );
+ $htmlForm->prepareForm()->displayForm( false );
+ $this->addHelpLink( 'Help:Linksearch' );
if ( $target != '' ) {
$this->setParams( array(
@@ -220,7 +218,7 @@ class LinkSearchPage extends QueryPage {
return $params;
}
- function getQueryInfo() {
+ public function getQueryInfo() {
$dbr = wfGetDB( DB_SLAVE );
// strip everything past first wildcard, so that
// index-based-only lookup would be done
diff --git a/includes/specials/SpecialListDuplicatedFiles.php b/includes/specials/SpecialListDuplicatedFiles.php
index 1e3dff6f..317b62fe 100644
--- a/includes/specials/SpecialListDuplicatedFiles.php
+++ b/includes/specials/SpecialListDuplicatedFiles.php
@@ -34,7 +34,7 @@ class ListDuplicatedFilesPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() {
+ public function isExpensive() {
return true;
}
@@ -53,7 +53,7 @@ class ListDuplicatedFilesPage extends QueryPage {
* with however we are doing cached special pages.
* @return array
*/
- function getQueryInfo() {
+ public function getQueryInfo() {
return array(
'tables' => array( 'image' ),
'fields' => array(
diff --git a/includes/specials/SpecialListfiles.php b/includes/specials/SpecialListfiles.php
index d4b45fb3..2d79aaf8 100644
--- a/includes/specials/SpecialListfiles.php
+++ b/includes/specials/SpecialListfiles.php
@@ -84,14 +84,26 @@ class ImageListPager extends TablePager {
function __construct( IContextSource $context, $userName = null, $search = '',
$including = false, $showAll = false
) {
+ $this->setContext( $context );
$this->mIncluding = $including;
$this->mShowAll = $showAll;
if ( $userName !== null && $userName !== '' ) {
$nt = Title::newFromText( $userName, NS_USER );
+ $user = User::newFromName( $userName, false );
if ( !is_null( $nt ) ) {
$this->mUserName = $nt->getText();
}
+ if ( !$user || ( $user->isAnon() && !User::isIP( $user->getName() ) ) ) {
+ $this->getOutput()->wrapWikiMsg(
+ "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
+ array(
+ 'listfiles-userdoesnotexist',
+ wfEscapeWikiText( $userName ),
+ )
+ );
+ }
+
}
if ( $search !== '' && !$this->getConfig()->get( 'MiserMode' ) ) {
@@ -107,7 +119,7 @@ class ImageListPager extends TablePager {
}
if ( !$including ) {
- if ( $context->getRequest()->getText( 'sort', 'img_date' ) == 'img_date' ) {
+ if ( $this->getRequest()->getText( 'sort', 'img_date' ) == 'img_date' ) {
$this->mDefaultDirection = IndexPager::DIR_DESCENDING;
} else {
$this->mDefaultDirection = IndexPager::DIR_ASCENDING;
diff --git a/includes/specials/SpecialListredirects.php b/includes/specials/SpecialListredirects.php
index 2df48347..fa94b4ab 100644
--- a/includes/specials/SpecialListredirects.php
+++ b/includes/specials/SpecialListredirects.php
@@ -33,7 +33,7 @@ class ListredirectsPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() {
+ public function isExpensive() {
return true;
}
@@ -45,7 +45,7 @@ class ListredirectsPage extends QueryPage {
return false;
}
- function getQueryInfo() {
+ public function getQueryInfo() {
return array(
'tables' => array( 'p1' => 'page', 'redirect', 'p2' => 'page' ),
'fields' => array( 'namespace' => 'p1.page_namespace',
diff --git a/includes/specials/SpecialListusers.php b/includes/specials/SpecialListusers.php
index 56c4eb50..31200c84 100644
--- a/includes/specials/SpecialListusers.php
+++ b/includes/specials/SpecialListusers.php
@@ -263,6 +263,8 @@ class UsersPager extends AlphabeticPager {
function getPageHeader() {
list( $self ) = explode( '/', $this->getTitle()->getPrefixedDBkey() );
+ $this->getOutput()->addModules( 'mediawiki.userSuggest' );
+
# Form tag
$out = Xml::openElement(
'form',
@@ -271,13 +273,14 @@ class UsersPager extends AlphabeticPager {
Xml::fieldset( $this->msg( 'listusers' )->text() ) .
Html::hidden( 'title', $self );
- # Username field
+ # Username field (with autocompletion support)
$out .= Xml::label( $this->msg( 'listusersfrom' )->text(), 'offset' ) . ' ' .
Html::input(
'username',
$this->requestedUser,
'text',
array(
+ 'class' => 'mw-autocomplete-user',
'id' => 'offset',
'size' => 20,
'autofocus' => $this->requestedUser === ''
@@ -285,13 +288,14 @@ class UsersPager extends AlphabeticPager {
) . ' ';
# Group drop-down list
- $out .= Xml::label( $this->msg( 'group' )->text(), 'group' ) . ' ' .
- Xml::openElement( 'select', array( 'name' => 'group', 'id' => 'group' ) ) .
- Xml::option( $this->msg( 'group-all' )->text(), '' );
+ $sel = new XmlSelect( 'group', 'group', $this->requestedGroup );
+ $sel->addOption( $this->msg( 'group-all' )->text(), '' );
foreach ( $this->getAllGroups() as $group => $groupText ) {
- $out .= Xml::option( $groupText, $group, $group == $this->requestedGroup );
+ $sel->addOption( $groupText, $group );
}
- $out .= Xml::closeElement( 'select' ) . '<br />';
+
+ $out .= Xml::label( $this->msg( 'group' )->text(), 'group' ) . ' ';
+ $out .= $sel->getHTML() . '<br />';
$out .= Xml::checkLabel(
$this->msg( 'listusers-editsonly' )->text(),
'editsOnly',
diff --git a/includes/specials/SpecialLockdb.php b/includes/specials/SpecialLockdb.php
index 1c1f1250..a276197d 100644
--- a/includes/specials/SpecialLockdb.php
+++ b/includes/specials/SpecialLockdb.php
@@ -73,9 +73,9 @@ class SpecialLockdb extends FormSpecialPage {
return Status::newFatal( 'locknoconfirm' );
}
- wfSuppressWarnings();
+ MediaWiki\suppressWarnings();
$fp = fopen( $this->getConfig()->get( 'ReadOnlyFile' ), 'w' );
- wfRestoreWarnings();
+ MediaWiki\restoreWarnings();
if ( false === $fp ) {
# This used to show a file not found error, but the likeliest reason for fopen()
diff --git a/includes/specials/SpecialMIMEsearch.php b/includes/specials/SpecialMIMEsearch.php
index 60225ea5..32344a84 100644
--- a/includes/specials/SpecialMIMEsearch.php
+++ b/includes/specials/SpecialMIMEsearch.php
@@ -34,8 +34,8 @@ class MIMEsearchPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() {
- return false;
+ public function isExpensive() {
+ return true;
}
function isSyndicated() {
@@ -109,7 +109,6 @@ class MIMEsearchPage extends QueryPage {
* Return HTML to put just before the results.
*/
function getPageHeader() {
-
return Xml::openElement(
'form',
array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => wfScript() )
@@ -124,7 +123,7 @@ class MIMEsearchPage extends QueryPage {
Xml::closeElement( 'form' );
}
- function execute( $par ) {
+ public 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 );
diff --git a/includes/specials/SpecialMediaStatistics.php b/includes/specials/SpecialMediaStatistics.php
index b62de5d2..e5ba8c60 100644
--- a/includes/specials/SpecialMediaStatistics.php
+++ b/includes/specials/SpecialMediaStatistics.php
@@ -36,7 +36,7 @@ class MediaStatisticsPage extends QueryPage {
$this->shownavigation = false;
}
- function isExpensive() {
+ public function isExpensive() {
return true;
}
@@ -111,7 +111,11 @@ class MediaStatisticsPage extends QueryPage {
protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
$prevMediaType = null;
foreach ( $res as $row ) {
- list( $mediaType, $mime, $totalCount, $totalBytes ) = $this->splitFakeTitle( $row->title );
+ $mediaStats = $this->splitFakeTitle( $row->title );
+ if ( count( $mediaStats ) < 4 ) {
+ continue;
+ }
+ list( $mediaType, $mime, $totalCount, $totalBytes ) = $mediaStats;
if ( $prevMediaType !== $mediaType ) {
if ( $prevMediaType !== null ) {
// We're not at beginning, so we have to
@@ -232,7 +236,7 @@ class MediaStatisticsPage extends QueryPage {
'mw-mediastats-table-' . strtolower( $mediaType ),
'sortable',
'wikitable'
- ))
+ ) )
)
);
$this->getOutput()->addHTML( $this->getTableHeaderRow() );
@@ -271,7 +275,7 @@ class MediaStatisticsPage extends QueryPage {
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,
diff --git a/includes/specials/SpecialMergeHistory.php b/includes/specials/SpecialMergeHistory.php
index 1f0b6d45..7edf961a 100644
--- a/includes/specials/SpecialMergeHistory.php
+++ b/includes/specials/SpecialMergeHistory.php
@@ -109,6 +109,8 @@ class SpecialMergeHistory extends SpecialPage {
}
public function execute( $par ) {
+ $this->useTransactionalTimeLimit();
+
$this->checkPermissions();
$this->checkReadOnly();
diff --git a/includes/specials/SpecialMostcategories.php b/includes/specials/SpecialMostcategories.php
index c70bbdba..18083f61 100644
--- a/includes/specials/SpecialMostcategories.php
+++ b/includes/specials/SpecialMostcategories.php
@@ -34,7 +34,7 @@ class MostcategoriesPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() {
+ public function isExpensive() {
return true;
}
@@ -42,7 +42,7 @@ class MostcategoriesPage extends QueryPage {
return false;
}
- function getQueryInfo() {
+ public function getQueryInfo() {
return array(
'tables' => array( 'categorylinks', 'page' ),
'fields' => array(
diff --git a/includes/specials/SpecialMostinterwikis.php b/includes/specials/SpecialMostinterwikis.php
index ab3d9c91..b07b8331 100644
--- a/includes/specials/SpecialMostinterwikis.php
+++ b/includes/specials/SpecialMostinterwikis.php
@@ -34,7 +34,7 @@ class MostinterwikisPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() {
+ public function isExpensive() {
return true;
}
@@ -42,7 +42,7 @@ class MostinterwikisPage extends QueryPage {
return false;
}
- function getQueryInfo() {
+ public function getQueryInfo() {
return array(
'tables' => array(
'langlinks',
diff --git a/includes/specials/SpecialMostlinked.php b/includes/specials/SpecialMostlinked.php
index ae0b0708..019df493 100644
--- a/includes/specials/SpecialMostlinked.php
+++ b/includes/specials/SpecialMostlinked.php
@@ -35,7 +35,7 @@ class MostlinkedPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() {
+ public function isExpensive() {
return true;
}
@@ -43,7 +43,7 @@ class MostlinkedPage extends QueryPage {
return false;
}
- function getQueryInfo() {
+ public function getQueryInfo() {
return array(
'tables' => array( 'pagelinks', 'page' ),
'fields' => array(
diff --git a/includes/specials/SpecialMostlinkedcategories.php b/includes/specials/SpecialMostlinkedcategories.php
index cc718e06..6eeab91b 100644
--- a/includes/specials/SpecialMostlinkedcategories.php
+++ b/includes/specials/SpecialMostlinkedcategories.php
@@ -38,7 +38,7 @@ class MostlinkedCategoriesPage extends QueryPage {
return false;
}
- function getQueryInfo() {
+ public function getQueryInfo() {
return array(
'tables' => array( 'category' ),
'fields' => array( 'title' => 'cat_title',
diff --git a/includes/specials/SpecialMovepage.php b/includes/specials/SpecialMovepage.php
index ae1fefea..8091f1b0 100644
--- a/includes/specials/SpecialMovepage.php
+++ b/includes/specials/SpecialMovepage.php
@@ -28,7 +28,7 @@
*/
class MovePageForm extends UnlistedSpecialPage {
/** @var Title */
- protected $oldTitle;
+ protected $oldTitle = null;
/** @var Title */
protected $newTitle;
@@ -64,6 +64,8 @@ class MovePageForm extends UnlistedSpecialPage {
}
public function execute( $par ) {
+ $this->useTransactionalTimeLimit();
+
$this->checkReadOnly();
$this->setHeaders();
@@ -75,9 +77,12 @@ class MovePageForm extends UnlistedSpecialPage {
// Yes, the use of getVal() and getText() is wanted, see bug 20365
$oldTitleText = $request->getVal( 'wpOldTitle', $target );
- $this->oldTitle = Title::newFromText( $oldTitleText );
+ if ( is_string( $oldTitleText ) ) {
+ $this->oldTitle = Title::newFromText( $oldTitleText );
+ }
- if ( is_null( $this->oldTitle ) ) {
+ if ( $this->oldTitle === null ) {
+ // Either oldTitle wasn't passed, or newFromText returned null
throw new ErrorPageError( 'notargettitle', 'notargettext' );
}
if ( !$this->oldTitle->exists() ) {
@@ -99,7 +104,9 @@ class MovePageForm extends UnlistedSpecialPage {
$permErrors = $this->oldTitle->getUserPermissionsErrors( 'move', $user );
if ( count( $permErrors ) ) {
// Auto-block user's IP if the account was "hard" blocked
- $user->spreadAnyEditBlock();
+ DeferredUpdates::addCallableUpdate( function() use ( $user ) {
+ $user->spreadAnyEditBlock();
+ } );
throw new PermissionsError( 'move', $permErrors );
}
@@ -140,6 +147,7 @@ class MovePageForm extends UnlistedSpecialPage {
$out = $this->getOutput();
$out->setPageTitle( $this->msg( 'move-page', $this->oldTitle->getPrefixedText() ) );
$out->addModules( 'mediawiki.special.movePage' );
+ $out->addModuleStyles( 'mediawiki.special.movePage.styles' );
$this->addHelpLink( 'Help:Moving a page' );
$newTitle = $this->newTitle;
@@ -283,7 +291,6 @@ class MovePageForm extends UnlistedSpecialPage {
// is enforced in the mediawiki.special.movePage module
$immovableNamespaces = array();
-
foreach ( array_keys( $this->getLanguage()->getNamespaces() ) as $nsId ) {
if ( !MWNamespace::isMovable( $nsId ) ) {
$immovableNamespaces[] = $nsId;
@@ -292,202 +299,207 @@ class MovePageForm extends UnlistedSpecialPage {
$handler = ContentHandler::getForTitle( $this->oldTitle );
- $out->addHTML(
- Xml::openElement(
- 'form',
- array(
- 'method' => 'post',
- 'action' => $this->getPageTitle()->getLocalURL( 'action=submit' ),
- 'id' => 'movepage'
- )
- ) .
- Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, $this->msg( 'move-page-legend' )->text() ) .
- Xml::openElement( 'table', array( 'id' => 'mw-movepage-table' ) )
+ $out->enableOOUI();
+ $fields = array();
+
+ $fields[] = new OOUI\FieldLayout(
+ new OOUI\LabelWidget( array(
+ 'label' => new OOUI\HtmlSnippet( "<strong>$oldTitleLink</strong>" )
+ ) ),
+ array(
+ 'label' => $this->msg( 'movearticle' )->text(),
+ 'align' => 'top',
+ )
);
- $out->addHTML(
- "<tr>
- <td class='mw-label'>" .
- $this->msg( 'movearticle' )->escaped() .
- "</td>
- <td class='mw-input'>
- <strong>{$oldTitleLink}</strong>
- </td>
- </tr>
- <tr>
- <td class='mw-label'>" .
- Xml::label( $this->msg( 'newtitle' )->text(), 'wpNewTitleMain' ) .
- "</td>
- <td class='mw-input'>" .
- Html::namespaceSelector(
- array(
- 'selected' => $newTitle->getNamespace(),
- 'exclude' => $immovableNamespaces
- ),
- array( 'name' => 'wpNewTitleNs', 'id' => 'wpNewTitleNs' )
- ) .
- Xml::input(
- 'wpNewTitleMain',
- 60,
- $wgContLang->recodeForEdit( $newTitle->getText() ),
- array(
- 'type' => 'text',
- 'id' => 'wpNewTitleMain',
- 'maxlength' => 255
- )
- ) .
- Html::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
- "</td>
- </tr>
- <tr>
- <td class='mw-label'>" .
- Xml::label( $this->msg( 'movereason' )->text(), 'wpReason' ) .
- "</td>
- <td class='mw-input'>" .
- Xml::input( 'wpReason', 60, $this->reason, array(
- 'type' => 'text',
- 'id' => 'wpReason',
- 'maxlength' => 200,
- ) ) .
- "</td>
- </tr>"
+ $fields[] = new OOUI\FieldLayout(
+ new MediaWiki\Widget\ComplexTitleInputWidget( array(
+ 'id' => 'wpNewTitle',
+ 'namespace' => array(
+ 'id' => 'wpNewTitleNs',
+ 'name' => 'wpNewTitleNs',
+ 'value' => $newTitle->getNamespace(),
+ 'exclude' => $immovableNamespaces,
+ ),
+ 'title' => array(
+ 'id' => 'wpNewTitleMain',
+ 'name' => 'wpNewTitleMain',
+ 'value' => $wgContLang->recodeForEdit( $newTitle->getText() ),
+ // Inappropriate, since we're expecting the user to input a non-existent page's title
+ 'suggestions' => false,
+ ),
+ 'infusable' => true,
+ ) ),
+ array(
+ 'label' => $this->msg( 'newtitle' )->text(),
+ 'align' => 'top',
+ )
+ );
+
+ $fields[] = new OOUI\FieldLayout(
+ new OOUI\TextInputWidget( array(
+ 'name' => 'wpReason',
+ 'id' => 'wpReason',
+ 'maxLength' => 200,
+ 'infusable' => true,
+ ) ),
+ array(
+ 'label' => $this->msg( 'movereason' )->text(),
+ 'align' => 'top',
+ )
);
if ( $considerTalk ) {
- $out->addHTML( "
- <tr>
- <td></td>
- <td class='mw-input'>" .
- Xml::checkLabel(
- $this->msg( 'movetalk' )->text(),
- 'wpMovetalk',
- 'wpMovetalk',
- $this->moveTalk
- ) .
- "</td>
- </tr>"
+ $fields[] = new OOUI\FieldLayout(
+ new OOUI\CheckboxInputWidget( array(
+ 'name' => 'wpMovetalk',
+ 'id' => 'wpMovetalk',
+ 'value' => '1',
+ 'selected' => $this->moveTalk,
+ ) ),
+ array(
+ 'label' => $this->msg( 'movetalk' )->text(),
+ 'align' => 'inline',
+ )
);
}
if ( $user->isAllowed( 'suppressredirect' ) ) {
if ( $handler->supportsRedirects() ) {
$isChecked = $this->leaveRedirect;
- $options = array();
+ $isDisabled = false;
} else {
$isChecked = false;
- $options = array(
- 'disabled' => 'disabled'
- );
+ $isDisabled = true;
}
- $out->addHTML( "
- <tr>
- <td></td>
- <td class='mw-input'>" .
- Xml::checkLabel(
- $this->msg( 'move-leave-redirect' )->text(),
- 'wpLeaveRedirect',
- 'wpLeaveRedirect',
- $isChecked,
- $options
- ) .
- "</td>
- </tr>"
+ $fields[] = new OOUI\FieldLayout(
+ new OOUI\CheckboxInputWidget( array(
+ 'name' => 'wpLeaveRedirect',
+ 'id' => 'wpLeaveRedirect',
+ 'value' => '1',
+ 'selected' => $isChecked,
+ 'disabled' => $isDisabled,
+ ) ),
+ array(
+ 'label' => $this->msg( 'move-leave-redirect' )->text(),
+ 'align' => 'inline',
+ )
);
}
if ( $hasRedirects ) {
- $out->addHTML( "
- <tr>
- <td></td>
- <td class='mw-input'>" .
- Xml::checkLabel(
- $this->msg( 'fix-double-redirects' )->text(),
- 'wpFixRedirects',
- 'wpFixRedirects',
- $this->fixRedirects
- ) .
- "</td>
- </tr>"
+ $fields[] = new OOUI\FieldLayout(
+ new OOUI\CheckboxInputWidget( array(
+ 'name' => 'wpFixRedirects',
+ 'id' => 'wpFixRedirects',
+ 'value' => '1',
+ 'selected' => $this->fixRedirects,
+ ) ),
+ array(
+ 'label' => $this->msg( 'fix-double-redirects' )->text(),
+ 'align' => 'inline',
+ )
);
}
if ( $canMoveSubpage ) {
$maximumMovedPages = $this->getConfig()->get( 'MaximumMovedPages' );
- $out->addHTML( "
- <tr>
- <td></td>
- <td class='mw-input'>" .
- Xml::check(
- 'wpMovesubpages',
- # Don't check the box if we only have talk subpages to
- # move and we aren't moving the talk page.
- $this->moveSubpages && ( $this->oldTitle->hasSubpages() || $this->moveTalk ),
- array( 'id' => 'wpMovesubpages' )
- ) . '&#160;' .
- Xml::tags(
- 'label',
- array( 'for' => 'wpMovesubpages' ),
- $this->msg(
- ( $this->oldTitle->hasSubpages()
- ? 'move-subpages'
- : 'move-talk-subpages' )
- )->numParams( $maximumMovedPages )->params( $maximumMovedPages )->parse()
- ) .
- "</td>
- </tr>"
+ $fields[] = new OOUI\FieldLayout(
+ new OOUI\CheckboxInputWidget( array(
+ 'name' => 'wpMovesubpages',
+ 'id' => 'wpMovesubpages',
+ 'value' => '1',
+ # Don't check the box if we only have talk subpages to
+ # move and we aren't moving the talk page.
+ 'selected' => $this->moveSubpages && ( $this->oldTitle->hasSubpages() || $this->moveTalk ),
+ ) ),
+ array(
+ 'label' => new OOUI\HtmlSnippet(
+ $this->msg(
+ ( $this->oldTitle->hasSubpages()
+ ? 'move-subpages'
+ : 'move-talk-subpages' )
+ )->numParams( $maximumMovedPages )->params( $maximumMovedPages )->parse()
+ ),
+ 'align' => 'inline',
+ )
);
}
- $watchChecked = $user->isLoggedIn() && ( $this->watch || $user->getBoolOption( 'watchmoves' )
- || $user->isWatched( $this->oldTitle ) );
# Don't allow watching if user is not logged in
if ( $user->isLoggedIn() ) {
- $out->addHTML( "
- <tr>
- <td></td>
- <td class='mw-input'>" .
- Xml::checkLabel(
- $this->msg( 'move-watch' )->text(),
- 'wpWatch',
- 'watch',
- $watchChecked
- ) .
- "</td>
- </tr>"
+ $watchChecked = $user->isLoggedIn() && ( $this->watch || $user->getBoolOption( 'watchmoves' )
+ || $user->isWatched( $this->oldTitle ) );
+ $fields[] = new OOUI\FieldLayout(
+ new OOUI\CheckboxInputWidget( array(
+ 'name' => 'wpWatch',
+ 'id' => 'watch', # ew
+ 'value' => '1',
+ 'selected' => $watchChecked,
+ ) ),
+ array(
+ 'label' => $this->msg( 'move-watch' )->text(),
+ 'align' => 'inline',
+ )
);
}
if ( $confirm ) {
- $out->addHTML( "
- <tr>
- <td></td>
- <td class='mw-input'>" .
- Xml::checkLabel(
- $this->msg( 'delete_and_move_confirm' )->text(),
- 'wpConfirm',
- 'wpConfirm'
- ) .
- "</td>
- </tr>"
+ $watchChecked = $user->isLoggedIn() && ( $this->watch || $user->getBoolOption( 'watchmoves' )
+ || $user->isWatched( $this->oldTitle ) );
+ $fields[] = new OOUI\FieldLayout(
+ new OOUI\CheckboxInputWidget( array(
+ 'name' => 'wpConfirm',
+ 'id' => 'wpConfirm',
+ 'value' => '1',
+ ) ),
+ array(
+ 'label' => $this->msg( 'delete_and_move_confirm' )->text(),
+ 'align' => 'inline',
+ )
);
}
- $out->addHTML( "
- <tr>
- <td></td>
- <td class='mw-submit'>" .
- Xml::submitButton( $movepagebtn, array( 'name' => $submitVar ) ) .
- "</td>
- </tr>"
+ $fields[] = new OOUI\FieldLayout(
+ new OOUI\ButtonInputWidget( array(
+ 'name' => $submitVar,
+ 'value' => $movepagebtn,
+ 'label' => $movepagebtn,
+ 'flags' => array( 'progressive', 'primary' ),
+ 'type' => 'submit',
+ ) ),
+ array(
+ 'align' => 'top',
+ )
+ );
+
+ $fieldset = new OOUI\FieldsetLayout( array(
+ 'label' => $this->msg( 'move-page-legend' )->text(),
+ 'id' => 'mw-movepage-table',
+ 'items' => $fields,
+ ) );
+
+ $form = new OOUI\FormLayout( array(
+ 'method' => 'post',
+ 'action' => $this->getPageTitle()->getLocalURL( 'action=submit' ),
+ 'id' => 'movepage',
+ ) );
+ $form->appendContent(
+ $fieldset,
+ new OOUI\HtmlSnippet(
+ Html::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
+ Html::hidden( 'wpEditToken', $user->getEditToken() )
+ )
);
$out->addHTML(
- Xml::closeElement( 'table' ) .
- Html::hidden( 'wpEditToken', $user->getEditToken() ) .
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' ) .
- "\n"
+ new OOUI\PanelLayout( array(
+ 'classes' => array( 'movepage-wrapper' ),
+ 'expanded' => false,
+ 'padded' => true,
+ 'framed' => true,
+ 'content' => $form,
+ ) )
);
$this->showLogFragment( $this->oldTitle );
diff --git a/includes/specials/SpecialMyLanguage.php b/includes/specials/SpecialMyLanguage.php
index 6cea1581..3d8ff97b 100644
--- a/includes/specials/SpecialMyLanguage.php
+++ b/includes/specials/SpecialMyLanguage.php
@@ -41,11 +41,11 @@ class SpecialMyLanguage extends RedirectSpecialArticle {
* 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
+ * @param string|null $subpage
+ * @return Title
*/
- public function getRedirect( $par ) {
- $title = $this->findTitle( $par );
+ public function getRedirect( $subpage ) {
+ $title = $this->findTitle( $subpage );
// Go to the main page if given invalid title.
if ( !$title ) {
$title = Title::newMainPage();
@@ -59,18 +59,22 @@ class SpecialMyLanguage extends RedirectSpecialArticle {
* it returns Page/fi if it exists, otherwise Page/de if it exists,
* otherwise Page.
*
- * @param string $par
+ * @param string|null $subpage
* @return Title|null
*/
- public function findTitle( $par ) {
+ public function findTitle( $subpage ) {
// base = title without language code suffix
// provided = the title as it was given
- $base = $provided = Title::newFromText( $par );
+ $base = $provided = null;
+ if ( $subpage !== null ) {
+ $provided = Title::newFromText( $subpage );
+ $base = $provided;
+ }
- if ( $base && strpos( $par, '/' ) !== false ) {
- $pos = strrpos( $par, '/' );
- $basepage = substr( $par, 0, $pos );
- $code = substr( $par, $pos + 1 );
+ if ( $provided && strpos( $subpage, '/' ) !== false ) {
+ $pos = strrpos( $subpage, '/' );
+ $basepage = substr( $subpage, 0, $pos );
+ $code = substr( $subpage, $pos + 1 );
if ( strlen( $code ) && Language::isKnownLanguageTag( $code ) ) {
$base = Title::newFromText( $basepage );
}
diff --git a/includes/specials/SpecialMyRedirectPages.php b/includes/specials/SpecialMyRedirectPages.php
index 9b8d52bb..5ef03f13 100644
--- a/includes/specials/SpecialMyRedirectPages.php
+++ b/includes/specials/SpecialMyRedirectPages.php
@@ -30,16 +30,20 @@
* @ingroup SpecialPage
*/
class SpecialMypage extends RedirectSpecialArticle {
- function __construct() {
+ public function __construct() {
parent::__construct( 'Mypage' );
}
- function getRedirect( $subpage ) {
- if ( strval( $subpage ) !== '' ) {
- return Title::makeTitle( NS_USER, $this->getUser()->getName() . '/' . $subpage );
- } else {
+ /**
+ * @param string|null $subpage
+ * @return Title
+ */
+ public function getRedirect( $subpage ) {
+ if ( $subpage === null || $subpage === '' ) {
return Title::makeTitle( NS_USER, $this->getUser()->getName() );
}
+
+ return Title::makeTitle( NS_USER, $this->getUser()->getName() . '/' . $subpage );
}
}
@@ -49,16 +53,20 @@ class SpecialMypage extends RedirectSpecialArticle {
* @ingroup SpecialPage
*/
class SpecialMytalk extends RedirectSpecialArticle {
- function __construct() {
+ public function __construct() {
parent::__construct( 'Mytalk' );
}
- function getRedirect( $subpage ) {
- if ( strval( $subpage ) !== '' ) {
- return Title::makeTitle( NS_USER_TALK, $this->getUser()->getName() . '/' . $subpage );
- } else {
+ /**
+ * @param string|null $subpage
+ * @return Title
+ */
+ public function getRedirect( $subpage ) {
+ if ( $subpage === null || $subpage === '' ) {
return Title::makeTitle( NS_USER_TALK, $this->getUser()->getName() );
}
+
+ return Title::makeTitle( NS_USER_TALK, $this->getUser()->getName() . '/' . $subpage );
}
}
@@ -68,13 +76,18 @@ class SpecialMytalk extends RedirectSpecialArticle {
* @ingroup SpecialPage
*/
class SpecialMycontributions extends RedirectSpecialPage {
- function __construct() {
+ public function __construct() {
parent::__construct( 'Mycontributions' );
$this->mAllowedRedirectParams = array( 'limit', 'namespace', 'tagfilter',
- 'offset', 'dir', 'year', 'month', 'feed' );
+ 'offset', 'dir', 'year', 'month', 'feed', 'deletedOnly',
+ 'nsInvert', 'associated', 'newOnly', 'topOnly' );
}
- function getRedirect( $subpage ) {
+ /**
+ * @param string|null $subpage
+ * @return Title
+ */
+ public function getRedirect( $subpage ) {
return SpecialPage::getTitleFor( 'Contributions', $this->getUser()->getName() );
}
}
@@ -85,12 +98,16 @@ class SpecialMycontributions extends RedirectSpecialPage {
* @ingroup SpecialPage
*/
class SpecialMyuploads extends RedirectSpecialPage {
- function __construct() {
+ public function __construct() {
parent::__construct( 'Myuploads' );
$this->mAllowedRedirectParams = array( 'limit', 'ilshowall', 'ilsearch' );
}
- function getRedirect( $subpage ) {
+ /**
+ * @param string|null $subpage
+ * @return Title
+ */
+ public function getRedirect( $subpage ) {
return SpecialPage::getTitleFor( 'Listfiles', $this->getUser()->getName() );
}
}
@@ -101,12 +118,16 @@ class SpecialMyuploads extends RedirectSpecialPage {
* @ingroup SpecialPage
*/
class SpecialAllMyUploads extends RedirectSpecialPage {
- function __construct() {
+ public function __construct() {
parent::__construct( 'AllMyUploads' );
$this->mAllowedRedirectParams = array( 'limit', 'ilsearch' );
}
- function getRedirect( $subpage ) {
+ /**
+ * @param string|null $subpage
+ * @return Title
+ */
+ public function getRedirect( $subpage ) {
$this->mAddedRedirectParams['ilshowall'] = 1;
return SpecialPage::getTitleFor( 'Listfiles', $this->getUser()->getName() );
diff --git a/includes/specials/SpecialNewpages.php b/includes/specials/SpecialNewpages.php
index 899c7368..251a8e03 100644
--- a/includes/specials/SpecialNewpages.php
+++ b/includes/specials/SpecialNewpages.php
@@ -233,7 +233,7 @@ class SpecialNewpages extends IncludableSpecialPage {
'name' => 'invert',
'label-message' => 'invert',
'default' => $nsinvert,
- 'tooltip' => $this->msg( 'tooltip-invert' )->text(),
+ 'tooltip' => 'invert',
),
'tagFilter' => array(
'type' => 'tagfilter',
@@ -594,7 +594,7 @@ class NewPagesPager extends ReverseChronologicalPager {
foreach ( $this->mResult as $row ) {
$linkBatch->add( NS_USER, $row->rc_user_text );
$linkBatch->add( NS_USER_TALK, $row->rc_user_text );
- $linkBatch->add( $row->rc_namespace, $row->rc_title );
+ $linkBatch->add( $row->page_namespace, $row->page_title );
}
$linkBatch->execute();
diff --git a/includes/specials/SpecialPageLanguage.php b/includes/specials/SpecialPageLanguage.php
index 79b2444e..6756f274 100644
--- a/includes/specials/SpecialPageLanguage.php
+++ b/includes/specials/SpecialPageLanguage.php
@@ -87,11 +87,14 @@ class SpecialPageLanguage extends FormSpecialPage {
}
protected function postText() {
- return $this->showLogFragment( $this->par );
+ if ( $this->par ) {
+ return $this->showLogFragment( $this->par );
+ }
+ return '';
}
protected function getDisplayFormat() {
- return 'vform';
+ return 'ooui';
}
public function alterForm( HTMLForm $form ) {
diff --git a/includes/specials/SpecialPagesWithProp.php b/includes/specials/SpecialPagesWithProp.php
index 670a3973..f211ec9b 100644
--- a/includes/specials/SpecialPagesWithProp.php
+++ b/includes/specials/SpecialPagesWithProp.php
@@ -40,7 +40,7 @@ class SpecialPagesWithProp extends QueryPage {
return false;
}
- function execute( $par ) {
+ public function execute( $par ) {
$this->setHeaders();
$this->outputHeader();
$this->getOutput()->addModuleStyles( 'mediawiki.special.pagesWithProp' );
@@ -100,7 +100,7 @@ class SpecialPagesWithProp extends QueryPage {
return false;
}
- function getQueryInfo() {
+ public function getQueryInfo() {
return array(
'tables' => array( 'page_props', 'page' ),
'fields' => array(
@@ -113,9 +113,11 @@ class SpecialPagesWithProp extends QueryPage {
'pp_value',
),
'conds' => array(
- 'page_id = pp_page',
'pp_propname' => $this->propName,
),
+ 'join_conds' => array(
+ 'page' => array( 'INNER JOIN', 'page_id = pp_page' )
+ ),
'options' => array()
);
}
diff --git a/includes/specials/SpecialPasswordReset.php b/includes/specials/SpecialPasswordReset.php
index a2dc2add..8cad6168 100644
--- a/includes/specials/SpecialPasswordReset.php
+++ b/includes/specials/SpecialPasswordReset.php
@@ -104,7 +104,7 @@ class SpecialPasswordReset extends FormSpecialPage {
}
protected function getDisplayFormat() {
- return 'vform';
+ return 'ooui';
}
public function alterForm( HTMLForm $form ) {
diff --git a/includes/specials/SpecialPermanentLink.php b/includes/specials/SpecialPermanentLink.php
index 17115e88..53789c0d 100644
--- a/includes/specials/SpecialPermanentLink.php
+++ b/includes/specials/SpecialPermanentLink.php
@@ -27,12 +27,16 @@
* @ingroup SpecialPage
*/
class SpecialPermanentLink extends RedirectSpecialPage {
- function __construct() {
+ public function __construct() {
parent::__construct( 'PermanentLink' );
$this->mAllowedRedirectParams = array();
}
- function getRedirect( $subpage ) {
+ /**
+ * @param string|null $subpage
+ * @return Title|bool
+ */
+ public function getRedirect( $subpage ) {
$subpage = intval( $subpage );
if ( $subpage === 0 ) {
# throw an error page when no subpage was given
diff --git a/includes/specials/SpecialPreferences.php b/includes/specials/SpecialPreferences.php
index 7371da74..4b75e5f6 100644
--- a/includes/specials/SpecialPreferences.php
+++ b/includes/specials/SpecialPreferences.php
@@ -50,7 +50,14 @@ class SpecialPreferences extends SpecialPage {
if ( $this->getRequest()->getCheck( 'success' ) ) {
$out->wrapWikiMsg(
- "<div class=\"successbox\">\n$1\n</div>",
+ Html::rawElement(
+ 'div',
+ array(
+ 'class' => 'mw-preferences-messagebox successbox',
+ 'id' => 'mw-preferences-success'
+ ),
+ Html::element( 'p', array(), '$1' )
+ ),
'savedprefs'
);
}
diff --git a/includes/specials/SpecialProtectedtitles.php b/includes/specials/SpecialProtectedtitles.php
index dd9198cb..85ce78ff 100644
--- a/includes/specials/SpecialProtectedtitles.php
+++ b/includes/specials/SpecialProtectedtitles.php
@@ -67,16 +67,8 @@ class SpecialProtectedtitles extends SpecialPage {
* @return string
*/
function formatRow( $row ) {
-
- static $infinity = null;
-
- if ( is_null( $infinity ) ) {
- $infinity = wfGetDB( DB_SLAVE )->getInfinity();
- }
-
$title = Title::makeTitleSafe( $row->pt_namespace, $row->pt_title );
if ( !$title ) {
-
return Html::rawElement(
'li',
array(),
@@ -100,9 +92,9 @@ class SpecialProtectedtitles extends SpecialPage {
$lang = $this->getLanguage();
$expiry = strlen( $row->pt_expiry ) ?
$lang->formatExpiry( $row->pt_expiry, TS_MW ) :
- $infinity;
+ 'infinity';
- if ( $expiry != $infinity ) {
+ if ( $expiry !== 'infinity' ) {
$user = $this->getUser();
$description_items[] = $this->msg(
'protect-expiring-local',
diff --git a/includes/specials/SpecialRandomInCategory.php b/includes/specials/SpecialRandomInCategory.php
index b5c9e19a..7cf6b0a1 100644
--- a/includes/specials/SpecialRandomInCategory.php
+++ b/includes/specials/SpecialRandomInCategory.php
@@ -70,15 +70,15 @@ class SpecialRandomInCategory extends FormSpecialPage {
protected function getFormFields() {
$this->addHelpLink( 'Help:RandomInCategory' );
- $form = array(
+ return array(
'category' => array(
- 'type' => 'text',
+ 'type' => 'title',
+ 'namespace' => NS_CATEGORY,
+ 'relative' => true,
'label-message' => 'randomincategory-category',
'required' => true,
)
);
-
- return $form;
}
public function requiresWrite() {
@@ -89,6 +89,14 @@ class SpecialRandomInCategory extends FormSpecialPage {
return false;
}
+ protected function getDisplayFormat() {
+ return 'ooui';
+ }
+
+ protected function alterForm( HTMLForm $form ) {
+ $form->setSubmitTextMsg( 'randomincategory-submit' );
+ }
+
protected function setParameter( $par ) {
// if subpage present, fake form submission
$this->onSubmit( array( 'category' => $par ) );
diff --git a/includes/specials/SpecialRandompage.php b/includes/specials/SpecialRandompage.php
index 73a88b9e..9f7ef669 100644
--- a/includes/specials/SpecialRandompage.php
+++ b/includes/specials/SpecialRandompage.php
@@ -135,20 +135,26 @@ class RandomPage extends SpecialPage {
protected function getQueryInfo( $randstr ) {
$redirect = $this->isRedirect() ? 1 : 0;
+ $tables = array( 'page' );
+ $conds = array_merge( array(
+ 'page_namespace' => $this->namespaces,
+ 'page_is_redirect' => $redirect,
+ 'page_random >= ' . $randstr
+ ), $this->extra );
+ $joinConds = array();
+
+ // Allow extensions to modify the query
+ Hooks::run( 'RandomPageQuery', array( &$tables, &$conds, &$joinConds ) );
return array(
- 'tables' => array( 'page' ),
+ 'tables' => $tables,
'fields' => array( 'page_title', 'page_namespace' ),
- 'conds' => array_merge( array(
- 'page_namespace' => $this->namespaces,
- 'page_is_redirect' => $redirect,
- 'page_random >= ' . $randstr
- ), $this->extra ),
+ 'conds' => $conds,
'options' => array(
'ORDER BY' => 'page_random',
'LIMIT' => 1,
),
- 'join_conds' => array()
+ 'join_conds' => $joinConds
);
}
diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php
index 64b0ecae..0f201d52 100644
--- a/includes/specials/SpecialRecentchanges.php
+++ b/includes/specials/SpecialRecentchanges.php
@@ -57,6 +57,10 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
return;
}
+ $this->addHelpLink(
+ '//meta.wikimedia.org/wiki/Special:MyLanguage/Help:Recent_changes',
+ true
+ );
parent::execute( $subpage );
}
@@ -233,14 +237,21 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
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).
+ // array_merge() is used intentionally here so that hooks can, should
+ // they so desire, override the ORDER BY / LIMIT condition(s); prior to
+ // MediaWiki 1.26 this used to use the plus operator instead, which meant
+ // that extensions weren't able to change these conditions
+ $query_options = array_merge( array(
+ 'ORDER BY' => 'rc_timestamp DESC',
+ 'LIMIT' => $opts['limit'] ), $query_options );
$rows = $dbr->select(
$tables,
$fields,
+ // 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).
$conds + array( 'rc_new' => array( 0, 1 ) ),
__METHOD__,
- array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $opts['limit'] ) + $query_options,
+ $query_options,
$join_conds
);
@@ -263,6 +274,10 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
);
}
+ protected function getDB() {
+ return wfGetDB( DB_SLAVE, 'recentchanges' );
+ }
+
public function outputFeedLinks() {
$this->addFeedLinks( $this->getFeedQuery() );
}
@@ -272,7 +287,7 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
*
* @return array
*/
- private function getFeedQuery() {
+ protected function getFeedQuery() {
$query = array_filter( $this->getOptions()->getAllValues(), function ( $value ) {
// API handles empty parameters in a different way
return $value !== '';
diff --git a/includes/specials/SpecialRecentchangeslinked.php b/includes/specials/SpecialRecentchangeslinked.php
index 3ad9f0f4..3c403feb 100644
--- a/includes/specials/SpecialRecentchangeslinked.php
+++ b/includes/specials/SpecialRecentchangeslinked.php
@@ -244,6 +244,7 @@ class SpecialRecentChangesLinked extends SpecialRecentChanges {
Xml::check( 'showlinkedto', $opts['showlinkedto'], array( 'id' => 'showlinkedto' ) ) . ' ' .
Xml::label( $this->msg( 'recentchangeslinked-to' )->text(), 'showlinkedto' ) );
+ $this->addHelpLink( 'Help:Related changes' );
return $extraOpts;
}
diff --git a/includes/specials/SpecialResetTokens.php b/includes/specials/SpecialResetTokens.php
index ba2b9a5b..cba5a449 100644
--- a/includes/specials/SpecialResetTokens.php
+++ b/includes/specials/SpecialResetTokens.php
@@ -25,6 +25,7 @@
* Let users reset tokens like the watchlist token.
*
* @ingroup SpecialPage
+ * @deprecated 1.26
*/
class SpecialResetTokens extends FormSpecialPage {
private $tokensList;
@@ -123,6 +124,10 @@ class SpecialResetTokens extends FormSpecialPage {
}
}
+ protected function getDisplayFormat() {
+ return 'ooui';
+ }
+
public function onSubmit( array $formData ) {
if ( $formData['tokens'] ) {
$user = $this->getUser();
diff --git a/includes/specials/SpecialRevisiondelete.php b/includes/specials/SpecialRevisiondelete.php
index 3b5ef9d4..d1072b4e 100644
--- a/includes/specials/SpecialRevisiondelete.php
+++ b/includes/specials/SpecialRevisiondelete.php
@@ -110,6 +110,8 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
}
public function execute( $par ) {
+ $this->useTransactionalTimeLimit();
+
$this->checkPermissions();
$this->checkReadOnly();
@@ -158,6 +160,13 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$this->ids
);
+ # We need a target page!
+ if ( $this->targetObj === null ) {
+ $output->addWikiMsg( 'undelete-header' );
+
+ return;
+ }
+
$this->typeLabels = self::$UILabels[$this->typeName];
$list = $this->getList();
$list->reset();
@@ -168,12 +177,6 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$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
$this->showConvenienceLinks();
@@ -449,9 +452,8 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
Xml::closeElement( 'form' ) . "\n";
// Show link to edit the dropdown reasons
if ( $this->getUser()->isAllowed( 'editinterface' ) ) {
- $title = Title::makeTitle( NS_MEDIAWIKI, 'Revdelete-reason-dropdown' );
- $link = Linker::link(
- $title,
+ $link = Linker::linkKnown(
+ $this->msg( 'revdelete-reason-dropdown' )->inContentLanguage()->getTitle(),
$this->msg( 'revdelete-edit-reasonlist' )->escaped(),
array(),
array( 'action' => 'edit' )
@@ -585,7 +587,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
throw new PermissionsError( 'suppressrevision' );
}
# If the save went through, go to success message...
- $status = $this->save( $bitParams, $comment, $this->targetObj );
+ $status = $this->save( $bitParams, $comment );
if ( $status->isGood() ) {
$this->success();
@@ -605,7 +607,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
// Messages: revdelete-success, logdelete-success
$this->getOutput()->setPageTitle( $this->msg( 'actioncomplete' ) );
$this->getOutput()->wrapWikiMsg(
- "<span class=\"success\">\n$1\n</span>",
+ "<div class=\"successbox\">\n$1\n</div>",
$this->typeLabels['success']
);
$this->wasSaved = true;
@@ -620,7 +622,10 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
protected function failure( $status ) {
// Messages: revdelete-failure, logdelete-failure
$this->getOutput()->setPageTitle( $this->msg( 'actionfailed' ) );
- $this->getOutput()->addWikiText( $status->getWikiText( $this->typeLabels['failure'] ) );
+ $this->getOutput()->addWikiText( '<div class="errorbox">' .
+ $status->getWikiText( $this->typeLabels['failure'] ) .
+ '</div>'
+ );
$this->showForm();
}
@@ -648,14 +653,13 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
/**
* Do the write operations. Simple wrapper for RevDel*List::setVisibility().
- * @param int $bitfield
+ * @param array $bitPars ExtractBitParams() bitfield array
* @param string $reason
- * @param Title $title
* @return Status
*/
- protected function save( $bitfield, $reason, $title ) {
+ protected function save( array $bitPars, $reason ) {
return $this->getList()->setVisibility(
- array( 'value' => $bitfield, 'comment' => $reason )
+ array( 'value' => $bitPars, 'comment' => $reason )
);
}
diff --git a/includes/specials/SpecialRunJobs.php b/includes/specials/SpecialRunJobs.php
index 8cf93670..286a7456 100644
--- a/includes/specials/SpecialRunJobs.php
+++ b/includes/specials/SpecialRunJobs.php
@@ -38,14 +38,14 @@ class SpecialRunJobs extends UnlistedSpecialPage {
$this->getOutput()->disable();
if ( wfReadOnly() ) {
- header( "HTTP/1.0 423 Locked" );
+ // HTTP 423 Locked
+ HttpStatus::header( 423 );
print 'Wiki is in read-only mode';
return;
} elseif ( !$this->getRequest()->wasPosted() ) {
- header( "HTTP/1.0 400 Bad Request" );
+ HttpStatus::header( 400 );
print 'Request must be POSTed';
-
return;
}
@@ -55,9 +55,8 @@ class SpecialRunJobs extends UnlistedSpecialPage {
$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" );
+ HttpStatus::header( 400 );
print 'Missing parameters: ' . implode( ', ', array_keys( $missing ) );
-
return;
}
@@ -69,9 +68,8 @@ class SpecialRunJobs extends UnlistedSpecialPage {
$verified = is_string( $providedSignature )
&& hash_equals( $correctSignature, $providedSignature );
if ( !$verified || $params['sigexpiry'] < time() ) {
- header( "HTTP/1.0 400 Bad Request" );
+ HttpStatus::header( 400 );
print 'Invalid or stale signature provided';
-
return;
}
@@ -83,7 +81,8 @@ class SpecialRunJobs extends UnlistedSpecialPage {
// 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" );
+ // HTTP 202 Accepted
+ HttpStatus::header( 202 );
ob_flush();
flush();
// Once the client receives this response, it can disconnect
diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php
index 608d62e6..f50fb732 100644
--- a/includes/specials/SpecialSearch.php
+++ b/includes/specials/SpecialSearch.php
@@ -47,7 +47,10 @@ class SpecialSearch extends SpecialPage {
/** @var array For links */
protected $extraParams = array();
- /** @var string No idea, apparently used by some other classes */
+ /**
+ * @var string The prefix url parameter. Set on the searcher and the
+ * is expected to treat it as prefix filter on titles.
+ */
protected $mPrefix;
/**
@@ -65,6 +68,11 @@ class SpecialSearch extends SpecialPage {
*/
protected $fulltext;
+ /**
+ * @var bool
+ */
+ protected $runSuggestion = true;
+
const NAMESPACES_CURRENT = 'sense';
public function __construct() {
@@ -166,6 +174,7 @@ class SpecialSearch extends SpecialPage {
}
$this->fulltext = $request->getVal( 'fulltext' );
+ $this->runSuggestion = (bool)$request->getVal( 'runsuggestion', true );
$this->profile = $profile;
}
@@ -207,11 +216,11 @@ class SpecialSearch extends SpecialPage {
global $wgContLang;
$search = $this->getSearchEngine();
+ $search->setFeatureData( 'rewrite', $this->runSuggestion );
$search->setLimitOffset( $this->limit, $this->offset );
$search->setNamespaces( $this->namespaces );
$search->prefix = $this->mPrefix;
$term = $search->transformSearchTerm( $term );
- $didYouMeanHtml = '';
Hooks::run( 'SpecialSearchSetupEngine', array( $this, $this->profile, $search ) );
@@ -262,37 +271,13 @@ class SpecialSearch extends SpecialPage {
}
// did you mean... suggestions
- if ( $showSuggestion && $textMatches && !$textStatus && $textMatches->hasSuggestion() ) {
- # mirror Go/Search behavior of original request ..
- $didYouMeanParams = array( 'search' => $textMatches->getSuggestionQuery() );
-
- if ( $this->fulltext != null ) {
- $didYouMeanParams['fulltext'] = $this->fulltext;
- }
-
- $stParams = array_merge(
- $didYouMeanParams,
- $this->powerSearchOptions()
- );
-
- $suggestionSnippet = $textMatches->getSuggestionSnippet();
-
- if ( $suggestionSnippet == '' ) {
- $suggestionSnippet = null;
+ $didYouMeanHtml = '';
+ if ( $showSuggestion && $textMatches && !$textStatus ) {
+ if ( $textMatches->hasRewrittenQuery() ) {
+ $didYouMeanHtml = $this->getDidYouMeanRewrittenHtml( $term, $textMatches );
+ } elseif ( $textMatches->hasSuggestion() ) {
+ $didYouMeanHtml = $this->getDidYouMeanHtml( $textMatches );
}
-
- $suggestLink = Linker::linkKnown(
- $this->getPageTitle(),
- $suggestionSnippet,
- array(),
- $stParams
- );
-
- # html of did you mean... search suggestion link
- $didYouMeanHtml =
- Xml::openElement( 'div', array( 'class' => 'searchdidyoumean' ) ) .
- $this->msg( 'search-suggest' )->rawParams( $suggestLink )->text() .
- Xml::closeElement( 'div' );
}
if ( !Hooks::run( 'SpecialSearchResultsPrepend', array( $this, $out, $term ) ) ) {
@@ -381,14 +366,14 @@ class SpecialSearch extends SpecialPage {
$out->wrapWikiMsg( "==$1==\n", 'textmatches' );
}
- // show interwiki results if any
- if ( $textMatches->hasInterwikiResults() ) {
- $out->addHTML( $this->showInterwiki( $textMatches->getInterwikiResults(), $term ) );
- }
// show results
if ( $numTextMatches > 0 ) {
$out->addHTML( $this->showMatches( $textMatches ) );
}
+ // show interwiki results if any
+ if ( $textMatches->hasInterwikiResults() ) {
+ $out->addHTML( $this->showInterwiki( $textMatches->getInterwikiResults(), $term ) );
+ }
$textMatches->free();
}
@@ -402,11 +387,104 @@ class SpecialSearch extends SpecialPage {
$this->showCreateLink( $title, $num, $titleMatches, $textMatches );
}
}
- $out->addHtml( "</div>" );
+ $out->addHTML( '<div class="visualClear"></div>\n' );
if ( $prevnext ) {
$out->addHTML( "<p class='mw-search-pager-bottom'>{$prevnext}</p>\n" );
}
+
+ $out->addHtml( "</div>" );
+
+ Hooks::run( 'SpecialSearchResultsAppend', array( $this, $out ) );
+
+ }
+
+ /**
+ * Decide if the suggested query should be run, and it's results returned
+ * instead of the provided $textMatches
+ *
+ * @param SearchResultSet $textMatches The results of a users query
+ * @return bool
+ */
+ protected function shouldRunSuggestedQuery( SearchResultSet $textMatches ) {
+ if ( !$this->runSuggestion ||
+ !$textMatches->hasSuggestion() ||
+ $textMatches->numRows() > 0 ||
+ $textMatches->searchContainedSyntax()
+ ) {
+ return false;
+ }
+
+ return $this->getConfig()->get( 'SearchRunSuggestedQuery' );
+ }
+
+ /**
+ * Generates HTML shown to the user when we have a suggestion about a query
+ * that might give more results than their current query.
+ */
+ protected function getDidYouMeanHtml( SearchResultSet $textMatches ) {
+ # mirror Go/Search behavior of original request ..
+ $params = array( 'search' => $textMatches->getSuggestionQuery() );
+ if ( $this->fulltext != null ) {
+ $params['fulltext'] = $this->fulltext;
+ }
+ $stParams = array_merge( $params, $this->powerSearchOptions() );
+
+ $suggest = Linker::linkKnown(
+ $this->getPageTitle(),
+ $textMatches->getSuggestionSnippet() ?: null,
+ array(),
+ $stParams
+ );
+
+ # html of did you mean... search suggestion link
+ return Html::rawElement(
+ 'div',
+ array( 'class' => 'searchdidyoumean' ),
+ $this->msg( 'search-suggest' )->rawParams( $suggest )->parse()
+ );
+ }
+
+ /**
+ * Generates HTML shown to user when their query has been internally rewritten,
+ * and the results of the rewritten query are being returned.
+ *
+ * @param string $term The users search input
+ * @param SearchResultSet $textMatches The response to the users initial search request
+ * @return string HTML linking the user to their original $term query, and the one
+ * suggested by $textMatches.
+ */
+ protected function getDidYouMeanRewrittenHtml( $term, SearchResultSet $textMatches ) {
+ // Showing results for '$rewritten'
+ // Search instead for '$orig'
+
+ $params = array( 'search' => $textMatches->getQueryAfterRewrite() );
+ if ( $this->fulltext != null ) {
+ $params['fulltext'] = $this->fulltext;
+ }
+ $stParams = array_merge( $params, $this->powerSearchOptions() );
+
+ $rewritten = Linker::linkKnown(
+ $this->getPageTitle(),
+ $textMatches->getQueryAfterRewriteSnippet() ?: null,
+ array(),
+ $stParams
+ );
+
+ $stParams['search'] = $term;
+ $stParams['runsuggestion'] = 0;
+ $original = Linker::linkKnown(
+ $this->getPageTitle(),
+ htmlspecialchars( $term ),
+ array(),
+ $stParams
+ );
+
+ return Html::rawElement(
+ 'div',
+ array( 'class' => 'searchdidyoumean' ),
+ $this->msg( 'search-rewritten' )->rawParams( $rewritten, $original )->escaped()
+ );
}
/**
diff --git a/includes/specials/SpecialShortpages.php b/includes/specials/SpecialShortpages.php
index 7ec69e06..ba11862e 100644
--- a/includes/specials/SpecialShortpages.php
+++ b/includes/specials/SpecialShortpages.php
@@ -37,7 +37,7 @@ class ShortPagesPage extends QueryPage {
return false;
}
- function getQueryInfo() {
+ public function getQueryInfo() {
return array(
'tables' => array( 'page' ),
'fields' => array(
diff --git a/includes/specials/SpecialSpecialpages.php b/includes/specials/SpecialSpecialpages.php
index eaa90072..cf3804e5 100644
--- a/includes/specials/SpecialSpecialpages.php
+++ b/includes/specials/SpecialSpecialpages.php
@@ -96,22 +96,14 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
$includesCachedPages = false;
foreach ( $groups as $group => $sortedPages ) {
- $total = count( $sortedPages );
- $middle = ceil( $total / 2 );
- $count = 0;
$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( 'tr' ) . "\n" .
- Html::openElement( 'td', array( 'style' => 'width:30%;vertical-align:top' ) ) . "\n" .
- Html::openElement( 'ul' ) . "\n"
+ Html::openElement( 'div', array( 'class' => 'mw-specialpages-list' ) )
+ . '<ul>'
);
foreach ( $sortedPages as $desc => $specialpage ) {
list( $title, $restricted, $cached ) = $specialpage;
@@ -132,21 +124,10 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
array( 'class' => implode( ' ', $pageClasses ) ),
$link
) . "\n" );
-
- # Split up the larger groups
- $count++;
- if ( $total > 3 && $count == $middle ) {
- $out->addHTML(
- Html::closeElement( 'ul' ) . Html::closeElement( 'td' ) .
- Html::element( 'td', array( 'style' => 'width:10%' ), '' ) .
- Html::openElement( 'td', array( 'style' => 'width:30%' ) ) . Html::openElement( 'ul' ) . "\n"
- );
- }
}
$out->addHTML(
- Html::closeElement( 'ul' ) . Html::closeElement( 'td' ) .
- Html::element( 'td', array( 'style' => 'width:30%' ), '' ) .
- Html::closeElement( 'tr' ) . Html::closeElement( 'table' ) . "\n"
+ Html::closeElement( 'ul' ) .
+ Html::closeElement( 'div' )
);
}
diff --git a/includes/specials/SpecialStatistics.php b/includes/specials/SpecialStatistics.php
index c35de241..8de6f8b8 100644
--- a/includes/specials/SpecialStatistics.php
+++ b/includes/specials/SpecialStatistics.php
@@ -107,10 +107,11 @@ class SpecialStatistics extends SpecialPage {
) {
if ( $descMsg ) {
$msg = $this->msg( $descMsg, $descMsgParam );
- if ( $msg->exists() ) {
- $descriptionText = $this->msg( 'parentheses' )->rawParams( $msg->parse() )->escaped();
- $text .= "<br />" . Xml::element( 'small', array( 'class' => 'mw-statistic-desc' ),
- " $descriptionText" );
+ if ( !$msg->isDisabled() ) {
+ $descriptionHtml = $this->msg( 'parentheses' )->rawParams( $msg->parse() )
+ ->escaped();
+ $text .= "<br />" . Html::rawElement( 'small', array( 'class' => 'mw-statistic-desc' ),
+ " $descriptionHtml" );
}
}
@@ -126,26 +127,36 @@ class SpecialStatistics extends SpecialPage {
* @return string
*/
private function getPageStats() {
- return Xml::openElement( 'tr' ) .
- Xml::tags( 'th', array( 'colspan' => '2' ), $this->msg( 'statistics-header-pages' )->parse() ) .
+ $pageStatsHtml = Xml::openElement( 'tr' ) .
+ 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' ) ) .
+ array( 'class' => 'mw-statistics-articles' ),
+ 'statistics-articles-desc' ) .
$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( 'MediaStatistics' ),
- $this->msg( 'statistics-files' )->parse() ),
- $this->getLanguage()->formatNum( $this->images ),
- array( 'class' => 'mw-statistics-files' ) );
+ 'statistics-pages-desc' );
+
+ // Show the image row only, when there are files or upload is possible
+ if ( $this->images !== 0 || $this->getConfig()->get( 'EnableUploads' ) ) {
+ $pageStatsHtml .= $this->formatRow(
+ Linker::linkKnown( SpecialPage::getTitleFor( 'MediaStatistics' ),
+ $this->msg( 'statistics-files' )->parse() ),
+ $this->getLanguage()->formatNum( $this->images ),
+ array( 'class' => 'mw-statistics-files' ) );
+ }
+
+ return $pageStatsHtml;
}
private function getEditStats() {
return Xml::openElement( 'tr' ) .
- Xml::tags( 'th', array( 'colspan' => '2' ), $this->msg( 'statistics-header-edits' )->parse() ) .
+ 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 ),
@@ -160,7 +171,8 @@ class SpecialStatistics extends SpecialPage {
private function getUserStats() {
return Xml::openElement( 'tr' ) .
- Xml::tags( 'th', array( 'colspan' => '2' ), $this->msg( 'statistics-header-users' )->parse() ) .
+ 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 ),
@@ -223,7 +235,8 @@ class SpecialStatistics extends SpecialPage {
}
$text .= $this->formatRow( $grouppage . ' ' . $grouplink,
$this->getLanguage()->formatNum( $countUsers ),
- array( 'class' => 'statistics-group-' . Sanitizer::escapeClass( $group ) . $classZero ) );
+ array( 'class' => 'statistics-group-' . Sanitizer::escapeClass( $group ) .
+ $classZero ) );
}
return $text;
diff --git a/includes/specials/SpecialTags.php b/includes/specials/SpecialTags.php
index 0b8147e1..70eee9f0 100644
--- a/includes/specials/SpecialTags.php
+++ b/includes/specials/SpecialTags.php
@@ -27,14 +27,21 @@
* @ingroup SpecialPage
*/
class SpecialTags extends SpecialPage {
+
/**
- * @var array List of defined tags
+ * @var array List of explicitly defined tags
*/
- public $definedTags;
+ protected $explicitlyDefinedTags;
+
/**
- * @var array List of active tags
+ * @var array List of extension defined tags
*/
- public $activeTags;
+ protected $extensionDefinedTags;
+
+ /**
+ * @var array List of extension activated tags
+ */
+ protected $extensionActivatedTags;
function __construct() {
parent::__construct( 'Tags' );
@@ -69,9 +76,11 @@ class SpecialTags extends SpecialPage {
$out->wrapWikiMsg( "<div class='mw-tags-intro'>\n$1\n</div>", 'tags-intro' );
$user = $this->getUser();
+ $userCanManage = $user->isAllowed( 'managechangetags' );
+ $userCanEditInterface = $user->isAllowed( 'editinterface' );
// Show form to create a tag
- if ( $user->isAllowed( 'managechangetags' ) ) {
+ if ( $userCanManage ) {
$fields = array(
'Tag' => array(
'type' => 'text',
@@ -108,40 +117,50 @@ class SpecialTags extends SpecialPage {
}
}
- // Whether to show the "Actions" column in the tag list
- // If any actions added in the future require other user rights, add those
- // rights here
- $showActions = $user->isAllowed( 'managechangetags' );
+ // Used to get hitcounts for #doTagRow()
+ $tagStats = ChangeTags::tagUsageStatistics();
- // Write the headers
- $tagUsageStatistics = ChangeTags::tagUsageStatistics();
+ // Used in #doTagRow()
+ $this->explicitlyDefinedTags = array_fill_keys(
+ ChangeTags::listExplicitlyDefinedTags(), true );
+ $this->extensionDefinedTags = array_fill_keys(
+ ChangeTags::listExtensionDefinedTags(), true );
+
+ // List all defined tags, even if they were never applied
+ $definedTags = array_keys( array_merge(
+ $this->explicitlyDefinedTags, $this->extensionDefinedTags ) );
// Show header only if there exists atleast one tag
- if ( !$tagUsageStatistics ) {
+ if ( !$tagStats && !$definedTags ) {
return;
}
+
+ // 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-source-header' )->parse() ) .
Xml::tags( 'th', null, $this->msg( 'tags-active-header' )->parse() ) .
Xml::tags( 'th', null, $this->msg( 'tags-hitcount-header' )->parse() ) .
- ( $showActions ?
+ ( $userCanManage ?
Xml::tags( 'th', array( 'class' => 'unsortable' ),
$this->msg( 'tags-actions-header' )->parse() ) :
'' )
);
// Used in #doTagRow()
- $this->explicitlyDefinedTags = array_fill_keys(
- ChangeTags::listExplicitlyDefinedTags(), true );
- $this->extensionDefinedTags = array_fill_keys(
- ChangeTags::listExtensionDefinedTags(), true );
$this->extensionActivatedTags = array_fill_keys(
ChangeTags::listExtensionActivatedTags(), true );
- foreach ( $tagUsageStatistics as $tag => $hitcount ) {
- $html .= $this->doTagRow( $tag, $hitcount, $showActions );
+ // Insert tags that have been applied at least once
+ foreach ( $tagStats as $tag => $hitcount ) {
+ $html .= $this->doTagRow( $tag, $hitcount, $userCanManage, $userCanEditInterface );
+ }
+ // Insert tags defined somewhere but never applied
+ foreach ( $definedTags as $tag ) {
+ if ( !isset( $tagStats[$tag] ) ) {
+ $html .= $this->doTagRow( $tag, 0, $userCanManage, $userCanEditInterface );
+ }
}
$out->addHTML( Xml::tags(
@@ -151,16 +170,15 @@ class SpecialTags extends SpecialPage {
) );
}
- function doTagRow( $tag, $hitcount, $showActions ) {
- $user = $this->getUser();
+ function doTagRow( $tag, $hitcount, $showActions, $showEditLinks ) {
$newRow = '';
$newRow .= Xml::tags( 'td', null, Xml::element( 'code', null, $tag ) );
$disp = ChangeTags::tagDescription( $tag );
- if ( $user->isAllowed( 'editinterface' ) ) {
+ if ( $showEditLinks ) {
$disp .= ' ';
$editLink = Linker::link(
- Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag" ),
+ $this->msg( "tag-$tag" )->inContentLanguage()->getTitle(),
$this->msg( 'tags-edit' )->escaped()
);
$disp .= $this->msg( 'parentheses' )->rawParams( $editLink )->escaped();
@@ -169,10 +187,10 @@ class SpecialTags extends SpecialPage {
$msg = $this->msg( "tag-$tag-description" );
$desc = !$msg->exists() ? '' : $msg->parse();
- if ( $user->isAllowed( 'editinterface' ) ) {
+ if ( $showEditLinks ) {
$desc .= ' ';
$editDescLink = Linker::link(
- Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag-description" ),
+ $this->msg( "tag-$tag-description" )->inContentLanguage()->getTitle(),
$this->msg( 'tags-edit' )->escaped()
);
$desc .= $this->msg( 'parentheses' )->rawParams( $editDescLink )->escaped();
@@ -198,21 +216,24 @@ class SpecialTags extends SpecialPage {
$newRow .= Xml::tags( 'td', null, $this->msg( $activeMsg )->escaped() );
$hitcountLabel = $this->msg( 'tags-hitcount' )->numParams( $hitcount )->escaped();
- $hitcountLink = Linker::link(
- SpecialPage::getTitleFor( 'Recentchanges' ),
- $hitcountLabel,
- array(),
- array( 'tagfilter' => $tag )
- );
+ if ( $this->getConfig()->get( 'UseTagFilter' ) ) {
+ $hitcountLabel = Linker::link(
+ SpecialPage::getTitleFor( 'Recentchanges' ),
+ $hitcountLabel,
+ array(),
+ array( 'tagfilter' => $tag )
+ );
+ }
// add raw $hitcount for sorting, because tags-hitcount contains numbers and letters
- $newRow .= Xml::tags( 'td', array( 'data-sort-value' => $hitcount ), $hitcountLink );
+ $newRow .= Xml::tags( 'td', array( 'data-sort-value' => $hitcount ), $hitcountLabel );
// actions
- $actionLinks = array();
- if ( $showActions ) {
+ if ( $showActions ) { // we've already checked that the user had the requisite userright
+ $actionLinks = array();
+
// delete
- if ( ChangeTags::canDeleteTag( $tag, $user )->isOK() ) {
+ if ( ChangeTags::canDeleteTag( $tag )->isOK() ) {
$actionLinks[] = Linker::linkKnown( $this->getPageTitle( 'delete' ),
$this->msg( 'tags-delete' )->escaped(),
array(),
@@ -220,7 +241,7 @@ class SpecialTags extends SpecialPage {
}
// activate
- if ( ChangeTags::canActivateTag( $tag, $user )->isOK() ) {
+ if ( ChangeTags::canActivateTag( $tag )->isOK() ) {
$actionLinks[] = Linker::linkKnown( $this->getPageTitle( 'activate' ),
$this->msg( 'tags-activate' )->escaped(),
array(),
@@ -228,7 +249,7 @@ class SpecialTags extends SpecialPage {
}
// deactivate
- if ( ChangeTags::canDeactivateTag( $tag, $user )->isOK() ) {
+ if ( ChangeTags::canDeactivateTag( $tag )->isOK() ) {
$actionLinks[] = Linker::linkKnown( $this->getPageTitle( 'deactivate' ),
$this->msg( 'tags-deactivate' )->escaped(),
array(),
@@ -318,7 +339,7 @@ class SpecialTags extends SpecialPage {
$preText = $this->msg( 'tags-delete-explanation-initial', $tag )->parseAsBlock();
$tagUsage = ChangeTags::tagUsageStatistics();
- if ( $tagUsage[$tag] > 0 ) {
+ if ( isset( $tagUsage[$tag] ) && $tagUsage[$tag] > 0 ) {
$preText .= $this->msg( 'tags-delete-explanation-in-use', $tag,
$tagUsage[$tag] )->parseAsBlock();
}
diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php
index f2362a18..a66a3d10 100644
--- a/includes/specials/SpecialUndelete.php
+++ b/includes/specials/SpecialUndelete.php
@@ -149,7 +149,8 @@ class PageArchive {
$fields,
$conds,
$join_conds,
- $options
+ $options,
+ ''
);
return $dbr->select( $tables,
@@ -756,8 +757,8 @@ class SpecialUndelete extends SpecialPage {
* @param User $user
* @return bool
*/
- private function isAllowed( $permission, User $user = null ) {
- $user = $user ? : $this->getUser();
+ protected function isAllowed( $permission, User $user = null ) {
+ $user = $user ?: $this->getUser();
if ( $this->mTargetObj !== null ) {
return $this->mTargetObj->userCan( $permission, $user );
} else {
@@ -770,6 +771,8 @@ class SpecialUndelete extends SpecialPage {
}
function execute( $par ) {
+ $this->useTransactionalTimeLimit();
+
$user = $this->getUser();
$this->setHeaders();
@@ -998,7 +1001,7 @@ class SpecialUndelete extends SpecialPage {
return;
}
- if ( $this->mPreview || !$isText ) {
+ if ( ( $this->mPreview || !$isText ) && $content ) {
// NOTE: non-text content has no source view, so always use rendered preview
// Hide [edit]s
@@ -1206,7 +1209,7 @@ class SpecialUndelete extends SpecialPage {
$repo->streamFile( $path );
}
- private function showHistory() {
+ protected function showHistory() {
$out = $this->getOutput();
if ( $this->mAllowed ) {
$out->addModules( 'mediawiki.special.undelete' );
@@ -1377,7 +1380,7 @@ class SpecialUndelete extends SpecialPage {
return true;
}
- private function formatRevisionRow( $row, $earliestLiveTime, $remaining ) {
+ protected function formatRevisionRow( $row, $earliestLiveTime, $remaining ) {
$rev = Revision::newFromArchiveRow( $row,
array(
'title' => $this->mTargetObj
diff --git a/includes/specials/SpecialUnlockdb.php b/includes/specials/SpecialUnlockdb.php
index a8b97d78..dc03a4a7 100644
--- a/includes/specials/SpecialUnlockdb.php
+++ b/includes/specials/SpecialUnlockdb.php
@@ -65,9 +65,9 @@ class SpecialUnlockdb extends FormSpecialPage {
}
$readOnlyFile = $this->getConfig()->get( 'ReadOnlyFile' );
- wfSuppressWarnings();
+ MediaWiki\suppressWarnings();
$res = unlink( $readOnlyFile );
- wfRestoreWarnings();
+ MediaWiki\restoreWarnings();
if ( $res ) {
return Status::newGood();
diff --git a/includes/specials/SpecialUnusedcategories.php b/includes/specials/SpecialUnusedcategories.php
index 713823bb..0d3216cd 100644
--- a/includes/specials/SpecialUnusedcategories.php
+++ b/includes/specials/SpecialUnusedcategories.php
@@ -29,7 +29,7 @@ class UnusedCategoriesPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() {
+ public function isExpensive() {
return true;
}
@@ -37,7 +37,7 @@ class UnusedCategoriesPage extends QueryPage {
return $this->msg( 'unusedcategoriestext' )->parseAsBlock();
}
- function getQueryInfo() {
+ public function getQueryInfo() {
return array(
'tables' => array( 'page', 'categorylinks' ),
'fields' => array(
diff --git a/includes/specials/SpecialUnusedtemplates.php b/includes/specials/SpecialUnusedtemplates.php
index 0c2b8707..33444f63 100644
--- a/includes/specials/SpecialUnusedtemplates.php
+++ b/includes/specials/SpecialUnusedtemplates.php
@@ -34,7 +34,7 @@ class UnusedtemplatesPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() {
+ public function isExpensive() {
return true;
}
@@ -46,7 +46,7 @@ class UnusedtemplatesPage extends QueryPage {
return false;
}
- function getQueryInfo() {
+ public function getQueryInfo() {
return array(
'tables' => array( 'page', 'templatelinks' ),
'fields' => array(
diff --git a/includes/specials/SpecialUnwatchedpages.php b/includes/specials/SpecialUnwatchedpages.php
index bb07c197..b3ca006c 100644
--- a/includes/specials/SpecialUnwatchedpages.php
+++ b/includes/specials/SpecialUnwatchedpages.php
@@ -35,7 +35,7 @@ class UnwatchedpagesPage extends QueryPage {
parent::__construct( $name, 'unwatchedpages' );
}
- function isExpensive() {
+ public function isExpensive() {
return true;
}
@@ -43,7 +43,7 @@ class UnwatchedpagesPage extends QueryPage {
return false;
}
- function getQueryInfo() {
+ public function getQueryInfo() {
return array(
'tables' => array( 'page', 'watchlist' ),
'fields' => array(
diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php
index 2e0699af..16f4d161 100644
--- a/includes/specials/SpecialUpload.php
+++ b/includes/specials/SpecialUpload.php
@@ -152,6 +152,8 @@ class SpecialUpload extends SpecialPage {
* @throws UserBlockedError
*/
public function execute( $par ) {
+ $this->useTransactionalTimeLimit();
+
$this->setHeaders();
$this->outputHeader();
@@ -357,7 +359,7 @@ class SpecialUpload extends SpecialPage {
$sessionKey = $this->mUpload->stashSession();
$warningHtml = '<h2>' . $this->msg( 'uploadwarning' )->escaped() . "</h2>\n"
- . '<ul class="warning">';
+ . '<div class="warningbox"><ul>';
foreach ( $warnings as $warning => $args ) {
if ( $warning == 'badfilename' ) {
$this->mDesiredDestName = Title::makeTitle( NS_FILE, $args )->getText();
@@ -385,7 +387,7 @@ class SpecialUpload extends SpecialPage {
}
$warningHtml .= $msg;
}
- $warningHtml .= "</ul>\n";
+ $warningHtml .= "</ul></div>\n";
$warningHtml .= $this->msg( 'uploadwarning-text' )->parseAsBlock();
$form = $this->getUploadForm( $warningHtml, $sessionKey, /* $hideIgnoreWarning */ true );
@@ -795,6 +797,10 @@ class UploadForm extends HTMLForm {
protected $mMaxUploadSize = array();
public function __construct( array $options = array(), IContextSource $context = null ) {
+ if ( $context instanceof IContextSource ) {
+ $this->setContext( $context );
+ }
+
$this->mWatch = !empty( $options['watch'] );
$this->mForReUpload = !empty( $options['forreupload'] );
$this->mSessionKey = isset( $options['sessionkey'] ) ? $options['sessionkey'] : '';
@@ -821,8 +827,8 @@ class UploadForm extends HTMLForm {
# Add a link to edit MediaWik:Licenses
if ( $this->getUser()->isAllowed( 'editinterface' ) ) {
- $licensesLink = Linker::link(
- Title::makeTitle( NS_MEDIAWIKI, 'Licenses' ),
+ $licensesLink = Linker::linkKnown(
+ $this->msg( 'licenses' )->inContentLanguage()->getTitle(),
$this->msg( 'licenses-edit' )->escaped(),
array(),
array( 'action' => 'edit' )
diff --git a/includes/specials/SpecialUploadStash.php b/includes/specials/SpecialUploadStash.php
index 12e103e7..dd905900 100644
--- a/includes/specials/SpecialUploadStash.php
+++ b/includes/specials/SpecialUploadStash.php
@@ -58,6 +58,8 @@ class SpecialUploadStash extends UnlistedSpecialPage {
* @return bool Success
*/
public function execute( $subPage ) {
+ $this->useTransactionalTimeLimit();
+
$this->stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash( $this->getUser() );
$this->checkPermissions();
@@ -226,7 +228,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
*/
private function outputRemoteScaledThumb( $file, $params, $flags ) {
// This option probably looks something like
- // 'http://upload.wikimedia.org/wikipedia/test/thumb/temp'. Do not use
+ // '//upload.wikimedia.org/wikipedia/test/thumb/temp'. Do not use
// trailing slash.
$scalerBaseUrl = $this->getConfig()->get( 'UploadStashScalerBaseUrl' );
diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php
index 10edbcfb..21f1194f 100644
--- a/includes/specials/SpecialUserlogin.php
+++ b/includes/specials/SpecialUserlogin.php
@@ -20,6 +20,7 @@
* @file
* @ingroup SpecialPage
*/
+use MediaWiki\Logger\LoggerFactory;
/**
* Implements Special:UserLogin
@@ -43,6 +44,24 @@ class LoginForm extends SpecialPage {
const WRONG_TOKEN = 13;
const USER_MIGRATED = 14;
+ public static $statusCodes = array(
+ self::SUCCESS => 'success',
+ self::NO_NAME => 'no_name',
+ self::ILLEGAL => 'illegal',
+ self::WRONG_PLUGIN_PASS => 'wrong_plugin_pass',
+ self::NOT_EXISTS => 'not_exists',
+ self::WRONG_PASS => 'wrong_pass',
+ self::EMPTY_PASS => 'empty_pass',
+ self::RESET_PASS => 'reset_pass',
+ self::ABORTED => 'aborted',
+ self::CREATE_BLOCKED => 'create_blocked',
+ self::THROTTLED => 'throttled',
+ self::USER_BLOCKED => 'user_blocked',
+ self::NEED_TOKEN => 'need_token',
+ self::WRONG_TOKEN => 'wrong_token',
+ self::USER_MIGRATED => 'user_migrated',
+ );
+
/**
* Valid error and warning messages
*
@@ -194,13 +213,13 @@ class LoginForm extends SpecialPage {
&& in_array( $entryError->getKey(), self::getValidErrorMessages() )
) {
$this->mEntryErrorType = 'error';
- $this->mEntryError = $entryError->rawParams( $loginreqlink )->escaped();
+ $this->mEntryError = $entryError->rawParams( $loginreqlink )->parse();
} elseif ( $entryWarning->exists()
&& in_array( $entryWarning->getKey(), self::getValidErrorMessages() )
) {
$this->mEntryErrorType = 'warning';
- $this->mEntryError = $entryWarning->rawParams( $loginreqlink )->escaped();
+ $this->mEntryError = $entryWarning->rawParams( $loginreqlink )->parse();
}
if ( $wgEnableEmail ) {
@@ -338,6 +357,10 @@ class LoginForm extends SpecialPage {
}
$status = $this->addNewAccountInternal();
+ LoggerFactory::getInstance( 'authmanager' )->info( 'Account creation attempt with mailed password', array(
+ 'event' => 'accountcreation',
+ 'status' => $status,
+ ) );
if ( !$status->isGood() ) {
$error = $status->getMessage();
$this->mainLoginForm( $error->toString() );
@@ -375,6 +398,11 @@ class LoginForm extends SpecialPage {
# Create the account and abort if there's a problem doing so
$status = $this->addNewAccountInternal();
+ LoggerFactory::getInstance( 'authmanager' )->info( 'Account creation attempt', array(
+ 'event' => 'accountcreation',
+ 'status' => $status,
+ ) );
+
if ( !$status->isGood() ) {
$error = $status->getMessage();
$this->mainLoginForm( $error->toString() );
@@ -453,8 +481,7 @@ class LoginForm extends SpecialPage {
* @return Status
*/
public function addNewAccountInternal() {
- global $wgAuth, $wgMemc, $wgAccountCreationThrottle,
- $wgMinimalPasswordLength, $wgEmailConfirmToEdit;
+ global $wgAuth, $wgMemc, $wgAccountCreationThrottle, $wgEmailConfirmToEdit;
// If the user passes an invalid domain, something is fishy
if ( !$wgAuth->validDomain( $this->mDomain ) ) {
@@ -530,9 +557,15 @@ class LoginForm extends SpecialPage {
# Now create a dummy user ($u) and check if it is valid
$u = User::newFromName( $this->mUsername, 'creatable' );
- if ( !is_object( $u ) ) {
+ if ( !$u ) {
return Status::newFatal( 'noname' );
- } elseif ( 0 != $u->idForName() ) {
+ }
+
+ # Make sure the user does not exist already
+ $lock = $wgMemc->getScopedLock( wfGlobalCacheKey( 'account', md5( $this->mUsername ) ) );
+ if ( !$lock ) {
+ return Status::newFatal( 'usernameinprogress' );
+ } elseif ( $u->idForName( User::READ_LOCKING ) ) {
return Status::newFatal( 'userexists' );
}
@@ -546,7 +579,7 @@ class LoginForm extends SpecialPage {
}
# check for password validity, return a fatal Status if invalid
- $validity = $u->checkPasswordValidity( $this->mPassword );
+ $validity = $u->checkPasswordValidity( $this->mPassword, 'create' );
if ( !$validity->isGood() ) {
$validity->ok = false; // make sure this Status is fatal
return $validity;
@@ -641,7 +674,12 @@ class LoginForm extends SpecialPage {
$u->setRealName( $this->mRealName );
$u->setToken();
+ Hooks::run( 'LocalUserCreated', array( $u, $autocreate ) );
+ $oldUser = $u;
$wgAuth->initUser( $u, $autocreate );
+ if ( $oldUser !== $u ) {
+ wfWarn( get_class( $wgAuth ) . '::initUser() replaced the user object' );
+ }
$u->saveSettings();
@@ -710,7 +748,11 @@ class LoginForm extends SpecialPage {
}
$u = User::newFromName( $this->mUsername );
+ if ( $u === false ) {
+ return self::ILLEGAL;
+ }
+ $msg = null;
// Give extensions a way to indicate the username has been updated,
// rather than telling the user the account doesn't exist.
if ( !Hooks::run( 'LoginUserMigrated', array( $u, &$msg ) ) ) {
@@ -718,7 +760,7 @@ class LoginForm extends SpecialPage {
return self::USER_MIGRATED;
}
- if ( !( $u instanceof User ) || !User::isUsableName( $u->getName() ) ) {
+ if ( !User::isUsableName( $u->getName() ) ) {
return self::ILLEGAL;
}
@@ -736,7 +778,6 @@ class LoginForm extends SpecialPage {
// Give general extensions, such as a captcha, a chance to abort logins
$abort = self::ABORTED;
- $msg = null;
if ( !Hooks::run( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$msg ) ) ) {
$this->mAbortLoginErrorMsg = $msg;
@@ -784,7 +825,12 @@ class LoginForm extends SpecialPage {
$retval = self::RESET_PASS;
$this->mAbortLoginErrorMsg = 'resetpass-expired';
} else {
+ Hooks::run( 'UserLoggedIn', array( $u ) );
+ $oldUser = $u;
$wgAuth->updateUser( $u );
+ if ( $oldUser !== $u ) {
+ wfWarn( get_class( $wgAuth ) . '::updateUser() replaced the user object' );
+ }
$wgUser = $u;
// This should set it for OutputPage and the Skin
// which is needed or the personal links will be
@@ -909,7 +955,8 @@ class LoginForm extends SpecialPage {
global $wgMemc, $wgLang, $wgSecureLogin, $wgPasswordAttemptThrottle,
$wgInvalidPasswordReset;
- switch ( $this->authenticateUserData() ) {
+ $authRes = $this->authenticateUserData();
+ switch ( $authRes ) {
case self::SUCCESS:
# We've verified now, update the real record
$user = $this->getUser();
@@ -946,7 +993,10 @@ class LoginForm extends SpecialPage {
} elseif ( $wgInvalidPasswordReset
&& !$user->isValidPassword( $this->mPassword )
) {
- $status = $user->checkPasswordValidity( $this->mPassword );
+ $status = $user->checkPasswordValidity(
+ $this->mPassword,
+ 'login'
+ );
$this->resetLoginForm(
$status->getMessage( 'resetpass-validity-soft' )
);
@@ -1029,6 +1079,12 @@ class LoginForm extends SpecialPage {
default:
throw new MWException( 'Unhandled case value' );
}
+
+ LoggerFactory::getInstance( 'authmanager' )->info( 'Login attempt', array(
+ 'event' => 'login',
+ 'successful' => $authRes === self::SUCCESS,
+ 'status' => LoginForm::$statusCodes[$authRes],
+ ) );
}
/**
@@ -1280,8 +1336,9 @@ class LoginForm extends SpecialPage {
function mainLoginForm( $msg, $msgtype = 'error' ) {
global $wgEnableEmail, $wgEnableUserEmail;
global $wgHiddenPrefs, $wgLoginLanguageSelector;
- global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration;
+ global $wgAuth, $wgEmailConfirmToEdit;
global $wgSecureLogin, $wgPasswordResetRoutes;
+ global $wgExtendedLoginCookieExpiration, $wgCookieExpiration;
$titleObj = $this->getPageTitle();
$user = $this->getUser();
@@ -1320,9 +1377,6 @@ class LoginForm extends SpecialPage {
'mediawiki.ui.input',
'mediawiki.special.userlogin.common.styles'
) );
- $out->addModules( array(
- 'mediawiki.special.userlogin.common.js'
- ) );
if ( $this->mType == 'signup' ) {
// XXX hack pending RL or JS parse() support for complex content messages
@@ -1384,6 +1438,7 @@ class LoginForm extends SpecialPage {
: is_array( $wgPasswordResetRoutes ) && in_array( true, array_values( $wgPasswordResetRoutes ) );
$template->set( 'header', '' );
+ $template->set( 'formheader', '' );
$template->set( 'skin', $this->getSkin() );
$template->set( 'name', $this->mUsername );
$template->set( 'password', $this->mPassword );
@@ -1404,7 +1459,7 @@ class LoginForm extends SpecialPage {
$template->set( 'emailothers', $wgEnableUserEmail );
$template->set( 'canreset', $wgAuth->allowPasswordChange() );
$template->set( 'resetlink', $resetLink );
- $template->set( 'canremember', ( $wgCookieExpiration > 0 ) );
+ $template->set( 'canremember', $wgExtendedLoginCookieExpiration === null ? ( $wgCookieExpiration > 0 ) : ( $wgExtendedLoginCookieExpiration > 0 ) );
$template->set( 'usereason', $user->isLoggedIn() );
$template->set( 'remember', $this->mRemember );
$template->set( 'cansecurelogin', ( $wgSecureLogin === true ) );
@@ -1526,7 +1581,6 @@ class LoginForm extends SpecialPage {
*/
public static function getCreateaccountToken() {
global $wgRequest;
-
return $wgRequest->getSessionData( 'wsCreateaccountToken' );
}
@@ -1601,22 +1655,21 @@ class LoginForm extends SpecialPage {
*/
function makeLanguageSelector() {
$msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage();
- if ( !$msg->isBlank() ) {
- $langs = explode( "\n", $msg->text() );
- $links = array();
- foreach ( $langs as $lang ) {
- $lang = trim( $lang, '* ' );
- $parts = explode( '|', $lang );
- if ( count( $parts ) >= 2 ) {
- $links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) );
- }
- }
-
- return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams(
- $this->getLanguage()->pipeList( $links ) )->escaped() : '';
- } else {
+ if ( $msg->isBlank() ) {
return '';
}
+ $langs = explode( "\n", $msg->text() );
+ $links = array();
+ foreach ( $langs as $lang ) {
+ $lang = trim( $lang, '* ' );
+ $parts = explode( '|', $lang );
+ if ( count( $parts ) >= 2 ) {
+ $links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) );
+ }
+ }
+
+ return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams(
+ $this->getLanguage()->pipeList( $links ) )->escaped() : '';
}
/**
diff --git a/includes/specials/SpecialUserrights.php b/includes/specials/SpecialUserrights.php
index 758e3c05..e91c0bde 100644
--- a/includes/specials/SpecialUserrights.php
+++ b/includes/specials/SpecialUserrights.php
@@ -106,7 +106,7 @@ class UserrightsPage extends SpecialPage {
}
}
- if ( User::getCanonicalName( $this->mTarget ) === $user->getName() ) {
+ if ( $this->mTarget !== null && User::getCanonicalName( $this->mTarget ) === $user->getName() ) {
$this->isself = true;
}
@@ -145,6 +145,7 @@ class UserrightsPage extends SpecialPage {
if (
$request->wasPosted() &&
$request->getCheck( 'saveusergroups' ) &&
+ $this->mTarget !== null &&
$user->matchEditToken( $request->getVal( 'wpEditToken' ), $this->mTarget )
) {
// save settings
@@ -249,7 +250,7 @@ class UserrightsPage extends SpecialPage {
if ( $remove ) {
foreach ( $remove as $index => $group ) {
if ( !$user->removeGroup( $group ) ) {
- unset($remove[$index]);
+ unset( $remove[$index] );
}
}
$newGroups = array_diff( $newGroups, $remove );
@@ -257,7 +258,7 @@ class UserrightsPage extends SpecialPage {
if ( $add ) {
foreach ( $add as $index => $group ) {
if ( !$user->addGroup( $group ) ) {
- unset($add[$index]);
+ unset( $add[$index] );
}
}
$newGroups = array_merge( $newGroups, $add );
@@ -268,6 +269,7 @@ class UserrightsPage extends SpecialPage {
$user->invalidateCache();
// update groups in external authentication database
+ Hooks::run( 'UserGroupsChanged', array( $user, $add, $remove, $this->getUser() ) );
$wgAuth->updateExternalDBGroups( $user, $add, $remove );
wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) . "\n" );
diff --git a/includes/specials/SpecialVersion.php b/includes/specials/SpecialVersion.php
index 9a1c5e5d..38baf5b9 100644
--- a/includes/specials/SpecialVersion.php
+++ b/includes/specials/SpecialVersion.php
@@ -92,7 +92,14 @@ class SpecialVersion extends SpecialPage {
if ( $file ) {
$wikiText = file_get_contents( $file );
if ( substr( $file, -4 ) === '.txt' ) {
- $wikiText = Html::element( 'pre', array(), $wikiText );
+ $wikiText = Html::element(
+ 'pre',
+ array(
+ 'lang' => 'en',
+ 'dir' => 'ltr',
+ ),
+ $wikiText
+ );
}
}
}
@@ -109,7 +116,14 @@ class SpecialVersion extends SpecialPage {
$file = $this->getExtLicenseFileName( dirname( $extNode['path'] ) );
if ( $file ) {
$wikiText = file_get_contents( $file );
- $wikiText = "<pre>$wikiText</pre>";
+ $wikiText = Html::element(
+ 'pre',
+ array(
+ 'lang' => 'en',
+ 'dir' => 'ltr',
+ ),
+ $wikiText
+ );
}
}
@@ -215,6 +229,10 @@ class SpecialVersion extends SpecialPage {
}
$software[$dbr->getSoftwareLink()] = $dbr->getServerInfo();
+ if ( IcuCollation::getICUVersion() ) {
+ $software['[http://site.icu-project.org/ ICU]'] = IcuCollation::getICUVersion();
+ }
+
// Allow a hook to add/remove items.
Hooks::run( 'SoftwareInfo', array( &$software ) );
@@ -522,6 +540,9 @@ class SpecialVersion extends SpecialPage {
$out .= Html::openElement( 'tr' )
. Html::element( 'th', array(), $this->msg( 'version-libraries-library' )->text() )
. Html::element( 'th', array(), $this->msg( 'version-libraries-version' )->text() )
+ . Html::element( 'th', array(), $this->msg( 'version-libraries-license' )->text() )
+ . Html::element( 'th', array(), $this->msg( 'version-libraries-description' )->text() )
+ . Html::element( 'th', array(), $this->msg( 'version-libraries-authors' )->text() )
. Html::closeElement( 'tr' );
foreach ( $lock->getInstalledDependencies() as $name => $info ) {
@@ -530,13 +551,32 @@ class SpecialVersion extends SpecialPage {
// in their proper section
continue;
}
+ $authors = array_map( function( $arr ) {
+ // If a homepage is set, link to it
+ if ( isset( $arr['homepage'] ) ) {
+ return "[{$arr['homepage']} {$arr['name']}]";
+ }
+ return $arr['name'];
+ }, $info['authors'] );
+ $authors = $this->listAuthors( $authors, false, "$IP/vendor/$name" );
+
+ // We can safely assume that the libraries' names and descriptions
+ // are written in English and aren't going to be translated,
+ // so set appropriate lang and dir attributes
$out .= Html::openElement( 'tr' )
. Html::rawElement(
'td',
array(),
- Linker::makeExternalLink( "https://packagist.org/packages/$name", $name )
+ Linker::makeExternalLink(
+ "https://packagist.org/packages/$name", $name,
+ true, '',
+ array( 'class' => 'mw-version-library-name' )
+ )
)
- . Html::element( 'td', array(), $info['version'] )
+ . Html::element( 'td', array( 'dir' => 'auto' ), $info['version'] )
+ . Html::element( 'td', array( 'dir' => 'auto' ), $this->listToText( $info['licenses'] ) )
+ . Html::element( 'td', array( 'lang' => 'en', 'dir' => 'ltr' ), $info['description'] )
+ . Html::rawElement( 'td', array(), $authors )
. Html::closeElement( 'tr' );
}
$out .= Html::closeElement( 'table' );
@@ -557,7 +597,10 @@ class SpecialVersion extends SpecialPage {
if ( count( $tags ) ) {
$out = Html::rawElement(
'h2',
- array( 'class' => 'mw-headline plainlinks' ),
+ array(
+ 'class' => 'mw-headline plainlinks',
+ 'id' => 'mw-version-parser-extensiontags',
+ ),
Linker::makeExternalLink(
'//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
$this->msg( 'version-parser-extensiontags' )->parse(),
@@ -597,7 +640,10 @@ class SpecialVersion extends SpecialPage {
if ( count( $fhooks ) ) {
$out = Html::rawElement(
'h2',
- array( 'class' => 'mw-headline plainlinks' ),
+ array(
+ 'class' => 'mw-headline plainlinks',
+ 'id' => 'mw-version-parser-function-hooks',
+ ),
Linker::makeExternalLink(
'//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
$this->msg( 'version-parser-function-hooks' )->parse(),
@@ -842,7 +888,7 @@ class SpecialVersion extends SpecialPage {
// Finally! Create the table
$html = Html::openElement( 'tr', array(
'class' => 'mw-version-ext',
- 'id' => "mw-version-ext-{$extension['name']}"
+ 'id' => Sanitizer::escapeId( 'mw-version-ext-' . $extension['name'] )
)
);
@@ -959,7 +1005,8 @@ class SpecialVersion extends SpecialPage {
* '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|bool $extName Name of the extension for link creation,
+ * false if no links should be created
* @param string $extDir Path to the extension root directory
*
* @return string HTML fragment
@@ -972,7 +1019,7 @@ class SpecialVersion extends SpecialPage {
if ( $item == '...' ) {
$hasOthers = true;
- if ( $this->getExtAuthorsFileName( $extDir ) ) {
+ if ( $extName && $this->getExtAuthorsFileName( $extDir ) ) {
$text = Linker::link(
$this->getPageTitle( "Credits/$extName" ),
$this->msg( 'version-poweredby-others' )->escaped()
@@ -991,7 +1038,7 @@ class SpecialVersion extends SpecialPage {
}
}
- if ( !$hasOthers && $this->getExtAuthorsFileName( $extDir ) ) {
+ if ( $extName && !$hasOthers && $this->getExtAuthorsFileName( $extDir ) ) {
$list[] = $text = Linker::link(
$this->getPageTitle( "Credits/$extName" ),
$this->msg( 'version-poweredby-others' )->escaped()
@@ -1091,7 +1138,10 @@ class SpecialVersion extends SpecialPage {
if ( is_array( $list ) && count( $list ) == 1 ) {
$list = $list[0];
}
- if ( is_object( $list ) ) {
+ if ( $list instanceof Closure ) {
+ // Don't output stuff like "Closure$;1028376090#8$48499d94fe0147f7c633b365be39952b$"
+ return 'Closure';
+ } elseif ( is_object( $list ) ) {
$class = wfMessage( 'parentheses' )->params( get_class( $list ) )->escaped();
return $class;
@@ -1146,9 +1196,9 @@ class SpecialVersion extends SpecialPage {
}
// SimpleXml whines about the xmlns...
- wfSuppressWarnings();
+ MediaWiki\suppressWarnings();
$xml = simplexml_load_file( $entries );
- wfRestoreWarnings();
+ MediaWiki\restoreWarnings();
if ( $xml ) {
foreach ( $xml->entry as $entry ) {
diff --git a/includes/specials/SpecialWantedfiles.php b/includes/specials/SpecialWantedfiles.php
index 8a1a6c6c..a718aa81 100644
--- a/includes/specials/SpecialWantedfiles.php
+++ b/includes/specials/SpecialWantedfiles.php
@@ -52,7 +52,7 @@ class WantedFilesPage extends WantedQueryPage {
$noForeign = '';
if ( !$this->likelyToHaveFalsePositives() ) {
// Additional messages for grep:
- // wantedfiletext-cat-noforeign, wantedfiletext-nocat
+ // wantedfiletext-cat-noforeign, wantedfiletext-nocat-noforeign
$noForeign = '-noforeign';
}
diff --git a/includes/specials/SpecialWantedtemplates.php b/includes/specials/SpecialWantedtemplates.php
index a4b9dd84..9e26f0fa 100644
--- a/includes/specials/SpecialWantedtemplates.php
+++ b/includes/specials/SpecialWantedtemplates.php
@@ -44,10 +44,7 @@ class WantedTemplatesPage extends WantedQueryPage {
'title' => 'tl_title',
'value' => 'COUNT(*)'
),
- 'conds' => array(
- 'page_title IS NULL',
- 'tl_namespace' => NS_TEMPLATE
- ),
+ 'conds' => array( 'page_title IS NULL' ),
'options' => array( 'GROUP BY' => array( 'tl_namespace', 'tl_title' ) ),
'join_conds' => array( 'page' => array( 'LEFT JOIN',
array( 'page_namespace = tl_namespace',
diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php
index df9d3639..20f57760 100644
--- a/includes/specials/SpecialWatchlist.php
+++ b/includes/specials/SpecialWatchlist.php
@@ -43,6 +43,7 @@ class SpecialWatchlist extends ChangesListSpecialPage {
$output = $this->getOutput();
$request = $this->getRequest();
+ $this->addHelpLink( 'Help:Watching pages' );
$mode = SpecialEditWatchlist::getMode( $request, $subpage );
if ( $mode !== false ) {
diff --git a/includes/specials/SpecialWhatlinkshere.php b/includes/specials/SpecialWhatlinkshere.php
index 0b3175a6..39980d29 100644
--- a/includes/specials/SpecialWhatlinkshere.php
+++ b/includes/specials/SpecialWhatlinkshere.php
@@ -46,6 +46,7 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
$this->setHeaders();
$this->outputHeader();
+ $this->addHelpLink( 'Help:What links here' );
$opts = new FormOptions();
@@ -154,7 +155,7 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
$conds['pagelinks'][] = 'rd_from is NOT NULL';
}
- $queryFunc = function ( $dbr, $table, $fromCol ) use (
+ $queryFunc = function ( IDatabase $dbr, $table, $fromCol ) use (
$conds, $target, $limit, $useLinkNamespaceDBFields
) {
// Read an extra row as an at-end check
@@ -169,11 +170,12 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
}
// Inner LIMIT is 2X in case of stale backlinks with wrong namespaces
$subQuery = $dbr->selectSqlText(
- array( $table, 'page', 'redirect' ),
+ array( $table, 'redirect', 'page' ),
array( $fromCol, 'rd_from' ),
$conds[$table],
__CLASS__ . '::showIndirectLinks',
- array( 'ORDER BY' => $fromCol, 'LIMIT' => 2 * $queryLimit ),
+ // Force JOIN order per T106682 to avoid large filesorts
+ array( 'ORDER BY' => $fromCol, 'LIMIT' => 2 * $queryLimit, 'STRAIGHT_JOIN' ),
array(
'page' => array( 'INNER JOIN', "$fromCol = page_id" ),
'redirect' => array( 'LEFT JOIN', $on )
@@ -266,6 +268,14 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
}
$prevId = $from;
+ // use LinkBatch to make sure, that all required data (associated with Titles)
+ // is loaded in one query
+ $lb = new LinkBatch();
+ foreach ( $rows as $row ) {
+ $lb->add( $row->page_namespace, $row->page_title );
+ }
+ $lb->execute();
+
if ( $level == 0 ) {
if ( !$this->including() ) {
$out->addHTML( $this->whatlinkshereForm() );
@@ -313,7 +323,7 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
static $msgcache = null;
if ( $msgcache === null ) {
static $msgs = array( 'isredirect', 'istemplate', 'semicolon-separator',
- 'whatlinkshere-links', 'isimage' );
+ 'whatlinkshere-links', 'isimage', 'editlink' );
$msgcache = array();
foreach ( $msgs as $msg ) {
$msgcache[$msg] = $this->msg( $msg )->escaped();
@@ -354,7 +364,7 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
}
# Space for utilities links, with a what-links-here link provided
- $wlhLink = $this->wlhLink( $nt, $msgcache['whatlinkshere-links'] );
+ $wlhLink = $this->wlhLink( $nt, $msgcache['whatlinkshere-links'], $msgcache['editlink'] );
$wlh = Xml::wrapClass(
$this->msg( 'parentheses' )->rawParams( $wlhLink )->escaped(),
'mw-whatlinkshere-tools'
@@ -369,18 +379,39 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
return Xml::closeElement( 'ul' );
}
- protected function wlhLink( Title $target, $text ) {
+ protected function wlhLink( Title $target, $text, $editText ) {
static $title = null;
if ( $title === null ) {
$title = $this->getPageTitle();
}
- return Linker::linkKnown(
- $title,
- $text,
- array(),
- array( 'target' => $target->getPrefixedText() )
+ // always show a "<- Links" link
+ $links = array(
+ 'links' => Linker::linkKnown(
+ $title,
+ $text,
+ array(),
+ array( 'target' => $target->getPrefixedText() )
+ ),
);
+
+ // if the page is editable, add an edit link
+ if (
+ // check user permissions
+ $this->getUser()->isAllowed( 'edit' ) &&
+ // check, if the content model is editable through action=edit
+ ContentHandler::getForTitle( $target )->supportsDirectEditing()
+ ) {
+ $links['edit'] = Linker::linkKnown(
+ $target,
+ $editText,
+ array(),
+ array( 'action' => 'edit' )
+ );
+ }
+
+ // build the links html
+ return $this->getLanguage()->pipeList( $links );
}
function makeSelfLink( $text, $query ) {