diff options
Diffstat (limited to 'includes/specials')
93 files changed, 7008 insertions, 4252 deletions
diff --git a/includes/specials/SpecialActiveusers.php b/includes/specials/SpecialActiveusers.php index c5aa2389..705dab55 100644 --- a/includes/specials/SpecialActiveusers.php +++ b/includes/specials/SpecialActiveusers.php @@ -50,7 +50,7 @@ class ActiveUsersPager extends UsersPager { /** * @param $context IContextSource * @param $group null Unused - * @param $par string Parameter passed to the page + * @param string $par Parameter passed to the page */ function __construct( IContextSource $context = null, $group = null, $par = null ) { global $wgActiveUserDays; @@ -62,7 +62,7 @@ class ActiveUsersPager extends UsersPager { $this->requestedUser = ''; if ( $un != '' ) { $username = Title::makeTitleSafe( NS_USER, $un ); - if( !is_null( $username ) ) { + if ( !is_null( $username ) ) { $this->requestedUser = $username->getText(); } } @@ -91,39 +91,60 @@ class ActiveUsersPager extends UsersPager { } function getQueryInfo() { - $dbr = wfGetDB( DB_SLAVE ); + $dbr = $this->getDatabase(); + $conds = array( 'rc_user > 0' ); // Users - no anons - $conds[] = 'ipb_deleted IS NULL'; // don't show hidden names $conds[] = 'rc_log_type IS NULL OR rc_log_type != ' . $dbr->addQuotes( 'newusers' ); - $conds[] = 'rc_timestamp >= ' . $dbr->addQuotes( $dbr->timestamp( wfTimestamp( TS_UNIX ) - $this->RCMaxAge*24*3600 ) ); + $conds[] = 'rc_timestamp >= ' . $dbr->addQuotes( + $dbr->timestamp( wfTimestamp( TS_UNIX ) - $this->RCMaxAge * 24 * 3600 ) ); - if( $this->requestedUser != '' ) { + if ( $this->requestedUser != '' ) { $conds[] = 'rc_user_text >= ' . $dbr->addQuotes( $this->requestedUser ); } - $query = array( - 'tables' => array( 'recentchanges', 'user', 'ipblocks' ), - 'fields' => array( 'user_name' => 'rc_user_text', // inheritance + if ( !$this->getUser()->isAllowed( 'hideuser' ) ) { + $conds[] = 'NOT EXISTS (' . $dbr->selectSQLText( + 'ipblocks', '1', array( 'rc_user=ipb_user', 'ipb_deleted' => 1 ) + ) . ')'; + } + + return array( + 'tables' => array( 'recentchanges' ), + 'fields' => array( + 'user_name' => 'rc_user_text', // for Pager inheritance 'rc_user_text', // for Pager - 'user_id', - 'recentedits' => 'COUNT(*)', - 'blocked' => 'MAX(ipb_user)' + 'user_id' => 'MAX(rc_user)', // Postgres + 'recentedits' => 'COUNT(*)' ), 'options' => array( - 'GROUP BY' => array( 'rc_user_text', 'user_id' ), + 'GROUP BY' => array( 'rc_user_text' ), 'USE INDEX' => array( 'recentchanges' => 'rc_user_text' ) ), - 'join_conds' => array( - 'user' => array( 'INNER JOIN', 'rc_user_text=user_name' ), - 'ipblocks' => array( 'LEFT JOIN', array( - 'user_id=ipb_user', - 'ipb_auto' => 0, - 'ipb_deleted' => 1 - )), - ), 'conds' => $conds ); - return $query; + } + + function doBatchLookups() { + $uids = array(); + foreach ( $this->mResult as $row ) { + $uids[] = $row->user_id; + } + // Fetch the block status of the user for showing "(blocked)" text and for + // striking out names of suppressed users when privileged user views the list. + // Although the first query already hits the block table for un-privileged, this + // is done in two queries to avoid huge quicksorts and to make COUNT(*) correct. + $dbr = $this->getDatabase(); + $res = $dbr->select( 'ipblocks', + array( 'ipb_user', 'MAX(ipb_deleted) AS block_status' ), + array( 'ipb_user' => $uids ), + __METHOD__, + array( 'GROUP BY' => array( 'ipb_user' ) ) + ); + $this->blockStatusByUid = array(); + foreach ( $res as $row ) { + $this->blockStatusByUid[$row->ipb_user] = $row->block_status; // 0 or 1 + } + $this->mResult->seek( 0 ); } function formatRow( $row ) { @@ -138,7 +159,7 @@ class ActiveUsersPager extends UsersPager { $user = User::newFromId( $row->user_id ); // User right filter - foreach( $this->hideRights as $right ) { + foreach ( $this->hideRights as $right ) { // Calling User::getRights() within the loop so that // if the hideRights() filter is empty, we don't have to // trigger the lazy-init of the big userrights array in the @@ -152,7 +173,7 @@ class ActiveUsersPager extends UsersPager { // Note: This is a different loop than for user rights, // because we're reusing it to build the group links // at the same time - foreach( $user->getGroups() as $group ) { + foreach ( $user->getGroups() as $group ) { if ( in_array( $group, $this->hideGroups ) ) { return ''; } @@ -162,9 +183,14 @@ class ActiveUsersPager extends UsersPager { $groups = $lang->commaList( $list ); $item = $lang->specialList( $ulinks, $groups ); + + $isBlocked = isset( $this->blockStatusByUid[$row->user_id] ); + if ( $isBlocked && $this->blockStatusByUid[$row->user_id] == 1 ) { + $item = "<span class=\"deleted\">$item</span>"; + } $count = $this->msg( 'activeusers-count' )->numParams( $row->recentedits ) ->params( $userName )->numParams( $this->RCMaxAge )->escaped(); - $blocked = $row->blocked ? ' ' . $this->msg( 'listusers-blocked', $userName )->escaped() : ''; + $blocked = $isBlocked ? ' ' . $this->msg( 'listusers-blocked', $userName )->escaped() : ''; return Html::rawElement( 'li', array(), "{$item} [{$count}]{$blocked}" ); } @@ -180,15 +206,15 @@ class ActiveUsersPager extends UsersPager { $out .= Html::hidden( 'title', $self->getPrefixedDBkey() ) . $limit . "\n"; $out .= Xml::inputLabel( $this->msg( 'activeusers-from' )->text(), - 'username', 'offset', 20, $this->requestedUser ) . '<br />';# Username field + 'username', 'offset', 20, $this->requestedUser, array( 'tabindex' => 1 ) ) . '<br />';# Username field $out .= Xml::checkLabel( $this->msg( 'activeusers-hidebots' )->text(), - 'hidebots', 'hidebots', $this->opts->getValue( 'hidebots' ) ); + 'hidebots', 'hidebots', $this->opts->getValue( 'hidebots' ), array( 'tabindex' => 2 ) ); $out .= Xml::checkLabel( $this->msg( 'activeusers-hidesysops' )->text(), - 'hidesysops', 'hidesysops', $this->opts->getValue( 'hidesysops' ) ) . '<br />'; + 'hidesysops', 'hidesysops', $this->opts->getValue( 'hidesysops' ), array( 'tabindex' => 3 ) ) . '<br />'; - $out .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . "\n";# Submit button and form bottom + $out .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text(), array( 'tabindex' => 4 ) ) . "\n";# Submit button and form bottom $out .= Xml::closeElement( 'fieldset' ); $out .= Xml::closeElement( 'form' ); @@ -240,4 +266,7 @@ class SpecialActiveUsers extends SpecialPage { } } + protected function getGroupName() { + return 'users'; + } } diff --git a/includes/specials/SpecialAllmessages.php b/includes/specials/SpecialAllmessages.php index fe9d41e5..35d6a0c0 100644 --- a/includes/specials/SpecialAllmessages.php +++ b/includes/specials/SpecialAllmessages.php @@ -28,7 +28,6 @@ * @ingroup SpecialPage */ class SpecialAllmessages extends SpecialPage { - /** * @var AllmessagesTablePager */ @@ -53,8 +52,9 @@ class SpecialAllmessages extends SpecialPage { $this->setHeaders(); global $wgUseDatabaseMessages; - if( !$wgUseDatabaseMessages ) { + if ( !$wgUseDatabaseMessages ) { $out->addWikiMsg( 'allmessagesnotsupportedDB' ); + return; } else { $this->outputHeader( 'allmessagestext' ); @@ -74,9 +74,11 @@ class SpecialAllmessages extends SpecialPage { $this->table->getNavigationBar() . $this->table->getBody() . $this->table->getNavigationBar() ); - } + protected function getGroupName() { + return 'wiki'; + } } /** @@ -84,7 +86,6 @@ class SpecialAllmessages extends SpecialPage { * getting data from a table when in fact not all of it comes from the database. */ class AllmessagesTablePager extends TablePager { - protected $filter, $prefix, $langcode, $displayPrefix; public $mLimitsShown; @@ -113,20 +114,20 @@ class AllmessagesTablePager extends TablePager { $this->lang = ( $langObj ? $langObj : $wgContLang ); $this->langcode = $this->lang->getCode(); - $this->foreign = $this->langcode != $wgContLang->getCode(); + $this->foreign = $this->langcode != $wgContLang->getCode(); $request = $this->getRequest(); $this->filter = $request->getVal( 'filter', 'all' ); - if( $this->filter === 'all' ){ + if ( $this->filter === 'all' ) { $this->custom = null; // So won't match in either case } else { - $this->custom = ($this->filter == 'unmodified'); + $this->custom = ( $this->filter == 'unmodified' ); } $prefix = $this->getLanguage()->ucfirst( $request->getVal( 'prefix', '' ) ); $prefix = $prefix != '' ? Title::makeTitleSafe( NS_MEDIAWIKI, $request->getVal( 'prefix', null ) ) : null; - if( $prefix !== null ){ + if ( $prefix !== null ) { $this->displayPrefix = $prefix->getDBkey(); $this->prefix = '/^' . preg_quote( $this->displayPrefix ) . '/i'; } else { @@ -136,7 +137,7 @@ class AllmessagesTablePager extends TablePager { // The suffix that may be needed for message names if we're in a // different language (eg [[MediaWiki:Foo/fr]]: $suffix = '/fr' - if( $this->foreign ) { + if ( $this->foreign ) { $this->suffix = '/' . $this->langcode; } else { $this->suffix = ''; @@ -150,42 +151,42 @@ class AllmessagesTablePager extends TablePager { $msg = wfMessage( 'allmessages-language' ); $langSelect = Xml::languageSelector( $this->langcode, false, null, $attrs, $msg ); - $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-allmessages-form' ) ) . + $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-allmessages-form' ) ) . Xml::fieldset( $this->msg( 'allmessages-filter-legend' )->text() ) . Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . Xml::openElement( 'table', array( 'class' => 'mw-allmessages-table' ) ) . "\n" . '<tr> <td class="mw-label">' . - Xml::label( $this->msg( 'allmessages-prefix' )->text(), 'mw-allmessages-form-prefix' ) . - "</td>\n - <td class=\"mw-input\">" . - Xml::input( 'prefix', 20, str_replace( '_', ' ', $this->displayPrefix ), array( 'id' => 'mw-allmessages-form-prefix' ) ) . - "</td>\n + Xml::label( $this->msg( 'allmessages-prefix' )->text(), 'mw-allmessages-form-prefix' ) . + "</td>\n + <td class=\"mw-input\">" . + Xml::input( 'prefix', 20, str_replace( '_', ' ', $this->displayPrefix ), array( 'id' => 'mw-allmessages-form-prefix' ) ) . + "</td>\n </tr> <tr>\n - <td class='mw-label'>" . - $this->msg( 'allmessages-filter' )->escaped() . - "</td>\n + <td class='mw-label'>" . + $this->msg( 'allmessages-filter' )->escaped() . + "</td>\n <td class='mw-input'>" . - Xml::radioLabel( $this->msg( 'allmessages-filter-unmodified' )->text(), - 'filter', - 'unmodified', - 'mw-allmessages-form-filter-unmodified', - ( $this->filter == 'unmodified' ) - ) . - Xml::radioLabel( $this->msg( 'allmessages-filter-all' )->text(), - 'filter', - 'all', - 'mw-allmessages-form-filter-all', - ( $this->filter == 'all' ) - ) . - Xml::radioLabel( $this->msg( 'allmessages-filter-modified' )->text(), - 'filter', - 'modified', - 'mw-allmessages-form-filter-modified', - ( $this->filter == 'modified' ) - ) . - "</td>\n + Xml::radioLabel( $this->msg( 'allmessages-filter-unmodified' )->text(), + 'filter', + 'unmodified', + 'mw-allmessages-form-filter-unmodified', + ( $this->filter == 'unmodified' ) + ) . + Xml::radioLabel( $this->msg( 'allmessages-filter-all' )->text(), + 'filter', + 'all', + 'mw-allmessages-form-filter-all', + ( $this->filter == 'all' ) + ) . + Xml::radioLabel( $this->msg( 'allmessages-filter-modified' )->text(), + 'filter', + 'modified', + 'mw-allmessages-form-filter-modified', + ( $this->filter == 'modified' ) + ) . + "</td>\n </tr> <tr>\n <td class=\"mw-label\">" . $langSelect[0] . "</td>\n @@ -194,29 +195,30 @@ class AllmessagesTablePager extends TablePager { '<tr> <td class="mw-label">' . - Xml::label( $this->msg( 'table_pager_limit_label' )->text(), 'mw-table_pager_limit_label' ) . - '</td> - <td class="mw-input">' . - $this->getLimitSelect() . - '</td> + Xml::label( $this->msg( 'table_pager_limit_label' )->text(), 'mw-table_pager_limit_label' ) . + '</td> + <td class="mw-input">' . + $this->getLimitSelect() . + '</td> <tr> <td></td> <td>' . - Xml::submitButton( $this->msg( 'allmessages-filter-submit' )->text() ) . - "</td>\n + Xml::submitButton( $this->msg( 'allmessages-filter-submit' )->text() ) . + "</td>\n </tr>" . Xml::closeElement( 'table' ) . $this->getHiddenFields( array( 'title', 'prefix', 'filter', 'lang', 'limit' ) ) . Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ); + return $out; } function getAllMessages( $descending ) { wfProfileIn( __METHOD__ ); $messageNames = Language::getLocalisationCache()->getSubitemList( 'en', 'messages' ); - if( $descending ){ + if ( $descending ) { rsort( $messageNames ); } else { asort( $messageNames ); @@ -226,6 +228,7 @@ class AllmessagesTablePager extends TablePager { $messageNames = array_map( array( $this->lang, 'ucfirst' ), $messageNames ); wfProfileOut( __METHOD__ ); + return $messageNames; } @@ -257,18 +260,19 @@ class AllmessagesTablePager extends TablePager { foreach ( $res as $s ) { $exists = false; - if( $foreign ) { + if ( $foreign ) { $title = explode( '/', $s->page_title ); - if( count( $title ) === 2 && $langcode == $title[1] - && isset( $xNames[$title[0]] ) ) { + if ( count( $title ) === 2 && $langcode == $title[1] + && isset( $xNames[$title[0]] ) + ) { $exists = $title[0]; } - } elseif( isset( $xNames[$s->page_title] ) ) { + } elseif ( isset( $xNames[$s->page_title] ) ) { $exists = $s->page_title; } - if( $exists && $s->page_namespace == NS_MEDIAWIKI ) { + if ( $exists && $s->page_namespace == NS_MEDIAWIKI ) { $pageFlags[$exists] = true; - } elseif( $exists && $s->page_namespace == NS_MEDIAWIKI_TALK ) { + } elseif ( $exists && $s->page_namespace == NS_MEDIAWIKI_TALK ) { $talkFlags[$exists] = true; } } @@ -281,6 +285,9 @@ class AllmessagesTablePager extends TablePager { /** * This function normally does a database query to get the results; we need * to make a pretend result using a FakeResultWrapper. + * @param string $offset + * @param int $limit + * @param bool $descending * @return FakeResultWrapper */ function reallyDoQuery( $offset, $limit, $descending ) { @@ -290,27 +297,29 @@ class AllmessagesTablePager extends TablePager { $statuses = self::getCustomisedStatuses( $messageNames, $this->langcode, $this->foreign ); $count = 0; - foreach( $messageNames as $key ) { + foreach ( $messageNames as $key ) { $customised = isset( $statuses['pages'][$key] ); - if( $customised !== $this->custom && + if ( $customised !== $this->custom && ( $descending && ( $key < $offset || !$offset ) || !$descending && $key > $offset ) && ( ( $this->prefix && preg_match( $this->prefix, $key ) ) || $this->prefix === false ) ) { $actual = wfMessage( $key )->inLanguage( $this->langcode )->plain(); $default = wfMessage( $key )->inLanguage( $this->langcode )->useDatabase( false )->plain(); $result->result[] = array( - 'am_title' => $key, - 'am_actual' => $actual, - 'am_default' => $default, + 'am_title' => $key, + 'am_actual' => $actual, + 'am_default' => $default, 'am_customised' => $customised, 'am_talk_exists' => isset( $statuses['talks'][$key] ) ); $count++; } - if( $count == $limit ) { + + if ( $count == $limit ) { break; } } + return $result; } @@ -318,28 +327,26 @@ class AllmessagesTablePager extends TablePager { return Xml::openElement( 'table', array( 'class' => 'mw-datatable TablePager', 'id' => 'mw-allmessagestable' ) ) . "\n" . "<thead><tr> <th rowspan=\"2\">" . - $this->msg( 'allmessagesname' )->escaped() . " + $this->msg( 'allmessagesname' )->escaped() . " </th> <th>" . - $this->msg( 'allmessagesdefault' )->escaped() . - "</th> + $this->msg( 'allmessagesdefault' )->escaped() . + "</th> </tr>\n <tr> <th>" . - $this->msg( 'allmessagescurrent' )->escaped() . - "</th> + $this->msg( 'allmessagescurrent' )->escaped() . + "</th> </tr></thead><tbody>\n"; } - function formatValue( $field, $value ){ - switch( $field ){ - + function formatValue( $field, $value ) { + switch ( $field ) { case 'am_title' : - $title = Title::makeTitle( NS_MEDIAWIKI, $value . $this->suffix ); - $talk = Title::makeTitle( NS_MEDIAWIKI_TALK, $value . $this->suffix ); + $talk = Title::makeTitle( NS_MEDIAWIKI_TALK, $value . $this->suffix ); - if( $this->mCurrentRow->am_customised ){ + if ( $this->mCurrentRow->am_customised ) { $title = Linker::linkKnown( $title, $this->getLanguage()->lcfirst( $value ) ); } else { $title = Linker::link( @@ -351,7 +358,7 @@ class AllmessagesTablePager extends TablePager { ); } if ( $this->mCurrentRow->am_talk_exists ) { - $talk = Linker::linkKnown( $talk , $this->talk ); + $talk = Linker::linkKnown( $talk, $this->talk ); } else { $talk = Linker::link( $talk, @@ -361,6 +368,7 @@ class AllmessagesTablePager extends TablePager { array( 'broken' ) ); } + return $title . ' ' . $this->msg( 'parentheses' )->rawParams( $talk )->escaped(); case 'am_default' : @@ -370,12 +378,12 @@ class AllmessagesTablePager extends TablePager { return ''; } - function formatRow( $row ){ + function formatRow( $row ) { // Do all the normal stuff $s = parent::formatRow( $row ); // But if there's a customised message, add that too. - if( $row->am_customised ){ + if ( $row->am_customised ) { $s .= Xml::openElement( 'tr', $this->getRowAttrs( $row, true ) ); $formatted = strval( $this->formatValue( 'am_actual', $row->am_actual ) ); if ( $formatted == '' ) { @@ -384,24 +392,26 @@ class AllmessagesTablePager extends TablePager { $s .= Xml::tags( 'td', $this->getCellAttrs( 'am_actual', $row->am_actual ), $formatted ) . "</tr>\n"; } + return $s; } - function getRowAttrs( $row, $isSecond = false ){ + function getRowAttrs( $row, $isSecond = false ) { $arr = array(); - if( $row->am_customised ){ + if ( $row->am_customised ) { $arr['class'] = 'allmessages-customised'; } - if( !$isSecond ){ + if ( !$isSecond ) { $arr['id'] = Sanitizer::escapeId( 'msg_' . $this->getLanguage()->lcfirst( $row->am_title ) ); } + return $arr; } - function getCellAttrs( $field, $value ){ - if( $this->mCurrentRow->am_customised && $field == 'am_title' ){ + function getCellAttrs( $field, $value ) { + if ( $this->mCurrentRow->am_customised && $field == 'am_title' ) { return array( 'rowspan' => '2', 'class' => $field ); - } elseif( $field == 'am_title' ) { + } elseif ( $field == 'am_title' ) { return array( 'class' => $field ); } else { return array( 'lang' => $this->langcode, 'dir' => $this->lang->getDir(), 'class' => $field ); @@ -420,16 +430,15 @@ class AllmessagesTablePager extends TablePager { return SpecialPage::getTitleFor( 'Allmessages', false ); } - function isFieldSortable( $x ){ + function isFieldSortable( $x ) { return false; } - function getDefaultSort(){ + function getDefaultSort() { return ''; } - function getQueryInfo(){ + function getQueryInfo() { return ''; } } - diff --git a/includes/specials/SpecialAllpages.php b/includes/specials/SpecialAllpages.php index 0f8b2557..a0820493 100644 --- a/includes/specials/SpecialAllpages.php +++ b/includes/specials/SpecialAllpages.php @@ -59,19 +59,18 @@ class SpecialAllpages extends IncludableSpecialPage { /** * Constructor * - * @param $name string: name of the special page, as seen in links and URLs (default: 'Allpages') + * @param string $name name of the special page, as seen in links and URLs (default: 'Allpages') */ - function __construct( $name = 'Allpages' ){ + function __construct( $name = 'Allpages' ) { parent::__construct( $name ); } /** * Entry point : initialise variables and call subfunctions. * - * @param $par String: becomes "FOO" when called like Special:Allpages/FOO (default NULL) + * @param string $par becomes "FOO" when called like Special:Allpages/FOO (default NULL) */ function execute( $par ) { - global $wgContLang; $request = $this->getRequest(); $out = $this->getOutput(); @@ -85,18 +84,18 @@ class SpecialAllpages extends IncludableSpecialPage { $namespace = $request->getInt( 'namespace' ); $hideredirects = $request->getBool( 'hideredirects', false ); - $namespaces = $wgContLang->getNamespaces(); + $namespaces = $this->getContext()->getLanguage()->getNamespaces(); $out->setPageTitle( - ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces) ) ) ? - $this->msg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) : - $this->msg( 'allarticles' ) + ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces ) ) ) ? + $this->msg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) : + $this->msg( 'allarticles' ) ); $out->addModuleStyles( 'mediawiki.special' ); - if( $par !== null ) { + if ( $par !== null ) { $this->showChunk( $namespace, $par, $to, $hideredirects ); - } elseif( $from !== null && $to === null ) { + } elseif ( $from !== null && $to === null ) { $this->showChunk( $namespace, $from, $to, $hideredirects ); } else { $this->showToplevel( $namespace, $from, $to, $hideredirects ); @@ -107,16 +106,16 @@ class SpecialAllpages extends IncludableSpecialPage { * HTML for the top form * * @param $namespace Integer: a namespace constant (default NS_MAIN). - * @param $from String: dbKey we are starting listing at. - * @param $to String: dbKey we are ending listing at. - * @param $hideredirects Bool: dont show redirects (default FALSE) + * @param string $from dbKey we are starting listing at. + * @param string $to dbKey we are ending listing at. + * @param bool $hideredirects dont show redirects (default FALSE) * @return string */ function namespaceForm( $namespace = NS_MAIN, $from = '', $to = '', $hideredirects = false ) { global $wgScript; $t = $this->getTitle(); - $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) ); + $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) ); $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); $out .= Html::hidden( 'title', $t->getPrefixedText() ); $out .= Xml::openElement( 'fieldset' ); @@ -127,7 +126,7 @@ class SpecialAllpages extends IncludableSpecialPage { Xml::label( $this->msg( 'allpagesfrom' )->text(), 'nsfrom' ) . " </td> <td class='mw-input'>" . - Xml::input( 'from', 30, str_replace('_',' ',$from), array( 'id' => 'nsfrom' ) ) . + Xml::input( 'from', 30, str_replace( '_', ' ', $from ), array( 'id' => 'nsfrom' ) ) . " </td> </tr> <tr> @@ -135,7 +134,7 @@ class SpecialAllpages extends IncludableSpecialPage { Xml::label( $this->msg( 'allpagesto' )->text(), 'nsto' ) . " </td> <td class='mw-input'>" . - Xml::input( 'to', 30, str_replace('_',' ',$to), array( 'id' => 'nsto' ) ) . + Xml::input( 'to', 30, str_replace( '_', ' ', $to ), array( 'id' => 'nsto' ) ) . " </td> </tr> <tr> @@ -160,14 +159,15 @@ class SpecialAllpages extends IncludableSpecialPage { $out .= Xml::closeElement( 'fieldset' ); $out .= Xml::closeElement( 'form' ); $out .= Xml::closeElement( 'div' ); + return $out; } /** * @param $namespace Integer (default NS_MAIN) - * @param $from String: list all pages from this name - * @param $to String: list all pages to this name - * @param $hideredirects Bool: dont show redirects (default FALSE) + * @param string $from list all pages from this name + * @param string $to list all pages to this name + * @param bool $hideredirects dont show redirects (default FALSE) */ function showToplevel( $namespace = NS_MAIN, $from = '', $to = '', $hideredirects = false ) { $output = $this->getOutput(); @@ -180,7 +180,7 @@ class SpecialAllpages extends IncludableSpecialPage { $where = array( 'page_namespace' => $namespace ); if ( $hideredirects ) { - $where[ 'page_is_redirect' ] = 0; + $where['page_is_redirect'] = 0; } $from = Title::makeTitleSafe( $namespace, $from ); @@ -188,20 +188,23 @@ class SpecialAllpages extends IncludableSpecialPage { $from = ( $from && $from->isLocal() ) ? $from->getDBkey() : null; $to = ( $to && $to->isLocal() ) ? $to->getDBkey() : null; - if( isset($from) ) - $where[] = 'page_title >= '.$dbr->addQuotes( $from ); - if( isset($to) ) - $where[] = 'page_title <= '.$dbr->addQuotes( $to ); + if ( isset( $from ) ) { + $where[] = 'page_title >= ' . $dbr->addQuotes( $from ); + } + + if ( isset( $to ) ) { + $where[] = 'page_title <= ' . $dbr->addQuotes( $to ); + } global $wgMemc; - $key = wfMemcKey( 'allpages', 'ns', $namespace, $from, $to ); + $key = wfMemcKey( 'allpages', 'ns', $namespace, sha1( $from ), sha1( $to ) ); $lines = $wgMemc->get( $key ); $count = $dbr->estimateRowCount( 'page', '*', $where, __METHOD__ ); - $maxPerSubpage = intval($count/$this->maxLineCount); - $maxPerSubpage = max($maxPerSubpage,$this->maxPerPage); + $maxPerSubpage = intval( $count / $this->maxLineCount ); + $maxPerSubpage = max( $maxPerSubpage, $this->maxPerPage ); - if( !is_array( $lines ) ) { + if ( !is_array( $lines ) ) { $options = array( 'LIMIT' => 1 ); $options['ORDER BY'] = 'page_title ASC'; $firstTitle = $dbr->selectField( 'page', 'page_title', $where, __METHOD__, $options ); @@ -210,31 +213,32 @@ class SpecialAllpages extends IncludableSpecialPage { $lines = array( $firstTitle ); # If we are going to show n rows, we need n+1 queries to find the relevant titles. $done = false; - while( !$done ) { + while ( !$done ) { // Fetch the last title of this chunk and the first of the next $chunk = ( $lastTitle === false ) ? array() : array( 'page_title >= ' . $dbr->addQuotes( $lastTitle ) ); $res = $dbr->select( 'page', /* FROM */ 'page_title', /* WHAT */ - array_merge($where,$chunk), + array_merge( $where, $chunk ), __METHOD__, - array ('LIMIT' => 2, 'OFFSET' => $maxPerSubpage - 1, 'ORDER BY' => 'page_title ASC') + array( 'LIMIT' => 2, 'OFFSET' => $maxPerSubpage - 1, 'ORDER BY' => 'page_title ASC' ) ); $s = $dbr->fetchObject( $res ); - if( $s ) { + if ( $s ) { array_push( $lines, $s->page_title ); } else { // Final chunk, but ended prematurely. Go back and find the end. $endTitle = $dbr->selectField( 'page', 'MAX(page_title)', - array_merge($where,$chunk), + array_merge( $where, $chunk ), __METHOD__ ); array_push( $lines, $endTitle ); $done = true; } + $s = $res->fetchObject(); - if( $s ) { + if ( $s ) { array_push( $lines, $s->page_title ); $lastTitle = $s->page_title; } else { @@ -249,18 +253,19 @@ class SpecialAllpages extends IncludableSpecialPage { // If there are only two or less sections, don't even display them. // Instead, display the first section directly. - if( count( $lines ) <= 2 ) { - if( !empty($lines) ) { + if ( count( $lines ) <= 2 ) { + if ( !empty( $lines ) ) { $this->showChunk( $namespace, $from, $to, $hideredirects ); } else { $output->addHTML( $this->namespaceForm( $namespace, $from, $to, $hideredirects ) ); } + return; } # At this point, $lines should contain an even number of elements. $out .= Xml::openElement( 'table', array( 'class' => 'allpageslist' ) ); - while( count ( $lines ) > 0 ) { + while ( count( $lines ) > 0 ) { $inpoint = array_shift( $lines ); $outpoint = array_shift( $lines ); $out .= $this->showline( $inpoint, $outpoint, $namespace, $hideredirects ); @@ -269,19 +274,19 @@ class SpecialAllpages extends IncludableSpecialPage { $nsForm = $this->namespaceForm( $namespace, $from, $to, $hideredirects ); # Is there more? - if( $this->including() ) { + if ( $this->including() ) { $out2 = ''; } else { - if( isset($from) || isset($to) ) { - $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ). - '<tr> + if ( isset( $from ) || isset( $to ) ) { + $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ) . + '<tr> <td>' . - $nsForm . - '</td> + $nsForm . + '</td> <td class="mw-allpages-nav">' . - Linker::link( $this->getTitle(), $this->msg( 'allpages' )->escaped(), - array(), array(), 'known' ) . - "</td> + Linker::link( $this->getTitle(), $this->msg( 'allpages' )->escaped(), + array(), array(), 'known' ) . + "</td> </tr>" . Xml::closeElement( 'table' ); } else { @@ -294,50 +299,59 @@ class SpecialAllpages extends IncludableSpecialPage { /** * Show a line of "ABC to DEF" ranges of articles * - * @param $inpoint String: lower limit of pagenames - * @param $outpoint String: upper limit of pagenames + * @param string $inpoint lower limit of pagenames + * @param string $outpoint upper limit of pagenames * @param $namespace Integer (Default NS_MAIN) - * @param $hideredirects Bool: dont show redirects (default FALSE) + * @param bool $hideRedirects don't show redirects. Default: false * @return string */ - function showline( $inpoint, $outpoint, $namespace = NS_MAIN, $hideredirects ) { + function showline( $inpoint, $outpoint, $namespace = NS_MAIN, $hideRedirects = false ) { + // Use content language since page titles are considered to use content language global $wgContLang; - $inpointf = htmlspecialchars( str_replace( '_', ' ', $inpoint ) ); - $outpointf = htmlspecialchars( str_replace( '_', ' ', $outpoint ) ); + + $inpointf = str_replace( '_', ' ', $inpoint ); + $outpointf = str_replace( '_', ' ', $outpoint ); + // Don't let the length runaway $inpointf = $wgContLang->truncate( $inpointf, $this->maxPageLength ); $outpointf = $wgContLang->truncate( $outpointf, $this->maxPageLength ); - $queryparams = $namespace ? "namespace=$namespace&" : ''; + $queryParams = array( + 'from' => $inpoint, + 'to' => $outpoint, + ); - $queryhideredirects = array(); - if ($hideredirects) { - $queryhideredirects[ 'hideredirects' ] = 1; + if ( $namespace ) { + $queryParams['namespace'] = $namespace; + } + if ( $hideRedirects ) { + $queryParams['hideredirects'] = 1; } - $special = $this->getTitle(); - $link = htmlspecialchars( $special->getLocalUrl( $queryparams . 'from=' . urlencode($inpoint) . '&to=' . urlencode($outpoint), $queryhideredirects ) ); + $url = $this->getTitle()->getLocalURL( $queryParams ); + $inlink = Html::element( 'a', array( 'href' => $url ), $inpointf ); + $outlink = Html::element( 'a', array( 'href' => $url ), $outpointf ); $out = $this->msg( 'alphaindexline' )->rawParams( - "<a href=\"$link\">$inpointf</a></td><td>", - "</td><td><a href=\"$link\">$outpointf</a>" + "$inlink</td><td>", + "</td><td>$outlink" )->escaped(); + return '<tr><td class="mw-allpages-alphaindexline">' . $out . '</td></tr>'; } /** - * @param $namespace Integer (Default NS_MAIN) - * @param $from String: list all pages from this name (default FALSE) - * @param $to String: list all pages to this name (default FALSE) - * @param $hideredirects Bool: dont show redirects (default FALSE) + * @param int $namespace Namespace (Default NS_MAIN) + * @param string $from list all pages from this name (default FALSE) + * @param string $to list all pages to this name (default FALSE) + * @param bool $hideredirects dont show redirects (default FALSE) */ function showChunk( $namespace = NS_MAIN, $from = false, $to = false, $hideredirects = false ) { - global $wgContLang; $output = $this->getOutput(); - $fromList = $this->getNamespaceKeyAndText($namespace, $from); + $fromList = $this->getNamespaceKeyAndText( $namespace, $from ); $toList = $this->getNamespaceKeyAndText( $namespace, $to ); - $namespaces = $wgContLang->getNamespaces(); + $namespaces = $this->getContext()->getLanguage()->getNamespaces(); $n = 0; if ( !$fromList || !$toList ) { @@ -357,10 +371,10 @@ class SpecialAllpages extends IncludableSpecialPage { ); if ( $hideredirects ) { - $conds[ 'page_is_redirect' ] = 0; + $conds['page_is_redirect'] = 0; } - if( $toKey !== "" ) { + if ( $toKey !== "" ) { $conds[] = 'page_title <= ' . $dbr->addQuotes( $toKey ); } @@ -369,33 +383,36 @@ class SpecialAllpages extends IncludableSpecialPage { $conds, __METHOD__, array( - 'ORDER BY' => 'page_title', - 'LIMIT' => $this->maxPerPage + 1, + 'ORDER BY' => 'page_title', + 'LIMIT' => $this->maxPerPage + 1, 'USE INDEX' => 'name_title', ) ); - if( $res->numRows() > 0 ) { + if ( $res->numRows() > 0 ) { $out = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-chunk' ) ); - while( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) { + while ( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) { $t = Title::newFromRow( $s ); - if( $t ) { + if ( $t ) { $link = ( $s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) . Linker::link( $t ) . - ($s->page_is_redirect ? '</div>' : '' ); + ( $s->page_is_redirect ? '</div>' : '' ); } else { $link = '[[' . htmlspecialchars( $s->page_title ) . ']]'; } - if( $n % 3 == 0 ) { + + if ( $n % 3 == 0 ) { $out .= '<tr>'; } + $out .= "<td style=\"width:33%\">$link</td>"; $n++; - if( $n % 3 == 0 ) { + if ( $n % 3 == 0 ) { $out .= "</tr>\n"; } } - if( ($n % 3) != 0 ) { + + if ( ( $n % 3 ) != 0 ) { $out .= "</tr>\n"; } $out .= Xml::closeElement( 'table' ); @@ -407,7 +424,7 @@ class SpecialAllpages extends IncludableSpecialPage { if ( $this->including() ) { $out2 = ''; } else { - if( $from == '' ) { + if ( $from == '' ) { // First chunk; no previous link. $prevTitle = null; } else { @@ -416,29 +433,29 @@ class SpecialAllpages extends IncludableSpecialPage { $res_prev = $dbr->select( 'page', 'page_title', - array( 'page_namespace' => $namespace, 'page_title < '.$dbr->addQuotes($from) ), + array( 'page_namespace' => $namespace, 'page_title < ' . $dbr->addQuotes( $from ) ), __METHOD__, array( 'ORDER BY' => 'page_title DESC', - 'LIMIT' => $this->maxPerPage, 'OFFSET' => ($this->maxPerPage - 1 ) + 'LIMIT' => $this->maxPerPage, 'OFFSET' => ( $this->maxPerPage - 1 ) ) ); # Get first title of previous complete chunk - if( $dbr->numrows( $res_prev ) >= $this->maxPerPage ) { + 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() ) { + 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 ); + if ( $from != $reallyFirstPage_title ) { + $prevTitle = Title::makeTitle( $namespace, $reallyFirstPage_title ); } else { $prevTitle = null; } @@ -448,23 +465,25 @@ class SpecialAllpages extends IncludableSpecialPage { $self = $this->getTitle(); $nsForm = $this->namespaceForm( $namespace, $from, $to, $hideredirects ); - $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ). - '<tr> + $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ) . + '<tr> <td>' . - $nsForm . - '</td> + $nsForm . + '</td> <td class="mw-allpages-nav">' . - Linker::link( $self, $this->msg( 'allpages' )->escaped() ); + Linker::link( $self, $this->msg( 'allpages' )->escaped() ); # Do we put a previous link ? - if( isset( $prevTitle ) && $pt = $prevTitle->getText() ) { + if ( isset( $prevTitle ) && $pt = $prevTitle->getText() ) { $query = array( 'from' => $prevTitle->getText() ); - if( $namespace ) + if ( $namespace ) { $query['namespace'] = $namespace; + } - if( $hideredirects ) + if ( $hideredirects ) { $query['hideredirects'] = $hideredirects; + } $prevLink = Linker::linkKnown( $self, @@ -475,16 +494,18 @@ class SpecialAllpages extends IncludableSpecialPage { $out2 = $this->getLanguage()->pipeList( array( $out2, $prevLink ) ); } - if( $n == $this->maxPerPage && $s = $res->fetchObject() ) { + if ( $n == $this->maxPerPage && $s = $res->fetchObject() ) { # $s is the first link of the next chunk - $t = Title::makeTitle($namespace, $s->page_title); + $t = Title::makeTitle( $namespace, $s->page_title ); $query = array( 'from' => $t->getText() ); - if( $namespace ) + if ( $namespace ) { $query['namespace'] = $namespace; + } - if( $hideredirects ) + if ( $hideredirects ) { $query['hideredirects'] = $hideredirects; + } $nextLink = Linker::linkKnown( $self, @@ -500,29 +521,36 @@ class SpecialAllpages extends IncludableSpecialPage { $output->addHTML( $out2 . $out ); $links = array(); - if ( isset( $prevLink ) ) $links[] = $prevLink; - if ( isset( $nextLink ) ) $links[] = $nextLink; + if ( isset( $prevLink ) ) { + $links[] = $prevLink; + } + + if ( isset( $nextLink ) ) { + $links[] = $nextLink; + } if ( count( $links ) ) { $output->addHTML( Html::element( 'hr' ) . - Html::rawElement( 'div', array( 'class' => 'mw-allpages-nav' ), - $this->getLanguage()->pipeList( $links ) - ) ); + Html::rawElement( 'div', array( 'class' => 'mw-allpages-nav' ), + $this->getLanguage()->pipeList( $links ) + ) + ); } - } /** * @param $ns Integer: the namespace of the article - * @param $text String: the name of the article + * @param string $text the name of the article * @return array( int namespace, string dbkey, string pagename ) or NULL on error */ - protected function getNamespaceKeyAndText($ns, $text) { - if ( $text == '' ) - return array( $ns, '', '' ); # shortcut for common case + protected function getNamespaceKeyAndText( $ns, $text ) { + if ( $text == '' ) { + # shortcut for common case + return array( $ns, '', '' ); + } - $t = Title::makeTitleSafe($ns, $text); + $t = Title::makeTitleSafe( $ns, $text ); if ( $t && $t->isLocal() ) { return array( $t->getNamespace(), $t->getDBkey(), $t->getText() ); } elseif ( $t ) { @@ -530,12 +558,16 @@ class SpecialAllpages extends IncludableSpecialPage { } # try again, in case the problem was an empty pagename - $text = preg_replace('/(#|$)/', 'X$1', $text); - $t = Title::makeTitleSafe($ns, $text); + $text = preg_replace( '/(#|$)/', 'X$1', $text ); + $t = Title::makeTitleSafe( $ns, $text ); if ( $t && $t->isLocal() ) { return array( $t->getNamespace(), '', '' ); } else { return null; } } + + protected function getGroupName() { + return 'pages'; + } } diff --git a/includes/specials/SpecialAncientpages.php b/includes/specials/SpecialAncientpages.php index 6e3d49bd..b0830327 100644 --- a/includes/specials/SpecialAncientpages.php +++ b/includes/specials/SpecialAncientpages.php @@ -36,17 +36,23 @@ class AncientPagesPage extends QueryPage { return true; } - function isSyndicated() { return false; } + function isSyndicated() { + return false; + } function getQueryInfo() { return array( 'tables' => array( 'page', 'revision' ), - 'fields' => array( 'namespace' => 'page_namespace', - 'title' => 'page_title', - 'value' => 'rev_timestamp' ), - 'conds' => array( 'page_namespace' => MWNamespace::getContentNamespaces(), - 'page_is_redirect' => 0, - 'page_latest=rev_id' ) + 'fields' => array( + 'namespace' => 'page_namespace', + 'title' => 'page_title', + 'value' => 'rev_timestamp' + ), + 'conds' => array( + 'page_namespace' => MWNamespace::getContentNamespaces(), + 'page_is_redirect' => 0, + 'page_latest=rev_id' + ) ); } @@ -58,6 +64,11 @@ class AncientPagesPage extends QueryPage { return false; } + /** + * @param Skin $skin + * @param object $result Result row + * @return string + */ function formatResult( $skin, $result ) { global $wgContLang; @@ -67,6 +78,11 @@ class AncientPagesPage extends QueryPage { $title, htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) ) ); + return $this->getLanguage()->specialList( $link, htmlspecialchars( $d ) ); } + + protected function getGroupName() { + return 'maintenance'; + } } diff --git a/includes/specials/SpecialBlankpage.php b/includes/specials/SpecialBlankpage.php index 42d33779..e61f12b9 100644 --- a/includes/specials/SpecialBlankpage.php +++ b/includes/specials/SpecialBlankpage.php @@ -31,8 +31,9 @@ class SpecialBlankpage extends UnlistedSpecialPage { public function __construct() { parent::__construct( 'Blankpage' ); } + public function execute( $par ) { $this->setHeaders(); - $this->getOutput()->addWikiMsg('intentionallyblankpage'); + $this->getOutput()->addWikiMsg( 'intentionallyblankpage' ); } } diff --git a/includes/specials/SpecialBlock.php b/includes/specials/SpecialBlock.php index 1d6656ab..3b73a374 100644 --- a/includes/specials/SpecialBlock.php +++ b/includes/specials/SpecialBlock.php @@ -62,7 +62,7 @@ class SpecialBlock extends FormSpecialPage { * @throws ErrorPageError */ protected function checkExecutePermissions( User $user ) { - parent::checkExecutePermissions( $user ); + parent::checkExecutePermissions( $user ); # bug 15810: blocked admins should have limited access here $status = self::checkUnblockSelf( $this->target, $user ); @@ -110,10 +110,10 @@ class SpecialBlock extends FormSpecialPage { $s = HTMLForm::formatErrors( $this->preErrors ); if ( $s ) { $form->addHeaderText( Html::rawElement( - 'div', - array( 'class' => 'error' ), - $s - ) ); + 'div', + array( 'class' => 'error' ), + $s + ) ); } } } @@ -127,6 +127,8 @@ class SpecialBlock extends FormSpecialPage { $user = $this->getUser(); + $suggestedDurations = self::getSuggestedDurations(); + $a = array( 'Target' => array( 'type' => 'text', @@ -134,15 +136,16 @@ class SpecialBlock extends FormSpecialPage { 'tabindex' => '1', 'id' => 'mw-bi-target', 'size' => '45', + 'autofocus' => true, 'required' => true, 'validation-callback' => array( __CLASS__, 'validateTargetField' ), ), 'Expiry' => array( - 'type' => !count( self::getSuggestedDurations() ) ? 'text' : 'selectorother', + 'type' => !count( $suggestedDurations ) ? 'text' : 'selectorother', 'label-message' => 'ipbexpiry', 'required' => true, 'tabindex' => '2', - 'options' => self::getSuggestedDurations(), + 'options' => $suggestedDurations, 'other' => $this->msg( 'ipbother' )->text(), 'default' => $this->msg( 'ipb-default-expiry' )->inContentLanguage()->text(), ), @@ -224,7 +227,7 @@ class SpecialBlock extends FormSpecialPage { /** * If the user has already been blocked with similar settings, load that block * and change the defaults for the form fields to match the existing settings. - * @param $fields Array HTMLForm descriptor array + * @param array $fields HTMLForm descriptor array * @return Bool whether fields were altered (that is, whether the target is * already blocked) */ @@ -239,9 +242,8 @@ class SpecialBlock extends FormSpecialPage { if ( $block instanceof Block && !$block->mAuto # The block exists and isn't an autoblock && ( $this->type != Block::TYPE_RANGE # The block isn't a rangeblock - || $block->getTarget() == $this->target ) # or if it is, the range is what we're about to block - ) - { + || $block->getTarget() == $this->target ) # or if it is, the range is what we're about to block + ) { $fields['HardBlock']['default'] = $block->isHardblock(); $fields['CreateAccount']['default'] = $block->prevents( 'createaccount' ); $fields['AutoBlock']['default'] = $block->isAutoblocking(); @@ -386,7 +388,7 @@ class SpecialBlock extends FormSpecialPage { ); } - $text = Html::rawElement( + $text = Html::rawElement( 'p', array( 'class' => 'mw-ipb-conveniencelinks' ), $this->getLanguage()->pipeList( $links ) @@ -444,13 +446,14 @@ class SpecialBlock extends FormSpecialPage { } elseif ( IP::isIPAddress( $target ) ) { return Title::makeTitleSafe( NS_USER, $target ); } + return null; } /** * Determine the target of the block, and the type of target * TODO: should be in Block.php? - * @param $par String subpage parameter passed to setup, or data value from + * @param string $par subpage parameter passed to setup, or data value from * the HTMLForm * @param $request WebRequest optionally try and get data from a request too * @return array( User|string|null, Block::TYPE_ constant|null ) @@ -459,8 +462,8 @@ class SpecialBlock extends FormSpecialPage { $i = 0; $target = null; - while( true ) { - switch( $i++ ) { + while ( true ) { + switch ( $i++ ) { case 0: # The HTMLForm will check wpTarget first and only if it doesn't get # a value use the default, which will be generated from the options @@ -507,53 +510,76 @@ class SpecialBlock extends FormSpecialPage { * @return Message */ public static function validateTargetField( $value, $alldata, $form ) { + $status = self::validateTarget( $value, $form->getUser() ); + if ( !$status->isOK() ) { + $errors = $status->getErrorsArray(); + + return call_user_func_array( array( $form, 'msg' ), $errors[0] ); + } else { + return true; + } + } + + /** + * Validate a block target. + * + * @since 1.21 + * @param string $value Block target to check + * @param User $user Performer of the block + * @return Status + */ + public static function validateTarget( $value, User $user ) { global $wgBlockCIDRLimit; + /** @var User $target */ list( $target, $type ) = self::getTargetAndType( $value ); + $status = Status::newGood( $target ); if ( $type == Block::TYPE_USER ) { - # TODO: why do we not have a User->exists() method? - if ( !$target->getId() ) { - return $form->msg( 'nosuchusershort', - wfEscapeWikiText( $target->getName() ) ); + if ( $target->isAnon() ) { + $status->fatal( + 'nosuchusershort', + wfEscapeWikiText( $target->getName() ) + ); } - $status = self::checkUnblockSelf( $target, $form->getUser() ); - if ( $status !== true ) { - return $form->msg( 'badaccess', $status ); + $unblockStatus = self::checkUnblockSelf( $target, $user ); + if ( $unblockStatus !== true ) { + $status->fatal( 'badaccess', $unblockStatus ); } - } elseif ( $type == Block::TYPE_RANGE ) { list( $ip, $range ) = explode( '/', $target, 2 ); - if ( ( IP::isIPv4( $ip ) && $wgBlockCIDRLimit['IPv4'] == 32 ) - || ( IP::isIPv6( $ip ) && $wgBlockCIDRLimit['IPv6'] == 128 ) ) - { - # Range block effectively disabled - return $form->msg( 'range_block_disabled' ); + if ( + ( IP::isIPv4( $ip ) && $wgBlockCIDRLimit['IPv4'] == 32 ) || + ( IP::isIPv6( $ip ) && $wgBlockCIDRLimit['IPv6'] == 128 ) + ) { + // Range block effectively disabled + $status->fatal( 'range_block_disabled' ); } - if ( ( IP::isIPv4( $ip ) && $range > 32 ) - || ( IP::isIPv6( $ip ) && $range > 128 ) ) - { - # Dodgy range - return $form->msg( 'ip_range_invalid' ); + if ( + ( IP::isIPv4( $ip ) && $range > 32 ) || + ( IP::isIPv6( $ip ) && $range > 128 ) + ) { + // Dodgy range + $status->fatal( 'ip_range_invalid' ); } if ( IP::isIPv4( $ip ) && $range < $wgBlockCIDRLimit['IPv4'] ) { - return $form->msg( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv4'] ); + $status->fatal( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv4'] ); } if ( IP::isIPv6( $ip ) && $range < $wgBlockCIDRLimit['IPv6'] ) { - return $form->msg( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv6'] ); + $status->fatal( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv6'] ); } } elseif ( $type == Block::TYPE_IP ) { # All is well } else { - return $form->msg( 'badipaddress' ); + $status->fatal( 'badipaddress' ); } - return true; + return $status; } /** @@ -584,6 +610,7 @@ class SpecialBlock extends FormSpecialPage { # can come from it $data['Confirm'] = !in_array( $data['Confirm'], array( '', '0', null, false ), true ); + /** @var User $target */ list( $target, $type ) = self::getTargetAndType( $data['Target'] ); if ( $type == Block::TYPE_USER ) { $user = $target; @@ -598,8 +625,8 @@ class SpecialBlock extends FormSpecialPage { # but $data['target'] gets overriden by (non-normalized) request variable # from previous request. if ( $target === $performer->getName() && - ( $data['PreviousTarget'] !== $target || !$data['Confirm'] ) ) - { + ( $data['PreviousTarget'] !== $target || !$data['Confirm'] ) + ) { return array( 'ipb-blockingself' ); } } elseif ( $type == Block::TYPE_RANGE ) { @@ -612,9 +639,9 @@ class SpecialBlock extends FormSpecialPage { return array( 'badipaddress' ); } - if ( ( strlen( $data['Expiry'] ) == 0) || ( strlen( $data['Expiry'] ) > 50 ) - || !self::parseExpiryInput( $data['Expiry'] ) ) - { + if ( ( strlen( $data['Expiry'] ) == 0 ) || ( strlen( $data['Expiry'] ) > 50 ) + || !self::parseExpiryInput( $data['Expiry'] ) + ) { return array( 'ipb_expiry_invalid' ); } @@ -629,7 +656,7 @@ class SpecialBlock extends FormSpecialPage { } if ( $data['HideUser'] ) { - if ( !$performer->isAllowed('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 @@ -672,12 +699,18 @@ class SpecialBlock extends FormSpecialPage { # Try to insert block. Is there a conflicting block? $status = $block->insert(); if ( !$status ) { + # Indicates whether the user is confirming the block and is aware of + # the conflict (did not change the block target in the meantime) + $blockNotConfirmed = !$data['Confirm'] || ( array_key_exists( 'PreviousTarget', $data ) + && $data['PreviousTarget'] !== $target ); + + # Special case for API - bug 32434 + $reblockNotAllowed = ( array_key_exists( 'Reblock', $data ) && !$data['Reblock'] ); + # Show form unless the user is already aware of this... - if ( !$data['Confirm'] || ( array_key_exists( 'PreviousTarget', $data ) - && $data['PreviousTarget'] !== $target ) ) - { + if ( $blockNotConfirmed || $reblockNotAllowed ) { return array( array( 'ipb_already_blocked', $block->getTarget() ) ); - # Otherwise, try to update the block... + # Otherwise, try to update the block... } else { # This returns direct blocks before autoblocks/rangeblocks, since we should # be sure the user is blocked by now it should work for our purposes @@ -720,7 +753,7 @@ class SpecialBlock extends FormSpecialPage { # Can't watch a rangeblock if ( $type != Block::TYPE_RANGE && $data['Watch'] ) { - $performer->addWatch( Title::makeTitle( NS_USER, $target ) ); + WatchAction::doWatch( Title::makeTitle( NS_USER, $target ), $performer, WatchedItem::IGNORE_USER_RIGHTS ); } # Block constructor sanitizes certain block options on insert @@ -739,10 +772,11 @@ class SpecialBlock extends FormSpecialPage { $logaction, Title::makeTitle( NS_USER, $target ), $data['Reason'][0], - $logParams + $logParams, + $performer ); # Relate log ID to block IDs (bug 25763) - $blockIds = array_merge( array( $status['id'] ), $status['autoIds'] ); + $blockIds = array_merge( array( $status['id'] ), $status['autoIds'] ); $log->addRelations( 'ipb_id', $blockIds, $log_id ); # Report to the user @@ -782,7 +816,7 @@ 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 $expiry String: whatever was typed into the form + * @param string $expiry whatever was typed into the form * @return String: timestamp or "infinity" string for the DB implementation */ public static function parseExpiryInput( $expiry ) { @@ -837,7 +871,7 @@ class SpecialBlock extends FormSpecialPage { # User is trying to unblock themselves if ( $performer->isAllowed( 'unblockself' ) ) { return true; - # User blocked themselves and is now trying to reverse it + # User blocked themselves and is now trying to reverse it } elseif ( $performer->blockedBy() === $performer->getName() ) { return true; } else { @@ -855,7 +889,7 @@ class SpecialBlock extends FormSpecialPage { /** * Return a comma-delimited list of "flags" to be passed to the log * reader for this block, to provide more information in the logs - * @param $data Array from HTMLForm data + * @param array $data from HTMLForm data * @param $type Block::TYPE_ constant (USER, RANGE, or IP) * @return string */ @@ -918,7 +952,12 @@ class SpecialBlock extends FormSpecialPage { $out->setPageTitle( $this->msg( 'blockipsuccesssub' ) ); $out->addWikiMsg( 'blockipsuccesstext', wfEscapeWikiText( $this->target ) ); } + + protected function getGroupName() { + return 'users'; + } } # BC @since 1.18 -class IPBlockForm extends SpecialBlock {} +class IPBlockForm extends SpecialBlock { +} diff --git a/includes/specials/SpecialBlockList.php b/includes/specials/SpecialBlockList.php index 7143d5bc..f1992c0f 100644 --- a/includes/specials/SpecialBlockList.php +++ b/includes/specials/SpecialBlockList.php @@ -37,7 +37,7 @@ class SpecialBlockList extends SpecialPage { /** * Main execution point * - * @param $par String title fragment + * @param string $par title fragment */ public function execute( $par ) { $this->setHeaders(); @@ -55,10 +55,11 @@ class SpecialBlockList extends SpecialPage { $action = $request->getText( 'action' ); - if( $action == 'unblock' || $action == 'submit' && $request->wasPosted() ) { + if ( $action == 'unblock' || $action == 'submit' && $request->wasPosted() ) { # B/C @since 1.18: Unblock interface is now at Special:Unblock $title = SpecialPage::getTitleFor( 'Unblock', $this->target ); - $out->redirect( $title->getFullUrl() ); + $out->redirect( $title->getFullURL() ); + return; } @@ -95,7 +96,9 @@ class SpecialBlockList extends SpecialPage { 'default' => 50, ), ); - $form = new HTMLForm( $fields, $this->getContext() ); + $context = new DerivativeContext( $this->getContext() ); + $context->setTitle( $this->getTitle() ); // Remove subpage + $form = new HTMLForm( $fields, $context ); $form->setMethod( 'get' ); $form->setWrapperLegendMsg( 'ipblocklist-legend' ); $form->setSubmitTextMsg( 'ipblocklist-submit' ); @@ -113,14 +116,14 @@ class SpecialBlockList extends SpecialPage { $conds = array(); # Is the user allowed to see hidden blocks? - if ( !$this->getUser()->isAllowed( 'hideuser' ) ){ + if ( !$this->getUser()->isAllowed( 'hideuser' ) ) { $conds['ipb_deleted'] = 0; } - if ( $this->target !== '' ){ + if ( $this->target !== '' ) { list( $target, $type ) = Block::parseTarget( $this->target ); - switch( $type ){ + switch ( $type ) { case Block::TYPE_ID: case Block::TYPE_AUTO: $conds['ipb_id'] = $target; @@ -141,23 +144,23 @@ class SpecialBlockList extends SpecialPage { break; case Block::TYPE_USER: - $conds['ipb_address'] = (string)$this->target; + $conds['ipb_address'] = $target->getName(); $conds['ipb_auto'] = 0; break; } } # Apply filters - if( in_array( 'userblocks', $this->options ) ) { + if ( in_array( 'userblocks', $this->options ) ) { $conds['ipb_user'] = 0; } - if( in_array( 'tempblocks', $this->options ) ) { + if ( in_array( 'tempblocks', $this->options ) ) { $conds['ipb_expiry'] = 'infinity'; } - if( in_array( 'addressblocks', $this->options ) ) { + if ( in_array( 'addressblocks', $this->options ) ) { $conds[] = "ipb_user != 0 OR ipb_range_end > ipb_range_start"; } - if( in_array( 'rangeblocks', $this->options ) ) { + if ( in_array( 'rangeblocks', $this->options ) ) { $conds[] = "ipb_range_end = ipb_range_start"; } @@ -169,7 +172,7 @@ class SpecialBlockList extends SpecialPage { # 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 ) ) { + if ( count( $otherBlockLink ) ) { $out->addHTML( Html::element( 'h2', array(), $this->msg( 'ipblocklist-localblock' )->text() ) . "\n" ); @@ -179,18 +182,16 @@ class SpecialBlockList extends SpecialPage { if ( $pager->getNumRows() ) { $out->addHTML( $pager->getNavigationBar() . - $pager->getBody(). - $pager->getNavigationBar() + $pager->getBody() . + $pager->getNavigationBar() ); - } elseif ( $this->target ) { $out->addWikiMsg( 'ipblocklist-no-results' ); - } else { $out->addWikiMsg( 'ipblocklist-empty' ); } - if( count( $otherBlockLink ) ) { + if ( count( $otherBlockLink ) ) { $out->addHTML( Html::rawElement( 'h2', @@ -199,12 +200,16 @@ class SpecialBlockList extends SpecialPage { ) . "\n" ); $list = ''; - foreach( $otherBlockLink as $link ) { + foreach ( $otherBlockLink as $link ) { $list .= Html::rawElement( 'li', array(), $link ) . "\n"; } $out->addHTML( Html::rawElement( 'ul', array( 'class' => 'mw-ipblocklist-otherblocks' ), $list ) . "\n" ); } } + + protected function getGroupName() { + return 'users'; + } } class BlockListPager extends TablePager { @@ -234,7 +239,7 @@ class BlockListPager extends TablePager { 'ipb_params' => 'blocklist-params', 'ipb_reason' => 'blocklist-reason', ); - foreach( $headers as $key => $val ) { + foreach ( $headers as $key => $val ) { $headers[$key] = $this->msg( $val )->text(); } } @@ -263,17 +268,17 @@ class BlockListPager extends TablePager { $formatted = ''; - switch( $name ) { + switch ( $name ) { case 'ipb_timestamp': $formatted = $this->getLanguage()->userTimeAndDate( $value, $this->getUser() ); break; case 'ipb_target': - if( $row->ipb_auto ){ + if ( $row->ipb_auto ) { $formatted = $this->msg( 'autoblockid', $row->ipb_id )->parse(); } else { list( $target, $type ) = Block::parseTarget( $row->ipb_address ); - switch( $type ){ + switch ( $type ) { case Block::TYPE_USER: case Block::TYPE_IP: $formatted = Linker::userLink( $target->getId(), $target ); @@ -291,9 +296,9 @@ class BlockListPager extends TablePager { break; case 'ipb_expiry': - $formatted = $this->getLanguage()->formatExpiry( $value, /* User preference timezone */ true ); - if( $this->getUser()->isAllowed( 'block' ) ){ - if( $row->ipb_auto ){ + $formatted = $this->getLanguage()->formatExpiry( $value, /* User preference timezone */true ); + if ( $this->getUser()->isAllowed( 'block' ) ) { + if ( $row->ipb_auto ) { $links[] = Linker::linkKnown( SpecialPage::getTitleFor( 'Unblock' ), $msg['unblocklink'], @@ -329,7 +334,7 @@ class BlockListPager extends TablePager { break; case 'ipb_reason': - $formatted = Linker::commentBlock( $value ); + $formatted = Linker::formatComment( $value ); break; case 'ipb_params': @@ -391,14 +396,14 @@ class BlockListPager extends TablePager { ); # Is the user allowed to see hidden blocks? - if ( !$this->getUser()->isAllowed( 'hideuser' ) ){ + if ( !$this->getUser()->isAllowed( 'hideuser' ) ) { $info['conds']['ipb_deleted'] = 0; } return $info; } - public function getTableClass(){ + public function getTableClass() { return 'TablePager mw-blocklist'; } @@ -416,9 +421,9 @@ class BlockListPager extends TablePager { /** * Do a LinkBatch query to minimise database load when generating all these links - * @param $result + * @param ResultWrapper $result */ - function preprocessResults( $result ){ + function preprocessResults( $result ) { wfProfileIn( __METHOD__ ); # Do a link batch query $lb = new LinkBatch; @@ -437,11 +442,11 @@ class BlockListPager extends TablePager { } $ua = UserArray::newFromIDs( $userids ); - foreach( $ua as $user ){ + foreach ( $ua as $user ) { $name = str_replace( ' ', '_', $user->getName() ); $lb->add( NS_USER, $name ); $lb->add( NS_USER_TALK, $name ); - } + } $lb->execute(); wfProfileOut( __METHOD__ ); @@ -472,11 +477,10 @@ class HTMLBlockedUsersItemSelect extends HTMLSelectField { // This adds the explicitly requested limit value to the drop-down, // then makes sure it's sorted correctly so when we output the list // later, the custom option doesn't just show up last. - $this->mParams['options'][ $this->mParent->getLanguage()->formatNum( $value ) ] = intval($value); + $this->mParams['options'][$this->mParent->getLanguage()->formatNum( $value )] = intval( $value ); asort( $this->mParams['options'] ); } return true; } - } diff --git a/includes/specials/SpecialBlockme.php b/includes/specials/SpecialBlockme.php deleted file mode 100644 index 3840b2ff..00000000 --- a/includes/specials/SpecialBlockme.php +++ /dev/null @@ -1,62 +0,0 @@ -<?php -/** - * Implements Special:Blockme - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - * @ingroup SpecialPage - */ - -/** - * A special page called by proxy_check.php to block open proxies - * - * @ingroup SpecialPage - */ -class SpecialBlockme extends UnlistedSpecialPage { - - function __construct() { - parent::__construct( 'Blockme' ); - } - - function execute( $par ) { - global $wgBlockOpenProxies, $wgProxyKey; - - $this->setHeaders(); - $this->outputHeader(); - - $ip = $this->getRequest()->getIP(); - if( !$wgBlockOpenProxies || $this->getRequest()->getText( 'ip' ) != md5( $ip . $wgProxyKey ) ) { - $this->getOutput()->addWikiMsg( 'proxyblocker-disabled' ); - return; - } - - $user = User::newFromName( $this->msg( 'proxyblocker' )->inContentLanguage()->text() ); - # FIXME: newFromName could return false on a badly configured wiki. - if ( !$user->isLoggedIn() ) { - $user->addToDatabase(); - } - - $block = new Block(); - $block->setTarget( $ip ); - $block->setBlocker( $user ); - $block->mReason = $this->msg( 'proxyblockreason' )->inContentLanguage()->text(); - - $block->insert(); - - $this->getOutput()->addWikiMsg( 'proxyblocksuccess' ); - } -} diff --git a/includes/specials/SpecialBooksources.php b/includes/specials/SpecialBooksources.php index bf7de3f5..5ad961c3 100644 --- a/includes/specials/SpecialBooksources.php +++ b/includes/specials/SpecialBooksources.php @@ -46,15 +46,15 @@ class SpecialBookSources extends SpecialPage { /** * Show the special page * - * @param $isbn string ISBN passed as a subpage parameter + * @param string $isbn ISBN passed as a subpage parameter */ public function execute( $isbn ) { $this->setHeaders(); $this->outputHeader(); $this->isbn = self::cleanIsbn( $isbn ? $isbn : $this->getRequest()->getText( 'isbn' ) ); $this->getOutput()->addHTML( $this->makeForm() ); - if( strlen( $this->isbn ) > 0 ) { - if( !self::isValidISBN( $this->isbn ) ) { + if ( strlen( $this->isbn ) > 0 ) { + if ( !self::isValidISBN( $this->isbn ) ) { $this->getOutput()->wrapWikiMsg( "<div class=\"error\">\n$1\n</div>", 'booksources-invalid-isbn' ); } $this->showList(); @@ -62,46 +62,47 @@ class SpecialBookSources extends SpecialPage { } /** - * Returns whether a given ISBN (10 or 13) is valid. True indicates validity. - * @param isbn string ISBN passed for check + * Returns whether a given ISBN (10 or 13) is valid. True indicates validity. + * @param string $isbn ISBN passed for check * @return bool */ public static function isValidISBN( $isbn ) { $isbn = self::cleanIsbn( $isbn ); $sum = 0; - if( strlen( $isbn ) == 13 ) { - for( $i = 0; $i < 12; $i++ ) { - if($i % 2 == 0) { + if ( strlen( $isbn ) == 13 ) { + for ( $i = 0; $i < 12; $i++ ) { + if ( $i % 2 == 0 ) { $sum += $isbn[$i]; } else { $sum += 3 * $isbn[$i]; } } - $check = (10 - ($sum % 10)) % 10; - if ($check == $isbn[12]) { + $check = ( 10 - ( $sum % 10 ) ) % 10; + if ( $check == $isbn[12] ) { return true; } - } elseif( strlen( $isbn ) == 10 ) { - for($i = 0; $i < 9; $i++) { - $sum += $isbn[$i] * ($i + 1); + } elseif ( strlen( $isbn ) == 10 ) { + for ( $i = 0; $i < 9; $i++ ) { + $sum += $isbn[$i] * ( $i + 1 ); } $check = $sum % 11; - if($check == 10) { + if ( $check == 10 ) { $check = "X"; } - if($check == $isbn[9]) { + if ( $check == $isbn[9] ) { return true; } } + return false; } /** * Trim ISBN and remove characters which aren't required * - * @param $isbn string Unclean ISBN + * @param string $isbn Unclean ISBN * @return string */ private static function cleanIsbn( $isbn ) { @@ -116,13 +117,14 @@ class SpecialBookSources extends SpecialPage { private function makeForm() { global $wgScript; - $form = '<fieldset><legend>' . $this->msg( 'booksources-search-legend' )->escaped() . '</legend>'; - $form .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); - $form .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ); - $form .= '<p>' . Xml::inputLabel( $this->msg( 'booksources-isbn' )->text(), 'isbn', 'isbn', 20, $this->isbn ); - $form .= ' ' . Xml::submitButton( $this->msg( 'booksources-go' )->text() ) . '</p>'; - $form .= Xml::closeElement( 'form' ); - $form .= '</fieldset>'; + $form = Html::openElement( 'fieldset' ) . "\n"; + $form .= Html::element( 'legend', array(), $this->msg( 'booksources-search-legend' )->text() ) . "\n"; + $form .= Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . "\n"; + $form .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n"; + $form .= '<p>' . Xml::inputLabel( $this->msg( 'booksources-isbn' )->text(), 'isbn', 'isbn', 20, $this->isbn, array( 'autofocus' => true ) ); + $form .= ' ' . Xml::submitButton( $this->msg( 'booksources-go' )->text() ) . "</p>\n"; + $form .= Html::closeElement( 'form' ) . "\n"; + $form .= Html::closeElement( 'fieldset' ) . "\n"; return $form; } @@ -130,6 +132,7 @@ class SpecialBookSources extends SpecialPage { * Determine where to get the list of book sources from, * format and output them * + * @throws MWException * @return string */ private function showList() { @@ -142,31 +145,49 @@ class SpecialBookSources extends SpecialPage { # Check for a local page such as Project:Book_sources and use that if available $page = $this->msg( 'booksources' )->inContentLanguage()->text(); $title = Title::makeTitleSafe( NS_PROJECT, $page ); # Show list in content language - if( is_object( $title ) && $title->exists() ) { + if ( is_object( $title ) && $title->exists() ) { $rev = Revision::newFromTitle( $title, false, Revision::READ_NORMAL ); - $this->getOutput()->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $rev->getText() ) ); - return true; + $content = $rev->getContent(); + + if ( $content instanceof TextContent ) { + //XXX: in the future, this could be stored as structured data, defining a list of book sources + + $text = $content->getNativeData(); + $this->getOutput()->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $text ) ); + + return true; + } else { + throw new MWException( "Unexpected content type for book sources: " . $content->getModel() ); + } } # Fall back to the defaults given in the language file $this->getOutput()->addWikiMsg( 'booksources-text' ); $this->getOutput()->addHTML( '<ul>' ); $items = $wgContLang->getBookstoreList(); - foreach( $items as $label => $url ) + foreach ( $items as $label => $url ) { $this->getOutput()->addHTML( $this->makeListItem( $label, $url ) ); + } $this->getOutput()->addHTML( '</ul>' ); + return true; } /** * Format a book source list item * - * @param $label string Book source label - * @param $url string Book source URL + * @param string $label Book source label + * @param string $url Book source URL * @return string */ private function makeListItem( $label, $url ) { $url = str_replace( '$1', $this->isbn, $url ); - return '<li><a href="' . htmlspecialchars( $url ) . '" class="external">' . htmlspecialchars( $label ) . '</a></li>'; + + return Html::rawElement( 'li', array(), + Html::element( 'a', array( 'href' => $url, 'class' => 'external' ), $label ) ); + } + + protected function getGroupName() { + return 'other'; } } diff --git a/includes/specials/SpecialBrokenRedirects.php b/includes/specials/SpecialBrokenRedirects.php index 8119e6d1..b2ddc220 100644 --- a/includes/specials/SpecialBrokenRedirects.php +++ b/includes/specials/SpecialBrokenRedirects.php @@ -33,35 +33,55 @@ class BrokenRedirectsPage extends QueryPage { parent::__construct( $name ); } - function isExpensive() { return true; } - function isSyndicated() { return false; } - function sortDescending() { return false; } + function isExpensive() { + return true; + } + + function isSyndicated() { + return false; + } + + function sortDescending() { + return false; + } function getPageHeader() { return $this->msg( 'brokenredirectstext' )->parseAsBlock(); } function getQueryInfo() { + $dbr = wfGetDB( DB_SLAVE ); + return array( - 'tables' => array( 'redirect', 'p1' => 'page', - 'p2' => 'page' ), - 'fields' => array( 'namespace' => 'p1.page_namespace', - 'title' => 'p1.page_title', - 'value' => 'p1.page_title', - 'rd_namespace', - 'rd_title' + 'tables' => array( + 'redirect', + 'p1' => 'page', + 'p2' => 'page', ), - 'conds' => array( 'rd_namespace >= 0', - 'p2.page_namespace IS NULL' + 'fields' => array( + 'namespace' => 'p1.page_namespace', + 'title' => 'p1.page_title', + 'value' => 'p1.page_title', + 'rd_namespace', + 'rd_title', + ), + 'conds' => array( + // Exclude pages that don't exist locally as wiki pages, + // but aren't "broken" either. + // Special pages and interwiki links + 'rd_namespace >= 0', + 'rd_interwiki IS NULL OR rd_interwiki = ' . $dbr->addQuotes( '' ), + 'p2.page_namespace IS NULL', + ), + 'join_conds' => array( + 'p1' => array( 'JOIN', array( + 'rd_from=p1.page_id', + ) ), + 'p2' => array( 'LEFT JOIN', array( + 'rd_namespace=p2.page_namespace', + 'rd_title=p2.page_title' + ) ), ), - 'join_conds' => array( 'p1' => array( 'JOIN', array( - 'rd_from=p1.page_id', - ) ), - 'p2' => array( 'LEFT JOIN', array( - 'rd_namespace=p2.page_namespace', - 'rd_title=p2.page_title' - ) ) - ) ); } @@ -69,13 +89,13 @@ class BrokenRedirectsPage extends QueryPage { * @return array */ function getOrderFields() { - return array ( 'rd_namespace', 'rd_title', 'rd_from' ); + return array( 'rd_namespace', 'rd_title', 'rd_from' ); } /** - * @param $skin Skin - * @param $result - * @return String + * @param Skin $skin + * @param object $result Result row + * @return string */ function formatResult( $skin, $result ) { $fromObj = Title::makeTitle( $result->namespace, $result->title ); @@ -119,7 +139,7 @@ class BrokenRedirectsPage extends QueryPage { $out = $from . $this->msg( 'word-separator' )->escaped(); - if( $this->getUser()->isAllowed( 'delete' ) ) { + if ( $this->getUser()->isAllowed( 'delete' ) ) { $links[] = Linker::linkKnown( $fromObj, $this->msg( 'brokenredirects-delete' )->escaped(), @@ -130,6 +150,11 @@ class BrokenRedirectsPage extends QueryPage { $out .= $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList( $links ) )->escaped(); $out .= " {$arr} {$to}"; + return $out; } + + protected function getGroupName() { + return 'maintenance'; + } } diff --git a/includes/specials/SpecialCachedPage.php b/includes/specials/SpecialCachedPage.php index b3f6c720..39305f01 100644 --- a/includes/specials/SpecialCachedPage.php +++ b/includes/specials/SpecialCachedPage.php @@ -119,7 +119,7 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper { * * @since 1.20 * - * @param {function} $computeFunction + * @param callable $computeFunction * @param array|mixed $args * @param string|null $key * @@ -137,7 +137,7 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper { * * @since 1.20 * - * @param {function} $computeFunction + * @param callable $computeFunction * @param array $args * @param string|null $key */ @@ -194,5 +194,4 @@ abstract class SpecialCachedPage extends SpecialPage implements ICacheHelper { $this->getOutput()->setSubtitle( $this->cacheHelper->getCachedNotice( $this->getContext() ) ); } } - } diff --git a/includes/specials/SpecialCategories.php b/includes/specials/SpecialCategories.php index 1232e3fa..d01bfd7d 100644 --- a/includes/specials/SpecialCategories.php +++ b/includes/specials/SpecialCategories.php @@ -42,14 +42,18 @@ class SpecialCategories extends SpecialPage { $this->getOutput()->addHTML( Html::openElement( 'div', array( 'class' => 'mw-spcontent' ) ) . - $this->msg( 'categoriespagetext', $cap->getNumRows() )->parseAsBlock() . - $cap->getStartForm( $from ) . - $cap->getNavigationBar() . - '<ul>' . $cap->getBody() . '</ul>' . - $cap->getNavigationBar() . - Html::closeElement( 'div' ) + $this->msg( 'categoriespagetext', $cap->getNumRows() )->parseAsBlock() . + $cap->getStartForm( $from ) . + $cap->getNavigationBar() . + '<ul>' . $cap->getBody() . '</ul>' . + $cap->getNavigationBar() . + Html::closeElement( 'div' ) ); } + + protected function getGroupName() { + return 'pages'; + } } /** @@ -62,7 +66,7 @@ class CategoryPager extends AlphabeticPager { function __construct( IContextSource $context, $from ) { parent::__construct( $context ); $from = str_replace( ' ', '_', $from ); - if( $from !== '' ) { + if ( $from !== '' ) { $from = Title::capitalize( $from, NS_CATEGORY ); $this->setOffset( $from ); $this->setIncludeOffset( true ); @@ -72,7 +76,7 @@ class CategoryPager extends AlphabeticPager { function getQueryInfo() { return array( 'tables' => array( 'category' ), - 'fields' => array( 'cat_title','cat_pages' ), + 'fields' => array( 'cat_title', 'cat_pages' ), 'conds' => array( 'cat_pages > 0' ), 'options' => array( 'USE INDEX' => 'cat_title' ), ); @@ -86,8 +90,10 @@ class CategoryPager extends AlphabeticPager { function getDefaultQuery() { parent::getDefaultQuery(); unset( $this->mDefaultQuery['from'] ); + return $this->mDefaultQuery; } + # protected function getOrderTypeMessages() { # return array( 'abc' => 'special-categories-sort-abc', # 'count' => 'special-categories-sort-count' ); @@ -109,26 +115,34 @@ class CategoryPager extends AlphabeticPager { } $batch->execute(); $this->mResult->rewind(); + return parent::getBody(); } - function formatRow($result) { + function formatRow( $result ) { $title = Title::makeTitle( NS_CATEGORY, $result->cat_title ); $titleText = Linker::link( $title, htmlspecialchars( $title->getText() ) ); $count = $this->msg( 'nmembers' )->numParams( $result->cat_pages )->escaped(); + return Xml::tags( 'li', null, $this->getLanguage()->specialList( $titleText, $count ) ) . "\n"; } public function getStartForm( $from ) { global $wgScript; - return - Xml::tags( 'form', array( 'method' => 'get', 'action' => $wgScript ), - Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . - Xml::fieldset( $this->msg( 'categories' )->text(), - Xml::inputLabel( $this->msg( 'categoriesfrom' )->text(), + return Xml::tags( + 'form', + array( 'method' => 'get', 'action' => $wgScript ), + Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . + Xml::fieldset( + $this->msg( 'categories' )->text(), + Xml::inputLabel( + $this->msg( 'categoriesfrom' )->text(), 'from', 'from', 20, $from ) . - ' ' . - Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) ) ); + ' ' . + Xml::submitButton( $this->msg( 'allpagessubmit' )->text() + ) + ) + ); } } diff --git a/includes/specials/SpecialChangeEmail.php b/includes/specials/SpecialChangeEmail.php index fc726106..aab839fd 100644 --- a/includes/specials/SpecialChangeEmail.php +++ b/includes/specials/SpecialChangeEmail.php @@ -41,7 +41,7 @@ class SpecialChangeEmail extends UnlistedSpecialPage { protected $mNewEmail; public function __construct() { - parent::__construct( 'ChangeEmail' ); + parent::__construct( 'ChangeEmail', 'editmyprivateinfo' ); } /** @@ -49,6 +49,7 @@ class SpecialChangeEmail extends UnlistedSpecialPage { */ function isListed() { global $wgAuth; + return $wgAuth->allowPropChange( 'emailaddress' ); } @@ -67,6 +68,7 @@ class SpecialChangeEmail extends UnlistedSpecialPage { if ( !$wgAuth->allowPropChange( 'emailaddress' ) ) { $this->error( 'cannotchangeemail' ); + return; } @@ -75,22 +77,31 @@ class SpecialChangeEmail extends UnlistedSpecialPage { if ( !$request->wasPosted() && !$user->isLoggedIn() ) { $this->error( 'changeemail-no-info' ); + return; } if ( $request->wasPosted() && $request->getBool( 'wpCancel' ) ) { $this->doReturnTo(); + return; } $this->checkReadOnly(); + $this->checkPermissions(); + + // This could also let someone check the current email address, so + // require both permissions. + if ( !$this->getUser()->isAllowed( 'viewmyprivateinfo' ) ) { + throw new PermissionsError( 'viewmyprivateinfo' ); + } $this->mPassword = $request->getVal( 'wpPassword' ); $this->mNewEmail = $request->getVal( 'wpNewEmail' ); if ( $request->wasPosted() - && $user->matchEditToken( $request->getVal( 'token' ) ) ) - { + && $user->matchEditToken( $request->getVal( 'token' ) ) + ) { $info = $this->attemptChange( $user, $this->mPassword, $this->mNewEmail ); if ( $info === true ) { $this->doReturnTo(); @@ -138,38 +149,38 @@ class SpecialChangeEmail extends UnlistedSpecialPage { $this->getOutput()->addHTML( Xml::fieldset( $this->msg( 'changeemail-header' )->text() ) . - Xml::openElement( 'form', - array( - 'method' => 'post', - 'action' => $this->getTitle()->getLocalUrl(), - 'id' => 'mw-changeemail-form' ) ) . "\n" . - Html::hidden( 'token', $user->getEditToken() ) . "\n" . - Html::hidden( 'returnto', $this->getRequest()->getVal( 'returnto' ) ) . "\n" . - $this->msg( 'changeemail-text' )->parseAsBlock() . "\n" . - Xml::openElement( 'table', array( 'id' => 'mw-changeemail-table' ) ) . "\n" + Xml::openElement( 'form', + array( + 'method' => 'post', + 'action' => $this->getTitle()->getLocalURL(), + 'id' => 'mw-changeemail-form' ) ) . "\n" . + Html::hidden( 'token', $user->getEditToken() ) . "\n" . + Html::hidden( 'returnto', $this->getRequest()->getVal( 'returnto' ) ) . "\n" . + $this->msg( 'changeemail-text' )->parseAsBlock() . "\n" . + Xml::openElement( 'table', array( 'id' => 'mw-changeemail-table' ) ) . "\n" ); $items = array( array( 'wpName', 'username', 'text', $user->getName() ), array( 'wpOldEmail', 'changeemail-oldemail', 'text', $oldEmailText ), - array( 'wpNewEmail', 'changeemail-newemail', 'input', $this->mNewEmail ), + array( 'wpNewEmail', 'changeemail-newemail', 'email', $this->mNewEmail ), ); if ( $wgRequirePasswordforEmailChange ) { - $items[] = array( 'wpPassword', 'yourpassword', 'password', $this->mPassword ); + $items[] = array( 'wpPassword', 'changeemail-password', 'password', $this->mPassword ); } $this->getOutput()->addHTML( $this->pretty( $items ) . - "\n" . - "<tr>\n" . + "\n" . + "<tr>\n" . "<td></td>\n" . '<td class="mw-input">' . - Xml::submitButton( $this->msg( 'changeemail-submit' )->text() ) . - Xml::submitButton( $this->msg( 'changeemail-cancel' )->text(), array( 'name' => 'wpCancel' ) ) . + Xml::submitButton( $this->msg( 'changeemail-submit' )->text() ) . + Xml::submitButton( $this->msg( 'changeemail-cancel' )->text(), array( 'name' => 'wpCancel' ) ) . "</td>\n" . - "</tr>\n" . - Xml::closeElement( 'table' ) . - Xml::closeElement( 'form' ) . - Xml::closeElement( 'fieldset' ) . "\n" + "</tr>\n" . + Xml::closeElement( 'table' ) . + Xml::closeElement( 'form' ) . + Xml::closeElement( 'fieldset' ) . "\n" ); } @@ -181,7 +192,7 @@ class SpecialChangeEmail extends UnlistedSpecialPage { $out = ''; foreach ( $fields as $list ) { list( $name, $label, $type, $value ) = $list; - if( $type == 'text' ) { + if ( $type == 'text' ) { $field = htmlspecialchars( $value ); } else { $attribs = array( 'id' => $name ); @@ -195,7 +206,7 @@ class SpecialChangeEmail extends UnlistedSpecialPage { if ( $type != 'text' ) { $out .= Xml::label( $this->msg( $label )->text(), $name ); } else { - $out .= $this->msg( $label )->escaped(); + $out .= $this->msg( $label )->escaped(); } $out .= "</td>\n"; $out .= "\t<td class='mw-input'>"; @@ -203,6 +214,7 @@ class SpecialChangeEmail extends UnlistedSpecialPage { $out .= "</td>\n"; $out .= "</tr>"; } + return $out; } @@ -213,20 +225,26 @@ class SpecialChangeEmail extends UnlistedSpecialPage { * @return bool|string true or string on success, false on failure */ protected function attemptChange( User $user, $pass, $newaddr ) { + global $wgAuth, $wgPasswordAttemptThrottle; + if ( $newaddr != '' && !Sanitizer::validateEmail( $newaddr ) ) { $this->error( 'invalidemailaddress' ); + return false; } $throttleCount = LoginForm::incLoginThrottle( $user->getName() ); if ( $throttleCount === true ) { - $this->error( 'login-throttled' ); + $lang = $this->getLanguage(); + $this->error( array( 'login-throttled', $lang->formatDuration( $wgPasswordAttemptThrottle['seconds'] ) ) ); + return false; } global $wgRequirePasswordforEmailChange; if ( $wgRequirePasswordforEmailChange && !$user->checkTemporaryPassword( $pass ) && !$user->checkPassword( $pass ) ) { $this->error( 'wrongpassword' ); + return false; } @@ -239,8 +257,9 @@ class SpecialChangeEmail extends UnlistedSpecialPage { if ( !$status->isGood() ) { $this->getOutput()->addHTML( '<p class="error">' . - $this->getOutput()->parseInline( $status->getWikiText( 'mailerror' ) ) . - '</p>' ); + $this->getOutput()->parseInline( $status->getWikiText( 'mailerror' ) ) . + '</p>' ); + return false; } @@ -248,6 +267,12 @@ class SpecialChangeEmail extends UnlistedSpecialPage { $user->saveSettings(); + $wgAuth->updateExternalDB( $user ); + return $status->value; } + + protected function getGroupName() { + return 'users'; + } } diff --git a/includes/specials/SpecialChangePassword.php b/includes/specials/SpecialChangePassword.php index ba728ac2..c54b5575 100644 --- a/includes/specials/SpecialChangePassword.php +++ b/includes/specials/SpecialChangePassword.php @@ -27,8 +27,11 @@ * @ingroup SpecialPage */ class SpecialChangePassword extends UnlistedSpecialPage { + + protected $mUserName, $mOldpass, $mNewpass, $mRetype, $mDomain; + public function __construct() { - parent::__construct( 'ChangePassword' ); + parent::__construct( 'ChangePassword', 'editmyprivateinfo' ); } /** @@ -49,64 +52,71 @@ class SpecialChangePassword extends UnlistedSpecialPage { $this->mDomain = $request->getVal( 'wpDomain' ); $user = $this->getUser(); - if( !$request->wasPosted() && !$user->isLoggedIn() ) { + if ( !$request->wasPosted() && !$user->isLoggedIn() ) { $this->error( $this->msg( 'resetpass-no-info' )->text() ); + return; } - if( $request->wasPosted() && $request->getBool( 'wpCancel' ) ) { - $this->doReturnTo(); + if ( $request->wasPosted() && $request->getBool( 'wpCancel' ) ) { + $titleObj = Title::newFromText( $request->getVal( 'returnto' ) ); + if ( !$titleObj instanceof Title ) { + $titleObj = Title::newMainPage(); + } + $query = $request->getVal( 'returntoquery' ); + $this->getOutput()->redirect( $titleObj->getFullURL( $query ) ); + return; } $this->checkReadOnly(); + $this->checkPermissions(); - if( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'token' ) ) ) { + if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'token' ) ) ) { try { $this->mDomain = $wgAuth->getDomain(); - if( !$wgAuth->allowPasswordChange() ) { + if ( !$wgAuth->allowPasswordChange() ) { $this->error( $this->msg( 'resetpass_forbidden' )->text() ); + return; } $this->attemptReset( $this->mNewpass, $this->mRetype ); - $this->getOutput()->addWikiMsg( 'resetpass_success' ); - if( !$user->isLoggedIn() ) { + + if ( $user->isLoggedIn() ) { + $this->getOutput()->wrapWikiMsg( + "<div class=\"successbox\">\n$1\n</div>", + 'changepassword-success' + ); + $this->getOutput()->returnToMain(); + } else { LoginForm::setLoginToken(); $token = LoginForm::getLoginToken(); $data = array( - 'action' => 'submitlogin', - 'wpName' => $this->mUserName, - 'wpDomain' => $this->mDomain, + 'action' => 'submitlogin', + 'wpName' => $this->mUserName, + 'wpDomain' => $this->mDomain, 'wpLoginToken' => $token, - 'wpPassword' => $this->mNewpass, - 'returnto' => $request->getVal( 'returnto' ), - ); - if( $request->getCheck( 'wpRemember' ) ) { - $data['wpRemember'] = 1; - } - $login = new LoginForm( new FauxRequest( $data, true ) ); + 'wpPassword' => $request->getVal( 'wpNewPassword' ), + ) + $request->getValues( 'wpRemember', 'returnto', 'returntoquery' ); + $login = new LoginForm( new DerivativeRequest( $request, $data, true ) ); $login->setContext( $this->getContext() ); $login->execute( null ); } - $this->doReturnTo(); - } catch( PasswordError $e ) { + + return; + } catch ( PasswordError $e ) { $this->error( $e->getMessage() ); } } $this->showForm(); } - function doReturnTo() { - $titleObj = Title::newFromText( $this->getRequest()->getVal( 'returnto' ) ); - if ( !$titleObj instanceof Title ) { - $titleObj = Title::newMainPage(); - } - $this->getOutput()->redirect( $titleObj->getFullURL() ); - } - + /** + * @param $msg string + */ function error( $msg ) { - $this->getOutput()->addHTML( Xml::element('p', array( 'class' => 'error' ), $msg ) ); + $this->getOutput()->addHTML( Xml::element( 'p', array( 'class' => 'error' ), $msg ) ); } function showForm() { @@ -121,12 +131,12 @@ class SpecialChangePassword extends UnlistedSpecialPage { $rememberMe = '<tr>' . '<td></td>' . '<td class="mw-input">' . - Xml::checkLabel( - $this->msg( 'remembermypassword' )->numParams( ceil( $wgCookieExpiration / ( 3600 * 24 ) ) )->text(), - 'wpRemember', 'wpRemember', - $this->getRequest()->getCheck( 'wpRemember' ) ) . + Xml::checkLabel( + $this->msg( 'remembermypassword' )->numParams( ceil( $wgCookieExpiration / ( 3600 * 24 ) ) )->text(), + 'wpRemember', 'wpRemember', + $this->getRequest()->getCheck( 'wpRemember' ) ) . '</td>' . - '</tr>'; + '</tr>'; $submitMsg = 'resetpass_submit'; $oldpassMsg = 'resetpass-temp-password'; } else { @@ -136,45 +146,55 @@ class SpecialChangePassword extends UnlistedSpecialPage { $extraFields = array(); wfRunHooks( 'ChangePasswordForm', array( &$extraFields ) ); $prettyFields = array( - array( 'wpName', 'username', 'text', $this->mUserName ), - array( 'wpPassword', $oldpassMsg, 'password', $this->mOldpass ), - array( 'wpNewPassword', 'newpassword', 'password', null ), - array( 'wpRetype', 'retypenew', 'password', null ), - ); + array( 'wpName', 'username', 'text', $this->mUserName ), + array( 'wpPassword', $oldpassMsg, 'password', $this->mOldpass ), + array( 'wpNewPassword', 'newpassword', 'password', null ), + array( 'wpRetype', 'retypenew', 'password', null ), + ); $prettyFields = array_merge( $prettyFields, $extraFields ); + $hiddenFields = array( + 'token' => $user->getEditToken(), + 'wpName' => $this->mUserName, + 'wpDomain' => $this->mDomain, + ) + $this->getRequest()->getValues( 'returnto', 'returntoquery' ); + $hiddenFieldsStr = ''; + foreach ( $hiddenFields as $fieldname => $fieldvalue ) { + $hiddenFieldsStr .= Html::hidden( $fieldname, $fieldvalue ) . "\n"; + } $this->getOutput()->addHTML( Xml::fieldset( $this->msg( 'resetpass_header' )->text() ) . - Xml::openElement( 'form', - array( - 'method' => 'post', - 'action' => $this->getTitle()->getLocalUrl(), - 'id' => 'mw-resetpass-form' ) ) . "\n" . - Html::hidden( 'token', $user->getEditToken() ) . "\n" . - Html::hidden( 'wpName', $this->mUserName ) . "\n" . - Html::hidden( 'wpDomain', $this->mDomain ) . "\n" . - Html::hidden( 'returnto', $this->getRequest()->getVal( 'returnto' ) ) . "\n" . - $this->msg( 'resetpass_text' )->parseAsBlock() . "\n" . - Xml::openElement( 'table', array( 'id' => 'mw-resetpass-table' ) ) . "\n" . - $this->pretty( $prettyFields ) . "\n" . - $rememberMe . - "<tr>\n" . + Xml::openElement( 'form', + array( + 'method' => 'post', + 'action' => $this->getTitle()->getLocalURL(), + 'id' => 'mw-resetpass-form' ) ) . "\n" . + $hiddenFieldsStr . + $this->msg( 'resetpass_text' )->parseAsBlock() . "\n" . + Xml::openElement( 'table', array( 'id' => 'mw-resetpass-table' ) ) . "\n" . + $this->pretty( $prettyFields ) . "\n" . + $rememberMe . + "<tr>\n" . "<td></td>\n" . '<td class="mw-input">' . - Xml::submitButton( $this->msg( $submitMsg )->text() ) . - Xml::submitButton( $this->msg( 'resetpass-submit-cancel' )->text(), array( 'name' => 'wpCancel' ) ) . + Xml::submitButton( $this->msg( $submitMsg )->text() ) . + Xml::submitButton( $this->msg( 'resetpass-submit-cancel' )->text(), array( 'name' => 'wpCancel' ) ) . "</td>\n" . - "</tr>\n" . - Xml::closeElement( 'table' ) . - Xml::closeElement( 'form' ) . - Xml::closeElement( 'fieldset' ) . "\n" + "</tr>\n" . + Xml::closeElement( 'table' ) . + Xml::closeElement( 'form' ) . + Xml::closeElement( 'fieldset' ) . "\n" ); } + /** + * @param $fields array + * @return string + */ function pretty( $fields ) { $out = ''; foreach ( $fields as $list ) { list( $name, $label, $type, $value ) = $list; - if( $type == 'text' ) { + if ( $type == 'text' ) { $field = htmlspecialchars( $value ); } else { $attribs = array( 'id' => $name ); @@ -189,16 +209,20 @@ class SpecialChangePassword extends UnlistedSpecialPage { } $out .= "<tr>\n"; $out .= "\t<td class='mw-label'>"; - if ( $type != 'text' ) + + if ( $type != 'text' ) { $out .= Xml::label( $this->msg( $label )->text(), $name ); - else - $out .= $this->msg( $label )->escaped(); + } else { + $out .= $this->msg( $label )->escaped(); + } + $out .= "</td>\n"; $out .= "\t<td class='mw-input'>"; $out .= $field; $out .= "</td>\n"; $out .= "</tr>"; } + return $out; } @@ -206,19 +230,31 @@ class SpecialChangePassword extends UnlistedSpecialPage { * @throws PasswordError when cannot set the new password because requirements not met. */ protected function attemptReset( $newpass, $retype ) { - $user = User::newFromName( $this->mUserName ); - if( !$user || $user->isAnon() ) { + global $wgPasswordAttemptThrottle; + + $isSelf = ( $this->mUserName === $this->getUser()->getName() ); + if ( $isSelf ) { + $user = $this->getUser(); + } else { + $user = User::newFromName( $this->mUserName ); + } + + if ( !$user || $user->isAnon() ) { throw new PasswordError( $this->msg( 'nosuchusershort', $this->mUserName )->text() ); } - if( $newpass !== $retype ) { + if ( $newpass !== $retype ) { wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'badretype' ) ); throw new PasswordError( $this->msg( 'badretype' )->text() ); } $throttleCount = LoginForm::incLoginThrottle( $this->mUserName ); if ( $throttleCount === true ) { - throw new PasswordError( $this->msg( 'login-throttled' )->text() ); + $lang = $this->getLanguage(); + throw new PasswordError( $this->msg( 'login-throttled' ) + ->params( $lang->formatDuration( $wgPasswordAttemptThrottle['seconds'] ) ) + ->text() + ); } $abortMsg = 'resetpass-abort-generic'; @@ -227,7 +263,7 @@ class SpecialChangePassword extends UnlistedSpecialPage { throw new PasswordError( $this->msg( $abortMsg )->text() ); } - if( !$user->checkTemporaryPassword($this->mOldpass) && !$user->checkPassword($this->mOldpass) ) { + if ( !$user->checkTemporaryPassword( $this->mOldpass ) && !$user->checkPassword( $this->mOldpass ) ) { wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'wrongpassword' ) ); throw new PasswordError( $this->msg( 'resetpass-wrong-oldpass' )->text() ); } @@ -240,13 +276,22 @@ class SpecialChangePassword extends UnlistedSpecialPage { try { $user->setPassword( $this->mNewpass ); wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'success' ) ); - $this->mNewpass = $this->mOldpass = $this->mRetypePass = ''; - } catch( PasswordError $e ) { + $this->mNewpass = $this->mOldpass = $this->mRetype = ''; + } catch ( PasswordError $e ) { wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'error' ) ); throw new PasswordError( $e->getMessage() ); } - $user->setCookies(); + if ( $isSelf ) { + // This is needed to keep the user connected since + // changing the password also modifies the user's token. + $user->setCookies(); + } + $user->saveSettings(); } + + protected function getGroupName() { + return 'users'; + } } diff --git a/includes/specials/SpecialComparePages.php b/includes/specials/SpecialComparePages.php index 9e3c52b9..fc6b0c58 100644 --- a/includes/specials/SpecialComparePages.php +++ b/includes/specials/SpecialComparePages.php @@ -106,31 +106,37 @@ class SpecialComparePages extends SpecialPage { $form->trySubmit(); } - public static function showDiff( $data, HTMLForm $form ){ + public static function showDiff( $data, HTMLForm $form ) { $rev1 = self::revOrTitle( $data['Revision1'], $data['Page1'] ); $rev2 = self::revOrTitle( $data['Revision2'], $data['Page2'] ); - if( $rev1 && $rev2 ) { - $de = new DifferenceEngine( $form->getContext(), - $rev1, - $rev2, - null, // rcid - ( $data['Action'] == 'purge' ), - ( $data['Unhide'] == '1' ) - ); - $de->showDiffPage( true ); + if ( $rev1 && $rev2 ) { + $revision = Revision::newFromId( $rev1 ); + + if ( $revision ) { // NOTE: $rev1 was already checked, should exist. + $contentHandler = $revision->getContentHandler(); + $de = $contentHandler->createDifferenceEngine( $form->getContext(), + $rev1, + $rev2, + null, // rcid + ( $data['Action'] == 'purge' ), + ( $data['Unhide'] == '1' ) + ); + $de->showDiffPage( true ); + } } } public static function revOrTitle( $revision, $title ) { - if( $revision ){ + if ( $revision ) { return $revision; - } elseif( $title ) { + } elseif ( $title ) { $title = Title::newFromText( $title ); - if( $title instanceof Title ){ + if ( $title instanceof Title ) { return $title->getLatestRevID(); } } + return null; } @@ -145,6 +151,7 @@ class SpecialComparePages extends SpecialPage { if ( !$title->exists() ) { return $this->msg( 'compare-title-not-exists' )->parseAsBlock(); } + return true; } @@ -156,6 +163,11 @@ class SpecialComparePages extends SpecialPage { if ( $revision === null ) { return $this->msg( 'compare-revision-not-exists' )->parseAsBlock(); } + return true; } + + protected function getGroupName() { + return 'pagetools'; + } } diff --git a/includes/specials/SpecialConfirmemail.php b/includes/specials/SpecialConfirmemail.php index 3e9ce128..3828b1c6 100644 --- a/includes/specials/SpecialConfirmemail.php +++ b/includes/specials/SpecialConfirmemail.php @@ -30,27 +30,30 @@ * @author Rob Church <robchur@gmail.com> */ class EmailConfirmation extends UnlistedSpecialPage { - - /** - * Constructor - */ public function __construct() { - parent::__construct( 'Confirmemail' ); + parent::__construct( 'Confirmemail', 'editmyprivateinfo' ); } /** * Main execution point * - * @param $code Confirmation code passed to the page + * @param null|string $code Confirmation code passed to the page */ function execute( $code ) { $this->setHeaders(); $this->checkReadOnly(); + $this->checkPermissions(); - if( $code === null || $code === '' ) { - if( $this->getUser()->isLoggedIn() ) { - if( Sanitizer::validateEmail( $this->getUser()->getEmail() ) ) { + // This could also let someone check the current email address, so + // require both permissions. + if ( !$this->getUser()->isAllowed( 'viewmyprivateinfo' ) ) { + throw new PermissionsError( 'viewmyprivateinfo' ); + } + + if ( $code === null || $code === '' ) { + if ( $this->getUser()->isLoggedIn() ) { + if ( Sanitizer::validateEmail( $this->getUser()->getEmail() ) ) { $this->showRequestForm(); } else { $this->getOutput()->addWikiMsg( 'confirmemail_noemail' ); @@ -62,7 +65,9 @@ class EmailConfirmation extends UnlistedSpecialPage { array(), array( 'returnto' => $this->getTitle()->getPrefixedText() ) ); - $this->getOutput()->addHTML( $this->msg( 'confirmemail_needlogin' )->rawParams( $llink )->parse() ); + $this->getOutput()->addHTML( + $this->msg( 'confirmemail_needlogin' )->rawParams( $llink )->parse() + ); } } else { $this->attemptConfirm( $code ); @@ -75,7 +80,10 @@ class EmailConfirmation extends UnlistedSpecialPage { function showRequestForm() { $user = $this->getUser(); $out = $this->getOutput(); - if( $this->getRequest()->wasPosted() && $user->matchEditToken( $this->getRequest()->getText( 'token' ) ) ) { + + if ( $this->getRequest()->wasPosted() && + $user->matchEditToken( $this->getRequest()->getText( 'token' ) ) + ) { $status = $user->sendConfirmationMail(); if ( $status->isGood() ) { $out->addWikiMsg( 'confirmemail_sent' ); @@ -83,7 +91,7 @@ class EmailConfirmation extends UnlistedSpecialPage { $out->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) ); } } else { - if( $user->isEmailConfirmed() ) { + if ( $user->isEmailConfirmed() ) { // date and time are separate parameters to facilitate localisation. // $time is kept for backward compat reasons. // 'emailauthenticated' is also used in SpecialPreferences.php @@ -94,14 +102,22 @@ class EmailConfirmation extends UnlistedSpecialPage { $t = $lang->userTime( $emailAuthenticated, $user ); $out->addWikiMsg( 'emailauthenticated', $time, $d, $t ); } - if( $user->isEmailConfirmationPending() ) { - $out->wrapWikiMsg( "<div class=\"error mw-confirmemail-pending\">\n$1\n</div>", 'confirmemail_pending' ); + + if ( $user->isEmailConfirmationPending() ) { + $out->wrapWikiMsg( + "<div class=\"error mw-confirmemail-pending\">\n$1\n</div>", + 'confirmemail_pending' + ); } + $out->addWikiMsg( 'confirmemail_text' ); - $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalUrl() ) ); - $form .= Html::hidden( 'token', $user->getEditToken() ); - $form .= Xml::submitButton( $this->msg( 'confirmemail_send' )->text() ); - $form .= Xml::closeElement( 'form' ); + $form = Html::openElement( + 'form', + array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL() ) + ) . "\n"; + $form .= Html::hidden( 'token', $user->getEditToken() ) . "\n"; + $form .= Xml::submitButton( $this->msg( 'confirmemail_send' )->text() ) . "\n"; + $form .= Html::closeElement( 'form' ) . "\n"; $out->addHTML( $form ); } } @@ -110,24 +126,26 @@ class EmailConfirmation extends UnlistedSpecialPage { * Attempt to confirm the user's email address and show success or failure * as needed; if successful, take the user to log in * - * @param $code string Confirmation code + * @param string $code Confirmation code */ function attemptConfirm( $code ) { $user = User::newFromConfirmationCode( $code ); - if( is_object( $user ) ) { - $user->confirmEmail(); - $user->saveSettings(); - $message = $this->getUser()->isLoggedIn() ? 'confirmemail_loggedin' : 'confirmemail_success'; - $this->getOutput()->addWikiMsg( $message ); - if( !$this->getUser()->isLoggedIn() ) { - $title = SpecialPage::getTitleFor( 'Userlogin' ); - $this->getOutput()->returnToMain( true, $title ); - } - } else { + if ( !is_object( $user ) ) { $this->getOutput()->addWikiMsg( 'confirmemail_invalid' ); + + return; } - } + $user->confirmEmail(); + $user->saveSettings(); + $message = $this->getUser()->isLoggedIn() ? 'confirmemail_loggedin' : 'confirmemail_success'; + $this->getOutput()->addWikiMsg( $message ); + + if ( !$this->getUser()->isLoggedIn() ) { + $title = SpecialPage::getTitleFor( 'Userlogin' ); + $this->getOutput()->returnToMain( true, $title ); + } + } } /** @@ -137,18 +155,14 @@ class EmailConfirmation extends UnlistedSpecialPage { * @ingroup SpecialPage */ class EmailInvalidation extends UnlistedSpecialPage { - public function __construct() { - parent::__construct( 'Invalidateemail' ); + parent::__construct( 'Invalidateemail', 'editmyprivateinfo' ); } function execute( $code ) { $this->setHeaders(); - - if ( wfReadOnly() ) { - throw new ReadOnlyError; - } - + $this->checkReadOnly(); + $this->checkPermissions(); $this->attemptInvalidate( $code ); } @@ -156,19 +170,22 @@ class EmailInvalidation extends UnlistedSpecialPage { * Attempt to invalidate the user's email address and show success or failure * as needed; if successful, link to main page * - * @param $code string Confirmation code + * @param string $code Confirmation code */ function attemptInvalidate( $code ) { $user = User::newFromConfirmationCode( $code ); - if( is_object( $user ) ) { - $user->invalidateEmail(); - $user->saveSettings(); - $this->getOutput()->addWikiMsg( 'confirmemail_invalidated' ); - if( !$this->getUser()->isLoggedIn() ) { - $this->getOutput()->returnToMain(); - } - } else { + if ( !is_object( $user ) ) { $this->getOutput()->addWikiMsg( 'confirmemail_invalid' ); + + return; + } + + $user->invalidateEmail(); + $user->saveSettings(); + $this->getOutput()->addWikiMsg( 'confirmemail_invalidated' ); + + if ( !$this->getUser()->isLoggedIn() ) { + $this->getOutput()->returnToMain(); } } } diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php index 54f8e261..1fe98190 100644 --- a/includes/specials/SpecialContributions.php +++ b/includes/specials/SpecialContributions.php @@ -28,7 +28,6 @@ */ class SpecialContributions extends SpecialPage { - protected $opts; public function __construct() { @@ -65,6 +64,7 @@ class SpecialContributions extends SpecialPage { if ( !strlen( $target ) ) { $out->addHTML( $this->getForm() ); + return; } @@ -77,11 +77,13 @@ class SpecialContributions extends SpecialPage { $nt = Title::makeTitleSafe( NS_USER, $target ); if ( !$nt ) { $out->addHTML( $this->getForm() ); + return; } $userObj = User::newFromName( $nt->getText(), false ); if ( !$userObj ) { $out->addHTML( $this->getForm() ); + return; } $id = $userObj->getID(); @@ -89,11 +91,17 @@ class SpecialContributions extends SpecialPage { if ( $this->opts['contribs'] != 'newbie' ) { $target = $nt->getText(); $out->addSubtitle( $this->contributionsSub( $userObj ) ); - $out->setHTMLTitle( $this->msg( 'pagetitle', $this->msg( 'contributions-title', $target )->plain() ) ); + $out->setHTMLTitle( $this->msg( + 'pagetitle', + $this->msg( 'contributions-title', $target )->plain() + ) ); $this->getSkin()->setRelevantUser( $userObj ); } else { $out->addSubtitle( $this->msg( 'sp-contributions-newbies-sub' ) ); - $out->setHTMLTitle( $this->msg( 'pagetitle', $this->msg( 'sp-contributions-newbies-title' )->plain() ) ); + $out->setHTMLTitle( $this->msg( + 'pagetitle', + $this->msg( 'sp-contributions-newbies-title' )->plain() + ) ); } if ( ( $ns = $request->getVal( 'namespace', null ) ) !== null && $ns !== '' ) { @@ -103,10 +111,8 @@ class SpecialContributions extends SpecialPage { } $this->opts['associated'] = $request->getBool( 'associated' ); - - $this->opts['nsInvert'] = (bool) $request->getVal( 'nsInvert' ); - - $this->opts['tagfilter'] = (string) $request->getVal( 'tagfilter' ); + $this->opts['nsInvert'] = (bool)$request->getVal( 'nsInvert' ); + $this->opts['tagfilter'] = (string)$request->getVal( 'tagfilter' ); // Allows reverts to have the bot flag in recent changes. It is just here to // be passed in the form at the top of the page @@ -152,9 +158,10 @@ class SpecialContributions extends SpecialPage { $apiParams['month'] = $this->opts['month']; } - $url = wfScript( 'api' ) . '?' . wfArrayToCGI( $apiParams ); + $url = wfAppendQuery( wfScript( 'api' ), $apiParams ); $out->redirect( $url, '301' ); + return; } @@ -162,13 +169,13 @@ class SpecialContributions extends SpecialPage { $this->addFeedLinks( array( 'action' => 'feedcontributions', 'user' => $target ) ); if ( wfRunHooks( 'SpecialContributionsBeforeMainOutput', array( $id ) ) ) { - $out->addHTML( $this->getForm() ); $pager = new ContribsPager( $this->getContext(), array( 'target' => $target, 'contribs' => $this->opts['contribs'], 'namespace' => $this->opts['namespace'], + 'tagfilter' => $this->opts['tagfilter'], 'year' => $this->opts['year'], 'month' => $this->opts['month'], 'deletedOnly' => $this->opts['deletedOnly'], @@ -176,36 +183,37 @@ class SpecialContributions extends SpecialPage { 'nsInvert' => $this->opts['nsInvert'], 'associated' => $this->opts['associated'], ) ); + if ( !$pager->getNumRows() ) { $out->addWikiMsg( 'nocontribs', $target ); } else { # Show a message about slave lag, if applicable $lag = wfGetLB()->safeGetLag( $pager->getDatabase() ); - if ( $lag > 0 ) + if ( $lag > 0 ) { $out->showLagWarning( $lag ); + } $out->addHTML( '<p>' . $pager->getNavigationBar() . '</p>' . - $pager->getBody() . - '<p>' . $pager->getNavigationBar() . '</p>' + $pager->getBody() . + '<p>' . $pager->getNavigationBar() . '</p>' ); } $out->preventClickjacking( $pager->getPreventClickjacking() ); - # Show the appropriate "footer" message - WHOIS tools, etc. if ( $this->opts['contribs'] == 'newbie' ) { $message = 'sp-contributions-footer-newbies'; - } elseif( IP::isIPAddress( $target ) ) { + } elseif ( IP::isIPAddress( $target ) ) { $message = 'sp-contributions-footer-anon'; - } elseif( $userObj->isAnon() ) { + } elseif ( $userObj->isAnon() ) { // No message for non-existing users $message = ''; } else { $message = 'sp-contributions-footer'; } - if( $message ) { + if ( $message ) { if ( !$this->msg( $message, $target )->isDisabled() ) { $out->wrapWikiMsg( "<div class='mw-contributions-footer'>\n$1\n</div>", @@ -219,7 +227,8 @@ class SpecialContributions extends SpecialPage { * Generates the subheading with links * @param $userObj User object for the target * @return String: appropriately-escaped HTML to be output literally - * @todo FIXME: Almost the same as getSubTitle in SpecialDeletedContributions.php. Could be combined. + * @todo FIXME: Almost the same as getSubTitle in SpecialDeletedContributions.php. + * Could be combined. */ protected function contributionsSub( $userObj ) { if ( $userObj->isAnon() ) { @@ -259,18 +268,7 @@ class SpecialContributions extends SpecialPage { } } - // Old message 'contribsub' had one parameter, but that doesn't work for - // languages that want to put the "for" bit right after $user but before - // $links. If 'contribsub' is around, use it for reverse compatibility, - // otherwise use 'contribsub2'. - // @todo Should this be removed at some point? - $oldMsg = $this->msg( 'contribsub' ); - if ( $oldMsg->exists() ) { - $linksWithParentheses = $this->msg( 'parentheses' )->rawParams( $links )->escaped(); - return $oldMsg->rawParams( "$user $linksWithParentheses" ); - } else { - return $this->msg( 'contribsub2' )->rawParams( $user, $links ); - } + return $this->msg( 'contribsub2' )->rawParams( $user, $links )->params( $userObj->getName() ); } /** @@ -289,7 +287,7 @@ class SpecialContributions extends SpecialPage { if ( ( $id !== null ) || ( $id === null && IP::isIPAddress( $username ) ) ) { if ( $this->getUser()->isAllowed( 'block' ) ) { # Block / Change block / Unblock links - if ( $target->isBlocked() ) { + if ( $target->isBlocked() && $target->getBlock()->getType() != Block::TYPE_AUTO ) { $tools[] = Linker::linkKnown( # Change block link SpecialPage::getTitleFor( 'Block', $username ), $this->msg( 'change-blocklink' )->escaped() @@ -305,14 +303,13 @@ class SpecialContributions extends SpecialPage { ); } } + # Block log link $tools[] = Linker::linkKnown( SpecialPage::getTitleFor( 'Log', 'block' ), $this->msg( 'sp-contributions-blocklog' )->escaped(), array(), - array( - 'page' => $userpage->getPrefixedText() - ) + array( 'page' => $userpage->getPrefixedText() ) ); } # Uploads @@ -346,6 +343,7 @@ class SpecialContributions extends SpecialPage { } wfRunHooks( 'ContributionsToolLinks', array( $id, $userpage, &$tools ) ); + return $tools; } @@ -360,7 +358,7 @@ class SpecialContributions extends SpecialPage { if ( !isset( $this->opts['target'] ) ) { $this->opts['target'] = ''; } else { - $this->opts['target'] = str_replace( '_' , ' ' , $this->opts['target'] ); + $this->opts['target'] = str_replace( '_', ' ', $this->opts['target'] ); } if ( !isset( $this->opts['namespace'] ) ) { @@ -399,10 +397,28 @@ class SpecialContributions extends SpecialPage { $this->opts['topOnly'] = false; } - $form = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'class' => 'mw-contributions-form' ) ); + $form = Html::openElement( + 'form', + array( + 'method' => 'get', + 'action' => $wgScript, + 'class' => 'mw-contributions-form' + ) + ); # Add hidden params for tracking except for parameters in $skipParameters - $skipParameters = array( 'namespace', 'nsInvert', 'deletedOnly', 'target', 'contribs', 'year', 'month', 'topOnly', 'associated' ); + $skipParameters = array( + 'namespace', + 'nsInvert', + 'deletedOnly', + 'target', + 'contribs', + 'year', + 'month', + 'topOnly', + 'associated' + ); + foreach ( $this->opts as $name => $value ) { if ( in_array( $name, $skipParameters ) ) { continue; @@ -413,65 +429,82 @@ class SpecialContributions extends SpecialPage { $tagFilter = ChangeTags::buildTagFilterSelector( $this->opts['tagfilter'] ); if ( $tagFilter ) { - $filterSelection = - Xml::tags( 'td', array( 'class' => 'mw-label' ), array_shift( $tagFilter ) ) . - Xml::tags( 'td', array( 'class' => 'mw-input' ), implode( ' ', $tagFilter ) ); + $filterSelection = Html::rawElement( + 'td', + array( 'class' => 'mw-label' ), + array_shift( $tagFilter ) + ); + $filterSelection .= Html::rawElement( + 'td', + array( 'class' => 'mw-input' ), + implode( ' ', $tagFilter ) + ); } else { - $filterSelection = Xml::tags( 'td', array( 'colspan' => 2 ), '' ); + $filterSelection = Html::rawElement( 'td', array( 'colspan' => 2 ), '' ); } - $targetSelection = Xml::tags( 'td', array( 'colspan' => 2 ), - Xml::radioLabel( - $this->msg( 'sp-contributions-newbies' )->text(), - 'contribs', - 'newbie' , - 'newbie', - $this->opts['contribs'] == 'newbie', - array( 'class' => 'mw-input' ) - ) . '<br />' . - Xml::radioLabel( - $this->msg( 'sp-contributions-username' )->text(), - 'contribs', - 'user', - 'user', - $this->opts['contribs'] == 'user', - array( 'class' => 'mw-input' ) - ) . ' ' . - Html::input( - 'target', - $this->opts['target'], - 'text', - array( 'size' => '40', 'required' => '', 'class' => 'mw-input' ) + - ( $this->opts['target'] ? array() : array( 'autofocus' ) - ) - ) . ' ' - ) ; - - $namespaceSelection = - Xml::tags( 'td', array( 'class' => 'mw-label' ), - Xml::label( - $this->msg( 'namespace' )->text(), - 'namespace', - '' + $labelNewbies = Xml::radioLabel( + $this->msg( 'sp-contributions-newbies' )->text(), + 'contribs', + 'newbie', + 'newbie', + $this->opts['contribs'] == 'newbie', + array( 'class' => 'mw-input' ) + ); + $labelUsername = Xml::radioLabel( + $this->msg( 'sp-contributions-username' )->text(), + 'contribs', + 'user', + 'user', + $this->opts['contribs'] == 'user', + array( 'class' => 'mw-input' ) + ); + $input = Html::input( + 'target', + $this->opts['target'], + 'text', + array( 'size' => '40', 'required' => '', 'class' => 'mw-input' ) + + ( $this->opts['target'] ? array() : array( 'autofocus' ) ) - ) . - Xml::tags( 'td', null, - Html::namespaceSelector( array( - 'selected' => $this->opts['namespace'], - 'all' => '', - ), array( - 'name' => 'namespace', - 'id' => 'namespace', + ); + $targetSelection = Html::rawElement( + 'td', + array( 'colspan' => 2 ), + $labelNewbies . '<br />' . $labelUsername . ' ' . $input . ' ' + ); + + $namespaceSelection = Xml::tags( + 'td', + array( 'class' => 'mw-label' ), + Xml::label( + $this->msg( 'namespace' )->text(), + 'namespace', + '' + ) + ); + $namespaceSelection .= Html::rawElement( + 'td', + null, + Html::namespaceSelector( + array( 'selected' => $this->opts['namespace'], 'all' => '' ), + array( + 'name' => 'namespace', + 'id' => 'namespace', 'class' => 'namespaceselector', - ) ) . - ' ' . - Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ), + ) + ) . ' ' . + Html::rawElement( + 'span', + array( 'style' => 'white-space: nowrap' ), Xml::checkLabel( $this->msg( 'invert' )->text(), 'nsInvert', 'nsInvert', $this->opts['nsInvert'], - array( 'title' => $this->msg( 'tooltip-invert' )->text(), 'class' => 'mw-input' ) + array( + 'title' => $this->msg( 'tooltip-invert' )->text(), + 'class' => 'mw-input' + ) ) . ' ' ) . Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ), @@ -480,13 +513,18 @@ class SpecialContributions extends SpecialPage { 'associated', 'associated', $this->opts['associated'], - array( 'title' => $this->msg( 'tooltip-namespace_association' )->text(), 'class' => 'mw-input' ) + array( + 'title' => $this->msg( 'tooltip-namespace_association' )->text(), + 'class' => 'mw-input' + ) ) . ' ' ) - ) ; + ); - $extraOptions = Xml::tags( 'td', array( 'colspan' => 2 ), - Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ), + if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) { + $deletedOnlyCheck = Html::rawElement( + 'span', + array( 'style' => 'white-space: nowrap' ), Xml::checkLabel( $this->msg( 'history-show-deleted' )->text(), 'deletedOnly', @@ -494,57 +532,61 @@ class SpecialContributions extends SpecialPage { $this->opts['deletedOnly'], array( 'class' => 'mw-input' ) ) - ) . - Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ), - Xml::checkLabel( - $this->msg( 'sp-contributions-toponly' )->text(), - 'topOnly', - 'mw-show-top-only', - $this->opts['topOnly'], - array( 'class' => 'mw-input' ) - ) + ); + } else { + $deletedOnlyCheck = ''; + } + + $checkLabelTopOnly = Html::rawElement( + 'span', + array( 'style' => 'white-space: nowrap' ), + Xml::checkLabel( + $this->msg( 'sp-contributions-toponly' )->text(), + 'topOnly', + 'mw-show-top-only', + $this->opts['topOnly'], + array( 'class' => 'mw-input' ) ) - ) ; + ); + $extraOptions = Html::rawElement( + 'td', + array( 'colspan' => 2 ), + $deletedOnlyCheck . $checkLabelTopOnly + ); $dateSelectionAndSubmit = Xml::tags( 'td', array( 'colspan' => 2 ), Xml::dateMenu( - $this->opts['year'], + $this->opts['year'] === '' ? MWTimestamp::getInstance()->format( 'Y' ) : $this->opts['year'], $this->opts['month'] ) . ' ' . - Xml::submitButton( - $this->msg( 'sp-contributions-submit' )->text(), - array( 'class' => 'mw-submit' ) - ) - ) ; - - $form .= - Xml::fieldset( $this->msg( 'sp-contributions-search' )->text() ) . - Xml::openElement( 'table', array( 'class' => 'mw-contributions-table' ) ) . - Xml::openElement( 'tr' ) . - $targetSelection . - Xml::closeElement( 'tr' ) . - Xml::openElement( 'tr' ) . - $namespaceSelection . - Xml::closeElement( 'tr' ) . - Xml::openElement( 'tr' ) . - $filterSelection . - Xml::closeElement( 'tr' ) . - Xml::openElement( 'tr' ) . - $extraOptions . - Xml::closeElement( 'tr' ) . - Xml::openElement( 'tr' ) . - $dateSelectionAndSubmit . - Xml::closeElement( 'tr' ) . - Xml::closeElement( 'table' ); + Xml::submitButton( + $this->msg( 'sp-contributions-submit' )->text(), + array( 'class' => 'mw-submit' ) + ) + ); + + $form .= Xml::fieldset( $this->msg( 'sp-contributions-search' )->text() ); + $form .= Html::rawElement( 'table', array( 'class' => 'mw-contributions-table' ), "\n" . + Html::rawElement( 'tr', array(), $targetSelection ) . "\n" . + Html::rawElement( 'tr', array(), $namespaceSelection ) . "\n" . + Html::rawElement( 'tr', array(), $filterSelection ) . "\n" . + Html::rawElement( 'tr', array(), $extraOptions ) . "\n" . + Html::rawElement( 'tr', array(), $dateSelectionAndSubmit ) . "\n" + ); $explain = $this->msg( 'sp-contributions-explain' ); - if ( $explain->exists() ) { - $form .= "<p id='mw-sp-contributions-explain'>{$explain}</p>"; + if ( !$explain->isBlank() ) { + $form .= "<p id='mw-sp-contributions-explain'>{$explain->parse()}</p>"; } - $form .= Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ); + + $form .= Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ); + return $form; } + + protected function getGroupName() { + return 'users'; + } } /** @@ -553,9 +595,11 @@ class SpecialContributions extends SpecialPage { */ class ContribsPager extends ReverseChronologicalPager { public $mDefaultDirection = true; - var $messages, $target; - var $namespace = '', $mDb; - var $preventClickjacking = false; + public $messages; + public $target; + public $namespace = ''; + public $mDb; + public $preventClickjacking = false; /** * @var array @@ -565,7 +609,15 @@ class ContribsPager extends ReverseChronologicalPager { function __construct( IContextSource $context, array $options ) { parent::__construct( $context ); - $msgs = array( 'uctop', 'diff', 'newarticle', 'rollbacklink', 'diff', 'hist', 'rev-delundel', 'pipe-separator' ); + $msgs = array( + 'diff', + 'hist', + 'newarticle', + 'pipe-separator', + 'rev-delundel', + 'rollbacklink', + 'uctop' + ); foreach ( $msgs as $msg ) { $this->messages[$msg] = $this->msg( $msg )->escaped(); @@ -591,6 +643,7 @@ class ContribsPager extends ReverseChronologicalPager { function getDefaultQuery() { $query = parent::getDefaultQuery(); $query['target'] = $this->target; + return $query; } @@ -598,13 +651,17 @@ class ContribsPager extends ReverseChronologicalPager { * This method basically executes the exact same code as the parent class, though with * a hook added, to allow extentions to add additional queries. * - * @param $offset String: index offset, inclusive + * @param string $offset index offset, inclusive * @param $limit Integer: exact query limit * @param $descending Boolean: query direction, false for ascending, true for descending * @return ResultWrapper */ function reallyDoQuery( $offset, $limit, $descending ) { - list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo( $offset, $limit, $descending ); + list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo( + $offset, + $limit, + $descending + ); $pager = $this; /* @@ -625,13 +682,18 @@ class ContribsPager extends ReverseChronologicalPager { * $limit: see phpdoc above * $descending: see phpdoc above */ - $data = array( $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds ) ); - wfRunHooks( 'ContribsPager::reallyDoQuery', array( &$data, $pager, $offset, $limit, $descending ) ); + $data = array( $this->mDb->select( + $tables, $fields, $conds, $fname, $options, $join_conds + ) ); + wfRunHooks( + 'ContribsPager::reallyDoQuery', + array( &$data, $pager, $offset, $limit, $descending ) + ); $result = array(); // loop all results and collect them in an array - foreach ( $data as $j => $query ) { + foreach ( $data as $query ) { foreach ( $query as $i => $row ) { // use index column as key, allowing us to easily sort in PHP $result[$row->{$this->getIndexField()} . "-$i"] = $row; @@ -674,15 +736,15 @@ class ContribsPager extends ReverseChronologicalPager { $join_cond['user'] = Revision::userJoinCond(); $queryInfo = array( - 'tables' => $tables, - 'fields' => array_merge( + 'tables' => $tables, + 'fields' => array_merge( Revision::selectFields(), Revision::selectUserFields(), array( 'page_namespace', 'page_title', 'page_is_new', 'page_latest', 'page_is_redirect', 'page_len' ) ), - 'conds' => $conds, - 'options' => array( 'USE INDEX' => array( 'revision' => $index ) ), + 'conds' => $conds, + 'options' => array( 'USE INDEX' => array( 'revision' => $index ) ), 'join_conds' => $join_cond ); @@ -696,6 +758,7 @@ class ContribsPager extends ReverseChronologicalPager { ); wfRunHooks( 'ContribsPager::getQueryInfo', array( &$this, &$queryInfo ) ); + return $queryInfo; } @@ -710,7 +773,7 @@ class ContribsPager extends ReverseChronologicalPager { # ignore local groups with the bot right # @todo FIXME: Global groups may have 'bot' rights $groupsWithBotPermission = User::getGroupsWithPermission( 'bot' ); - if( count( $groupsWithBotPermission ) ) { + if ( count( $groupsWithBotPermission ) ) { $tables[] = 'user_groups'; $condition[] = 'ug_group IS NULL'; $join_conds['user_groups'] = array( @@ -730,12 +793,15 @@ class ContribsPager extends ReverseChronologicalPager { $index = 'usertext_timestamp'; } } + if ( $this->deletedOnly ) { - $condition[] = "rev_deleted != '0'"; + $condition[] = 'rev_deleted != 0'; } + if ( $this->topOnly ) { - $condition[] = "rev_id = page_latest"; + $condition[] = 'rev_id = page_latest'; } + return array( $tables, $index, $condition, $join_conds ); } @@ -747,20 +813,20 @@ class ContribsPager extends ReverseChronologicalPager { if ( !$this->associated ) { return array( "page_namespace $eq_op $selectedNS" ); - } else { - $associatedNS = $this->mDb->addQuotes ( - MWNamespace::getAssociated( $this->namespace ) - ); - return array( - "page_namespace $eq_op $selectedNS " . - $bool_op . - " page_namespace $eq_op $associatedNS" - ); } - } else { - return array(); + $associatedNS = $this->mDb->addQuotes( + MWNamespace::getAssociated( $this->namespace ) + ); + + return array( + "page_namespace $eq_op $selectedNS " . + $bool_op . + " page_namespace $eq_op $associatedNS" + ); } + + return array(); } function getIndexField() { @@ -774,7 +840,7 @@ class ContribsPager extends ReverseChronologicalPager { $batch = new LinkBatch(); # Give some pointers to make (last) links foreach ( $this->mResult as $row ) { - if( isset( $row->rev_parent_id ) && $row->rev_parent_id ) { + if ( isset( $row->rev_parent_id ) && $row->rev_parent_id ) { $revIds[] = $row->rev_parent_id; } if ( isset( $row->rev_id ) ) { @@ -831,7 +897,7 @@ class ContribsPager extends ReverseChronologicalPager { */ wfSuppressWarnings(); $rev = new Revision( $row ); - $validRevision = $rev->getParentId() !== null; + $validRevision = (bool)$rev->getId(); wfRestoreWarnings(); if ( $validRevision ) { @@ -851,8 +917,8 @@ class ContribsPager extends ReverseChronologicalPager { $topmarktext .= '<span class="mw-uctop">' . $this->messages['uctop'] . '</span>'; # Add rollback link if ( !$row->page_is_new && $page->quickUserCan( 'rollback', $user ) - && $page->quickUserCan( 'edit', $user ) ) - { + && $page->quickUserCan( 'edit', $user ) + ) { $this->preventClickjacking(); $topmarktext .= ' ' . Linker::generateRollback( $rev, $this->getContext() ); } @@ -882,11 +948,22 @@ class ContribsPager extends ReverseChronologicalPager { // For some reason rev_parent_id isn't populated for this row. // Its rumoured this is true on wikipedia for some revisions (bug 34922). // Next best thing is to have the total number of bytes. - $chardiff = ' <span class="mw-changeslist-separator">. .</span> ' . Linker::formatRevisionSize( $row->rev_len ) . ' <span class="mw-changeslist-separator">. .</span> '; + $chardiff = ' <span class="mw-changeslist-separator">. .</span> '; + $chardiff .= Linker::formatRevisionSize( $row->rev_len ); + $chardiff .= ' <span class="mw-changeslist-separator">. .</span> '; } else { - $parentLen = isset( $this->mParentLens[$row->rev_parent_id] ) ? $this->mParentLens[$row->rev_parent_id] : 0; - $chardiff = ' <span class="mw-changeslist-separator">. .</span> ' . ChangesList::showCharacterDifference( - $parentLen, $row->rev_len, $this->getContext() ) . ' <span class="mw-changeslist-separator">. .</span> '; + $parentLen = 0; + if ( isset( $this->mParentLens[$row->rev_parent_id] ) ) { + $parentLen = $this->mParentLens[$row->rev_parent_id]; + } + + $chardiff = ' <span class="mw-changeslist-separator">. .</span> '; + $chardiff .= ChangesList::showCharacterDifference( + $parentLen, + $row->rev_len, + $this->getContext() + ); + $chardiff .= ' <span class="mw-changeslist-separator">. .</span> '; } $lang = $this->getLanguage(); @@ -909,7 +986,7 @@ class ContribsPager extends ReverseChronologicalPager { # Show user names for /newbies as there may be different users. # Note that we already excluded rows with hidden user names. if ( $this->contribs == 'newbie' ) { - $userlink = ' . . ' . Linker::userLink( $rev->getUser(), $rev->getUserText() ); + $userlink = ' . . ' . $lang->getDirMark() . Linker::userLink( $rev->getUser(), $rev->getUserText() ); $userlink .= ' ' . $this->msg( 'parentheses' )->rawParams( Linker::userTalkLink( $rev->getUser(), $rev->getUserText() ) )->escaped() . ' '; } else { @@ -933,16 +1010,24 @@ class ContribsPager extends ReverseChronologicalPager { $del .= ' '; } - $diffHistLinks = $this->msg( 'parentheses' )->rawParams( $difftext . $this->messages['pipe-separator'] . $histlink )->escaped(); - $ret = "{$del}{$d} {$diffHistLinks}{$chardiff}{$nflag}{$mflag} {$link}{$userlink} {$comment} {$topmarktext}"; + $diffHistLinks = $this->msg( 'parentheses' ) + ->rawParams( $difftext . $this->messages['pipe-separator'] . $histlink ) + ->escaped(); + $ret = "{$del}{$d} {$diffHistLinks}{$chardiff}{$nflag}{$mflag} "; + $ret .= "{$link}{$userlink} {$comment} {$topmarktext}"; # Denote if username is redacted for this edit if ( $rev->isDeleted( Revision::DELETED_USER ) ) { - $ret .= " <strong>" . $this->msg( 'rev-deleted-user-contribs' )->escaped() . "</strong>"; + $ret .= " <strong>" . + $this->msg( 'rev-deleted-user-contribs' )->escaped() . + "</strong>"; } # Tags, if any. - list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow( $row->ts_tags, 'contributions' ); + list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow( + $row->ts_tags, + 'contributions' + ); $classes = array_merge( $classes, $newClasses ); $ret .= " $tagSummary"; } @@ -950,10 +1035,15 @@ class ContribsPager extends ReverseChronologicalPager { // Let extensions add data wfRunHooks( 'ContributionsLineEnding', array( $this, &$ret, $row, &$classes ) ); - $classes = implode( ' ', $classes ); - $ret = "<li class=\"$classes\">$ret</li>\n"; + if ( $classes === array() && $ret === '' ) { + wfDebug( 'Dropping Special:Contribution row that could not be formatted' ); + $ret = "<!-- Could not format Special:Contribution row. -->\n"; + } else { + $ret = Html::rawElement( 'li', array( 'class' => $classes ), $ret ) . "\n"; + } wfProfileOut( __METHOD__ ); + return $ret; } @@ -963,7 +1053,8 @@ class ContribsPager extends ReverseChronologicalPager { */ function getSqlComment() { if ( $this->namespace || $this->deletedOnly ) { - return 'contributions page filtered for namespace or RevisionDeleted edits'; // potentially slow, see CR r58153 + // potentially slow, see CR r58153 + return 'contributions page filtered for namespace or RevisionDeleted edits'; } else { return 'contributions page unfiltered'; } diff --git a/includes/specials/SpecialDeadendpages.php b/includes/specials/SpecialDeadendpages.php index f4904a50..44137c1e 100644 --- a/includes/specials/SpecialDeadendpages.php +++ b/includes/specials/SpecialDeadendpages.php @@ -59,27 +59,36 @@ class DeadendPagesPage extends PageQueryPage { function getQueryInfo() { return array( 'tables' => array( 'page', 'pagelinks' ), - 'fields' => array( 'namespace' => 'page_namespace', - 'title' => 'page_title', - 'value' => 'page_title' + 'fields' => array( + 'namespace' => 'page_namespace', + 'title' => 'page_title', + 'value' => 'page_title' ), - 'conds' => array( 'pl_from IS NULL', - 'page_namespace' => MWNamespace::getContentNamespaces(), - 'page_is_redirect' => 0 + 'conds' => array( + 'pl_from IS NULL', + 'page_namespace' => MWNamespace::getContentNamespaces(), + 'page_is_redirect' => 0 ), - 'join_conds' => array( 'pagelinks' => array( 'LEFT JOIN', array( - 'page_id=pl_from' - ) ) ) + 'join_conds' => array( + 'pagelinks' => array( + 'LEFT JOIN', + array( 'page_id=pl_from' ) + ) + ) ); } function getOrderFields() { // For some crazy reason ordering by a constant // causes a filesort - if( count( MWNamespace::getContentNamespaces() ) > 1 ) { + if ( count( MWNamespace::getContentNamespaces() ) > 1 ) { return array( 'page_namespace', 'page_title' ); } else { return array( 'page_title' ); } } + + protected function getGroupName() { + return 'maintenance'; + } } diff --git a/includes/specials/SpecialDeletedContributions.php b/includes/specials/SpecialDeletedContributions.php index c880b617..9b9888ad 100644 --- a/includes/specials/SpecialDeletedContributions.php +++ b/includes/specials/SpecialDeletedContributions.php @@ -27,13 +27,20 @@ */ class DeletedContribsPager extends IndexPager { public $mDefaultDirection = true; - var $messages, $target; - var $namespace = '', $mDb; + public $messages; + public $target; + public $namespace = ''; + public $mDb; + + /** + * @var string Navigation bar with paging links. + */ + protected $mNavigationBar; function __construct( IContextSource $context, $target, $namespace = false ) { parent::__construct( $context ); $msgs = array( 'deletionlog', 'undeleteviewlink', 'diff' ); - foreach( $msgs as $msg ) { + foreach ( $msgs as $msg ) { $this->messages[$msg] = $this->msg( $msg )->escaped(); } $this->target = $target; @@ -44,6 +51,7 @@ class DeletedContribsPager extends IndexPager { function getDefaultQuery() { $query = parent::getDefaultQuery(); $query['target'] = $this->target; + return $query; } @@ -52,17 +60,18 @@ class DeletedContribsPager extends IndexPager { $conds = array_merge( $userCond, $this->getNamespaceCond() ); $user = $this->getUser(); // Paranoia: avoid brute force searches (bug 17792) - if( !$user->isAllowed( 'deletedhistory' ) ) { + if ( !$user->isAllowed( 'deletedhistory' ) ) { $conds[] = $this->mDb->bitAnd( 'ar_deleted', Revision::DELETED_USER ) . ' = 0'; - } elseif( !$user->isAllowed( 'suppressrevision' ) ) { + } elseif ( !$user->isAllowed( 'suppressrevision' ) ) { $conds[] = $this->mDb->bitAnd( 'ar_deleted', Revision::SUPPRESSED_USER ) . ' != ' . Revision::SUPPRESSED_USER; } + return array( 'tables' => array( 'archive' ), 'fields' => array( - 'ar_rev_id', 'ar_namespace', 'ar_title', 'ar_timestamp', 'ar_comment', 'ar_minor_edit', - 'ar_user', 'ar_user_text', 'ar_deleted' + 'ar_rev_id', 'ar_namespace', 'ar_title', 'ar_timestamp', 'ar_comment', + 'ar_minor_edit', 'ar_user', 'ar_user_text', 'ar_deleted' ), 'conds' => $conds, 'options' => array( 'USE INDEX' => $index ) @@ -107,8 +116,17 @@ class DeletedContribsPager extends IndexPager { $lang = $this->getLanguage(); $limits = $lang->pipeList( $limitLinks ); - $this->mNavigationBar = "(" . $lang->pipeList( array( $pagingLinks['first'], $pagingLinks['last'] ) ) . ") " . - $this->msg( 'viewprevnext' )->rawParams( $pagingLinks['prev'], $pagingLinks['next'], $limits )->escaped(); + $firstLast = $lang->pipeList( array( $pagingLinks['first'], $pagingLinks['last'] ) ); + $firstLast = $this->msg( 'parentheses' )->rawParams( $firstLast )->escaped(); + $prevNext = $this->msg( 'viewprevnext' ) + ->rawParams( + $pagingLinks['prev'], + $pagingLinks['next'], + $limits + )->escaped(); + $separator = $this->msg( 'word-separator' )->escaped(); + $this->mNavigationBar = $firstLast . $separator . $prevNext; + return $this->mNavigationBar; } @@ -135,18 +153,19 @@ class DeletedContribsPager extends IndexPager { function formatRow( $row ) { wfProfileIn( __METHOD__ ); - $rev = new Revision( array( - 'id' => $row->ar_rev_id, - 'comment' => $row->ar_comment, - 'user' => $row->ar_user, - 'user_text' => $row->ar_user_text, - 'timestamp' => $row->ar_timestamp, - 'minor_edit' => $row->ar_minor_edit, - 'deleted' => $row->ar_deleted, - ) ); - $page = Title::makeTitle( $row->ar_namespace, $row->ar_title ); + $rev = new Revision( array( + 'title' => $page, + 'id' => $row->ar_rev_id, + 'comment' => $row->ar_comment, + 'user' => $row->ar_user, + 'user_text' => $row->ar_user_text, + 'timestamp' => $row->ar_timestamp, + 'minor_edit' => $row->ar_minor_edit, + 'deleted' => $row->ar_deleted, + ) ); + $undelete = SpecialPage::getTitleFor( 'Undelete' ); $logs = SpecialPage::getTitleFor( 'Log' ); @@ -167,7 +186,7 @@ class DeletedContribsPager extends IndexPager { $user = $this->getUser(); - if( $user->isAllowed('deletedtext') ) { + if ( $user->isAllowed( 'deletedtext' ) ) { $last = Linker::linkKnown( $undelete, $this->messages['diff'], @@ -183,9 +202,10 @@ class DeletedContribsPager extends IndexPager { } $comment = Linker::revComment( $rev ); - $date = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $rev->getTimestamp(), $user ) ); + $date = $this->getLanguage()->userTimeAndDate( $rev->getTimestamp(), $user ); + $date = htmlspecialchars( $date ); - if( !$user->isAllowed( 'undelete' ) || !$rev->userCan( Revision::DELETED_TEXT, $user ) ) { + if ( !$user->isAllowed( 'undelete' ) || !$rev->userCan( Revision::DELETED_TEXT, $user ) ) { $link = $date; // unusable link } else { $link = Linker::linkKnown( @@ -199,7 +219,7 @@ class DeletedContribsPager extends IndexPager { ); } // Style deleted items - if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { + if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) { $link = '<span class="history-deleted">' . $link . '</span>'; } @@ -209,7 +229,7 @@ class DeletedContribsPager extends IndexPager { array( 'class' => 'mw-changeslist-title' ) ); - if( $rev->isMinor() ) { + if ( $rev->isMinor() ) { $mflag = ChangesList::flag( 'minor' ); } else { $mflag = ''; @@ -217,7 +237,9 @@ class DeletedContribsPager extends IndexPager { // Revision delete link $del = Linker::getRevDeleteLink( $user, $rev, $page ); - if ( $del ) $del .= ' '; + if ( $del ) { + $del .= ' '; + } $tools = Html::rawElement( 'span', @@ -230,13 +252,14 @@ class DeletedContribsPager extends IndexPager { $ret = "{$del}{$link} {$tools} {$separator} {$mflag} {$pagelink} {$comment}"; # Denote if username is redacted for this edit - if( $rev->isDeleted( Revision::DELETED_USER ) ) { + if ( $rev->isDeleted( Revision::DELETED_USER ) ) { $ret .= " <strong>" . $this->msg( 'rev-deleted-user-contribs' )->escaped() . "</strong>"; } $ret = Html::rawElement( 'li', array(), $ret ) . "\n"; wfProfileOut( __METHOD__ ); + return $ret; } @@ -253,17 +276,18 @@ class DeletedContribsPager extends IndexPager { class DeletedContributionsPage extends SpecialPage { function __construct() { parent::__construct( 'DeletedContributions', 'deletedhistory', - /*listed*/ true, /*function*/ false, /*file*/ false ); + /*listed*/true, /*function*/false, /*file*/false ); } /** * Special page "deleted user contributions". * Shows a list of the deleted contributions of a user. * - * @param $par String: (optional) user name of the user for which to show the contributions + * @param string $par (optional) user name of the user for which to show the contributions */ function execute( $par ) { global $wgQueryPageDefaultLimit; + $this->setHeaders(); $this->outputHeader(); @@ -271,6 +295,7 @@ class DeletedContributionsPage extends SpecialPage { if ( !$this->userCanExecute( $user ) ) { $this->displayRestrictionError(); + return; } @@ -288,6 +313,7 @@ class DeletedContributionsPage extends SpecialPage { if ( !strlen( $target ) ) { $out->addHTML( $this->getForm( '' ) ); + return; } @@ -297,6 +323,7 @@ class DeletedContributionsPage extends SpecialPage { $userObj = User::newFromName( $target, false ); if ( !$userObj ) { $out->addHTML( $this->getForm( '' ) ); + return; } $this->getSkin()->setRelevantUser( $userObj ); @@ -315,28 +342,33 @@ class DeletedContributionsPage extends SpecialPage { $pager = new DeletedContribsPager( $this->getContext(), $target, $options['namespace'] ); if ( !$pager->getNumRows() ) { $out->addWikiMsg( 'nocontribs' ); + return; } # Show a message about slave lag, if applicable $lag = wfGetLB()->safeGetLag( $pager->getDatabase() ); - if( $lag > 0 ) + if ( $lag > 0 ) { $out->showLagWarning( $lag ); + } $out->addHTML( '<p>' . $pager->getNavigationBar() . '</p>' . - $pager->getBody() . - '<p>' . $pager->getNavigationBar() . '</p>' ); + $pager->getBody() . + '<p>' . $pager->getNavigationBar() . '</p>' ); # If there were contributions, and it was a valid user or IP, show # the appropriate "footer" message - WHOIS tools, etc. - if( $target != 'newbies' ) { - $message = IP::isIPAddress( $target ) - ? 'sp-contributions-footer-anon' - : 'sp-contributions-footer'; - - if( !$this->msg( $message )->isDisabled() ) { - $out->wrapWikiMsg( "<div class='mw-contributions-footer'>\n$1\n</div>", array( $message, $target ) ); + if ( $target != 'newbies' ) { + $message = IP::isIPAddress( $target ) ? + 'sp-contributions-footer-anon' : + 'sp-contributions-footer'; + + if ( !$this->msg( $message )->isDisabled() ) { + $out->wrapWikiMsg( + "<div class='mw-contributions-footer'>\n$1\n</div>", + array( $message, $target ) + ); } } } @@ -357,11 +389,12 @@ class DeletedContributionsPage extends SpecialPage { $nt = $userObj->getUserPage(); $id = $userObj->getID(); $talk = $nt->getTalkPage(); - if( $talk ) { + if ( $talk ) { # Talk page link $tools[] = Linker::link( $talk, $this->msg( 'sp-contributions-talk' )->escaped() ); - if( ( $id !== null ) || ( $id === null && IP::isIPAddress( $nt->getText() ) ) ) { - if( $this->getUser()->isAllowed( 'block' ) ) { # Block / Change block / Unblock links + if ( ( $id !== null ) || ( $id === null && IP::isIPAddress( $nt->getText() ) ) ) { + # Block / Change block / Unblock links + if ( $this->getUser()->isAllowed( 'block' ) ) { if ( $userObj->isBlocked() ) { $tools[] = Linker::linkKnown( # Change block link SpecialPage::getTitleFor( 'Block', $nt->getDBkey() ), @@ -376,8 +409,8 @@ class DeletedContributionsPage extends SpecialPage { 'ip' => $nt->getDBkey() ) ); - } - else { # User is not blocked + } else { + # User is not blocked $tools[] = Linker::linkKnown( # Block link SpecialPage::getTitleFor( 'Block', $nt->getDBkey() ), $this->msg( 'blocklink' )->escaped() @@ -418,7 +451,7 @@ class DeletedContributionsPage extends SpecialPage { # Add a link to change user rights for privileged users $userrightsPage = new UserrightsPage(); $userrightsPage->setContext( $this->getContext() ); - if( $userrightsPage->userCanChangeRights( $userObj ) ) { + if ( $userrightsPage->userCanChangeRights( $userObj ) ) { $tools[] = Linker::linkKnown( SpecialPage::getTitleFor( 'Userrights', $nt->getDBkey() ), $this->msg( 'sp-contributions-userrights' )->escaped() @@ -431,7 +464,8 @@ class DeletedContributionsPage extends SpecialPage { // Show a note if the user is blocked and display the last block log entry. if ( $userObj->isBlocked() ) { - $out = $this->getOutput(); // LogEventsList::showLogExtract() wants the first parameter by ref + // LogEventsList::showLogExtract() wants the first parameter by ref + $out = $this->getOutput(); LogEventsList::showLogExtract( $out, 'block', @@ -450,21 +484,12 @@ class DeletedContributionsPage extends SpecialPage { } } - // Old message 'contribsub' had one parameter, but that doesn't work for - // languages that want to put the "for" bit right after $user but before - // $links. If 'contribsub' is around, use it for reverse compatibility, - // otherwise use 'contribsub2'. - $oldMsg = $this->msg( 'contribsub' ); - if ( $oldMsg->exists() ) { - return $oldMsg->rawParams( "$user ($links)" ); - } else { - return $this->msg( 'contribsub2' )->rawParams( $user, $links ); - } + return $this->msg( 'contribsub2' )->rawParams( $user, $links )->params( $userObj->getName() ); } /** * Generates the namespace selector form with hidden attributes. - * @param $options Array: the options to be included. + * @param array $options the options to be included. * @return string */ function getForm( $options ) { @@ -474,7 +499,7 @@ class DeletedContributionsPage extends SpecialPage { if ( !isset( $options['target'] ) ) { $options['target'] = ''; } else { - $options['target'] = str_replace( '_' , ' ' , $options['target'] ); + $options['target'] = str_replace( '_', ' ', $options['target'] ); } if ( !isset( $options['namespace'] ) ) { @@ -498,27 +523,42 @@ class DeletedContributionsPage extends SpecialPage { $f .= "\t" . Html::hidden( $name, $value ) . "\n"; } - $f .= Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', array(), $this->msg( 'sp-contributions-search' )->text() ) . - Xml::tags( 'label', array( 'for' => 'target' ), $this->msg( 'sp-contributions-username' )->parse() ) . ' ' . - Html::input( 'target', $options['target'], 'text', array( + $f .= Xml::openElement( 'fieldset' ); + $f .= Xml::element( 'legend', array(), $this->msg( 'sp-contributions-search' )->text() ); + $f .= Xml::tags( + 'label', + array( 'for' => 'target' ), + $this->msg( 'sp-contributions-username' )->parse() + ) . ' '; + $f .= Html::input( + 'target', + $options['target'], + 'text', + array( 'size' => '20', 'required' => '' - ) + ( $options['target'] ? array() : array( 'autofocus' ) ) ) . ' '. - Html::namespaceSelector( - array( - 'selected' => $options['namespace'], - 'all' => '', - 'label' => $this->msg( 'namespace' )->text() - ), array( - 'name' => 'namespace', - 'id' => 'namespace', - 'class' => 'namespaceselector', - ) - ) . ' ' . - Xml::submitButton( $this->msg( 'sp-contributions-submit' )->text() ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ); + ) + ( $options['target'] ? array() : array( 'autofocus' ) ) + ) . ' '; + $f .= Html::namespaceSelector( + array( + 'selected' => $options['namespace'], + 'all' => '', + 'label' => $this->msg( 'namespace' )->text() + ), + array( + 'name' => 'namespace', + 'id' => 'namespace', + 'class' => 'namespaceselector', + ) + ) . ' '; + $f .= Xml::submitButton( $this->msg( 'sp-contributions-submit' )->text() ); + $f .= Xml::closeElement( 'fieldset' ); + $f .= Xml::closeElement( 'form' ); + return $f; } + + protected function getGroupName() { + return 'users'; + } } diff --git a/includes/specials/SpecialDisambiguations.php b/includes/specials/SpecialDisambiguations.php deleted file mode 100644 index 48180a77..00000000 --- a/includes/specials/SpecialDisambiguations.php +++ /dev/null @@ -1,161 +0,0 @@ -<?php -/** - * Implements Special:Disambiguations - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - * @ingroup SpecialPage - */ - -/** - * A special page that lists pages containing links to disambiguations pages - * - * @ingroup SpecialPage - */ -class DisambiguationsPage extends QueryPage { - - function __construct( $name = 'Disambiguations' ) { - parent::__construct( $name ); - } - - function isExpensive() { - return true; - } - - function isSyndicated() { - return false; - } - - function getPageHeader() { - return $this->msg( 'disambiguations-text' )->parseAsBlock(); - } - - /** - * @return string|bool False on failure - */ - function getQueryFromLinkBatch() { - $dbr = wfGetDB( DB_SLAVE ); - $dMsgText = $this->msg( 'disambiguationspage' )->inContentLanguage()->text(); - $linkBatch = new LinkBatch; - - # If the text can be treated as a title, use it verbatim. - # Otherwise, pull the titles from the links table - $dp = Title::newFromText( $dMsgText ); - if( $dp ) { - if( $dp->getNamespace() != NS_TEMPLATE ) { - # @todo FIXME: We assume the disambiguation message is a template but - # the page can potentially be from another namespace :/ - wfDebug("Mediawiki:disambiguationspage message does not refer to a template!\n"); - } - $linkBatch->addObj( $dp ); - } else { - # Get all the templates linked from the Mediawiki:Disambiguationspage - $disPageObj = Title::makeTitleSafe( NS_MEDIAWIKI, 'disambiguationspage' ); - $res = $dbr->select( - array('pagelinks', 'page'), - 'pl_title', - array('page_id = pl_from', - 'pl_namespace' => NS_TEMPLATE, - 'page_namespace' => $disPageObj->getNamespace(), - 'page_title' => $disPageObj->getDBkey()), - __METHOD__ ); - - foreach ( $res as $row ) { - $linkBatch->addObj( Title::makeTitle( NS_TEMPLATE, $row->pl_title )); - } - } - $set = $linkBatch->constructSet( 'tl', $dbr ); - - if( $set === false ) { - # We must always return a valid SQL query, but this way - # the DB will always quickly return an empty result - $set = 'FALSE'; - wfDebug( "Mediawiki:disambiguationspage message does not link to any templates!\n" ); - } - return $set; - } - - function getQueryInfo() { - // @todo FIXME: What are pagelinks and p2 doing here? - return array ( - 'tables' => array( - 'templatelinks', - 'p1' => 'page', - 'pagelinks', - 'p2' => 'page' - ), - 'fields' => array( - 'namespace' => 'p1.page_namespace', - 'title' => 'p1.page_title', - 'value' => 'pl_from' - ), - 'conds' => array( - $this->getQueryFromLinkBatch(), - 'p1.page_id = tl_from', - 'pl_namespace = p1.page_namespace', - 'pl_title = p1.page_title', - 'p2.page_id = pl_from', - 'p2.page_namespace' => MWNamespace::getContentNamespaces() - ) - ); - } - - function getOrderFields() { - return array( 'tl_namespace', 'tl_title', 'value' ); - } - - function sortDescending() { - return false; - } - - /** - * Fetch links and cache their existence - * - * @param $db DatabaseBase - * @param $res - */ - function preprocessResults( $db, $res ) { - if ( !$res->numRows() ) { - return; - } - - $batch = new LinkBatch; - foreach ( $res as $row ) { - $batch->add( $row->namespace, $row->title ); - } - $batch->execute(); - - $res->seek( 0 ); - } - - function formatResult( $skin, $result ) { - $title = Title::newFromID( $result->value ); - $dp = Title::makeTitle( $result->namespace, $result->title ); - - $from = Linker::link( $title ); - $edit = Linker::link( - $title, - $this->msg( 'parentheses', $this->msg( 'editlink' )->text() )->escaped(), - array(), - array( 'redirect' => 'no', 'action' => 'edit' ) - ); - $arr = $this->getLanguage()->getArrow(); - $to = Linker::link( $dp ); - - return "$from $edit $arr $to"; - } -} diff --git a/includes/specials/SpecialDoubleRedirects.php b/includes/specials/SpecialDoubleRedirects.php index 5864ca9f..c364f70f 100644 --- a/includes/specials/SpecialDoubleRedirects.php +++ b/includes/specials/SpecialDoubleRedirects.php @@ -28,14 +28,21 @@ * @ingroup SpecialPage */ class DoubleRedirectsPage extends QueryPage { - function __construct( $name = 'DoubleRedirects' ) { parent::__construct( $name ); } - function isExpensive() { return true; } - function isSyndicated() { return false; } - function sortDescending() { return false; } + function isExpensive() { + return true; + } + + function isSyndicated() { + return false; + } + + function sortDescending() { + return false; + } function getPageHeader() { return $this->msg( 'doubleredirectstext' )->parseAsBlock(); @@ -43,28 +50,52 @@ class DoubleRedirectsPage extends QueryPage { function reallyGetQueryInfo( $namespace = null, $title = null ) { $limitToTitle = !( $namespace === null && $title === null ); - $retval = array ( - 'tables' => array ( 'ra' => 'redirect', - 'rb' => 'redirect', 'pa' => 'page', - 'pb' => 'page', 'pc' => 'page' ), - 'fields' => array ( 'namespace' => 'pa.page_namespace', - 'title' => 'pa.page_title', - 'value' => 'pa.page_title', - 'nsb' => 'pb.page_namespace', - 'tb' => 'pb.page_title', - 'nsc' => 'pc.page_namespace', - 'tc' => 'pc.page_title' ), - 'conds' => array ( 'ra.rd_from = pa.page_id', - 'pb.page_namespace = ra.rd_namespace', - 'pb.page_title = ra.rd_title', - 'rb.rd_from = pb.page_id', - 'pc.page_namespace = rb.rd_namespace', - 'pc.page_title = rb.rd_title' ) + $dbr = wfGetDB( DB_SLAVE ); + $retval = array( + 'tables' => array( + 'ra' => 'redirect', + 'rb' => 'redirect', + 'pa' => 'page', + 'pb' => 'page' + ), + 'fields' => array( + 'namespace' => 'pa.page_namespace', + 'title' => 'pa.page_title', + 'value' => 'pa.page_title', + + 'nsb' => 'pb.page_namespace', + 'tb' => 'pb.page_title', + + // Select fields from redirect instead of page. Because there may + // not actually be a page table row for this target (e.g. for interwiki redirects) + 'nsc' => 'rb.rd_namespace', + 'tc' => 'rb.rd_title', + 'iwc' => 'rb.rd_interwiki', + ), + 'conds' => array( + 'ra.rd_from = pa.page_id', + + // Filter out redirects where the target goes interwiki (bug 40353). + // This isn't an optimization, it is required for correct results, + // otherwise a non-double redirect like Bar -> w:Foo will show up + // like "Bar -> Foo -> w:Foo". + + // Need to check both NULL and "" for some reason, + // apparently either can be stored for non-iw entries. + 'ra.rd_interwiki IS NULL OR ra.rd_interwiki = ' . $dbr->addQuotes( '' ), + + 'pb.page_namespace = ra.rd_namespace', + 'pb.page_title = ra.rd_title', + + 'rb.rd_from = pb.page_id', + ) ); + if ( $limitToTitle ) { $retval['conds']['pa.page_namespace'] = $namespace; $retval['conds']['pa.page_title'] = $title; } + return $retval; } @@ -73,18 +104,35 @@ class DoubleRedirectsPage extends QueryPage { } function getOrderFields() { - return array ( 'ra.rd_namespace', 'ra.rd_title' ); + return array( 'ra.rd_namespace', 'ra.rd_title' ); } + /** + * @param Skin $skin + * @param object $result Result row + * @return string + */ function formatResult( $skin, $result ) { $titleA = Title::makeTitle( $result->namespace, $result->title ); + // If only titleA is in the query, it means this came from + // querycache (which only saves 3 columns). + // That does save the bulk of the query cost, but now we need to + // get a little more detail about each individual entry quickly + // using the filter of reallyGetQueryInfo. if ( $result && !isset( $result->nsb ) ) { $dbr = wfGetDB( DB_SLAVE ); - $qi = $this->reallyGetQueryInfo( $result->namespace, - $result->title ); - $res = $dbr->select($qi['tables'], $qi['fields'], - $qi['conds'], __METHOD__ ); + $qi = $this->reallyGetQueryInfo( + $result->namespace, + $result->title + ); + $res = $dbr->select( + $qi['tables'], + $qi['fields'], + $qi['conds'], + __METHOD__ + ); + if ( $res ) { $result = $dbr->fetchObject( $res ); } @@ -94,7 +142,7 @@ class DoubleRedirectsPage extends QueryPage { } $titleB = Title::makeTitle( $result->nsb, $result->tb ); - $titleC = Title::makeTitle( $result->nsc, $result->tc ); + $titleC = Title::makeTitle( $result->nsc, $result->tc, '', $result->iwc ); $linkA = Linker::linkKnown( $titleA, @@ -125,6 +173,10 @@ class DoubleRedirectsPage extends QueryPage { $lang = $this->getLanguage(); $arr = $lang->getArrow() . $lang->getDirMark(); - return( "{$linkA} {$edit} {$arr} {$linkB} {$arr} {$linkC}" ); + return ( "{$linkA} {$edit} {$arr} {$linkB} {$arr} {$linkC}" ); + } + + protected function getGroupName() { + return 'maintenance'; } } diff --git a/includes/specials/SpecialEditWatchlist.php b/includes/specials/SpecialEditWatchlist.php index 23cd9aa6..501552e9 100644 --- a/includes/specials/SpecialEditWatchlist.php +++ b/includes/specials/SpecialEditWatchlist.php @@ -35,7 +35,6 @@ * @author Rob Church <robchur@gmail.com> */ class SpecialEditWatchlist extends UnlistedSpecialPage { - /** * Editing modes */ @@ -49,8 +48,8 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { private $badItems = array(); - public function __construct(){ - parent::__construct( 'EditWatchlist' ); + public function __construct() { + parent::__construct( 'EditWatchlist', 'editmywatchlist' ); } /** @@ -64,7 +63,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { $out = $this->getOutput(); # Anons don't get a watchlist - if( $this->getUser()->isAnon() ) { + if ( $this->getUser()->isAnon() ) { $out->setPageTitle( $this->msg( 'watchnologin' ) ); $llink = Linker::linkKnown( SpecialPage::getTitleFor( 'Userlogin' ), @@ -73,27 +72,29 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { array( 'returnto' => $this->getTitle()->getPrefixedText() ) ); $out->addHTML( $this->msg( 'watchlistanontext' )->rawParams( $llink )->parse() ); + return; } $this->checkPermissions(); + $this->checkReadOnly(); $this->outputHeader(); - $out->addSubtitle( $this->msg( 'watchlistfor2', $this->getUser()->getName() - )->rawParams( SpecialEditWatchlist::buildTools( null ) ) ); + $out->addSubtitle( $this->msg( 'watchlistfor2', $this->getUser()->getName() ) + ->rawParams( SpecialEditWatchlist::buildTools( null ) ) ); # B/C: $mode used to be waaay down the parameter list, and the first parameter # was $wgUser - if( $mode instanceof User ){ + if ( $mode instanceof User ) { $args = func_get_args(); - if( count( $args >= 4 ) ){ + if ( count( $args ) >= 4 ) { $mode = $args[3]; } } $mode = self::getMode( $this->getRequest(), $mode ); - switch( $mode ) { + switch ( $mode ) { case self::EDIT_CLEAR: // The "Clear" link scared people too much. // Pass on to the raw editor, from which it's very easy to clear. @@ -101,7 +102,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { case self::EDIT_RAW: $out->setPageTitle( $this->msg( 'watchlistedit-raw-title' ) ); $form = $this->getRawForm(); - if( $form->show() ){ + if ( $form->show() ) { $out->addHTML( $this->successMessage ); $out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) ); } @@ -111,7 +112,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { default: $out->setPageTitle( $this->msg( 'watchlistedit-normal-title' ) ); $form = $this->getNormalForm(); - if( $form->show() ){ + if ( $form->show() ) { $out->addHTML( $this->successMessage ); $out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) ); } elseif ( $this->toc !== false ) { @@ -130,15 +131,17 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { */ private function extractTitles( $list ) { $list = explode( "\n", trim( $list ) ); - if( !is_array( $list ) ) { + if ( !is_array( $list ) ) { return array(); } + $titles = array(); - foreach( $list as $text ) { + + foreach ( $list as $text ) { $text = trim( $text ); - if( strlen( $text ) > 0 ) { + if ( strlen( $text ) > 0 ) { $title = Title::newFromText( $text ); - if( $title instanceof Title && $title->isWatchable() ) { + if ( $title instanceof Title && $title->isWatchable() ) { $titles[] = $title; } } @@ -147,54 +150,57 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { GenderCache::singleton()->doTitlesArray( $titles ); $list = array(); - foreach( $titles as $title ) { + /** @var Title $title */ + foreach ( $titles as $title ) { $list[] = $title->getPrefixedText(); } + return array_unique( $list ); } - public function submitRaw( $data ){ + public function submitRaw( $data ) { $wanted = $this->extractTitles( $data['Titles'] ); $current = $this->getWatchlist(); - if( count( $wanted ) > 0 ) { + if ( count( $wanted ) > 0 ) { $toWatch = array_diff( $wanted, $current ); $toUnwatch = array_diff( $current, $wanted ); $this->watchTitles( $toWatch ); $this->unwatchTitles( $toUnwatch ); $this->getUser()->invalidateCache(); - if( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 ){ + if ( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 ) { $this->successMessage = $this->msg( 'watchlistedit-raw-done' )->parse(); } else { return false; } - if( count( $toWatch ) > 0 ) { + if ( count( $toWatch ) > 0 ) { $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-added' - )->numParams( count( $toWatch ) )->parse(); + )->numParams( count( $toWatch ) )->parse(); $this->showTitles( $toWatch, $this->successMessage ); } - if( count( $toUnwatch ) > 0 ) { + if ( count( $toUnwatch ) > 0 ) { $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed' - )->numParams( count( $toUnwatch ) )->parse(); + )->numParams( count( $toUnwatch ) )->parse(); $this->showTitles( $toUnwatch, $this->successMessage ); } } else { $this->clearWatchlist(); $this->getUser()->invalidateCache(); - if( count( $current ) > 0 ){ + if ( count( $current ) > 0 ) { $this->successMessage = $this->msg( 'watchlistedit-raw-done' )->parse(); } else { return false; } - $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed' - )->numParams( count( $current ) )->parse(); + $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed' ) + ->numParams( count( $current ) )->parse(); $this->showTitles( $current, $this->successMessage ); } + return true; } @@ -204,36 +210,42 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { * $titles can be an array of strings or Title objects; the former * is preferred, since Titles are very memory-heavy * - * @param $titles array of strings, or Title objects + * @param array $titles of strings, or Title objects * @param $output String */ private function showTitles( $titles, &$output ) { $talk = $this->msg( 'talkpagelinktext' )->escaped(); // Do a batch existence check $batch = new LinkBatch(); - foreach( $titles as $title ) { - if( !$title instanceof Title ) { + foreach ( $titles as $title ) { + if ( !$title instanceof Title ) { $title = Title::newFromText( $title ); } - if( $title instanceof Title ) { + + if ( $title instanceof Title ) { $batch->addObj( $title ); $batch->addObj( $title->getTalkPage() ); } } + $batch->execute(); + // Print out the list $output .= "<ul>\n"; - foreach( $titles as $title ) { - if( !$title instanceof Title ) { + + foreach ( $titles as $title ) { + if ( !$title instanceof Title ) { $title = Title::newFromText( $title ); } - if( $title instanceof Title ) { + + if ( $title instanceof Title ) { $output .= "<li>" . Linker::link( $title ) . ' (' . Linker::link( $title->getTalkPage(), $talk ) . ")</li>\n"; } } + $output .= "</ul>\n"; } @@ -246,6 +258,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { private function getWatchlist() { $list = array(); $dbr = wfGetDB( DB_MASTER ); + $res = $dbr->select( 'watchlist', array( @@ -255,10 +268,12 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { ), __METHOD__ ); - if( $res->numRows() > 0 ) { + + if ( $res->numRows() > 0 ) { $titles = array(); foreach ( $res as $row ) { $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title ); + if ( $this->checkTitle( $title, $row->wl_namespace, $row->wl_title ) && !$title->isTalkPage() ) { @@ -269,11 +284,13 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { GenderCache::singleton()->doTitlesArray( $titles ); - foreach( $titles as $title ) { + foreach ( $titles as $title ) { $list[] = $title->getPrefixedText(); } } + $this->cleanupWatchlist(); + return $list; } @@ -289,13 +306,14 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { $res = $dbr->select( array( 'watchlist' ), - array( 'wl_namespace', 'wl_title' ), + array( 'wl_namespace', 'wl_title' ), array( 'wl_user' => $this->getUser()->getId() ), __METHOD__, array( 'ORDER BY' => array( 'wl_namespace', 'wl_title' ) ) ); $lb = new LinkBatch(); + foreach ( $res as $row ) { $lb->add( $row->wl_namespace, $row->wl_title ); if ( !MWNamespace::isTalk( $row->wl_namespace ) ) { @@ -304,6 +322,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { } $lb->execute(); + return $titles; } @@ -312,7 +331,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { * * @param Title $title * @param int $namespace - * @param String $dbKey + * @param string $dbKey * @return bool: Whether this item is valid */ private function checkTitle( $title, $namespace, $dbKey ) { @@ -323,12 +342,14 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { ) { $title = false; // unrecoverable } + if ( !$title || $title->getNamespace() != $namespace || $title->getDBkey() != $dbKey ) { $this->badItems[] = array( $title, $namespace, $dbKey ); } + return (bool)$title; } @@ -336,16 +357,17 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { * Attempts to clean up broken items */ private function cleanupWatchlist() { - if( !count( $this->badItems ) ) { + if ( !count( $this->badItems ) ) { return; //nothing to do } + $dbw = wfGetDB( DB_MASTER ); $user = $this->getUser(); + foreach ( $this->badItems as $row ) { list( $title, $namespace, $dbKey ) = $row; - wfDebug( "User {$user->getName()} has broken watchlist item ns($namespace):$dbKey, " - . ( $title ? 'cleaning up' : 'deleting' ) . ".\n" - ); + $action = $title ? 'cleaning up' : 'deleting'; + wfDebug( "User {$user->getName()} has broken watchlist item ns($namespace):$dbKey, $action.\n" ); $dbw->delete( 'watchlist', array( @@ -381,30 +403,33 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { * $titles can be an array of strings or Title objects; the former * is preferred, since Titles are very memory-heavy * - * @param $titles Array of strings, or Title objects + * @param array $titles of strings, or Title objects */ private function watchTitles( $titles ) { $dbw = wfGetDB( DB_MASTER ); $rows = array(); - foreach( $titles as $title ) { - if( !$title instanceof Title ) { + + foreach ( $titles as $title ) { + if ( !$title instanceof Title ) { $title = Title::newFromText( $title ); } - if( $title instanceof Title ) { + + if ( $title instanceof Title ) { $rows[] = array( 'wl_user' => $this->getUser()->getId(), - 'wl_namespace' => ( $title->getNamespace() & ~1 ), + 'wl_namespace' => MWNamespace::getSubject( $title->getNamespace() ), 'wl_title' => $title->getDBkey(), 'wl_notificationtimestamp' => null, ); $rows[] = array( 'wl_user' => $this->getUser()->getId(), - 'wl_namespace' => ( $title->getNamespace() | 1 ), + 'wl_namespace' => MWNamespace::getTalk( $title->getNamespace() ), 'wl_title' => $title->getDBkey(), 'wl_notificationtimestamp' => null, ); } } + $dbw->insert( 'watchlist', $rows, __METHOD__, 'IGNORE' ); } @@ -414,33 +439,37 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { * $titles can be an array of strings or Title objects; the former * is preferred, since Titles are very memory-heavy * - * @param $titles Array of strings, or Title objects + * @param array $titles of strings, or Title objects */ private function unwatchTitles( $titles ) { $dbw = wfGetDB( DB_MASTER ); - foreach( $titles as $title ) { - if( !$title instanceof Title ) { + + foreach ( $titles as $title ) { + if ( !$title instanceof Title ) { $title = Title::newFromText( $title ); } - if( $title instanceof Title ) { + + if ( $title instanceof Title ) { $dbw->delete( 'watchlist', array( 'wl_user' => $this->getUser()->getId(), - 'wl_namespace' => ( $title->getNamespace() & ~1 ), + 'wl_namespace' => MWNamespace::getSubject( $title->getNamespace() ), 'wl_title' => $title->getDBkey(), ), __METHOD__ ); + $dbw->delete( 'watchlist', array( 'wl_user' => $this->getUser()->getId(), - 'wl_namespace' => ( $title->getNamespace() | 1 ), + 'wl_namespace' => MWNamespace::getTalk( $title->getNamespace() ), 'wl_title' => $title->getDBkey(), ), __METHOD__ ); + $page = WikiPage::factory( $title ); wfRunHooks( 'UnwatchArticleComplete', array( $this->getUser(), &$page ) ); } @@ -450,15 +479,16 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { public function submitNormal( $data ) { $removed = array(); - foreach( $data as $titles ) { + foreach ( $data as $titles ) { $this->unwatchTitles( $titles ); $removed = array_merge( $removed, $titles ); } - if( count( $removed ) > 0 ) { + if ( count( $removed ) > 0 ) { $this->successMessage = $this->msg( 'watchlistedit-normal-done' - )->numParams( count( $removed ) )->parse(); + )->numParams( count( $removed ) )->parse(); $this->showTitles( $removed, $this->successMessage ); + return true; } else { return false; @@ -470,26 +500,27 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { * * @return HTMLForm */ - protected function getNormalForm(){ + protected function getNormalForm() { global $wgContLang; $fields = array(); $count = 0; - foreach( $this->getWatchlistInfo() as $namespace => $pages ){ + foreach ( $this->getWatchlistInfo() as $namespace => $pages ) { if ( $namespace >= 0 ) { - $fields['TitlesNs'.$namespace] = array( + $fields['TitlesNs' . $namespace] = array( 'class' => 'EditWatchlistCheckboxSeriesField', 'options' => array(), 'section' => "ns$namespace", ); } - foreach( array_keys( $pages ) as $dbkey ){ + foreach ( array_keys( $pages ) as $dbkey ) { $title = Title::makeTitleSafe( $namespace, $dbkey ); + if ( $this->checkTitle( $title, $namespace, $dbkey ) ) { $text = $this->buildRemoveLine( $title ); - $fields['TitlesNs'.$namespace]['options'][$text] = htmlspecialchars( $title->getPrefixedText() ); + $fields['TitlesNs' . $namespace]['options'][$text] = $title->getPrefixedText(); $count++; } } @@ -499,30 +530,33 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { if ( count( $fields ) > 1 && $count > 30 ) { $this->toc = Linker::tocIndent(); $tocLength = 0; - foreach( $fields as $data ) { + foreach ( $fields as $data ) { # strip out the 'ns' prefix from the section name: $ns = substr( $data['section'], 2 ); - $nsText = ($ns == NS_MAIN) + $nsText = ( $ns == NS_MAIN ) ? $this->msg( 'blanknamespace' )->escaped() : htmlspecialchars( $wgContLang->getFormattedNsText( $ns ) ); $this->toc .= Linker::tocLine( "editwatchlist-{$data['section']}", $nsText, $this->getLanguage()->formatNum( ++$tocLength ), 1 ) . Linker::tocLineEnd(); } + $this->toc = Linker::tocList( $this->toc ); } else { $this->toc = false; } - $form = new EditWatchlistNormalHTMLForm( $fields, $this->getContext() ); - $form->setTitle( $this->getTitle() ); + $context = new DerivativeContext( $this->getContext() ); + $context->setTitle( $this->getTitle() ); // Remove subpage + $form = new EditWatchlistNormalHTMLForm( $fields, $context ); $form->setSubmitTextMsg( 'watchlistedit-normal-submit' ); # Used message keys: 'accesskey-watchlistedit-normal-submit', 'tooltip-watchlistedit-normal-submit' - $form->setSubmitTooltip('watchlistedit-normal-submit'); + $form->setSubmitTooltip( 'watchlistedit-normal-submit' ); $form->setWrapperLegendMsg( 'watchlistedit-normal-legend' ); $form->addHeaderText( $this->msg( 'watchlistedit-normal-explain' )->parse() ); $form->setSubmitCallback( array( $this, 'submitNormal' ) ); + return $form; } @@ -534,12 +568,15 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { */ private function buildRemoveLine( $title ) { $link = Linker::link( $title ); - if( $title->isRedirect() ) { + + if ( $title->isRedirect() ) { // Linker already makes class mw-redirect, so this is redundant $link = '<span class="watchlistredir">' . $link . '</span>'; } + $tools[] = Linker::link( $title->getTalkPage(), $this->msg( 'talkpagelinktext' )->escaped() ); - if( $title->exists() ) { + + if ( $title->exists() ) { $tools[] = Linker::linkKnown( $title, $this->msg( 'history_short' )->escaped(), @@ -547,7 +584,8 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { array( 'action' => 'history' ) ); } - if( $title->getNamespace() == NS_USER && !$title->isSubpage() ) { + + if ( $title->getNamespace() == NS_USER && !$title->isSubpage() ) { $tools[] = Linker::linkKnown( SpecialPage::getTitleFor( 'Contributions', $title->getText() ), $this->msg( 'contributions' )->escaped() @@ -564,7 +602,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { * * @return HTMLForm */ - protected function getRawForm(){ + protected function getRawForm() { $titles = implode( $this->getWatchlist(), "\n" ); $fields = array( 'Titles' => array( @@ -573,14 +611,16 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { 'default' => $titles, ), ); - $form = new HTMLForm( $fields, $this->getContext() ); - $form->setTitle( $this->getTitle( 'raw' ) ); + $context = new DerivativeContext( $this->getContext() ); + $context->setTitle( $this->getTitle( 'raw' ) ); // Reset subpage + $form = new HTMLForm( $fields, $context ); $form->setSubmitTextMsg( 'watchlistedit-raw-submit' ); # Used message keys: 'accesskey-watchlistedit-raw-submit', 'tooltip-watchlistedit-raw-submit' - $form->setSubmitTooltip('watchlistedit-raw-submit'); + $form->setSubmitTooltip( 'watchlistedit-raw-submit' ); $form->setWrapperLegendMsg( 'watchlistedit-raw-legend' ); $form->addHeaderText( $this->msg( 'watchlistedit-raw-explain' )->parse() ); $form->setSubmitCallback( array( $this, 'submitRaw' ) ); + return $form; } @@ -594,19 +634,17 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { */ public static function getMode( $request, $par ) { $mode = strtolower( $request->getVal( 'action', $par ) ); - switch( $mode ) { + + switch ( $mode ) { case 'clear': case self::EDIT_CLEAR: return self::EDIT_CLEAR; - case 'raw': case self::EDIT_RAW: return self::EDIT_RAW; - case 'edit': case self::EDIT_NORMAL: return self::EDIT_NORMAL; - default: return false; } @@ -628,32 +666,39 @@ class SpecialEditWatchlist extends UnlistedSpecialPage { 'edit' => array( 'EditWatchlist', false ), 'raw' => array( 'EditWatchlist', 'raw' ), ); - foreach( $modes as $mode => $arr ) { + + foreach ( $modes as $mode => $arr ) { // can use messages 'watchlisttools-view', 'watchlisttools-edit', 'watchlisttools-raw' $tools[] = Linker::linkKnown( SpecialPage::getTitleFor( $arr[0], $arr[1] ), wfMessage( "watchlisttools-{$mode}" )->escaped() ); } - return Html::rawElement( 'span', - array( 'class' => 'mw-watchlist-toollinks' ), - wfMessage( 'parentheses', $wgLang->pipeList( $tools ) )->text() ); + + return Html::rawElement( + 'span', + array( 'class' => 'mw-watchlist-toollinks' ), + wfMessage( 'parentheses', $wgLang->pipeList( $tools ) )->text() + ); } } # B/C since 1.18 -class WatchlistEditor extends SpecialEditWatchlist {} +class WatchlistEditor extends SpecialEditWatchlist { +} /** * Extend HTMLForm purely so we can have a more sane way of getting the section headers */ class EditWatchlistNormalHTMLForm extends HTMLForm { - public function getLegend( $namespace ){ + public function getLegend( $namespace ) { $namespace = substr( $namespace, 2 ); + return $namespace == NS_MAIN ? $this->msg( 'blanknamespace' )->escaped() : htmlspecialchars( $this->getContext()->getLanguage()->getFormattedNsText( $namespace ) ); } + public function getBody() { return $this->displaySection( $this->mFieldTree, '', 'editwatchlist-' ); } @@ -667,8 +712,8 @@ class EditWatchlistCheckboxSeriesField extends HTMLMultiSelectField { * form is open (bug 32126), but we know that invalid items will * be harmless so we can override it here. * - * @param $value String the value the field was submitted with - * @param $alldata Array the data collected from the form + * @param string $value the value the field was submitted with + * @param array $alldata the data collected from the form * @return Mixed Bool true on success, or String error to display. */ function validate( $value, $alldata ) { diff --git a/includes/specials/SpecialEmailuser.php b/includes/specials/SpecialEmailuser.php index 4d875e6e..2e90d996 100644 --- a/includes/specials/SpecialEmailuser.php +++ b/includes/specials/SpecialEmailuser.php @@ -29,13 +29,18 @@ class SpecialEmailUser extends UnlistedSpecialPage { protected $mTarget; + /** + * @var User|string $mTargetObj + */ + protected $mTargetObj; + public function __construct() { parent::__construct( 'Emailuser' ); } public function getDescription() { $target = self::getTarget( $this->mTarget ); - if( !$target instanceof User ) { + if ( !$target instanceof User ) { return $this->msg( 'emailuser-title-notarget' )->text(); } @@ -106,7 +111,11 @@ class SpecialEmailUser extends UnlistedSpecialPage { $this->outputHeader(); // error out if sending user cannot do this - $error = self::getPermissionsError( $this->getUser(), $this->getRequest()->getVal( 'wpEditToken' ) ); + $error = self::getPermissionsError( + $this->getUser(), + $this->getRequest()->getVal( 'wpEditToken' ) + ); + switch ( $error ) { case null: # Wahey! @@ -119,42 +128,45 @@ class SpecialEmailUser extends UnlistedSpecialPage { throw new ThrottledError; case 'mailnologin': case 'usermaildisabled': - throw new ErrorPageError( $error, "{$error}text" ); + throw new ErrorPageError( $error, "{$error}text" ); default: # It's a hook error list( $title, $msg, $params ) = $error; - throw new ErrorPageError( $title, $msg, $params ); + throw new ErrorPageError( $title, $msg, $params ); } // Got a valid target user name? Else ask for one. $ret = self::getTarget( $this->mTarget ); - if( !$ret instanceof User ) { - if( $this->mTarget != '' ) { + if ( !$ret instanceof User ) { + if ( $this->mTarget != '' ) { $ret = ( $ret == 'notarget' ) ? 'emailnotarget' : ( $ret . 'text' ); $out->wrapWikiMsg( "<p class='error'>$1</p>", $ret ); } $out->addHTML( $this->userForm( $this->mTarget ) ); + return false; } $this->mTargetObj = $ret; - $form = new HTMLForm( $this->getFormFields(), $this->getContext() ); - $form->addPreText( $this->msg( 'emailpagetext' )->parse() ); + $context = new DerivativeContext( $this->getContext() ); + $context->setTitle( $this->getTitle() ); // Remove subpage + $form = new HTMLForm( $this->getFormFields(), $context ); + // By now we are supposed to be sure that $this->mTarget is a user name + $form->addPreText( $this->msg( 'emailpagetext', $this->mTarget )->parse() ); $form->setSubmitTextMsg( 'emailsend' ); - $form->setTitle( $this->getTitle() ); $form->setSubmitCallback( array( __CLASS__, 'uiSubmit' ) ); $form->setWrapperLegendMsg( 'email-legend' ); $form->loadData(); - if( !wfRunHooks( 'EmailUserForm', array( &$form ) ) ) { + if ( !wfRunHooks( 'EmailUserForm', array( &$form ) ) ) { return false; } $result = $form->show(); - if( $result === true || ( $result instanceof Status && $result->isGood() ) ) { + if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) { $out->setPageTitle( $this->msg( 'emailsent' ) ); - $out->addWikiMsg( 'emailsenttext' ); + $out->addWikiMsg( 'emailsenttext', $this->mTarget ); $out->returnToMain( false, $this->mTargetObj->getUserPage() ); } } @@ -162,24 +174,28 @@ class SpecialEmailUser extends UnlistedSpecialPage { /** * Validate target User * - * @param $target String: target user name + * @param string $target target user name * @return User object on success or a string on error */ public static function getTarget( $target ) { if ( $target == '' ) { wfDebug( "Target is empty.\n" ); + return 'notarget'; } $nu = User::newFromName( $target ); - if( !$nu instanceof User || !$nu->getId() ) { + if ( !$nu instanceof User || !$nu->getId() ) { wfDebug( "Target is invalid user.\n" ); + return 'notarget'; } elseif ( !$nu->isEmailConfirmed() ) { wfDebug( "User has no valid email.\n" ); + return 'noemail'; } elseif ( !$nu->canReceiveEmail() ) { wfDebug( "User does not allow user emails.\n" ); + return 'nowikiemail'; } @@ -190,36 +206,41 @@ class SpecialEmailUser extends UnlistedSpecialPage { * Check whether a user is allowed to send email * * @param $user User object - * @param $editToken String: edit token + * @param string $editToken edit token * @return null on success or string on error */ public static function getPermissionsError( $user, $editToken ) { global $wgEnableEmail, $wgEnableUserEmail; - if( !$wgEnableEmail || !$wgEnableUserEmail ) { + + if ( !$wgEnableEmail || !$wgEnableUserEmail ) { return 'usermaildisabled'; } - if( !$user->isAllowed( 'sendemail' ) ) { + if ( !$user->isAllowed( 'sendemail' ) ) { return 'badaccess'; } - if( !$user->isEmailConfirmed() ) { + if ( !$user->isEmailConfirmed() ) { return 'mailnologin'; } - if( $user->isBlockedFromEmailuser() ) { + if ( $user->isBlockedFromEmailuser() ) { wfDebug( "User is blocked from sending e-mail.\n" ); + return "blockedemailuser"; } - if( $user->pingLimiter( 'emailuser' ) ) { + if ( $user->pingLimiter( 'emailuser' ) ) { wfDebug( "Ping limiter triggered.\n" ); + return 'actionthrottledtext'; } $hookErr = false; + wfRunHooks( 'UserCanSendEmail', array( &$user, &$hookErr ) ); wfRunHooks( 'EmailUserPermissionsErrors', array( $user, $editToken, &$hookErr ) ); + if ( $hookErr ) { return $hookErr; } @@ -230,19 +251,30 @@ class SpecialEmailUser extends UnlistedSpecialPage { /** * Form to ask for target user name. * - * @param $name String: user name submitted. + * @param string $name user name submitted. * @return String: form asking for user name. */ protected function userForm( $name ) { global $wgScript; - $string = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'askusername' ) ) . + $string = Xml::openElement( + 'form', + array( 'method' => 'get', 'action' => $wgScript, 'id' => 'askusername' ) + ) . Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . Xml::openElement( 'fieldset' ) . Html::rawElement( 'legend', null, $this->msg( 'emailtarget' )->parse() ) . - Xml::inputLabel( $this->msg( 'emailusername' )->text(), 'target', 'emailusertarget', 30, $name ) . ' ' . + Xml::inputLabel( + $this->msg( 'emailusername' )->text(), + 'target', + 'emailusertarget', + 30, + $name + ) . + ' ' . Xml::submitButton( $this->msg( 'emailusernamesubmit' )->text() ) . Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ) . "\n"; + return $string; } @@ -263,6 +295,8 @@ class SpecialEmailUser extends UnlistedSpecialPage { * getPermissionsError(). It is probably also a good * idea to check the edit token and ping limiter in advance. * + * @param array $data + * @param IContextSource $context * @return Mixed: Status object, or potentially a String on error * or maybe even true on success if anything uses the EmailUser hook. */ @@ -270,9 +304,10 @@ class SpecialEmailUser extends UnlistedSpecialPage { global $wgUserEmailUseReplyTo; $target = self::getTarget( $data['Target'] ); - if( !$target instanceof User ) { + if ( !$target instanceof User ) { return $context->msg( $target . 'text' )->parseAsBlock(); } + $to = new MailAddress( $target ); $from = new MailAddress( $context->getUser() ); $subject = $data['Subject']; @@ -284,11 +319,11 @@ class SpecialEmailUser extends UnlistedSpecialPage { $from->name, $to->name )->inContentLanguage()->text(); $error = ''; - if( !wfRunHooks( 'EmailUser', array( &$to, &$from, &$subject, &$text, &$error ) ) ) { + if ( !wfRunHooks( 'EmailUser', array( &$to, &$from, &$subject, &$text, &$error ) ) ) { return $error; } - if( $wgUserEmailUseReplyTo ) { + if ( $wgUserEmailUseReplyTo ) { // Put the generic wiki autogenerated address in the From: // header and reserve the user for Reply-To. // @@ -296,6 +331,7 @@ class SpecialEmailUser extends UnlistedSpecialPage { // wiki-borne mails from direct mails and protects against // SPF and bounce problems with some mailers (see below). global $wgPasswordSender, $wgPasswordSenderName; + $mailFrom = new MailAddress( $wgPasswordSender, $wgPasswordSenderName ); $replyTo = $from; } else { @@ -318,7 +354,7 @@ class SpecialEmailUser extends UnlistedSpecialPage { $status = UserMailer::send( $to, $mailFrom, $subject, $text, $replyTo ); - if( !$status->isGood() ) { + if ( !$status->isGood() ) { return $status; } else { // if the user requested a copy of this mail, do this now, @@ -333,7 +369,12 @@ class SpecialEmailUser extends UnlistedSpecialPage { } wfRunHooks( 'EmailUserComplete', array( $to, $from, $subject, $text ) ); + return $status; } } + + protected function getGroupName() { + return 'users'; + } } diff --git a/includes/specials/SpecialExport.php b/includes/specials/SpecialExport.php index b4294b32..61ed34d4 100644 --- a/includes/specials/SpecialExport.php +++ b/includes/specials/SpecialExport.php @@ -29,7 +29,6 @@ * @ingroup SpecialPage */ class SpecialExport extends SpecialPage { - private $curonly, $doExport, $pageLinkDepth, $templates; private $images; @@ -75,12 +74,11 @@ class SpecialExport extends SpecialPage { } } } - } - elseif( $request->getCheck( 'addns' ) && $wgExportFromNamespaces ) { + } elseif ( $request->getCheck( 'addns' ) && $wgExportFromNamespaces ) { $page = $request->getText( 'pages' ); $nsindex = $request->getText( 'nsindex', '' ); - if ( strval( $nsindex ) !== '' ) { + if ( strval( $nsindex ) !== '' ) { /** * Same implementation as above, so same @todo */ @@ -89,8 +87,7 @@ class SpecialExport extends SpecialPage { $page .= "\n" . implode( "\n", $nspages ); } } - } - elseif( $request->getCheck( 'exportall' ) && $wgExportAllowAll ) { + } elseif ( $request->getCheck( 'exportall' ) && $wgExportAllowAll ) { $this->doExport = true; $exportall = true; @@ -100,13 +97,12 @@ class SpecialExport extends SpecialPage { doExport(...) further down) */ $page = ''; $history = ''; - } - elseif( $request->wasPosted() && $par == '' ) { + } elseif ( $request->wasPosted() && $par == '' ) { $page = $request->getText( 'pages' ); $this->curonly = $request->getCheck( 'curonly' ); $rawOffset = $request->getVal( 'offset' ); - if( $rawOffset ) { + if ( $rawOffset ) { $offset = wfTimestamp( TS_MW, $rawOffset ); } else { $offset = null; @@ -124,18 +120,20 @@ class SpecialExport extends SpecialPage { if ( $this->curonly ) { $history = WikiExporter::CURRENT; } elseif ( !$historyCheck ) { - if ( $limit > 0 && ($wgExportMaxHistory == 0 || $limit < $wgExportMaxHistory ) ) { + if ( $limit > 0 && ( $wgExportMaxHistory == 0 || $limit < $wgExportMaxHistory ) ) { $history['limit'] = $limit; } + if ( !is_null( $offset ) ) { $history['offset'] = $offset; } + if ( strtolower( $dir ) == 'desc' ) { $history['dir'] = 'desc'; } } - if( $page != '' ) { + if ( $page != '' ) { $this->doExport = true; } } else { @@ -143,25 +141,25 @@ class SpecialExport extends SpecialPage { $page = $request->getText( 'pages', $par ); $historyCheck = $request->getCheck( 'history' ); - if( $historyCheck ) { + if ( $historyCheck ) { $history = WikiExporter::FULL; } else { $history = WikiExporter::CURRENT; } - if( $page != '' ) { + if ( $page != '' ) { $this->doExport = true; } } - if( !$wgExportAllowHistory ) { + if ( !$wgExportAllowHistory ) { // Override $history = WikiExporter::CURRENT; } $list_authors = $request->getCheck( 'listauthors' ); if ( !$this->curonly || !$wgExportAllowListContributors ) { - $list_authors = false ; + $list_authors = false; } if ( $this->doExport ) { @@ -172,7 +170,7 @@ class SpecialExport extends SpecialPage { wfResetOutputBuffers(); $request->response()->header( "Content-type: application/xml; charset=utf-8" ); - if( $request->getCheck( 'wpDownload' ) ) { + if ( $request->getCheck( 'wpDownload' ) ) { // Provide a sane filename suggestion $filename = urlencode( $wgSitename . '-' . wfTimestampNow() . '.xml' ); $request->response()->header( "Content-disposition: attachment;filename={$filename}" ); @@ -187,9 +185,17 @@ class SpecialExport extends SpecialPage { $out->addWikiMsg( 'exporttext' ); $form = Xml::openElement( 'form', array( 'method' => 'post', - 'action' => $this->getTitle()->getLocalUrl( 'action=submit' ) ) ); - $form .= Xml::inputLabel( $this->msg( 'export-addcattext' )->text(), 'catname', 'catname', 40 ) . ' '; - $form .= Xml::submitButton( $this->msg( 'export-addcat' )->text(), array( 'name' => 'addcat' ) ) . '<br />'; + 'action' => $this->getTitle()->getLocalURL( 'action=submit' ) ) ); + $form .= Xml::inputLabel( + $this->msg( 'export-addcattext' )->text(), + 'catname', + 'catname', + 40 + ) . ' '; + $form .= Xml::submitButton( + $this->msg( 'export-addcat' )->text(), + array( 'name' => 'addcat' ) + ) . '<br />'; if ( $wgExportFromNamespaces ) { $form .= Html::namespaceSelector( @@ -197,12 +203,15 @@ class SpecialExport extends SpecialPage { 'selected' => $nsindex, 'label' => $this->msg( 'export-addnstext' )->text() ), array( - 'name' => 'nsindex', - 'id' => 'namespace', + 'name' => 'nsindex', + 'id' => 'namespace', 'class' => 'namespaceselector', ) ) . ' '; - $form .= Xml::submitButton( $this->msg( 'export-addns' )->text(), array( 'name' => 'addns' ) ) . '<br />'; + $form .= Xml::submitButton( + $this->msg( 'export-addns' )->text(), + array( 'name' => 'addns' ) + ) . '<br />'; } if ( $wgExportAllowAll ) { @@ -214,10 +223,15 @@ class SpecialExport extends SpecialPage { ) . '<br />'; } - $form .= Xml::element( 'textarea', array( 'name' => 'pages', 'cols' => 40, 'rows' => 10 ), $page, false ); + $form .= Xml::element( + 'textarea', + array( 'name' => 'pages', 'cols' => 40, 'rows' => 10 ), + $page, + false + ); $form .= '<br />'; - if( $wgExportAllowHistory ) { + if ( $wgExportAllowHistory ) { $form .= Xml::checkLabel( $this->msg( 'exportcuronly' )->text(), 'curonly', @@ -235,9 +249,16 @@ class SpecialExport extends SpecialPage { $request->wasPosted() ? $request->getCheck( 'templates' ) : false ) . '<br />'; - if( $wgExportMaxLinkDepth || $this->userCanOverrideExportDepth() ) { - $form .= Xml::inputLabel( $this->msg( 'export-pagelinks' )->text(), 'pagelink-depth', 'pagelink-depth', 20, 0 ) . '<br />'; + if ( $wgExportMaxLinkDepth || $this->userCanOverrideExportDepth() ) { + $form .= Xml::inputLabel( + $this->msg( 'export-pagelinks' )->text(), + 'pagelink-depth', + 'pagelink-depth', + 20, + 0 + ) . '<br />'; } + // Enable this when we can do something useful exporting/importing image information. :) //$form .= Xml::checkLabel( $this->msg( 'export-images' )->text(), 'images', 'wpExportImages', false ) . '<br />'; $form .= Xml::checkLabel( @@ -256,7 +277,10 @@ class SpecialExport extends SpecialPage { ) . '<br />'; } - $form .= Xml::submitButton( $this->msg( 'export-submit' )->text(), Linker::tooltipAndAccesskeyAttribs( 'export' ) ); + $form .= Xml::submitButton( + $this->msg( 'export-submit' )->text(), + Linker::tooltipAndAccesskeyAttribs( 'export' ) + ); $form .= Xml::closeElement( 'form' ); $out->addHTML( $form ); @@ -272,7 +296,7 @@ class SpecialExport extends SpecialPage { /** * Do the actual page exporting * - * @param $page String: user input on what page(s) to export + * @param string $page user input on what page(s) to export * @param $history Mixed: one of the WikiExporter history export constants * @param $list_authors Boolean: Whether to add distinct author list (when * not returning full history) @@ -286,12 +310,12 @@ class SpecialExport extends SpecialPage { } else { $pageSet = array(); // Inverted index of all pages to look up - + // Split up and normalize input - foreach( explode( "\n", $page ) as $pageName ) { + foreach ( explode( "\n", $page ) as $pageName ) { $pageName = trim( $pageName ); $title = Title::newFromText( $pageName ); - if( $title && $title->getInterwiki() == '' && $title->getText() !== '' ) { + if ( $title && $title->getInterwiki() == '' && $title->getText() !== '' ) { // Only record each page once! $pageSet[$title->getPrefixedText()] = true; } @@ -301,25 +325,23 @@ class SpecialExport extends SpecialPage { $inputPages = array_keys( $pageSet ); // Look up any linked pages if asked... - if( $this->templates ) { + if ( $this->templates ) { $pageSet = $this->getTemplates( $inputPages, $pageSet ); } $linkDepth = $this->pageLinkDepth; - if( $linkDepth ) { + if ( $linkDepth ) { $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 ); - } - */ + // 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 - foreach( $pages as $k => $v ) { + foreach ( $pages as $k => $v ) { $pages[$k] = str_replace( " ", "_", $v ); } @@ -327,7 +349,7 @@ class SpecialExport extends SpecialPage { } /* Ok, let's get to it... */ - if( $history == WikiExporter::CURRENT ) { + if ( $history == WikiExporter::CURRENT ) { $lb = false; $db = wfGetDB( DB_SLAVE ); $buffer = WikiExporter::BUFFER; @@ -339,7 +361,7 @@ class SpecialExport extends SpecialPage { // This might take a while... :D wfSuppressWarnings(); - set_time_limit(0); + set_time_limit( 0 ); wfRestoreWarnings(); } @@ -350,26 +372,17 @@ class SpecialExport extends SpecialPage { if ( $exportall ) { $exporter->allPages(); } else { - foreach( $pages as $page ) { - /* - if( $wgExportMaxHistory && !$this->curonly ) { - $title = Title::newFromText( $page ); - if( $title ) { - $count = Revision::countByTitle( $db, $title ); - if( $count > $wgExportMaxHistory ) { - wfDebug( __FUNCTION__ . - ": Skipped $page, $count revisions too big\n" ); - continue; - } - } - }*/ - #Bug 8824: Only export pages the user can read + foreach ( $pages as $page ) { + #Bug 8824: Only export pages the user can read $title = Title::newFromText( $page ); - if( is_null( $title ) ) { - continue; #TODO: perhaps output an <error> tag or something. + if ( is_null( $title ) ) { + // @todo Perhaps output an <error> tag or something. + continue; } - if( !$title->userCan( 'read', $this->getUser() ) ) { - continue; #TODO: perhaps output an <error> tag or something. + + if ( !$title->userCan( 'read', $this->getUser() ) ) { + // @todo Perhaps output an <error> tag or something. + continue; } $exporter->pageByTitle( $title ); @@ -378,7 +391,7 @@ class SpecialExport extends SpecialPage { $exporter->closeStream(); - if( $lb ) { + if ( $lb ) { $lb->closeAll(); } } @@ -405,13 +418,14 @@ class SpecialExport extends SpecialPage { foreach ( $res as $row ) { $n = $row->page_title; - if ($row->page_namespace) { + if ( $row->page_namespace ) { $ns = $wgContLang->getNsText( $row->page_namespace ); $n = $ns . ':' . $n; } $pages[] = $n; } + return $pages; } @@ -443,6 +457,7 @@ class SpecialExport extends SpecialPage { $pages[] = $n; } + return $pages; } @@ -468,12 +483,12 @@ class SpecialExport extends SpecialPage { private function validateLinkDepth( $depth ) { global $wgExportMaxLinkDepth; - if( $depth < 0 ) { + if ( $depth < 0 ) { return 0; } if ( !$this->userCanOverrideExportDepth() ) { - if( $depth > $wgExportMaxLinkDepth ) { + if ( $depth > $wgExportMaxLinkDepth ) { return $wgExportMaxLinkDepth; } } @@ -483,6 +498,7 @@ class SpecialExport extends SpecialPage { * crazy-big export from being done by someone setting the depth * number too high. In other words, last resort safety net. */ + return intval( min( $depth, 5 ) ); } @@ -494,7 +510,7 @@ class SpecialExport extends SpecialPage { * @return array */ private function getPageLinks( $inputPages, $pageSet, $depth ) { - for( ; $depth > 0; --$depth ) { + for ( ; $depth > 0; --$depth ) { $pageSet = $this->getLinks( $inputPages, $pageSet, 'pagelinks', array( 'namespace' => 'pl_namespace', 'title' => 'pl_title' ), @@ -526,15 +542,20 @@ class SpecialExport extends SpecialPage { /** * Expand a list of pages to include items used in those pages. + * @param array $inputPages Array of page titles + * @param array $pageSet + * @param string $table + * @param array $fields Array of field names + * @param array $join * @return array */ private function getLinks( $inputPages, $pageSet, $table, $fields, $join ) { $dbr = wfGetDB( DB_SLAVE ); - foreach( $inputPages as $page ) { + foreach ( $inputPages as $page ) { $title = Title::newFromText( $page ); - if( $title ) { + if ( $title ) { $pageSet[$title->getPrefixedText()] = true; /// @todo FIXME: May or may not be more efficient to batch these /// by namespace when given multiple input pages. @@ -551,7 +572,7 @@ class SpecialExport extends SpecialPage { __METHOD__ ); - foreach( $result as $row ) { + foreach ( $result as $row ) { $template = Title::makeTitle( $row->namespace, $row->title ); $pageSet[$template->getPrefixedText()] = true; } @@ -561,4 +582,7 @@ class SpecialExport extends SpecialPage { return $pageSet; } + protected function getGroupName() { + return 'pagetools'; + } } diff --git a/includes/specials/SpecialFewestrevisions.php b/includes/specials/SpecialFewestrevisions.php index 7e4bc9ce..47a4d75f 100644 --- a/includes/specials/SpecialFewestrevisions.php +++ b/includes/specials/SpecialFewestrevisions.php @@ -28,7 +28,6 @@ * @author Martin Drashkov */ class FewestrevisionsPage extends QueryPage { - function __construct( $name = 'Fewestrevisions' ) { parent::__construct( $name ); } @@ -42,41 +41,52 @@ class FewestrevisionsPage extends QueryPage { } function getQueryInfo() { - return array ( - 'tables' => array ( 'revision', 'page' ), - 'fields' => array ( 'namespace' => 'page_namespace', - 'title' => 'page_title', - 'value' => 'COUNT(*)', - 'redirect' => 'page_is_redirect' ), - 'conds' => array ( 'page_namespace' => MWNamespace::getContentNamespaces(), - 'page_id = rev_page' ), - 'options' => array ( 'HAVING' => 'COUNT(*) > 1', - // ^^^ This was probably here to weed out redirects. - // Since we mark them as such now, it might be - // useful to remove this. People _do_ create pages - // and never revise them, they aren't necessarily - // redirects. - 'GROUP BY' => array( 'page_namespace', 'page_title', 'page_is_redirect' ) ) + return array( + 'tables' => array( 'revision', 'page' ), + 'fields' => array( + 'namespace' => 'page_namespace', + 'title' => 'page_title', + 'value' => 'COUNT(*)', + 'redirect' => 'page_is_redirect' + ), + 'conds' => array( + 'page_namespace' => MWNamespace::getContentNamespaces(), + 'page_id = rev_page' ), + 'options' => array( + 'HAVING' => 'COUNT(*) > 1', + // ^^^ This was probably here to weed out redirects. + // Since we mark them as such now, it might be + // useful to remove this. People _do_ create pages + // and never revise them, they aren't necessarily + // redirects. + 'GROUP BY' => array( 'page_namespace', 'page_title', 'page_is_redirect' ) + ) ); } - function sortDescending() { return false; } /** - * @param $skin Skin object - * @param $result Object: database row + * @param Skin $skin + * @param object $result Database row * @return String */ function formatResult( $skin, $result ) { global $wgContLang; $nt = Title::makeTitleSafe( $result->namespace, $result->title ); - if( !$nt ) { - return Html::element( 'span', array( 'class' => 'mw-invalidtitle' ), - Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) ); + if ( !$nt ) { + return Html::element( + 'span', + array( 'class' => 'mw-invalidtitle' ), + Linker::getInvalidTitleDescription( + $this->getContext(), + $result->namespace, + $result->title + ) + ); } $text = htmlspecialchars( $wgContLang->convert( $nt->getPrefixedText() ) ); @@ -94,4 +104,8 @@ class FewestrevisionsPage extends QueryPage { return $this->getLanguage()->specialList( $plink, $nlink ); } + + protected function getGroupName() { + return 'maintenance'; + } } diff --git a/includes/specials/SpecialFileDuplicateSearch.php b/includes/specials/SpecialFileDuplicateSearch.php index ccf8ba17..4c6593b2 100644 --- a/includes/specials/SpecialFileDuplicateSearch.php +++ b/includes/specials/SpecialFileDuplicateSearch.php @@ -40,9 +40,17 @@ class FileDuplicateSearchPage extends QueryPage { parent::__construct( $name ); } - function isSyndicated() { return false; } - function isCacheable() { return false; } - function isCached() { return false; } + function isSyndicated() { + return false; + } + + function isCacheable() { + return false; + } + + function isCached() { + return false; + } function linkParameters() { return array( 'filename' => $this->filename ); @@ -51,7 +59,7 @@ class FileDuplicateSearchPage extends QueryPage { /** * Fetch dupes from all connected file repositories. * - * @return Array of File objects + * @return array of File objects */ function getDupes() { return RepoGroup::singleton()->findBySha1( $this->hash ); @@ -59,7 +67,7 @@ class FileDuplicateSearchPage extends QueryPage { /** * - * @param $dupes Array of File objects + * @param array $dupes of File objects */ function showList( $dupes ) { $html = array(); @@ -93,11 +101,11 @@ class FileDuplicateSearchPage extends QueryPage { $this->setHeaders(); $this->outputHeader(); - $this->filename = isset( $par ) ? $par : $this->getRequest()->getText( 'filename' ); + $this->filename = isset( $par ) ? $par : $this->getRequest()->getText( 'filename' ); $this->file = null; $this->hash = ''; $title = Title::newFromText( $this->filename, NS_FILE ); - if( $title && $title->getText() != '' ) { + if ( $title && $title->getText() != '' ) { $this->file = wfFindFile( $title ); } @@ -105,37 +113,46 @@ class FileDuplicateSearchPage extends QueryPage { # Create the input form $out->addHTML( - Xml::openElement( 'form', array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => $wgScript ) ) . - Html::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, $this->msg( 'fileduplicatesearch-legend' )->text() ) . - Xml::inputLabel( $this->msg( 'fileduplicatesearch-filename' )->text(), 'filename', 'filename', 50, $this->filename ) . ' ' . - Xml::submitButton( $this->msg( 'fileduplicatesearch-submit' )->text() ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) + Html::openElement( + 'form', + array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => $wgScript ) + ) . "\n" . + Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . "\n" . + Html::openElement( 'fieldset' ) . "\n" . + Html::element( 'legend', null, $this->msg( 'fileduplicatesearch-legend' )->text() ) . "\n" . + Xml::inputLabel( + $this->msg( 'fileduplicatesearch-filename' )->text(), + 'filename', + 'filename', + 50, + $this->filename + ) . "\n" . + Xml::submitButton( $this->msg( 'fileduplicatesearch-submit' )->text() ) . "\n" . + Html::closeElement( 'fieldset' ) . "\n" . + Html::closeElement( 'form' ) ); - if( $this->file ) { + if ( $this->file ) { $this->hash = $this->file->getSha1(); - } elseif( $this->filename !== '' ) { + } elseif ( $this->filename !== '' ) { $out->wrapWikiMsg( "<p class='mw-fileduplicatesearch-noresults'>\n$1\n</p>", array( 'fileduplicatesearch-noresults', wfEscapeWikiText( $this->filename ) ) ); } - if( $this->hash != '' ) { + if ( $this->hash != '' ) { # Show a thumbnail of the file $img = $this->file; if ( $img ) { $thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) ); - if( $thumb ) { + if ( $thumb ) { $out->addHTML( '<div id="mw-fileduplicatesearch-icon">' . $thumb->toHtml( array( 'desc-link' => false ) ) . '<br />' . $this->msg( 'fileduplicatesearch-info' )->numParams( $img->getWidth(), $img->getHeight() )->params( - $this->getLanguage()->formatSize( $img->getSize() ), - $img->getMimeType() )->parseAsBlock() . + $this->getLanguage()->formatSize( $img->getSize() ), + $img->getMimeType() )->parseAsBlock() . '</div>' ); } } @@ -144,7 +161,7 @@ class FileDuplicateSearchPage extends QueryPage { $numRows = count( $dupes ); # Show a short summary - if( $numRows == 1 ) { + if ( $numRows == 1 ) { $out->wrapWikiMsg( "<p class='mw-fileduplicatesearch-result-1'>\n$1\n</p>", array( 'fileduplicatesearch-result-1', wfEscapeWikiText( $this->filename ) ) @@ -164,14 +181,16 @@ class FileDuplicateSearchPage extends QueryPage { function doBatchLookups( $list ) { $batch = new LinkBatch(); - foreach( $list as $file ) { + /** @var File $file */ + foreach ( $list as $file ) { $batch->addObj( $file->getTitle() ); - if( $file->isLocal() ) { + if ( $file->isLocal() ) { $userName = $file->getUser( 'text' ); $batch->add( NS_USER, $userName ); $batch->add( NS_USER_TALK, $userName ); } } + $batch->execute(); } @@ -207,4 +226,8 @@ class FileDuplicateSearchPage extends QueryPage { return "$plink . . $user . . $time"; } + + protected function getGroupName() { + return 'media'; + } } diff --git a/includes/specials/SpecialFilepath.php b/includes/specials/SpecialFilepath.php index e0866504..e7ced52a 100644 --- a/includes/specials/SpecialFilepath.php +++ b/includes/specials/SpecialFilepath.php @@ -2,6 +2,7 @@ /** * Implements Special:Filepath * + * @section LICENSE * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or @@ -26,64 +27,19 @@ * * @ingroup SpecialPage */ -class SpecialFilepath extends SpecialPage { - +class SpecialFilepath extends RedirectSpecialPage { function __construct() { parent::__construct( 'Filepath' ); + $this->mAllowedRedirectParams = array( 'width', 'height' ); } - function execute( $par ) { - $this->setHeaders(); - $this->outputHeader(); - - $request = $this->getRequest(); - $file = !is_null( $par ) ? $par : $request->getText( 'file' ); - - $title = Title::newFromText( $file, NS_FILE ); - - if ( ! $title instanceof Title || $title->getNamespace() != NS_FILE ) { - $this->showForm( $title ); - } else { - $file = wfFindFile( $title ); - - if ( $file && $file->exists() ) { - // Default behaviour: Use the direct link to the file. - $url = $file->getURL(); - $width = $request->getInt( 'width', -1 ); - $height = $request->getInt( 'height', -1 ); - - // If a width is requested... - if ( $width != -1 ) { - $mto = $file->transform( array( 'width' => $width, 'height' => $height ) ); - // ... and we can - if ( $mto && !$mto->isError() ) { - // ... change the URL to point to a thumbnail. - $url = $mto->getURL(); - } - } - $this->getOutput()->redirect( $url ); - } else { - $this->getOutput()->setStatusCode( 404 ); - $this->showForm( $title ); - } - } + // implement by redirecting through Special:Redirect/file + function getRedirect( $par ) { + $file = $par ?: $this->getRequest()->getText( 'file' ); + return SpecialPage::getSafeTitleFor( 'Redirect', 'file/' . $file ); } - /** - * @param $title Title - */ - function showForm( $title ) { - global $wgScript; - - $this->getOutput()->addHTML( - Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'specialfilepath' ) ) . - Html::openElement( 'fieldset' ) . - Html::element( 'legend', null, $this->msg( 'filepath' )->text() ) . - Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . - Xml::inputLabel( $this->msg( 'filepath-page' )->text(), 'file', 'file', 25, is_object( $title ) ? $title->getText() : '' ) . ' ' . - Xml::submitButton( $this->msg( 'filepath-submit' )->text() ) . "\n" . - Html::closeElement( 'fieldset' ) . - Html::closeElement( 'form' ) - ); + protected function getGroupName() { + return 'media'; } } diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php index 362fc5cf..d7d860de 100644 --- a/includes/specials/SpecialImport.php +++ b/includes/specials/SpecialImport.php @@ -30,12 +30,11 @@ * @ingroup SpecialPage */ class SpecialImport extends SpecialPage { - private $interwiki = false; private $namespace; private $rootpage = ''; private $frompage = ''; - private $logcomment= false; + private $logcomment = false; private $history = true; private $includeTemplates = false; private $pageLinkDepth; @@ -101,20 +100,20 @@ class SpecialImport extends SpecialPage { $this->logcomment = $request->getText( 'log-comment' ); $this->pageLinkDepth = $wgExportMaxLinkDepth == 0 ? 0 : $request->getIntOrNull( 'pagelink-depth' ); - $this->rootpage = $request->getText( 'rootpage' );
+ $this->rootpage = $request->getText( 'rootpage' ); $user = $this->getUser(); if ( !$user->matchEditToken( $request->getVal( 'editToken' ) ) ) { $source = Status::newFatal( 'import-token-mismatch' ); } elseif ( $sourceName == 'upload' ) { $isUpload = true; - if( $user->isAllowed( 'importupload' ) ) { + if ( $user->isAllowed( 'importupload' ) ) { $source = ImportStreamSource::newFromUpload( "xmlimport" ); } else { throw new PermissionsError( 'importupload' ); } } elseif ( $sourceName == "interwiki" ) { - if( !$user->isAllowed( 'import' ) ){ + if ( !$user->isAllowed( 'import' ) ) { throw new PermissionsError( 'import' ); } $this->interwiki = $request->getVal( 'interwiki' ); @@ -136,24 +135,40 @@ class SpecialImport extends SpecialPage { } $out = $this->getOutput(); - if( !$source->isGood() ) { - $out->wrapWikiMsg( "<p class=\"error\">\n$1\n</p>", array( 'importfailed', $source->getWikiText() ) ); + if ( !$source->isGood() ) { + $out->wrapWikiMsg( + "<p class=\"error\">\n$1\n</p>", + array( 'importfailed', $source->getWikiText() ) + ); } else { $importer = new WikiImporter( $source->value ); - if( !is_null( $this->namespace ) ) { + if ( !is_null( $this->namespace ) ) { $importer->setTargetNamespace( $this->namespace ); } - if( !is_null( $this->rootpage ) ) { + if ( !is_null( $this->rootpage ) ) { $statusRootPage = $importer->setTargetRootPage( $this->rootpage ); - if( !$statusRootPage->isGood() ) { - $out->wrapWikiMsg( "<p class=\"error\">\n$1\n</p>", array( 'import-options-wrong', $statusRootPage->getWikiText(), count( $statusRootPage->getErrorsArray() ) ) ); + if ( !$statusRootPage->isGood() ) { + $out->wrapWikiMsg( + "<p class=\"error\">\n$1\n</p>", + array( + 'import-options-wrong', + $statusRootPage->getWikiText(), + count( $statusRootPage->getErrorsArray() ) + ) + ); + return; } } $out->addWikiMsg( "importstart" ); - $reporter = new ImportReporter( $importer, $isUpload, $this->interwiki , $this->logcomment); + $reporter = new ImportReporter( + $importer, + $isUpload, + $this->interwiki, + $this->logcomment + ); $reporter->setContext( $this->getContext() ); $exception = false; @@ -167,10 +182,16 @@ class SpecialImport extends SpecialPage { if ( $exception ) { # No source or XML parse error - $out->wrapWikiMsg( "<p class=\"error\">\n$1\n</p>", array( 'importfailed', $exception->getMessage() ) ); - } elseif( !$result->isGood() ) { + $out->wrapWikiMsg( + "<p class=\"error\">\n$1\n</p>", + array( 'importfailed', $exception->getMessage() ) + ); + } elseif ( !$result->isGood() ) { # Zero revisions - $out->wrapWikiMsg( "<p class=\"error\">\n$1\n</p>", array( 'importfailed', $result->getWikiText() ) ); + $out->wrapWikiMsg( + "<p class=\"error\">\n$1\n</p>", + array( 'importfailed', $result->getWikiText() ) + ); } else { # Success! $out->addWikiMsg( 'importsuccess' ); @@ -182,165 +203,202 @@ class SpecialImport extends SpecialPage { private function showForm() { global $wgImportSources, $wgExportMaxLinkDepth; - $action = $this->getTitle()->getLocalUrl( array( 'action' => 'submit' ) ); + $action = $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) ); $user = $this->getUser(); $out = $this->getOutput(); - if( $user->isAllowed( 'importupload' ) ) { + if ( $user->isAllowed( 'importupload' ) ) { $out->addHTML( - Xml::fieldset( $this->msg( 'import-upload' )->text() ). - Xml::openElement( 'form', array( 'enctype' => 'multipart/form-data', 'method' => 'post', - 'action' => $action, 'id' => 'mw-import-upload-form' ) ) . - $this->msg( 'importtext' )->parseAsBlock() . - Html::hidden( 'action', 'submit' ) . - Html::hidden( 'source', 'upload' ) . - Xml::openElement( 'table', array( 'id' => 'mw-import-table' ) ) . - - "<tr> + Xml::fieldset( $this->msg( 'import-upload' )->text() ) . + Xml::openElement( + 'form', + array( + 'enctype' => 'multipart/form-data', + 'method' => 'post', + 'action' => $action, + 'id' => 'mw-import-upload-form' + ) + ) . + $this->msg( 'importtext' )->parseAsBlock() . + Html::hidden( 'action', 'submit' ) . + Html::hidden( 'source', 'upload' ) . + Xml::openElement( 'table', array( 'id' => 'mw-import-table-upload' ) ) . + "<tr> <td class='mw-label'>" . - Xml::label( $this->msg( 'import-upload-filename' )->text(), 'xmlimport' ) . + Xml::label( $this->msg( 'import-upload-filename' )->text(), 'xmlimport' ) . "</td> <td class='mw-input'>" . - Xml::input( 'xmlimport', 50, '', array( 'type' => 'file' ) ) . ' ' . + Html::input( 'xmlimport', '', 'file', array( 'id' => 'xmlimport' ) ) . ' ' . "</td> </tr> <tr> <td class='mw-label'>" . - Xml::label( $this->msg( 'import-comment' )->text(), 'mw-import-comment' ) . + Xml::label( $this->msg( 'import-comment' )->text(), 'mw-import-comment' ) . "</td> <td class='mw-input'>" . - Xml::input( 'log-comment', 50, '', - array( 'id' => 'mw-import-comment', 'type' => 'text' ) ) . ' ' . + Xml::input( 'log-comment', 50, '', + 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' ) . + 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', 'type' => 'text' ) ) . ' ' . + Xml::input( 'rootpage', 50, $this->rootpage, + array( 'id' => 'mw-interwiki-rootpage-upload', 'type' => 'text' ) ) . ' ' . "</td> </tr> <tr> <td></td> <td class='mw-submit'>" . - Xml::submitButton( $this->msg( 'uploadbtn' )->text() ) . + Xml::submitButton( $this->msg( 'uploadbtn' )->text() ) . "</td> </tr>" . - Xml::closeElement( 'table' ). - Html::hidden( 'editToken', $user->getEditToken() ) . - Xml::closeElement( 'form' ) . - Xml::closeElement( 'fieldset' ) + Xml::closeElement( 'table' ) . + Html::hidden( 'editToken', $user->getEditToken() ) . + Xml::closeElement( 'form' ) . + Xml::closeElement( 'fieldset' ) ); } else { - if( empty( $wgImportSources ) ) { + if ( empty( $wgImportSources ) ) { $out->addWikiMsg( 'importnosources' ); } } - if( $user->isAllowed( 'import' ) && !empty( $wgImportSources ) ) { + if ( $user->isAllowed( 'import' ) && !empty( $wgImportSources ) ) { # Show input field for import depth only if $wgExportMaxLinkDepth > 0 $importDepth = ''; - if( $wgExportMaxLinkDepth > 0 ) { + if ( $wgExportMaxLinkDepth > 0 ) { $importDepth = "<tr> <td class='mw-label'>" . - $this->msg( 'export-pagelinks' )->parse() . - "</td> + $this->msg( 'export-pagelinks' )->parse() . + "</td> <td class='mw-input'>" . - Xml::input( 'pagelink-depth', 3, 0 ) . - "</td> - </tr>"; + Xml::input( 'pagelink-depth', 3, 0 ) . + "</td> + </tr>"; } $out->addHTML( Xml::fieldset( $this->msg( 'importinterwiki' )->text() ) . - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'mw-import-interwiki-form' ) ) . - $this->msg( 'import-interwiki-text' )->parseAsBlock() . - Html::hidden( 'action', 'submit' ) . - Html::hidden( 'source', 'interwiki' ) . - Html::hidden( 'editToken', $user->getEditToken() ) . - Xml::openElement( 'table', array( 'id' => 'mw-import-table' ) ) . - "<tr> + Xml::openElement( + 'form', + array( + 'method' => 'post', + 'action' => $action, + 'id' => 'mw-import-interwiki-form' + ) + ) . + $this->msg( 'import-interwiki-text' )->parseAsBlock() . + Html::hidden( 'action', 'submit' ) . + Html::hidden( 'source', 'interwiki' ) . + Html::hidden( 'editToken', $user->getEditToken() ) . + Xml::openElement( 'table', array( 'id' => 'mw-import-table-interwiki' ) ) . + "<tr> <td class='mw-label'>" . - Xml::label( $this->msg( 'import-interwiki-source' )->text(), 'interwiki' ) . + Xml::label( $this->msg( 'import-interwiki-source' )->text(), 'interwiki' ) . "</td> <td class='mw-input'>" . - Xml::openElement( 'select', array( 'name' => 'interwiki' ) ) + Xml::openElement( + 'select', + array( 'name' => 'interwiki', 'id' => 'interwiki' ) + ) ); - foreach( $wgImportSources as $prefix ) { + + foreach ( $wgImportSources as $prefix ) { $selected = ( $this->interwiki === $prefix ) ? ' selected="selected"' : ''; $out->addHTML( Xml::option( $prefix, $prefix, $selected ) ); } $out->addHTML( - Xml::closeElement( 'select' ) . - Xml::input( 'frompage', 50, $this->frompage ) . + Xml::closeElement( 'select' ) . + Xml::input( 'frompage', 50, $this->frompage, array( 'id' => 'frompage' ) ) . "</td> </tr> <tr> <td> </td> <td class='mw-input'>" . - Xml::checkLabel( $this->msg( 'import-interwiki-history' )->text(), 'interwikiHistory', 'interwikiHistory', $this->history ) . + Xml::checkLabel( + $this->msg( 'import-interwiki-history' )->text(), + 'interwikiHistory', + 'interwikiHistory', + $this->history + ) . "</td> </tr> <tr> <td> </td> <td class='mw-input'>" . - Xml::checkLabel( $this->msg( 'import-interwiki-templates' )->text(), 'interwikiTemplates', 'interwikiTemplates', $this->includeTemplates ) . + Xml::checkLabel( + $this->msg( 'import-interwiki-templates' )->text(), + 'interwikiTemplates', + 'interwikiTemplates', + $this->includeTemplates + ) . "</td> </tr> $importDepth <tr> <td class='mw-label'>" . - Xml::label( $this->msg( 'import-interwiki-namespace' )->text(), 'namespace' ) . + 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', - ) - ) . + 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' ) . + Xml::label( $this->msg( 'import-comment' )->text(), 'mw-interwiki-comment' ) . "</td> <td class='mw-input'>" . - Xml::input( 'log-comment', 50, '', - array( 'id' => 'mw-interwiki-comment', 'type' => 'text' ) ) . ' ' . + Xml::input( 'log-comment', 50, '', + 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' ) . + 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', 'type' => 'text' ) ) . ' ' . + Xml::input( 'rootpage', 50, $this->rootpage, + array( 'id' => 'mw-interwiki-rootpage-interwiki', 'type' => 'text' ) ) . ' ' . "</td> </tr> <tr> <td> </td> <td class='mw-submit'>" . - Xml::submitButton( $this->msg( 'import-interwiki-submit' )->text(), Linker::tooltipAndAccesskeyAttribs( 'import' ) ) . + Xml::submitButton( + $this->msg( 'import-interwiki-submit' )->text(), + Linker::tooltipAndAccesskeyAttribs( 'import' ) + ) . "</td> </tr>" . - Xml::closeElement( 'table' ). - Xml::closeElement( 'form' ) . - Xml::closeElement( 'fieldset' ) + Xml::closeElement( 'table' ) . + Xml::closeElement( 'form' ) . + Xml::closeElement( 'fieldset' ) ); } } + + protected function getGroupName() { + return 'pagetools'; + } } /** @@ -348,14 +406,21 @@ class SpecialImport extends SpecialPage { * @ingroup SpecialPage */ class ImportReporter extends ContextSource { - private $reason=false; + private $reason = false; private $mOriginalLogCallback = null; private $mOriginalPageOutCallback = null; private $mLogItemCount = 0; - function __construct( $importer, $upload, $interwiki , $reason=false ) { + + /** + * @param WikiImporter $importer + * @param $upload + * @param $interwiki + * @param string|bool $reason + */ + function __construct( $importer, $upload, $interwiki, $reason = false ) { $this->mOriginalPageOutCallback = - $importer->setPageOutCallback( array( $this, 'reportPage' ) ); + $importer->setPageOutCallback( array( $this, 'reportPage' ) ); $this->mOriginalLogCallback = $importer->setLogItemCallback( array( $this, 'reportLogItem' ) ); $importer->setNoticeCallback( array( $this, 'reportNotice' ) ); @@ -399,36 +464,37 @@ class ImportReporter extends ContextSource { $this->mPageCount++; - if( $successCount > 0 ) { - $this->getOutput()->addHTML( "<li>" . Linker::linkKnown( $title ) . " " . - $this->msg( 'import-revision-count' )->numParams( $successCount )->escaped() . - "</li>\n" + if ( $successCount > 0 ) { + $this->getOutput()->addHTML( + "<li>" . Linker::linkKnown( $title ) . " " . + $this->msg( 'import-revision-count' )->numParams( $successCount )->escaped() . + "</li>\n" ); $log = new LogPage( 'import' ); - if( $this->mIsUpload ) { + if ( $this->mIsUpload ) { $detail = $this->msg( 'import-logentry-upload-detail' )->numParams( $successCount )->inContentLanguage()->text(); if ( $this->reason ) { - $detail .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $this->reason; + $detail .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $this->reason; } - $log->addEntry( 'upload', $title, $detail ); + $log->addEntry( 'upload', $title, $detail, array(), $this->getUser() ); } else { $interwiki = '[[:' . $this->mInterwiki . ':' . $origTitle->getPrefixedText() . ']]'; $detail = $this->msg( 'import-logentry-interwiki-detail' )->numParams( $successCount )->params( $interwiki )->inContentLanguage()->text(); if ( $this->reason ) { - $detail .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $this->reason; + $detail .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $this->reason; } - $log->addEntry( 'interwiki', $title, $detail ); + $log->addEntry( 'interwiki', $title, $detail, array(), $this->getUser() ); } $comment = $detail; // quick $dbw = wfGetDB( DB_MASTER ); $latest = $title->getLatestRevID(); $nullRevision = Revision::newNullRevision( $dbw, $title->getArticleID(), $comment, true ); - if (!is_null($nullRevision)) { + if ( !is_null( $nullRevision ) ) { $nullRevision->insertOn( $dbw ); $page = WikiPage::factory( $title ); # Update page record @@ -446,8 +512,9 @@ class ImportReporter extends ContextSource { if ( $this->mLogItemCount > 0 ) { $msg = $this->msg( 'imported-log-entries' )->numParams( $this->mLogItemCount )->parse(); $out->addHTML( Xml::tags( 'li', null, $msg ) ); - } elseif( $this->mPageCount == 0 && $this->mLogItemCount == 0 ) { + } elseif ( $this->mPageCount == 0 && $this->mLogItemCount == 0 ) { $out->addHTML( "</ul>\n" ); + return Status::newFatal( 'importnopages' ); } $out->addHTML( "</ul>\n" ); diff --git a/includes/specials/SpecialJavaScriptTest.php b/includes/specials/SpecialJavaScriptTest.php index c217eccb..7069d527 100644 --- a/includes/specials/SpecialJavaScriptTest.php +++ b/includes/specials/SpecialJavaScriptTest.php @@ -40,19 +40,11 @@ class SpecialJavaScriptTest extends SpecialPage { } public function execute( $par ) { - global $wgEnableJavaScriptTest; - $out = $this->getOutput(); $this->setHeaders(); $out->disallowUserJs(); - // Abort early if we're disabled - if ( $wgEnableJavaScriptTest !== true ) { - $out->addWikiMsg( 'javascripttest-disabled' ); - return; - } - $out->addModules( 'mediawiki.special.javaScriptTest' ); // Determine framework @@ -63,24 +55,29 @@ class SpecialJavaScriptTest extends SpecialPage { if ( $par == '' ) { $out->setPageTitle( $this->msg( 'javascripttest' ) ); $summary = $this->wrapSummaryHtml( - $this->msg( 'javascripttest-pagetext-noframework' )->escaped() . $this->getFrameworkListHtml(), + $this->msg( 'javascripttest-pagetext-noframework' )->escaped() . + $this->getFrameworkListHtml(), 'noframework' ); $out->addHtml( $summary ); - - // Matched! Display proper title and initialize the framework } elseif ( isset( self::$frameworks[$framework] ) ) { - $out->setPageTitle( $this->msg( 'javascripttest-title', $this->msg( "javascripttest-$framework-name" )->plain() ) ); - $out->setSubtitle( $this->msg( 'javascripttest-backlink' )->rawParams( Linker::linkKnown( $this->getTitle() ) ) ); + // Matched! Display proper title and initialize the framework + $out->setPageTitle( $this->msg( + 'javascripttest-title', + // Messages: javascripttest-qunit-name + $this->msg( "javascripttest-$framework-name" )->plain() + ) ); + $out->setSubtitle( $this->msg( 'javascripttest-backlink' ) + ->rawParams( Linker::linkKnown( $this->getTitle() ) ) ); $this->{self::$frameworks[$framework]}(); - - // Framework not found, display error } else { + // Framework not found, display error $out->setPageTitle( $this->msg( 'javascripttest' ) ); - $summary = $this->wrapSummaryHtml( '<p class="error">' - . $this->msg( 'javascripttest-pagetext-unknownframework', $par )->escaped() - . '</p>' - . $this->getFrameworkListHtml(), + $summary = $this->wrapSummaryHtml( + '<p class="error">' . + $this->msg( 'javascripttest-pagetext-unknownframework', $par )->escaped() . + '</p>' . + $this->getFrameworkListHtml(), 'unknownframework' ); $out->addHtml( $summary ); @@ -88,40 +85,49 @@ class SpecialJavaScriptTest extends SpecialPage { } /** - * Get a list of frameworks (including introduction paragraph and links to the framework run pages) - * @return String: HTML + * Get a list of frameworks (including introduction paragraph and links + * to the framework run pages) + * + * @return string HTML */ private function getFrameworkListHtml() { $list = '<ul>'; - foreach( self::$frameworks as $framework => $initFn ) { + foreach ( self::$frameworks as $framework => $initFn ) { $list .= Html::rawElement( 'li', array(), - Linker::link( $this->getTitle( $framework ), $this->msg( "javascripttest-$framework-name" )->escaped() ) + Linker::link( + $this->getTitle( $framework ), + // Message: javascripttest-qunit-name + $this->msg( "javascripttest-$framework-name" )->escaped() + ) ); } $list .= '</ul>'; - $msg = $this->msg( 'javascripttest-pagetext-frameworks' )->rawParams( $list )->parseAsBlock(); - return $msg; + return $this->msg( 'javascripttest-pagetext-frameworks' )->rawParams( $list ) + ->parseAsBlock(); } /** * Function to wrap the summary. * It must be given a valid state as a second parameter or an exception will * be thrown. - * @param $html String: The raw HTML. - * @param $state String: State, one of 'noframework', 'unknownframework' or 'frameworkfound' + * @param string $html The raw HTML. + * @param string $state State, one of 'noframework', 'unknownframework' or 'frameworkfound' + * @throws MWException * @return string */ private function wrapSummaryHtml( $html, $state ) { $validStates = array( 'noframework', 'unknownframework', 'frameworkfound' ); - if( !in_array( $state, $validStates ) ) { + + if ( !in_array( $state, $validStates ) ) { throw new MWException( __METHOD__ . ' given an invalid state. Must be one of "' - . join( '", "', $validStates) . '".' + . join( '", "', $validStates ) . '".' ); } + return "<div id=\"mw-javascripttest-summary\" class=\"mw-javascripttest-$state\">$html</div>"; } @@ -162,12 +168,13 @@ HTML; // Used in ./tests/qunit/data/testrunner.js, see also documentation of // $wgJavaScriptTestConfig in DefaultSettings.php - $out->addJsConfigVars( 'QUnitTestSwarmInjectJSPath', $wgJavaScriptTestConfig['qunit']['testswarm-injectjs'] ); + $out->addJsConfigVars( + 'QUnitTestSwarmInjectJSPath', + $wgJavaScriptTestConfig['qunit']['testswarm-injectjs'] + ); } - public function isListed(){ - global $wgEnableJavaScriptTest; - return $wgEnableJavaScriptTest === true; + protected function getGroupName() { + return 'other'; } - } diff --git a/includes/specials/SpecialLinkSearch.php b/includes/specials/SpecialLinkSearch.php index 0810ee77..5b0c56e5 100644 --- a/includes/specials/SpecialLinkSearch.php +++ b/includes/specials/SpecialLinkSearch.php @@ -22,7 +22,6 @@ * @author Brion Vibber */ - /** * Special:LinkSearch to search the external-links table. * @ingroup SpecialPage @@ -43,7 +42,7 @@ class LinkSearchPage extends QueryPage { } function execute( $par ) { - global $wgUrlProtocols, $wgMiserMode; + global $wgUrlProtocols, $wgMiserMode, $wgScript; $this->setHeaders(); $this->outputHeader(); @@ -56,7 +55,7 @@ class LinkSearchPage extends QueryPage { $namespace = $request->getIntorNull( 'namespace', null ); $protocols_list = array(); - foreach( $wgUrlProtocols as $prot ) { + foreach ( $wgUrlProtocols as $prot ) { if ( $prot !== '//' ) { $protocols_list[] = $prot; } @@ -64,16 +63,16 @@ class LinkSearchPage extends QueryPage { $target2 = $target; $protocol = ''; - $pr_sl = strpos($target2, '//' ); - $pr_cl = strpos($target2, ':' ); + $pr_sl = strpos( $target2, '//' ); + $pr_cl = strpos( $target2, ':' ); if ( $pr_sl ) { // For protocols with '//' - $protocol = substr( $target2, 0 , $pr_sl+2 ); - $target2 = substr( $target2, $pr_sl+2 ); + $protocol = substr( $target2, 0, $pr_sl + 2 ); + $target2 = substr( $target2, $pr_sl + 2 ); } elseif ( !$pr_sl && $pr_cl ) { // For protocols without '//' like 'mailto:' - $protocol = substr( $target2, 0 , $pr_cl+1 ); - $target2 = substr( $target2, $pr_cl+1 ); + $protocol = substr( $target2, 0, $pr_cl + 1 ); + $target2 = substr( $target2, $pr_cl + 1 ); } elseif ( $protocol == '' && $target2 != '' ) { // default $protocol = 'http://'; @@ -84,12 +83,26 @@ class LinkSearchPage extends QueryPage { $protocol = ''; } - $out->addWikiMsg( 'linksearch-text', '<nowiki>' . $this->getLanguage()->commaList( $protocols_list ) . '</nowiki>' ); - $s = Xml::openElement( 'form', array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => $GLOBALS['wgScript'] ) ) . - Html::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) . - '<fieldset>' . - Xml::element( 'legend', array(), $this->msg( 'linksearch' )->text() ) . - Xml::inputLabel( $this->msg( 'linksearch-pat' )->text(), 'target', 'target', 50, $target ) . ' '; + $out->addWikiMsg( + 'linksearch-text', + '<nowiki>' . $this->getLanguage()->commaList( $protocols_list ) . '</nowiki>', + count( $protocols_list ) + ); + $s = Html::openElement( + 'form', + array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => $wgScript ) + ) . "\n" . + Html::hidden( 'title', $this->getTitle()->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 + ) . "\n"; + if ( !$wgMiserMode ) { $s .= Html::namespaceSelector( array( @@ -97,25 +110,27 @@ class LinkSearchPage extends QueryPage { 'all' => '', 'label' => $this->msg( 'linksearch-ns' )->text() ), array( - 'name' => 'namespace', - 'id' => 'namespace', + 'name' => 'namespace', + 'id' => 'namespace', 'class' => 'namespaceselector', ) ); } - $s .= Xml::submitButton( $this->msg( 'linksearch-ok' )->text() ) . - '</fieldset>' . - Xml::closeElement( 'form' ); + + $s .= Xml::submitButton( $this->msg( 'linksearch-ok' )->text() ) . "\n" . + Html::closeElement( 'fieldset' ) . "\n" . + Html::closeElement( 'form' ) . "\n"; $out->addHTML( $s ); - if( $target != '' ) { + if ( $target != '' ) { $this->setParams( array( 'query' => $target2, 'namespace' => $namespace, 'protocol' => $protocol ) ); parent::execute( $par ); - if( $this->mMungedQuery === false ) + if ( $this->mMungedQuery === false ) { $out->addWikiMsg( 'linksearch-error' ); + } } } @@ -130,19 +145,23 @@ class LinkSearchPage extends QueryPage { /** * Return an appropriately formatted LIKE query and the clause * + * @param string $query + * @param string $prot * @return array */ static function mungeQuery( $query, $prot ) { $field = 'el_index'; - $rv = LinkFilter::makeLikeArray( $query , $prot ); + $rv = LinkFilter::makeLikeArray( $query, $prot ); if ( $rv === false ) { // LinkFilter doesn't handle wildcard in IP, so we'll have to munge here. - if (preg_match('/^(:?[0-9]{1,3}\.)+\*\s*$|^(:?[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]*\*\s*$/', $query)) { + $pattern = '/^(:?[0-9]{1,3}\.)+\*\s*$|^(:?[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]*\*\s*$/'; + if ( preg_match( $pattern, $query ) ) { $dbr = wfGetDB( DB_SLAVE ); $rv = array( $prot . rtrim( $query, " \t*" ), $dbr->anyString() ); $field = 'el_to'; } } + return array( $rv, $field ); } @@ -150,9 +169,10 @@ class LinkSearchPage extends QueryPage { global $wgMiserMode; $params = array(); $params['target'] = $this->mProt . $this->mQuery; - if( isset( $this->mNs ) && !$wgMiserMode ) { + if ( isset( $this->mNs ) && !$wgMiserMode ) { $params['namespace'] = $this->mNs; } + return $params; } @@ -161,29 +181,41 @@ class LinkSearchPage extends QueryPage { $dbr = wfGetDB( DB_SLAVE ); // strip everything past first wildcard, so that // index-based-only lookup would be done - list( $this->mMungedQuery, $clause ) = self::mungeQuery( - $this->mQuery, $this->mProt ); - if( $this->mMungedQuery === false ) + list( $this->mMungedQuery, $clause ) = self::mungeQuery( $this->mQuery, $this->mProt ); + if ( $this->mMungedQuery === false ) { // Invalid query; return no results return array( 'tables' => 'page', 'fields' => 'page_id', 'conds' => '0=1' ); + } $stripped = LinkFilter::keepOneWildcard( $this->mMungedQuery ); $like = $dbr->buildLike( $stripped ); - $retval = array ( - 'tables' => array ( 'page', 'externallinks' ), - 'fields' => array ( 'namespace' => 'page_namespace', - 'title' => 'page_title', - 'value' => 'el_index', 'url' => 'el_to' ), - 'conds' => array ( 'page_id = el_from', - "$clause $like" ), + $retval = array( + 'tables' => array( 'page', 'externallinks' ), + 'fields' => array( + 'namespace' => 'page_namespace', + 'title' => 'page_title', + 'value' => 'el_index', + 'url' => 'el_to' + ), + 'conds' => array( + 'page_id = el_from', + "$clause $like" + ), 'options' => array( 'USE INDEX' => $clause ) ); + if ( isset( $this->mNs ) && !$wgMiserMode ) { $retval['conds']['page_namespace'] = $this->mNs; } + return $retval; } + /** + * @param Skin $skin + * @param object $result Result row + * @return string + */ function formatResult( $skin, $result ) { $title = Title::makeTitle( $result->namespace, $result->title ); $url = $result->url; @@ -197,8 +229,8 @@ class LinkSearchPage extends QueryPage { * Override to check query validity. */ function doQuery( $offset = false, $limit = false ) { - list( $this->mMungedQuery, ) = LinkSearchPage::mungeQuery( $this->mQuery, $this->mProt ); - if( $this->mMungedQuery === false ) { + list( $this->mMungedQuery, ) = LinkSearchPage::mungeQuery( $this->mQuery, $this->mProt ); + if ( $this->mMungedQuery === false ) { $this->getOutput()->addWikiMsg( 'linksearch-error' ); } else { // For debugging @@ -218,4 +250,8 @@ class LinkSearchPage extends QueryPage { function getOrderFields() { return array(); } + + protected function getGroupName() { + return 'redirects'; + } } diff --git a/includes/specials/SpecialListfiles.php b/includes/specials/SpecialListfiles.php index cc055221..dff1cf70 100644 --- a/includes/specials/SpecialListfiles.php +++ b/includes/specials/SpecialListfiles.php @@ -22,12 +22,11 @@ */ class SpecialListFiles extends IncludableSpecialPage { - - public function __construct(){ + public function __construct() { parent::__construct( 'Listfiles' ); } - public function execute( $par ){ + public function execute( $par ) { $this->setHeaders(); $this->outputHeader(); @@ -37,9 +36,16 @@ class SpecialListFiles extends IncludableSpecialPage { } else { $userName = $this->getRequest()->getText( 'user', $par ); $search = $this->getRequest()->getText( 'ilsearch', '' ); + $showAll = $this->getRequest()->getBool( 'ilshowall', false ); } - $pager = new ImageListPager( $this->getContext(), $userName, $search, $this->including() ); + $pager = new ImageListPager( + $this->getContext(), + $userName, + $search, + $this->including(), + $showAll + ); if ( $this->including() ) { $html = $pager->getBody(); @@ -51,6 +57,10 @@ class SpecialListFiles extends IncludableSpecialPage { } $this->getOutput()->addHTML( $html ); } + + protected function getGroupName() { + return 'media'; + } } /** @@ -58,27 +68,33 @@ class SpecialListFiles extends IncludableSpecialPage { */ class ImageListPager extends TablePager { var $mFieldNames = null; + // Subclasses should override buildQueryConds instead of using $mQueryConds variable. var $mQueryConds = array(); var $mUserName = null; var $mSearch = ''; var $mIncluding = false; + var $mShowAll = false; + var $mTableName = 'image'; - function __construct( IContextSource $context, $userName = null, $search = '', $including = false ) { + function __construct( IContextSource $context, $userName = null, $search = '', + $including = false, $showAll = false + ) { global $wgMiserMode; $this->mIncluding = $including; + $this->mShowAll = $showAll; if ( $userName ) { $nt = Title::newFromText( $userName, NS_USER ); if ( !is_null( $nt ) ) { $this->mUserName = $nt->getText(); - $this->mQueryConds['img_user_text'] = $this->mUserName; } } - if ( $search != '' && !$wgMiserMode ) { + if ( $search !== '' && !$wgMiserMode ) { $this->mSearch = $search; $nt = Title::newFromURL( $this->mSearch ); + if ( $nt ) { $dbr = wfGetDB( DB_SLAVE ); $this->mQueryConds[] = 'LOWER(img_name)' . @@ -101,6 +117,42 @@ class ImageListPager extends TablePager { } /** + * Build the where clause of the query. + * + * Replaces the older mQueryConds member variable. + * @param $table String Either "image" or "oldimage" + * @return array The query conditions. + */ + protected function buildQueryConds( $table ) { + $prefix = $table === 'image' ? 'img' : 'oi'; + $conds = array(); + + if ( !is_null( $this->mUserName ) ) { + $conds[ $prefix . '_user_text' ] = $this->mUserName; + } + + if ( $this->mSearch !== '' ) { + $nt = Title::newFromURL( $this->mSearch ); + if ( $nt ) { + $dbr = wfGetDB( DB_SLAVE ); + $conds[] = 'LOWER(' . $prefix . '_name)' . + $dbr->buildLike( $dbr->anyString(), + strtolower( $nt->getDBkey() ), $dbr->anyString() ); + } + } + + if ( $table === 'oldimage' ) { + // Don't want to deal with revdel. + // Future fixme: Show partial information as appropriate. + // Would have to be careful about filtering by username when username is deleted. + $conds['oi_deleted'] = 0; + } + + // Add mQueryConds in case anyone was subclassing and using the old variable. + return $conds + $this->mQueryConds; + } + + /** * @return Array */ function getFieldNames() { @@ -114,34 +166,94 @@ class ImageListPager extends TablePager { 'img_user_text' => $this->msg( 'listfiles_user' )->text(), 'img_description' => $this->msg( 'listfiles_description' )->text(), ); - if( !$wgMiserMode ) { + if ( !$wgMiserMode && !$this->mShowAll ) { $this->mFieldNames['count'] = $this->msg( 'listfiles_count' )->text(); } + if ( $this->mShowAll ) { + $this->mFieldNames['top'] = $this->msg( 'listfiles-latestversion' )->text(); + } } + return $this->mFieldNames; } function isFieldSortable( $field ) { + global $wgMiserMode; if ( $this->mIncluding ) { return false; } - static $sortable = array( 'img_timestamp', 'img_name' ); - if ( $field == 'img_size' ) { - # No index for both img_size and img_user_text - return !isset( $this->mQueryConds['img_user_text'] ); + $sortable = array( 'img_timestamp', 'img_name', 'img_size' ); + /* For reference, the indicies we can use for sorting are: + * On the image table: img_usertext_timestamp, img_size, img_timestamp + * On oldimage: oi_usertext_timestamp, oi_name_timestamp + * + * In particular that means we cannot sort by timestamp when not filtering + * by user and including old images in the results. Which is sad. + */ + if ( $wgMiserMode && !is_null( $this->mUserName ) ) { + // If we're sorting by user, the index only supports sorting by time. + if ( $field === 'img_timestamp' ) { + return true; + } else { + return false; + } + } elseif ( $wgMiserMode && $this->mShowAll /* && mUserName === null */ ) { + // no oi_timestamp index, so only alphabetical sorting in this case. + if ( $field === 'img_name' ) { + return true; + } else { + return false; + } } + return in_array( $field, $sortable ); } function getQueryInfo() { - $tables = array( 'image' ); + // Hacky Hacky Hacky - I want to get query info + // for two different tables, without reimplementing + // the pager class. + $qi = $this->getQueryInfoReal( $this->mTableName ); + return $qi; + } + + /** + * Actually get the query info. + * + * This is to allow displaying both stuff from image and oldimage table. + * + * This is a bit hacky. + * + * @param $table String Either 'image' or 'oldimage' + * @return array Query info + */ + protected function getQueryInfoReal( $table ) { + $prefix = $table === 'oldimage' ? 'oi' : 'img'; + + $tables = array( $table ); $fields = array_keys( $this->getFieldNames() ); - $fields[] = 'img_user'; - $fields[array_search('thumb', $fields)] = 'img_name AS thumb'; + + if ( $table === 'oldimage' ) { + foreach ( $fields as $id => &$field ) { + if ( substr( $field, 0, 4 ) !== 'img_' ) { + continue; + } + $field = $prefix . substr( $field, 3 ) . ' AS ' . $field; + } + $fields[array_search('top', $fields)] = "'no' AS top"; + } else { + if ( $this->mShowAll ) { + $fields[array_search( 'top', $fields )] = "'yes' AS top"; + } + } + $fields[] = $prefix . '_user AS img_user'; + $fields[array_search( 'thumb', $fields )] = $prefix . '_name AS thumb'; + $options = $join_conds = array(); # Depends on $wgMiserMode - if( isset( $this->mFieldNames['count'] ) ) { + # Will also not happen if mShowAll is true. + if ( isset( $this->mFieldNames['count'] ) ) { $tables[] = 'oldimage'; # Need to rewrite this one @@ -153,7 +265,7 @@ class ImageListPager extends TablePager { unset( $field ); $dbr = wfGetDB( DB_SLAVE ); - if( $dbr->implicitGroupby() ) { + if ( $dbr->implicitGroupby() ) { $options = array( 'GROUP BY' => 'img_name' ); } else { $columnlist = preg_grep( '/^img/', array_keys( $this->getFieldNames() ) ); @@ -161,17 +273,107 @@ class ImageListPager extends TablePager { } $join_conds = array( 'oldimage' => array( 'LEFT JOIN', 'oi_name = img_name' ) ); } + return array( - 'tables' => $tables, - 'fields' => $fields, - 'conds' => $this->mQueryConds, - 'options' => $options, + 'tables' => $tables, + 'fields' => $fields, + 'conds' => $this->buildQueryConds( $table ), + 'options' => $options, 'join_conds' => $join_conds ); } + /** + * Override reallyDoQuery to mix together two queries. + * + * @note $asc is named $descending in IndexPager base class. However + * it is true when the order is ascending, and false when the order + * is descending, so I renamed it to $asc here. + */ + function reallyDoQuery( $offset, $limit, $asc ) { + $prevTableName = $this->mTableName; + $this->mTableName = 'image'; + list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo( $offset, $limit, $asc ); + $imageRes = $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds ); + $this->mTableName = $prevTableName; + + if ( !$this->mShowAll ) { + return $imageRes; + } + + $this->mTableName = 'oldimage'; + + # Hacky... + $oldIndex = $this->mIndexField; + if ( substr( $this->mIndexField, 0, 4 ) !== 'img_' ) { + throw new MWException( "Expected to be sorting on an image table field" ); + } + $this->mIndexField = 'oi_' . substr( $this->mIndexField, 4 ); + + list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo( $offset, $limit, $asc ); + $oldimageRes = $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds ); + + $this->mTableName = $prevTableName; + $this->mIndexField = $oldIndex; + + return $this->combineResult( $imageRes, $oldimageRes, $limit, $asc ); + } + + /** + * Combine results from 2 tables. + * + * Note: This will throw away some results + * + * @param $res1 ResultWrapper + * @param $res2 ResultWrapper + * @param $limit int + * @param $ascending boolean See note about $asc in $this->reallyDoQuery + * @return FakeResultWrapper $res1 and $res2 combined + */ + protected function combineResult( $res1, $res2, $limit, $ascending ) { + $res1->rewind(); + $res2->rewind(); + $topRes1 = $res1->next(); + $topRes2 = $res2->next(); + $resultArray = array(); + for ( $i = 0; $i < $limit && $topRes1 && $topRes2; $i++ ) { + if ( strcmp( $topRes1->{ $this->mIndexField }, $topRes2->{ $this->mIndexField } ) > 0 ) { + if ( !$ascending ) { + $resultArray[] = $topRes1; + $topRes1 = $res1->next(); + } else { + $resultArray[] = $topRes2; + $topRes2 = $res2->next(); + } + } else { + if ( !$ascending ) { + $resultArray[] = $topRes2; + $topRes2 = $res2->next(); + } else { + $resultArray[] = $topRes1; + $topRes1 = $res1->next(); + } + } + } + for ( ; $i < $limit && $topRes1; $i++ ) { + $resultArray[] = $topRes1; + $topRes1 = $res1->next(); + } + for ( ; $i < $limit && $topRes2; $i++ ) { + $resultArray[] = $topRes2; + $topRes2 = $res2->next(); + } + return new FakeResultWrapper( $resultArray ); + } + function getDefaultSort() { - return 'img_timestamp'; + global $wgMiserMode; + if ( $this->mShowAll && $wgMiserMode && is_null( $this->mUserName ) ) { + // Unfortunately no index on oi_timestamp. + return 'img_name'; + } else { + return 'img_timestamp'; + } } function doBatchLookups() { @@ -187,24 +389,37 @@ class ImageListPager extends TablePager { function formatValue( $field, $value ) { switch ( $field ) { case 'thumb': - $file = wfLocalFile( $value ); - $thumb = $file->transform( array( 'width' => 180, 'height' => 360 ) ); - return $thumb->toHtml( array( 'desc-link' => true ) ); + $opt = array( 'time' => $this->mCurrentRow->img_timestamp ); + $file = RepoGroup::singleton()->getLocalRepo()->findFile( $value, $opt ); + // If statement for paranoia + if ( $file ) { + $thumb = $file->transform( array( 'width' => 180, 'height' => 360 ) ); + return $thumb->toHtml( array( 'desc-link' => true ) ); + } else { + return htmlspecialchars( $value ); + } case 'img_timestamp': + // We may want to make this a link to the "old" version when displaying old files return htmlspecialchars( $this->getLanguage()->userTimeAndDate( $value, $this->getUser() ) ); case 'img_name': static $imgfile = null; - if ( $imgfile === null ) $imgfile = $this->msg( 'imgfile' )->text(); + if ( $imgfile === null ) { + $imgfile = $this->msg( 'imgfile' )->text(); + } // Weird files can maybe exist? Bug 22227 $filePage = Title::makeTitleSafe( NS_FILE, $value ); - if( $filePage ) { - $link = Linker::linkKnown( $filePage, htmlspecialchars( $filePage->getText() ) ); + if ( $filePage ) { + $link = Linker::linkKnown( + $filePage, + htmlspecialchars( $filePage->getText() ) + ); $download = Xml::element( 'a', array( 'href' => wfLocalFile( $filePage )->getURL() ), $imgfile ); $download = $this->msg( 'parentheses' )->rawParams( $download )->escaped(); + return "$link $download"; } else { return htmlspecialchars( $value ); @@ -219,39 +434,55 @@ class ImageListPager extends TablePager { } else { $link = htmlspecialchars( $value ); } + return $link; case 'img_size': return htmlspecialchars( $this->getLanguage()->formatSize( $value ) ); case 'img_description': - return Linker::commentBlock( $value ); + return Linker::formatComment( $value ); case 'count': return intval( $value ) + 1; + case 'top': + // Messages: listfiles-latestversion-yes, listfiles-latestversion-no + return $this->msg( 'listfiles-latestversion-' . $value ); } } function getForm() { global $wgScript, $wgMiserMode; $inputForm = array(); - $inputForm['table_pager_limit_label'] = $this->getLimitSelect(); + $inputForm['table_pager_limit_label'] = $this->getLimitSelect( array( 'tabindex' => 1 ) ); if ( !$wgMiserMode ) { - $inputForm['listfiles_search_for'] = Html::input( 'ilsearch', $this->mSearch, 'text', + $inputForm['listfiles_search_for'] = Html::input( + 'ilsearch', + $this->mSearch, + 'text', array( - 'size' => '40', + 'size' => '40', 'maxlength' => '255', - 'id' => 'mw-ilsearch', - ) ); + 'id' => 'mw-ilsearch', + 'tabindex' => 2, + ) + ); } $inputForm['username'] = Html::input( 'user', $this->mUserName, 'text', array( - 'size' => '40', + 'size' => '40', 'maxlength' => '255', - 'id' => 'mw-listfiles-user', + 'id' => 'mw-listfiles-user', + 'tabindex' => 3, + ) ); + + $inputForm['listfiles-show-all'] = Html::input( 'ilshowall', 1, 'checkbox', array( + 'checked' => $this->mShowAll, + 'tabindex' => 4, ) ); return Html::openElement( 'form', - array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listfiles-form' ) ) . + array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listfiles-form' ) + ) . Xml::fieldset( $this->msg( 'listfiles' )->text() ) . Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . - Xml::buildForm( $inputForm, 'table_pager_limit_submit' ) . - $this->getHiddenFields( array( 'limit', 'ilsearch', 'user', 'title' ) ) . + Xml::buildForm( $inputForm, 'table_pager_limit_submit', array( 'tabindex' => 5 ) ) . + $this->getHiddenFields( array( 'limit', 'ilsearch', 'user', 'title', 'ilshowall' ) ) . Html::closeElement( 'fieldset' ) . Html::closeElement( 'form' ) . "\n"; } @@ -276,6 +507,7 @@ class ImageListPager extends TablePager { $query['user'] = $this->mUserName; } } + return $queries; } @@ -284,6 +516,7 @@ class ImageListPager extends TablePager { if ( !isset( $queries['user'] ) && !is_null( $this->mUserName ) ) { $queries['user'] = $this->mUserName; } + return $queries; } diff --git a/includes/specials/SpecialListgrouprights.php b/includes/specials/SpecialListgrouprights.php index 1f95c225..82a4f70f 100644 --- a/includes/specials/SpecialListgrouprights.php +++ b/includes/specials/SpecialListgrouprights.php @@ -29,7 +29,6 @@ * @author Petr Kadlec <mormegil@centrum.cz> */ class SpecialListGroupRights extends SpecialPage { - /** * Constructor */ @@ -51,11 +50,13 @@ class SpecialListGroupRights extends SpecialPage { $out = $this->getOutput(); $out->addModuleStyles( 'mediawiki.special' ); + $out->wrapWikiMsg( "<div class=\"mw-listgrouprights-key\">\n$1\n</div>", 'listgrouprights-key' ); + $out->addHTML( Xml::openElement( 'table', array( 'class' => 'wikitable mw-listgrouprights-table' ) ) . '<tr>' . - Xml::element( 'th', null, $this->msg( 'listgrouprights-group' )->text() ) . - Xml::element( 'th', null, $this->msg( 'listgrouprights-rights' )->text() ) . + Xml::element( 'th', null, $this->msg( 'listgrouprights-group' )->text() ) . + Xml::element( 'th', null, $this->msg( 'listgrouprights-rights' )->text() ) . '</tr>' ); @@ -85,7 +86,7 @@ class SpecialListGroupRights extends SpecialPage { $msg->text() : MWNamespace::getCanonicalName( NS_PROJECT ) . ':' . $groupname; - if( $group == '*' ) { + if ( $group == '*' ) { // Do not make a link for the generic * group $grouppage = htmlspecialchars( $groupnameLocalized ); } else { @@ -124,34 +125,31 @@ class SpecialListGroupRights extends SpecialPage { " <td>$grouppage$grouplink</td> <td>" . - $this->formatPermissions( $permissions, $revoke, $addgroups, $removegroups, - $addgroupsSelf, $removegroupsSelf ) . + $this->formatPermissions( $permissions, $revoke, $addgroups, $removegroups, + $addgroupsSelf, $removegroupsSelf ) . '</td> ' ) ); } - $out->addHTML( - Xml::closeElement( 'table' ) . "\n<br /><hr />\n" - ); - $out->wrapWikiMsg( "<div class=\"mw-listgrouprights-key\">\n$1\n</div>", 'listgrouprights-key' ); + $out->addHTML( Xml::closeElement( 'table' ) ); } /** * Create a user-readable list of permissions from the given array. * - * @param $permissions Array of permission => bool (from $wgGroupPermissions items) - * @param $revoke Array of permission => bool (from $wgRevokePermissions items) - * @param $add Array of groups this group is allowed to add or true - * @param $remove Array of groups this group is allowed to remove or true - * @param $addSelf Array of groups this group is allowed to add to self or true - * @param $removeSelf Array of group this group is allowed to remove from self or true + * @param array $permissions of permission => bool (from $wgGroupPermissions items) + * @param array $revoke of permission => bool (from $wgRevokePermissions items) + * @param array $add of groups this group is allowed to add or true + * @param array $remove of groups this group is allowed to remove or true + * @param array $addSelf of groups this group is allowed to add to self or true + * @param array $removeSelf of group this group is allowed to remove from self or true * @return string List of all granted permissions, separated by comma separator */ - private function formatPermissions( $permissions, $revoke, $add, $remove, $addSelf, $removeSelf ) { + private function formatPermissions( $permissions, $revoke, $add, $remove, $addSelf, $removeSelf ) { $r = array(); - foreach( $permissions as $permission => $granted ) { + foreach ( $permissions as $permission => $granted ) { //show as granted only if it isn't revoked to prevent duplicate display of permissions - if( $granted && ( !isset( $revoke[$permission] ) || !$revoke[$permission] ) ) { + if ( $granted && ( !isset( $revoke[$permission] ) || !$revoke[$permission] ) ) { $description = $this->msg( 'listgrouprights-right-display', User::getRightDescription( $permission ), '<span class="mw-listgrouprights-right-name">' . $permission . '</span>' @@ -159,8 +157,8 @@ class SpecialListGroupRights extends SpecialPage { $r[] = $description; } } - foreach( $revoke as $permission => $revoked ) { - if( $revoked ) { + foreach ( $revoke as $permission => $revoked ) { + if ( $revoked ) { $description = $this->msg( 'listgrouprights-right-revoked', User::getRightDescription( $permission ), '<span class="mw-listgrouprights-right-name">' . $permission . '</span>' @@ -168,48 +166,59 @@ class SpecialListGroupRights extends SpecialPage { $r[] = $description; } } + sort( $r ); + $lang = $this->getLanguage(); - if( $add === true ){ + + if ( $add === true ) { $r[] = $this->msg( 'listgrouprights-addgroup-all' )->escaped(); - } elseif( is_array( $add ) && count( $add ) ) { + } elseif ( is_array( $add ) && count( $add ) ) { $add = array_values( array_unique( $add ) ); $r[] = $this->msg( 'listgrouprights-addgroup', $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $add ) ), count( $add ) )->parse(); } - if( $remove === true ){ + + if ( $remove === true ) { $r[] = $this->msg( 'listgrouprights-removegroup-all' )->escaped(); - } elseif( is_array( $remove ) && count( $remove ) ) { + } elseif ( is_array( $remove ) && count( $remove ) ) { $remove = array_values( array_unique( $remove ) ); $r[] = $this->msg( 'listgrouprights-removegroup', $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $remove ) ), count( $remove ) )->parse(); } - if( $addSelf === true ){ + + if ( $addSelf === true ) { $r[] = $this->msg( 'listgrouprights-addgroup-self-all' )->escaped(); - } elseif( is_array( $addSelf ) && count( $addSelf ) ) { + } elseif ( is_array( $addSelf ) && count( $addSelf ) ) { $addSelf = array_values( array_unique( $addSelf ) ); $r[] = $this->msg( 'listgrouprights-addgroup-self', $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $addSelf ) ), count( $addSelf ) )->parse(); } - if( $removeSelf === true ){ + + if ( $removeSelf === true ) { $r[] = $this->msg( 'listgrouprights-removegroup-self-all' )->parse(); - } elseif( is_array( $removeSelf ) && count( $removeSelf ) ) { + } elseif ( is_array( $removeSelf ) && count( $removeSelf ) ) { $removeSelf = array_values( array_unique( $removeSelf ) ); $r[] = $this->msg( 'listgrouprights-removegroup-self', $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $removeSelf ) ), count( $removeSelf ) )->parse(); } - if( empty( $r ) ) { + + if ( empty( $r ) ) { return ''; } else { return '<ul><li>' . implode( "</li>\n<li>", $r ) . '</li></ul>'; } } + + protected function getGroupName() { + return 'users'; + } } diff --git a/includes/specials/SpecialListredirects.php b/includes/specials/SpecialListredirects.php index fe338a08..2c8792ff 100644 --- a/includes/specials/SpecialListredirects.php +++ b/includes/specials/SpecialListredirects.php @@ -29,29 +29,36 @@ * @ingroup SpecialPage */ class ListredirectsPage extends QueryPage { - function __construct( $name = 'Listredirects' ) { parent::__construct( $name ); } - function isExpensive() { return true; } - function isSyndicated() { return false; } - function sortDescending() { return false; } + function isExpensive() { + return true; + } + + function isSyndicated() { + return false; + } + + function sortDescending() { + return false; + } function getQueryInfo() { return array( 'tables' => array( 'p1' => 'page', 'redirect', 'p2' => 'page' ), 'fields' => array( 'namespace' => 'p1.page_namespace', - 'title' => 'p1.page_title', - 'value' => 'p1.page_title', - 'rd_namespace', - 'rd_title', - 'rd_fragment', - 'rd_interwiki', - 'redirid' => 'p2.page_id' ), + 'title' => 'p1.page_title', + 'value' => 'p1.page_title', + 'rd_namespace', + 'rd_title', + 'rd_fragment', + 'rd_interwiki', + 'redirid' => 'p2.page_id' ), 'conds' => array( 'p1.page_is_redirect' => 1 ), 'join_conds' => array( 'redirect' => array( - 'LEFT JOIN', 'rd_from=p1.page_id' ), + 'LEFT JOIN', 'rd_from=p1.page_id' ), 'p2' => array( 'LEFT JOIN', array( 'p2.page_namespace=rd_namespace', 'p2.page_title=rd_title' ) ) ) @@ -59,25 +66,27 @@ class ListredirectsPage extends QueryPage { } function getOrderFields() { - return array ( 'p1.page_namespace', 'p1.page_title' ); + return array( 'p1.page_namespace', 'p1.page_title' ); } /** * Cache page existence for performance * - * @param $db DatabaseBase - * @param $res ResultWrapper + * @param DatabaseBase $db + * @param ResultWrapper $res */ function preprocessResults( $db, $res ) { $batch = new LinkBatch; + foreach ( $res as $row ) { $batch->add( $row->namespace, $row->title ); $batch->addObj( $this->getRedirectTarget( $row ) ); } + $batch->execute(); // Back to start for display - if ( $db->numRows( $res ) > 0 ) { + if ( $res->numRows() > 0 ) { // If there are no rows we get an error seeking. $db->dataSeek( $res, 0 ); } @@ -92,10 +101,16 @@ class ListredirectsPage extends QueryPage { } else { $title = Title::makeTitle( $row->namespace, $row->title ); $article = WikiPage::factory( $title ); + return $article->getRedirectTarget(); } } + /** + * @param Skin $skin + * @param object $result Result row + * @return string + */ function formatResult( $skin, $result ) { # Make a link to the redirect itself $rd_title = Title::makeTitle( $result->namespace, $result->title ); @@ -108,14 +123,19 @@ class ListredirectsPage extends QueryPage { # Find out where the redirect leads $target = $this->getRedirectTarget( $result ); - if( $target ) { + if ( $target ) { # Make a link to the destination page $lang = $this->getLanguage(); $arr = $lang->getArrow() . $lang->getDirMark(); $targetLink = Linker::link( $target ); + return "$rd_link $arr $targetLink"; } else { return "<del>$rd_link</del>"; } } + + protected function getGroupName() { + return 'pages'; + } } diff --git a/includes/specials/SpecialListusers.php b/includes/specials/SpecialListusers.php index 1089fbbe..8cd9173c 100644 --- a/includes/specials/SpecialListusers.php +++ b/includes/specials/SpecialListusers.php @@ -36,7 +36,9 @@ class UsersPager extends AlphabeticPager { /** * @param $context IContextSource - * @param $par null|array + * @param array $par (Default null) + * @param $including boolean Whether this page is being transcluded in + * another page */ function __construct( IContextSource $context = null, $par = null, $including = null ) { if ( $context ) { @@ -47,7 +49,10 @@ class UsersPager extends AlphabeticPager { $par = ( $par !== null ) ? $par : ''; $parms = explode( '/', $par ); $symsForAll = array( '*', 'user' ); - if ( $parms[0] != '' && ( in_array( $par, User::getAllGroups() ) || in_array( $par, $symsForAll ) ) ) { + + if ( $parms[0] != '' && + ( in_array( $par, User::getAllGroups() ) || in_array( $par, $symsForAll ) ) + ) { $this->requestedGroup = $par; $un = $request->getText( 'username' ); } elseif ( count( $parms ) == 2 ) { @@ -57,20 +62,25 @@ class UsersPager extends AlphabeticPager { $this->requestedGroup = $request->getVal( 'group' ); $un = ( $par != '' ) ? $par : $request->getText( 'username' ); } + if ( in_array( $this->requestedGroup, $symsForAll ) ) { $this->requestedGroup = ''; } $this->editsOnly = $request->getBool( 'editsOnly' ); $this->creationSort = $request->getBool( 'creationSort' ); $this->including = $including; + $this->mDefaultDirection = $request->getBool( 'desc' ); $this->requestedUser = ''; + if ( $un != '' ) { $username = Title::makeTitleSafe( NS_USER, $un ); - if( ! is_null( $username ) ) { + + if ( !is_null( $username ) ) { $this->requestedUser = $username->getText(); } } + parent::__construct(); } @@ -87,34 +97,35 @@ class UsersPager extends AlphabeticPager { function getQueryInfo() { $dbr = wfGetDB( DB_SLAVE ); $conds = array(); + // Don't show hidden names - if( !$this->getUser()->isAllowed( 'hideuser' ) ) { - $conds[] = 'ipb_deleted IS NULL'; + if ( !$this->getUser()->isAllowed( 'hideuser' ) ) { + $conds[] = 'ipb_deleted IS NULL OR ipb_deleted = 0'; } $options = array(); - if( $this->requestedGroup != '' ) { + if ( $this->requestedGroup != '' ) { $conds['ug_group'] = $this->requestedGroup; - } else { - //$options['USE INDEX'] = $this->creationSort ? 'PRIMARY' : 'user_name'; } - if( $this->requestedUser != '' ) { + + if ( $this->requestedUser != '' ) { # Sorted either by account creation or name - if( $this->creationSort ) { + if ( $this->creationSort ) { $conds[] = 'user_id >= ' . intval( User::idFromName( $this->requestedUser ) ); } else { $conds[] = 'user_name >= ' . $dbr->addQuotes( $this->requestedUser ); } } - if( $this->editsOnly ) { + + if ( $this->editsOnly ) { $conds[] = 'user_editcount > 0'; } $options['GROUP BY'] = $this->creationSort ? 'user_id' : 'user_name'; $query = array( - 'tables' => array( 'user', 'user_groups', 'ipblocks'), + 'tables' => array( 'user', 'user_groups', 'ipblocks' ), 'fields' => array( 'user_name' => $this->creationSort ? 'MAX(user_name)' : 'user_name', 'user_id' => $this->creationSort ? 'user_id' : 'MAX(user_id)', @@ -127,16 +138,18 @@ class UsersPager extends AlphabeticPager { 'options' => $options, 'join_conds' => array( 'user_groups' => array( 'LEFT JOIN', 'user_id=ug_user' ), - 'ipblocks' => array( 'LEFT JOIN', array( - 'user_id=ipb_user', - 'ipb_deleted' => 1, - 'ipb_auto' => 0 - )), + 'ipblocks' => array( + 'LEFT JOIN', array( + 'user_id=ipb_user', + 'ipb_auto' => 0 + ) + ), ), 'conds' => $conds ); wfRunHooks( 'SpecialListusersQueryInfo', array( $this, &$query ) ); + return $query; } @@ -152,42 +165,56 @@ class UsersPager extends AlphabeticPager { $userName = $row->user_name; $ulinks = Linker::userLink( $row->user_id, $userName ); - $ulinks .= Linker::userToolLinks( $row->user_id, $userName ); + $ulinks .= Linker::userToolLinksRedContribs( + $row->user_id, + $userName, + (int)$row->edits + ); $lang = $this->getLanguage(); $groups = ''; $groups_list = self::getGroups( $row->user_id ); - if( !$this->including && count( $groups_list ) > 0 ) { + + if ( !$this->including && count( $groups_list ) > 0 ) { $list = array(); - foreach( $groups_list as $group ) + foreach ( $groups_list as $group ) { $list[] = self::buildGroupLink( $group, $userName ); + } $groups = $lang->commaList( $list ); } $item = $lang->specialList( $ulinks, $groups ); - if( $row->ipb_deleted ) { + + if ( $row->ipb_deleted ) { $item = "<span class=\"deleted\">$item</span>"; } $edits = ''; global $wgEdititis; if ( !$this->including && $wgEdititis ) { - $edits = ' [' . $this->msg( 'usereditcount' )->numParams( $row->edits )->escaped() . ']'; + // @fixme i18n issue: Hardcoded square brackets. + $edits = ' [' . + $this->msg( 'usereditcount' )->numParams( $row->edits )->escaped() . + ']'; } $created = ''; - # Some rows may be NULL - if( !$this->including && $row->creation ) { + # Some rows may be null + if ( !$this->including && $row->creation ) { $user = $this->getUser(); $d = $lang->userDate( $row->creation, $user ); $t = $lang->userTime( $row->creation, $user ); $created = $this->msg( 'usercreated', $d, $t, $row->user_name )->escaped(); $created = ' ' . $this->msg( 'parentheses' )->rawParams( $created )->escaped(); } + $blocked = !is_null( $row->ipb_deleted ) ? + ' ' . $this->msg( 'listusers-blocked', $userName )->escaped() : + ''; wfRunHooks( 'SpecialListusersFormatRow', array( &$item, $row ) ); - return Html::rawElement( 'li', array(), "{$item}{$edits}{$created}" ); + + return Html::rawElement( 'li', array(), "{$item}{$edits}{$created}{$blocked}" ); } function doBatchLookups() { @@ -204,30 +231,60 @@ class UsersPager extends AlphabeticPager { /** * @return string */ - function getPageHeader( ) { + function getPageHeader() { global $wgScript; list( $self ) = explode( '/', $this->getTitle()->getPrefixedDBkey() ); # Form tag - $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listusers-form' ) ) . + $out = Xml::openElement( + 'form', + array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listusers-form' ) + ) . Xml::fieldset( $this->msg( 'listusers' )->text() ) . Html::hidden( 'title', $self ); # Username field $out .= Xml::label( $this->msg( 'listusersfrom' )->text(), 'offset' ) . ' ' . - Xml::input( 'username', 20, $this->requestedUser, array( 'id' => 'offset' ) ) . ' '; + Html::input( + 'username', + $this->requestedUser, + 'text', + array( + 'id' => 'offset', + 'size' => 20, + 'autofocus' => $this->requestedUser === '' + ) + ) . ' '; # Group drop-down list $out .= Xml::label( $this->msg( 'group' )->text(), 'group' ) . ' ' . - Xml::openElement('select', array( 'name' => 'group', 'id' => 'group' ) ) . + Xml::openElement( 'select', array( 'name' => 'group', 'id' => 'group' ) ) . Xml::option( $this->msg( 'group-all' )->text(), '' ); - foreach( $this->getAllGroups() as $group => $groupText ) + foreach ( $this->getAllGroups() as $group => $groupText ) { $out .= Xml::option( $groupText, $group, $group == $this->requestedGroup ); + } $out .= Xml::closeElement( 'select' ) . '<br />'; - $out .= Xml::checkLabel( $this->msg( 'listusers-editsonly' )->text(), 'editsOnly', 'editsOnly', $this->editsOnly ); + $out .= Xml::checkLabel( + $this->msg( 'listusers-editsonly' )->text(), + 'editsOnly', + 'editsOnly', + $this->editsOnly + ); $out .= ' '; - $out .= Xml::checkLabel( $this->msg( 'listusers-creationsort' )->text(), 'creationSort', 'creationSort', $this->creationSort ); + $out .= Xml::checkLabel( + $this->msg( 'listusers-creationsort' )->text(), + 'creationSort', + 'creationSort', + $this->creationSort + ); + $out .= ' '; + $out .= Xml::checkLabel( + $this->msg( 'listusers-desc' )->text(), + 'desc', + 'desc', + $this->mDefaultDirection + ); $out .= '<br />'; wfRunHooks( 'SpecialListusersHeaderForm', array( $this, &$out ) ); @@ -248,10 +305,11 @@ class UsersPager extends AlphabeticPager { */ function getAllGroups() { $result = array(); - foreach( User::getAllGroups() as $group ) { + foreach ( User::getAllGroups() as $group ) { $result[$group] = User::getGroupName( $group ); } asort( $result ); + return $result; } @@ -261,13 +319,14 @@ class UsersPager extends AlphabeticPager { */ function getDefaultQuery() { $query = parent::getDefaultQuery(); - if( $this->requestedGroup != '' ) { + if ( $this->requestedGroup != '' ) { $query['group'] = $this->requestedGroup; } - if( $this->requestedUser != '' ) { + if ( $this->requestedUser != '' ) { $query['username'] = $this->requestedUser; } wfRunHooks( 'SpecialListusersDefaultQuery', array( $this, &$query ) ); + return $query; } @@ -280,38 +339,40 @@ class UsersPager extends AlphabeticPager { protected static function getGroups( $uid ) { $user = User::newFromId( $uid ); $groups = array_diff( $user->getEffectiveGroups(), User::getImplicitGroups() ); + return $groups; } /** * Format a link to a group description page * - * @param $group String: group name - * @param $username String Username + * @param string $group group name + * @param string $username Username * @return string */ protected static function buildGroupLink( $group, $username ) { - return User::makeGroupLinkHtml( $group, htmlspecialchars( User::getGroupMember( $group, $username ) ) ); + return User::makeGroupLinkHtml( + $group, + htmlspecialchars( User::getGroupMember( $group, $username ) ) + ); } } /** * @ingroup SpecialPage */ -class SpecialListUsers extends SpecialPage { - +class SpecialListUsers extends IncludableSpecialPage { /** * Constructor */ public function __construct() { parent::__construct( 'Listusers' ); - $this->mIncludable = true; } /** * Show the special page * - * @param $par string (optional) A group to list users from + * @param string $par (optional) A group to list users from */ public function execute( $par ) { $this->setHeaders(); @@ -327,7 +388,7 @@ class SpecialListUsers extends SpecialPage { $s = $up->getPageHeader(); } - if( $usersbody ) { + if ( $usersbody ) { $s .= $up->getNavigationBar(); $s .= Html::rawElement( 'ul', array(), $usersbody ); $s .= $up->getNavigationBar(); @@ -337,4 +398,8 @@ class SpecialListUsers extends SpecialPage { $this->getOutput()->addHTML( $s ); } + + protected function getGroupName() { + return 'users'; + } } diff --git a/includes/specials/SpecialLockdb.php b/includes/specials/SpecialLockdb.php index d71ac6e1..95ef9510 100644 --- a/includes/specials/SpecialLockdb.php +++ b/includes/specials/SpecialLockdb.php @@ -102,4 +102,8 @@ class SpecialLockdb extends FormSpecialPage { $out->addSubtitle( $this->msg( 'lockdbsuccesssub' ) ); $out->addWikiMsg( 'lockdbsuccesstext' ); } + + protected function getGroupName() { + return 'wiki'; + } } diff --git a/includes/specials/SpecialLog.php b/includes/specials/SpecialLog.php index 7800e566..2ffdd89d 100644 --- a/includes/specials/SpecialLog.php +++ b/includes/specials/SpecialLog.php @@ -29,7 +29,6 @@ * @ingroup SpecialPage */ class SpecialLog extends SpecialPage { - /** * List log type for which the target is a user * Thus if the given target is in NS_MAIN we can alter it to be an NS_USER @@ -75,14 +74,14 @@ class SpecialLog extends SpecialPage { $opts->setValue( 'month', '' ); } - // Reset the log type to default (nothing) if it's invalid or if the - // user does not possess the right to view it + // If the user doesn't have the right permission to view the specific + // log type, throw a PermissionsError + // If the log type is invalid, just show all public logs $type = $opts->getValue( 'type' ); - if ( !LogPage::isLogType( $type ) - || ( isset( $wgLogRestrictions[$type] ) - && !$this->getUser()->isAllowed( $wgLogRestrictions[$type] ) ) - ) { + if ( !LogPage::isLogType( $type ) ) { $opts->setValue( 'type', '' ); + } elseif ( isset( $wgLogRestrictions[$type] ) && !$this->getUser()->isAllowed( $wgLogRestrictions[$type] ) ) { + throw new PermissionsError( $wgLogRestrictions[$type] ); } # Handle type-specific inputs @@ -99,10 +98,10 @@ class SpecialLog extends SpecialPage { # Some log types are only for a 'User:' title but we might have been given # only the username instead of the full title 'User:username'. This part try # to lookup for a user by that name and eventually fix user input. See bug 1697. - if( in_array( $opts->getValue( 'type' ), $this->typeOnUser ) ) { + if ( in_array( $opts->getValue( 'type' ), $this->typeOnUser ) ) { # ok we have a type of log which expect a user title. $target = Title::newFromText( $opts->getValue( 'page' ) ); - if( $target && $target->getNamespace() === NS_MAIN ) { + if ( $target && $target->getNamespace() === NS_MAIN ) { # User forgot to add 'User:', we are adding it for him $opts->setValue( 'page', Title::makeTitleSafe( NS_USER, $opts->getValue( 'page' ) ) @@ -117,9 +116,11 @@ class SpecialLog extends SpecialPage { global $wgLogTypes; # Get parameters - $parms = explode( '/', ($par = ( $par !== null ) ? $par : '' ) ); + $parms = explode( '/', ( $par = ( $par !== null ) ? $par : '' ) ); $symsForAll = array( '*', 'all' ); - if ( $parms[0] != '' && ( in_array( $par, $wgLogTypes ) || in_array( $par, $symsForAll ) ) ) { + if ( $parms[0] != '' && + ( in_array( $par, $wgLogTypes ) || in_array( $par, $symsForAll ) ) + ) { $opts->setValue( 'type', $par ); } elseif ( count( $parms ) == 2 ) { $opts->setValue( 'type', $parms[0] ); @@ -131,10 +132,22 @@ class SpecialLog extends SpecialPage { private function show( FormOptions $opts, array $extraConds ) { # Create a LogPager item to get the results and a LogEventsList item to format them... - $loglist = new LogEventsList( $this->getContext(), null, LogEventsList::USE_REVDEL_CHECKBOXES ); - $pager = new LogPager( $loglist, $opts->getValue( 'type' ), $opts->getValue( 'user' ), - $opts->getValue( 'page' ), $opts->getValue( 'pattern' ), $extraConds, $opts->getValue( 'year' ), - $opts->getValue( 'month' ), $opts->getValue( 'tagfilter' ) ); + $loglist = new LogEventsList( + $this->getContext(), + null, + LogEventsList::USE_REVDEL_CHECKBOXES + ); + $pager = new LogPager( + $loglist, + $opts->getValue( 'type' ), + $opts->getValue( 'user' ), + $opts->getValue( 'page' ), + $opts->getValue( 'pattern' ), + $extraConds, + $opts->getValue( 'year' ), + $opts->getValue( 'month' ), + $opts->getValue( 'tagfilter' ) + ); $this->addHeader( $opts->getValue( 'type' ) ); @@ -144,16 +157,28 @@ class SpecialLog extends SpecialPage { } # Show form options - $loglist->showOptions( $pager->getType(), $opts->getValue( 'user' ), $pager->getPage(), $pager->getPattern(), - $pager->getYear(), $pager->getMonth(), $pager->getFilterParams(), $opts->getValue( 'tagfilter' ) ); + $loglist->showOptions( + $pager->getType(), + $opts->getValue( 'user' ), + $pager->getPage(), + $pager->getPattern(), + $pager->getYear(), + $pager->getMonth(), + $pager->getFilterParams(), + $opts->getValue( 'tagfilter' ) + ); # Insert list $logBody = $pager->getBody(); if ( $logBody ) { $this->getOutput()->addHTML( $pager->getNavigationBar() . - $this->getRevisionButton( $loglist->beginLogEventsList() . $logBody . $loglist->endLogEventsList() ) . - $pager->getNavigationBar() + $this->getRevisionButton( + $loglist->beginLogEventsList() . + $logBody . + $loglist->endLogEventsList() + ) . + $pager->getNavigationBar() ); } else { $this->getOutput()->addWikiMsg( 'logempty' ); @@ -161,19 +186,27 @@ class SpecialLog extends SpecialPage { } private function getRevisionButton( $formcontents ) { - # If the user doesn't have the ability to delete log entries, don't bother showing him/her the button. + # If the user doesn't have the ability to delete log entries, + # don't bother showing them the button. if ( !$this->getUser()->isAllowedAll( 'deletedhistory', 'deletelogentry' ) ) { return $formcontents; } # Show button to hide log entries global $wgScript; - $s = Html::openElement( 'form', array( 'action' => $wgScript, 'id' => 'mw-log-deleterevision-submit' ) ) . "\n"; + $s = Html::openElement( + 'form', + array( 'action' => $wgScript, 'id' => 'mw-log-deleterevision-submit' ) + ) . "\n"; $s .= Html::hidden( 'title', SpecialPage::getTitleFor( 'Revisiondelete' ) ) . "\n"; $s .= Html::hidden( 'target', SpecialPage::getTitleFor( 'Log' ) ) . "\n"; $s .= Html::hidden( 'type', 'logging' ) . "\n"; - $button = Html::element( 'button', - array( 'type' => 'submit', 'class' => "deleterevision-log-submit mw-log-deleterevision-button" ), + $button = Html::element( + 'button', + array( + 'type' => 'submit', + 'class' => "deleterevision-log-submit mw-log-deleterevision-button" + ), $this->msg( 'showhideselectedlogentries' )->text() ) . "\n"; $s .= $button . $formcontents . $button; @@ -182,7 +215,6 @@ class SpecialLog extends SpecialPage { return $s; } - /** * Set page title and show header for this log type * @param $type string @@ -194,4 +226,7 @@ class SpecialLog extends SpecialPage { $this->getOutput()->addHTML( $page->getDescription()->parseAsBlock() ); } + protected function getGroupName() { + return 'changes'; + } } diff --git a/includes/specials/SpecialLonelypages.php b/includes/specials/SpecialLonelypages.php index 763bbdb1..7c7771d7 100644 --- a/includes/specials/SpecialLonelypages.php +++ b/includes/specials/SpecialLonelypages.php @@ -28,7 +28,6 @@ * @ingroup SpecialPage */ class LonelyPagesPage extends PageQueryPage { - function __construct( $name = 'Lonelypages' ) { parent::__construct( $name ); } @@ -44,38 +43,56 @@ class LonelyPagesPage extends PageQueryPage { function isExpensive() { return true; } - function isSyndicated() { return false; } + + function isSyndicated() { + return false; + } function getQueryInfo() { - return array ( - 'tables' => array ( 'page', 'pagelinks', - 'templatelinks' ), - 'fields' => array ( 'namespace' => 'page_namespace', - 'title' => 'page_title', - 'value' => 'page_title' ), - 'conds' => array ( 'pl_namespace IS NULL', - 'page_namespace' => MWNamespace::getContentNamespaces(), - 'page_is_redirect' => 0, - 'tl_namespace IS NULL' ), - 'join_conds' => array ( - 'pagelinks' => array ( - 'LEFT JOIN', array ( + return array( + 'tables' => array( + 'page', 'pagelinks', + 'templatelinks' + ), + 'fields' => array( + 'namespace' => 'page_namespace', + 'title' => 'page_title', + 'value' => 'page_title' + ), + 'conds' => array( + 'pl_namespace IS NULL', + 'page_namespace' => MWNamespace::getContentNamespaces(), + 'page_is_redirect' => 0, + 'tl_namespace IS NULL' + ), + 'join_conds' => array( + 'pagelinks' => array( + 'LEFT JOIN', array( 'pl_namespace = page_namespace', - 'pl_title = page_title' ) ), - 'templatelinks' => array ( - 'LEFT JOIN', array ( + 'pl_title = page_title' + ) + ), + 'templatelinks' => array( + 'LEFT JOIN', array( 'tl_namespace = page_namespace', - 'tl_title = page_title' ) ) ) + 'tl_title = page_title' + ) + ) + ) ); } function getOrderFields() { // For some crazy reason ordering by a constant // causes a filesort in MySQL 5 - if( count( MWNamespace::getContentNamespaces() ) > 1 ) { + if ( count( MWNamespace::getContentNamespaces() ) > 1 ) { return array( 'page_namespace', 'page_title' ); } else { return array( 'page_title' ); } } + + protected function getGroupName() { + return 'maintenance'; + } } diff --git a/includes/specials/SpecialLongpages.php b/includes/specials/SpecialLongpages.php index dd60e37d..d90d2718 100644 --- a/includes/specials/SpecialLongpages.php +++ b/includes/specials/SpecialLongpages.php @@ -26,7 +26,6 @@ * @ingroup SpecialPage */ class LongPagesPage extends ShortPagesPage { - function __construct( $name = 'Longpages' ) { parent::__construct( $name ); } @@ -34,4 +33,8 @@ class LongPagesPage extends ShortPagesPage { function sortDescending() { return true; } + + protected function getGroupName() { + return 'maintenance'; + } } diff --git a/includes/specials/SpecialMIMEsearch.php b/includes/specials/SpecialMIMEsearch.php index 104c653f..3eeae310 100644 --- a/includes/specials/SpecialMIMEsearch.php +++ b/includes/specials/SpecialMIMEsearch.php @@ -34,28 +34,69 @@ class MIMEsearchPage extends QueryPage { parent::__construct( $name ); } - function isExpensive() { return true; } - function isSyndicated() { return false; } - function isCacheable() { return false; } + function isExpensive() { + return false; + } + + function isSyndicated() { + return false; + } + + function isCacheable() { + return false; + } function linkParameters() { return array( 'mime' => "{$this->major}/{$this->minor}" ); } public function getQueryInfo() { - return array( + $qi = array( 'tables' => array( 'image' ), - 'fields' => array( 'namespace' => NS_FILE, - 'title' => 'img_name', - 'value' => 'img_major_mime', - 'img_size', - 'img_width', - 'img_height', - 'img_user_text', - 'img_timestamp' ), - 'conds' => array( 'img_major_mime' => $this->major, - 'img_minor_mime' => $this->minor ) + 'fields' => array( + 'namespace' => NS_FILE, + 'title' => 'img_name', + // Still have a value field just in case, + // but it isn't actually used for sorting. + 'value' => 'img_name', + 'img_size', + 'img_width', + 'img_height', + 'img_user_text', + 'img_timestamp' + ), + 'conds' => array( + 'img_major_mime' => $this->major, + 'img_minor_mime' => $this->minor, + // This is in order to trigger using + // the img_media_mime index in "range" mode. + 'img_media_type' => array( + MEDIATYPE_BITMAP, + MEDIATYPE_DRAWING, + MEDIATYPE_AUDIO, + MEDIATYPE_VIDEO, + MEDIATYPE_MULTIMEDIA, + MEDIATYPE_UNKNOWN, + MEDIATYPE_OFFICE, + MEDIATYPE_TEXT, + MEDIATYPE_EXECUTABLE, + MEDIATYPE_ARCHIVE, + ), + ), ); + return $qi; + } + + /** + * The index is on (img_media_type, img_major_mime, img_minor_mime) + * which unfortunately doesn't have img_name at the end for sorting. + * So tell db to sort it however it wishes (Its not super important + * that this report gives results in a logical order). As an aditional + * note, mysql seems to by default order things by img_name ASC, which + * is what we ideally want, so everything works out fine anyhow. + */ + function getOrderFields() { + return array(); } function execute( $par ) { @@ -66,25 +107,36 @@ class MIMEsearchPage extends QueryPage { $this->setHeaders(); $this->outputHeader(); $this->getOutput()->addHTML( - Xml::openElement( 'form', array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => $wgScript ) ) . - Xml::openElement( 'fieldset' ) . - Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . - Xml::element( 'legend', null, $this->msg( 'mimesearch' )->text() ) . - Xml::inputLabel( $this->msg( 'mimetype' )->text(), 'mime', 'mime', 20, $mime ) . ' ' . - Xml::submitButton( $this->msg( 'ilsubmit' )->text() ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) + Xml::openElement( + 'form', + array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => $wgScript ) + ) . + Xml::openElement( 'fieldset' ) . + Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . + Xml::element( 'legend', null, $this->msg( 'mimesearch' )->text() ) . + Xml::inputLabel( $this->msg( 'mimetype' )->text(), 'mime', 'mime', 20, $mime ) . + ' ' . + Xml::submitButton( $this->msg( 'ilsubmit' )->text() ) . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) ); list( $this->major, $this->minor ) = File::splitMime( $mime ); + if ( $this->major == '' || $this->minor == '' || $this->minor == 'unknown' || - !self::isValidType( $this->major ) ) { + !self::isValidType( $this->major ) + ) { return; } + parent::execute( $par ); } - + /** + * @param Skin $skin + * @param object $result Result row + * @return string + */ function formatResult( $skin, $result ) { global $wgContLang; @@ -101,8 +153,13 @@ class MIMEsearchPage extends QueryPage { $bytes = htmlspecialchars( $lang->formatSize( $result->img_size ) ); $dimensions = $this->msg( 'widthheight' )->numParams( $result->img_width, $result->img_height )->escaped(); - $user = Linker::link( Title::makeTitle( NS_USER, $result->img_user_text ), htmlspecialchars( $result->img_user_text ) ); - $time = htmlspecialchars( $lang->userTimeAndDate( $result->img_timestamp, $this->getUser() ) ); + $user = Linker::link( + Title::makeTitle( NS_USER, $result->img_user_text ), + htmlspecialchars( $result->img_user_text ) + ); + + $time = $lang->userTimeAndDate( $result->img_timestamp, $this->getUser() ); + $time = htmlspecialchars( $time ); return "$download $plink . . $dimensions . . $bytes . . $user . . $time"; } @@ -124,6 +181,11 @@ class MIMEsearchPage extends QueryPage { 'model', 'multipart' ); + return in_array( $type, $types ); } + + protected function getGroupName() { + return 'media'; + } } diff --git a/includes/specials/SpecialMergeHistory.php b/includes/specials/SpecialMergeHistory.php index 1f057499..fb5ea657 100644 --- a/includes/specials/SpecialMergeHistory.php +++ b/includes/specials/SpecialMergeHistory.php @@ -52,14 +52,14 @@ class SpecialMergeHistory extends SpecialPage { $this->mTargetID = intval( $request->getVal( 'targetID' ) ); $this->mDestID = intval( $request->getVal( 'destID' ) ); $this->mTimestamp = $request->getVal( 'mergepoint' ); - if( !preg_match( '/[0-9]{14}/', $this->mTimestamp ) ) { + if ( !preg_match( '/[0-9]{14}/', $this->mTimestamp ) ) { $this->mTimestamp = ''; } $this->mComment = $request->getText( 'wpComment' ); $this->mMerge = $request->wasPosted() && $this->getUser()->matchEditToken( $request->getVal( 'wpEditToken' ) ); // target page - if( $this->mSubmitted ) { + if ( $this->mSubmitted ) { $this->mTargetObj = Title::newFromURL( $this->mTarget ); $this->mDestObj = Title::newFromURL( $this->mDest ); } else { @@ -75,7 +75,7 @@ class SpecialMergeHistory extends SpecialPage { */ function preCacheMessages() { // Precache various messages - if( !isset( $this->message ) ) { + if ( !isset( $this->message ) ) { $this->message['last'] = $this->msg( 'last' )->escaped(); } } @@ -89,20 +89,22 @@ class SpecialMergeHistory extends SpecialPage { $this->setHeaders(); $this->outputHeader(); - if( $this->mTargetID && $this->mDestID && $this->mAction == 'submit' && $this->mMerge ) { + if ( $this->mTargetID && $this->mDestID && $this->mAction == 'submit' && $this->mMerge ) { $this->merge(); + return; } if ( !$this->mSubmitted ) { $this->showMergeForm(); + return; } $errors = array(); if ( !$this->mTargetObj instanceof Title ) { $errors[] = $this->msg( 'mergehistory-invalid-source' )->parseAsBlock(); - } elseif( !$this->mTargetObj->exists() ) { + } elseif ( !$this->mTargetObj->exists() ) { $errors[] = $this->msg( 'mergehistory-no-source', array( 'parse' ), wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) )->parseAsBlock(); @@ -110,7 +112,7 @@ class SpecialMergeHistory extends SpecialPage { if ( !$this->mDestObj instanceof Title ) { $errors[] = $this->msg( 'mergehistory-invalid-destination' )->parseAsBlock(); - } elseif( !$this->mDestObj->exists() ) { + } elseif ( !$this->mDestObj->exists() ) { $errors[] = $this->msg( 'mergehistory-no-destination', array( 'parse' ), wfEscapeWikiText( $this->mDestObj->getPrefixedText() ) )->parseAsBlock(); @@ -126,7 +128,6 @@ class SpecialMergeHistory extends SpecialPage { } else { $this->showHistory(); } - } function showMergeForm() { @@ -138,25 +139,25 @@ class SpecialMergeHistory extends SpecialPage { Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . - '<fieldset>' . - Xml::element( 'legend', array(), - $this->msg( 'mergehistory-box' )->text() ) . - Html::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) . - Html::hidden( 'submitted', '1' ) . - Html::hidden( 'mergepoint', $this->mTimestamp ) . - Xml::openElement( 'table' ) . - '<tr> + '<fieldset>' . + Xml::element( 'legend', array(), + $this->msg( 'mergehistory-box' )->text() ) . + Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . + Html::hidden( 'submitted', '1' ) . + Html::hidden( 'mergepoint', $this->mTimestamp ) . + Xml::openElement( 'table' ) . + '<tr> <td>' . Xml::label( $this->msg( 'mergehistory-from' )->text(), 'target' ) . '</td> <td>' . Xml::input( 'target', 30, $this->mTarget, array( 'id' => 'target' ) ) . '</td> </tr><tr> <td>' . Xml::label( $this->msg( 'mergehistory-into' )->text(), 'dest' ) . '</td> <td>' . Xml::input( 'dest', 30, $this->mDest, array( 'id' => 'dest' ) ) . '</td> </tr><tr><td>' . - Xml::submitButton( $this->msg( 'mergehistory-go' )->text() ) . - '</td></tr>' . - Xml::closeElement( 'table' ) . - '</fieldset>' . - '</form>' + Xml::submitButton( $this->msg( 'mergehistory-go' )->text() ) . + '</td></tr>' . + Xml::closeElement( 'table' ) . + '</fieldset>' . + '</form>' ); } @@ -183,40 +184,40 @@ class SpecialMergeHistory extends SpecialPage { ); $out->addHTML( $top ); - if( $haveRevisions ) { + if ( $haveRevisions ) { # Format the user-visible controls (comment field, submission button) # in a nice little table $table = Xml::openElement( 'fieldset' ) . - $this->msg( 'mergehistory-merge', $this->mTargetObj->getPrefixedText(), - $this->mDestObj->getPrefixedText() )->parse() . - Xml::openElement( 'table', array( 'id' => 'mw-mergehistory-table' ) ) . + $this->msg( 'mergehistory-merge', $this->mTargetObj->getPrefixedText(), + $this->mDestObj->getPrefixedText() )->parse() . + Xml::openElement( 'table', array( 'id' => 'mw-mergehistory-table' ) ) . '<tr> <td class="mw-label">' . - Xml::label( $this->msg( 'mergehistory-reason' )->text(), 'wpComment' ) . - '</td> - <td class="mw-input">' . - Xml::input( 'wpComment', 50, $this->mComment, array( 'id' => 'wpComment' ) ) . - '</td> + Xml::label( $this->msg( 'mergehistory-reason' )->text(), 'wpComment' ) . + '</td> + <td class="mw-input">' . + Xml::input( 'wpComment', 50, $this->mComment, array( 'id' => 'wpComment' ) ) . + '</td> </tr> <tr> <td> </td> <td class="mw-submit">' . - Xml::submitButton( $this->msg( 'mergehistory-submit' )->text(), array( 'name' => 'merge', 'id' => 'mw-merge-submit' ) ) . - '</td> + Xml::submitButton( $this->msg( 'mergehistory-submit' )->text(), array( 'name' => 'merge', 'id' => 'mw-merge-submit' ) ) . + '</td> </tr>' . - Xml::closeElement( 'table' ) . - Xml::closeElement( 'fieldset' ); + Xml::closeElement( 'table' ) . + Xml::closeElement( 'fieldset' ); $out->addHTML( $table ); } $out->addHTML( '<h2 id="mw-mergehistory">' . - $this->msg( 'mergehistory-list' )->escaped() . "</h2>\n" + $this->msg( 'mergehistory-list' )->escaped() . "</h2>\n" ); - if( $haveRevisions ) { + if ( $haveRevisions ) { $out->addHTML( $revisions->getNavigationBar() ); $out->addHTML( '<ul>' ); $out->addHTML( $revisions->getBody() ); @@ -261,14 +262,14 @@ class SpecialMergeHistory extends SpecialPage { array(), array( 'oldid' => $rev->getId() ) ); - if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { + if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) { $pageLink = '<span class="history-deleted">' . $pageLink . '</span>'; } # Last link - if( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) { + if ( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) { $last = $this->message['last']; - } elseif( isset( $this->prevId[$row->rev_id] ) ) { + } elseif ( isset( $this->prevId[$row->rev_id] ) ) { $last = Linker::linkKnown( $rev->getTitle(), $this->message['last'], @@ -283,7 +284,7 @@ class SpecialMergeHistory extends SpecialPage { $userLink = Linker::revUserTools( $rev ); $size = $row->rev_len; - if( !is_null( $size ) ) { + if ( !is_null( $size ) ) { $stxt = Linker::formatRevisionSize( $size ); } $comment = Linker::revComment( $rev ); @@ -298,10 +299,10 @@ class SpecialMergeHistory extends SpecialPage { # keep it consistent... $targetTitle = Title::newFromID( $this->mTargetID ); $destTitle = Title::newFromID( $this->mDestID ); - if( is_null( $targetTitle ) || is_null( $destTitle ) ) { + if ( is_null( $targetTitle ) || is_null( $destTitle ) ) { return false; // validate these } - if( $targetTitle->getArticleID() == $destTitle->getArticleID() ) { + if ( $targetTitle->getArticleID() == $destTitle->getArticleID() ) { return false; } # Verify that this timestamp is valid @@ -317,8 +318,9 @@ class SpecialMergeHistory extends SpecialPage { __METHOD__ ); # Destination page must exist with revisions - if( !$maxtimestamp ) { + if ( !$maxtimestamp ) { $this->getOutput()->addWikiMsg( 'mergehistory-fail' ); + return false; } # Get the latest timestamp of the source @@ -329,12 +331,13 @@ class SpecialMergeHistory extends SpecialPage { __METHOD__ ); # $this->mTimestamp must be older than $maxtimestamp - if( $this->mTimestamp >= $maxtimestamp ) { + if ( $this->mTimestamp >= $maxtimestamp ) { $this->getOutput()->addWikiMsg( 'mergehistory-fail' ); + return false; } # Update the revisions - if( $this->mTimestamp ) { + if ( $this->mTimestamp ) { $timewhere = "rev_timestamp <= {$this->mTimestamp}"; $timestampLimit = wfTimestamp( TS_MW, $this->mTimestamp ); } else { @@ -354,12 +357,12 @@ class SpecialMergeHistory extends SpecialPage { $haveRevisions = $dbw->selectField( 'revision', 'rev_timestamp', - array( 'rev_page' => $this->mTargetID ), + array( 'rev_page' => $this->mTargetID ), __METHOD__, array( 'FOR UPDATE' ) ); - if( !$haveRevisions ) { - if( $this->mComment ) { + if ( !$haveRevisions ) { + if ( $this->mComment ) { $comment = $this->msg( 'mergehistory-comment', $targetTitle->getPrefixedText(), @@ -373,40 +376,48 @@ class SpecialMergeHistory extends SpecialPage { $destTitle->getPrefixedText() )->inContentLanguage()->text(); } - $mwRedir = MagicWord::get( 'redirect' ); - $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $destTitle->getPrefixedText() . "]]\n"; - $redirectPage = WikiPage::factory( $targetTitle ); - $redirectRevision = new Revision( array( - 'page' => $this->mTargetID, - 'comment' => $comment, - 'text' => $redirectText ) ); - $redirectRevision->insertOn( $dbw ); - $redirectPage->updateRevisionOn( $dbw, $redirectRevision ); - - # Now, we record the link from the redirect to the new title. - # It should have no other outgoing links... - $dbw->delete( 'pagelinks', array( 'pl_from' => $this->mDestID ), __METHOD__ ); - $dbw->insert( 'pagelinks', - array( - 'pl_from' => $this->mDestID, - 'pl_namespace' => $destTitle->getNamespace(), - 'pl_title' => $destTitle->getDBkey() ), - __METHOD__ - ); + + $contentHandler = ContentHandler::getForTitle( $targetTitle ); + $redirectContent = $contentHandler->makeRedirectContent( $destTitle ); + + if ( $redirectContent ) { + $redirectPage = WikiPage::factory( $targetTitle ); + $redirectRevision = new Revision( array( + 'title' => $targetTitle, + 'page' => $this->mTargetID, + 'comment' => $comment, + 'content' => $redirectContent ) ); + $redirectRevision->insertOn( $dbw ); + $redirectPage->updateRevisionOn( $dbw, $redirectRevision ); + + # Now, we record the link from the redirect to the new title. + # It should have no other outgoing links... + $dbw->delete( 'pagelinks', array( 'pl_from' => $this->mDestID ), __METHOD__ ); + $dbw->insert( 'pagelinks', + array( + 'pl_from' => $this->mDestID, + 'pl_namespace' => $destTitle->getNamespace(), + 'pl_title' => $destTitle->getDBkey() ), + __METHOD__ + ); + } else { + // would be nice to show a warning if we couldn't create a redirect + } } else { $targetTitle->invalidateCache(); // update histories } $destTitle->invalidateCache(); // update histories # Check if this did anything - if( !$count ) { + if ( !$count ) { $this->getOutput()->addWikiMsg( 'mergehistory-fail' ); + return false; } # Update our logs $log = new LogPage( 'merge' ); $log->addEntry( 'merge', $targetTitle, $this->mComment, - array( $destTitle->getPrefixedText(), $timestampLimit ) + array( $destTitle->getPrefixedText(), $timestampLimit ), $this->getUser() ); $this->getOutput()->addWikiMsg( 'mergehistory-success', @@ -416,6 +427,10 @@ class SpecialMergeHistory extends SpecialPage { return true; } + + protected function getGroupName() { + return 'pagetools'; + } } class MergeHistoryPager extends ReverseChronologicalPager { @@ -451,9 +466,9 @@ class MergeHistoryPager extends ReverseChronologicalPager { $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) ); $rev_id = isset( $rev_id ) ? $rev_id : $row->rev_id; - if( $rev_id > $row->rev_id ) { + if ( $rev_id > $row->rev_id ) { $this->mForm->prevId[$rev_id] = $row->rev_id; - } elseif( $rev_id < $row->rev_id ) { + } elseif ( $rev_id < $row->rev_id ) { $this->mForm->prevId[$row->rev_id] = $rev_id; } @@ -464,6 +479,7 @@ class MergeHistoryPager extends ReverseChronologicalPager { $this->mResult->seek( 0 ); wfProfileOut( __METHOD__ ); + return ''; } @@ -475,10 +491,11 @@ class MergeHistoryPager extends ReverseChronologicalPager { $conds = $this->mConds; $conds['rev_page'] = $this->articleID; $conds[] = "rev_timestamp < {$this->maxTimestamp}"; + return array( 'tables' => array( 'revision', 'page', 'user' ), 'fields' => array_merge( Revision::selectFields(), Revision::selectUserFields() ), - 'conds' => $conds, + 'conds' => $conds, 'join_conds' => array( 'page' => Revision::pageJoinCond(), 'user' => Revision::userJoinCond() ) diff --git a/includes/specials/SpecialMostcategories.php b/includes/specials/SpecialMostcategories.php index 3f0bafa3..9b67f343 100644 --- a/includes/specials/SpecialMostcategories.php +++ b/includes/specials/SpecialMostcategories.php @@ -30,31 +30,43 @@ * @ingroup SpecialPage */ class MostcategoriesPage extends QueryPage { - function __construct( $name = 'Mostcategories' ) { parent::__construct( $name ); } - function isExpensive() { return true; } - function isSyndicated() { return false; } + function isExpensive() { + return true; + } + + function isSyndicated() { + return false; + } function getQueryInfo() { - return array ( - 'tables' => array ( 'categorylinks', 'page' ), - 'fields' => array ( 'namespace' => 'page_namespace', - 'title' => 'page_title', - 'value' => 'COUNT(*)' ), - 'conds' => array ( 'page_namespace' => MWNamespace::getContentNamespaces() ), - 'options' => array ( 'HAVING' => 'COUNT(*) > 1', - 'GROUP BY' => array( 'page_namespace', 'page_title' ) ), - 'join_conds' => array ( 'page' => array ( 'LEFT JOIN', - 'page_id = cl_from' ) ) + return array( + 'tables' => array( 'categorylinks', 'page' ), + 'fields' => array( + 'namespace' => 'page_namespace', + 'title' => 'page_title', + 'value' => 'COUNT(*)' + ), + 'conds' => array( 'page_namespace' => MWNamespace::getContentNamespaces() ), + 'options' => array( + 'HAVING' => 'COUNT(*) > 1', + 'GROUP BY' => array( 'page_namespace', 'page_title' ) + ), + 'join_conds' => array( + 'page' => array( + 'LEFT JOIN', + 'page_id = cl_from' + ) + ) ); } /** - * @param $db DatabaseBase - * @param $res + * @param DatabaseBase $db + * @param ResultWrapper $res */ function preprocessResults( $db, $res ) { # There's no point doing a batch check if we aren't caching results; @@ -73,15 +85,22 @@ class MostcategoriesPage extends QueryPage { } /** - * @param $skin Skin - * @param $result + * @param Skin $skin + * @param object $result Result row * @return string */ function formatResult( $skin, $result ) { $title = Title::makeTitleSafe( $result->namespace, $result->title ); if ( !$title ) { - return Html::element( 'span', array( 'class' => 'mw-invalidtitle' ), - Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) ); + return Html::element( + 'span', + array( 'class' => 'mw-invalidtitle' ), + Linker::getInvalidTitleDescription( + $this->getContext(), + $result->namespace, + $result->title + ) + ); } if ( $this->isCached() ) { @@ -94,4 +113,8 @@ class MostcategoriesPage extends QueryPage { return $this->getLanguage()->specialList( $link, $count ); } + + protected function getGroupName() { + return 'highuse'; + } } diff --git a/includes/specials/SpecialMostimages.php b/includes/specials/SpecialMostimages.php index 3d797908..98d8da3a 100644 --- a/includes/specials/SpecialMostimages.php +++ b/includes/specials/SpecialMostimages.php @@ -30,22 +30,30 @@ * @ingroup SpecialPage */ class MostimagesPage extends ImageQueryPage { - function __construct( $name = 'Mostimages' ) { parent::__construct( $name ); } - function isExpensive() { return true; } - function isSyndicated() { return false; } + function isExpensive() { + return true; + } + + function isSyndicated() { + return false; + } function getQueryInfo() { - return array ( - 'tables' => array ( 'imagelinks' ), - 'fields' => array ( 'namespace' => NS_FILE, - 'title' => 'il_to', - 'value' => 'COUNT(*)' ), - 'options' => array ( 'GROUP BY' => 'il_to', - 'HAVING' => 'COUNT(*) > 1' ) + return array( + 'tables' => array( 'imagelinks' ), + 'fields' => array( + 'namespace' => NS_FILE, + 'title' => 'il_to', + 'value' => 'COUNT(*)' + ), + 'options' => array( + 'GROUP BY' => 'il_to', + 'HAVING' => 'COUNT(*) > 1' + ) ); } @@ -53,4 +61,7 @@ class MostimagesPage extends ImageQueryPage { return $this->msg( 'nimagelinks' )->numParams( $row->value )->escaped() . '<br />'; } + protected function getGroupName() { + return 'highuse'; + } } diff --git a/includes/specials/SpecialMostinterwikis.php b/includes/specials/SpecialMostinterwikis.php index 894d697b..98dd68e9 100644 --- a/includes/specials/SpecialMostinterwikis.php +++ b/includes/specials/SpecialMostinterwikis.php @@ -30,33 +30,37 @@ * @ingroup SpecialPage */ class MostinterwikisPage extends QueryPage { - function __construct( $name = 'Mostinterwikis' ) { parent::__construct( $name ); } - function isExpensive() { return true; } - function isSyndicated() { return false; } + function isExpensive() { + return true; + } + + function isSyndicated() { + return false; + } function getQueryInfo() { - return array ( - 'tables' => array ( + return array( + 'tables' => array( 'langlinks', 'page' - ), 'fields' => array ( + ), 'fields' => array( 'namespace' => 'page_namespace', 'title' => 'page_title', 'value' => 'COUNT(*)' - ), 'conds' => array ( + ), 'conds' => array( 'page_namespace' => MWNamespace::getContentNamespaces() - ), 'options' => array ( + ), 'options' => array( 'HAVING' => 'COUNT(*) > 1', - 'GROUP BY' => array ( + 'GROUP BY' => array( 'page_namespace', 'page_title' ) - ), 'join_conds' => array ( - 'page' => array ( + ), 'join_conds' => array( + 'page' => array( 'LEFT JOIN', 'page_id = ll_from' ) @@ -67,8 +71,8 @@ class MostinterwikisPage extends QueryPage { /** * Pre-fill the link cache * - * @param $db DatabaseBase - * @param $res + * @param DatabaseBase $db + * @param ResultWrapper $res */ function preprocessResults( $db, $res ) { # There's no point doing a batch check if we aren't caching results; @@ -95,8 +99,15 @@ class MostinterwikisPage extends QueryPage { function formatResult( $skin, $result ) { $title = Title::makeTitleSafe( $result->namespace, $result->title ); if ( !$title ) { - return Html::element( 'span', array( 'class' => 'mw-invalidtitle' ), - Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) ); + return Html::element( + 'span', + array( 'class' => 'mw-invalidtitle' ), + Linker::getInvalidTitleDescription( + $this->getContext(), + $result->namespace, + $result->title + ) + ); } if ( $this->isCached() ) { @@ -109,4 +120,8 @@ class MostinterwikisPage extends QueryPage { return $this->getLanguage()->specialList( $link, $count ); } + + protected function getGroupName() { + return 'highuse'; + } } diff --git a/includes/specials/SpecialMostlinked.php b/includes/specials/SpecialMostlinked.php index 89c43509..37593bf9 100644 --- a/includes/specials/SpecialMostlinked.php +++ b/includes/specials/SpecialMostlinked.php @@ -31,42 +31,60 @@ * @ingroup SpecialPage */ class MostlinkedPage extends QueryPage { - function __construct( $name = 'Mostlinked' ) { parent::__construct( $name ); } - function isExpensive() { return true; } - function isSyndicated() { return false; } + function isExpensive() { + return true; + } + + function isSyndicated() { + return false; + } function getQueryInfo() { - return array ( - 'tables' => array ( 'pagelinks', 'page' ), - 'fields' => array ( 'namespace' => 'pl_namespace', - 'title' => 'pl_title', - 'value' => 'COUNT(*)', - 'page_namespace' ), - 'options' => array ( 'HAVING' => 'COUNT(*) > 1', - 'GROUP BY' => array( 'pl_namespace', 'pl_title', - 'page_namespace' ) ), - 'join_conds' => array ( 'page' => array ( 'LEFT JOIN', - array ( 'page_namespace = pl_namespace', - 'page_title = pl_title' ) ) ) + return array( + 'tables' => array( 'pagelinks', 'page' ), + 'fields' => array( + 'namespace' => 'pl_namespace', + 'title' => 'pl_title', + 'value' => 'COUNT(*)', + 'page_namespace' + ), + 'options' => array( + 'HAVING' => 'COUNT(*) > 1', + 'GROUP BY' => array( + 'pl_namespace', 'pl_title', + 'page_namespace' + ) + ), + 'join_conds' => array( + 'page' => array( + 'LEFT JOIN', + array( + 'page_namespace = pl_namespace', + 'page_title = pl_title' + ) + ) + ) ); } /** * Pre-fill the link cache * - * @param $db DatabaseBase - * @param $res + * @param DatabaseBase $db + * @param ResultWrapper $res */ function preprocessResults( $db, $res ) { if ( $res->numRows() > 0 ) { $linkBatch = new LinkBatch(); + foreach ( $res as $row ) { $linkBatch->add( $row->namespace, $row->title ); } + $res->seek( 0 ); $linkBatch->execute(); } @@ -76,30 +94,46 @@ class MostlinkedPage extends QueryPage { * Make a link to "what links here" for the specified title * * @param $title Title being queried - * @param $caption String: text to display on the link + * @param string $caption text to display on the link * @return String */ function makeWlhLink( $title, $caption ) { $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedDBkey() ); + return Linker::linkKnown( $wlh, $caption ); } /** - * Make links to the page corresponding to the item, and the "what links here" page for it + * Make links to the page corresponding to the item, + * and the "what links here" page for it * - * @param $skin Skin to be used - * @param $result Result row + * @param Skin $skin Skin to be used + * @param object $result Result row * @return string */ function formatResult( $skin, $result ) { $title = Title::makeTitleSafe( $result->namespace, $result->title ); if ( !$title ) { - return Html::element( 'span', array( 'class' => 'mw-invalidtitle' ), - Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) ); + return Html::element( + 'span', + array( 'class' => 'mw-invalidtitle' ), + Linker::getInvalidTitleDescription( + $this->getContext(), + $result->namespace, + $result->title ) + ); } + $link = Linker::link( $title ); - $wlh = $this->makeWlhLink( $title, - $this->msg( 'nlinks' )->numParams( $result->value )->escaped() ); + $wlh = $this->makeWlhLink( + $title, + $this->msg( 'nlinks' )->numParams( $result->value )->escaped() + ); + return $this->getLanguage()->specialList( $link, $wlh ); } + + protected function getGroupName() { + return 'highuse'; + } } diff --git a/includes/specials/SpecialMostlinkedcategories.php b/includes/specials/SpecialMostlinkedcategories.php index dadef8bf..0d4641b1 100644 --- a/includes/specials/SpecialMostlinkedcategories.php +++ b/includes/specials/SpecialMostlinkedcategories.php @@ -30,29 +30,32 @@ * @ingroup SpecialPage */ class MostlinkedCategoriesPage extends QueryPage { - function __construct( $name = 'Mostlinkedcategories' ) { parent::__construct( $name ); } - function isSyndicated() { return false; } + function isSyndicated() { + return false; + } function getQueryInfo() { - return array ( - 'tables' => array ( 'category' ), - 'fields' => array ( 'title' => 'cat_title', - 'namespace' => NS_CATEGORY, - 'value' => 'cat_pages' ), + return array( + 'tables' => array( 'category' ), + 'fields' => array( 'title' => 'cat_title', + 'namespace' => NS_CATEGORY, + 'value' => 'cat_pages' ), ); } - function sortDescending() { return true; } + function sortDescending() { + return true; + } /** * Fetch user page links and cache their existence * - * @param $db DatabaseBase - * @param $res DatabaseResult + * @param DatabaseBase $db + * @param ResultWrapper $res */ function preprocessResults( $db, $res ) { if ( !$res->numRows() ) { @@ -70,8 +73,8 @@ class MostlinkedCategoriesPage extends QueryPage { } /** - * @param $skin Skin - * @param $result + * @param Skin $skin + * @param object $result Result row * @return string */ function formatResult( $skin, $result ) { @@ -79,15 +82,24 @@ class MostlinkedCategoriesPage extends QueryPage { $nt = Title::makeTitleSafe( NS_CATEGORY, $result->title ); if ( !$nt ) { - return Html::element( 'span', array( 'class' => 'mw-invalidtitle' ), - Linker::getInvalidTitleDescription( $this->getContext(), NS_CATEGORY, $result->title ) ); + return Html::element( + 'span', + array( 'class' => 'mw-invalidtitle' ), + Linker::getInvalidTitleDescription( + $this->getContext(), + NS_CATEGORY, + $result->title ) + ); } $text = $wgContLang->convert( $nt->getText() ); - $plink = Linker::link( $nt, htmlspecialchars( $text ) ); - $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped(); + return $this->getLanguage()->specialList( $plink, $nlinks ); } + + protected function getGroupName() { + return 'highuse'; + } } diff --git a/includes/specials/SpecialMostlinkedtemplates.php b/includes/specials/SpecialMostlinkedtemplates.php index 22932e5c..c90acb1f 100644 --- a/includes/specials/SpecialMostlinkedtemplates.php +++ b/includes/specials/SpecialMostlinkedtemplates.php @@ -29,7 +29,6 @@ * @ingroup SpecialPage */ class MostlinkedTemplatesPage extends QueryPage { - function __construct( $name = 'Mostlinkedtemplates' ) { parent::__construct( $name ); } @@ -62,12 +61,14 @@ class MostlinkedTemplatesPage extends QueryPage { } public function getQueryInfo() { - return array ( - 'tables' => array ( 'templatelinks' ), - 'fields' => array ( 'namespace' => 'tl_namespace', - 'title' => 'tl_title', - 'value' => 'COUNT(*)' ), - 'conds' => array ( 'tl_namespace' => NS_TEMPLATE ), + return array( + 'tables' => array( 'templatelinks' ), + 'fields' => array( + 'namespace' => 'tl_namespace', + 'title' => 'tl_title', + 'value' => 'COUNT(*)' + ), + 'conds' => array( 'tl_namespace' => NS_TEMPLATE ), 'options' => array( 'GROUP BY' => array( 'tl_namespace', 'tl_title' ) ) ); } @@ -76,7 +77,7 @@ class MostlinkedTemplatesPage extends QueryPage { * Pre-cache page existence to speed up link generation * * @param $db DatabaseBase connection - * @param $res ResultWrapper + * @param ResultWrapper $res */ public function preprocessResults( $db, $res ) { if ( !$res->numRows() ) { @@ -95,15 +96,22 @@ class MostlinkedTemplatesPage extends QueryPage { /** * Format a result row * - * @param $skin Skin to use for UI elements - * @param $result Result row - * @return String + * @param Skin $skin + * @param object $result Result row + * @return string */ public function formatResult( $skin, $result ) { $title = Title::makeTitleSafe( $result->namespace, $result->title ); if ( !$title ) { - return Html::element( 'span', array( 'class' => 'mw-invalidtitle' ), - Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) ); + return Html::element( + 'span', + array( 'class' => 'mw-invalidtitle' ), + Linker::getInvalidTitleDescription( + $this->getContext(), + $result->namespace, + $result->title + ) + ); } return $this->getLanguage()->specialList( @@ -115,14 +123,18 @@ class MostlinkedTemplatesPage extends QueryPage { /** * Make a "what links here" link for a given title * - * @param $title Title to make the link for - * @param $result Result row + * @param Title $title Title to make the link for + * @param object $result Result row * @return String */ private function makeWlhLink( $title, $result ) { $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() ); $label = $this->msg( 'ntransclusions' )->numParams( $result->value )->escaped(); + return Linker::link( $wlh, $label ); } -} + protected function getGroupName() { + return 'highuse'; + } +} diff --git a/includes/specials/SpecialMostrevisions.php b/includes/specials/SpecialMostrevisions.php index b0253316..ad6b788d 100644 --- a/includes/specials/SpecialMostrevisions.php +++ b/includes/specials/SpecialMostrevisions.php @@ -31,4 +31,8 @@ class MostrevisionsPage extends FewestrevisionsPage { function sortDescending() { return true; } + + protected function getGroupName() { + return 'highuse'; + } } diff --git a/includes/specials/SpecialMovepage.php b/includes/specials/SpecialMovepage.php index af3dbf3e..253e6cc3 100644 --- a/includes/specials/SpecialMovepage.php +++ b/includes/specials/SpecialMovepage.php @@ -27,13 +27,15 @@ * @ingroup SpecialPage */ class MovePageForm extends UnlistedSpecialPage { - /** + * Objects * @var Title */ - var $oldTitle, $newTitle; # Objects - var $reason; # Text input - var $moveTalk, $deleteAndMove, $moveSubpages, $fixRedirects, $leaveRedirect, $moveOverShared; # Checks + var $oldTitle, $newTitle; + // Text input + var $reason; + // Checks + var $moveTalk, $deleteAndMove, $moveSubpages, $fixRedirects, $leaveRedirect, $moveOverShared; private $watch = false; @@ -55,10 +57,10 @@ class MovePageForm extends UnlistedSpecialPage { $oldTitleText = $request->getVal( 'wpOldTitle', $target ); $this->oldTitle = Title::newFromText( $oldTitleText ); - if( is_null( $this->oldTitle ) ) { + if ( is_null( $this->oldTitle ) ) { throw new ErrorPageError( 'notargettitle', 'notargettext' ); } - if( !$this->oldTitle->exists() ) { + if ( !$this->oldTitle->exists() ) { throw new ErrorPageError( 'nopagetitle', 'nopagetext' ); } @@ -71,7 +73,6 @@ class MovePageForm extends UnlistedSpecialPage { ? Title::newFromText( $newTitleText_bc ) : Title::makeTitleSafe( $newTitleTextNs, $newTitleTextMain ); - $user = $this->getUser(); # Check rights @@ -94,7 +95,8 @@ class MovePageForm extends UnlistedSpecialPage { $this->watch = $request->getCheck( 'wpWatch' ) && $user->isLoggedIn(); if ( 'submit' == $request->getVal( 'action' ) && $request->wasPosted() - && $user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) { + && $user->matchEditToken( $request->getVal( 'wpEditToken' ) ) + ) { $this->doSubmit(); } else { $this->showForm( array() ); @@ -104,7 +106,7 @@ class MovePageForm extends UnlistedSpecialPage { /** * Show the form * - * @param $err Array: error messages. Each item is an error message. + * @param array $err error messages. Each item is an error message. * It may either be a string message name or array message name and * parameters, like the second argument to OutputPage::wrapWikiMsg(). */ @@ -130,7 +132,7 @@ class MovePageForm extends UnlistedSpecialPage { # link, check for validity. We can then show some diagnostic # information and save a click. $newerr = $this->oldTitle->isValidMoveOperation( $newTitle ); - if( is_array( $newerr ) ) { + if ( is_array( $newerr ) ) { $err = $newerr; } } @@ -147,16 +149,26 @@ class MovePageForm extends UnlistedSpecialPage { <tr> <td></td> <td class='mw-input'>" . - Xml::checkLabel( $this->msg( 'delete_and_move_confirm' )->text(), 'wpConfirm', 'wpConfirm' ) . - "</td> + Xml::checkLabel( + $this->msg( 'delete_and_move_confirm' )->text(), + 'wpConfirm', + 'wpConfirm' + ) . + "</td> </tr>"; $err = array(); } else { - if ($this->oldTitle->getNamespace() == NS_USER && !$this->oldTitle->isSubpage() ) { - $out->wrapWikiMsg( "<div class=\"error mw-moveuserpage-warning\">\n$1\n</div>", 'moveuserpage-warning' ); + if ( $this->oldTitle->getNamespace() == NS_USER && !$this->oldTitle->isSubpage() ) { + $out->wrapWikiMsg( + "<div class=\"error mw-moveuserpage-warning\">\n$1\n</div>", + 'moveuserpage-warning' + ); } - $out->addWikiMsg( $wgFixDoubleRedirects ? 'movepagetext' : - 'movepagetext-noredirectfixer' ); + + $out->addWikiMsg( $wgFixDoubleRedirects ? + 'movepagetext' : + 'movepagetext-noredirectfixer' + ); $movepagebtn = $this->msg( 'movepagebtn' )->text(); $submitVar = 'wpMove'; $confirm = false; @@ -189,7 +201,7 @@ class MovePageForm extends UnlistedSpecialPage { array( 'rd_namespace' => $this->oldTitle->getNamespace(), 'rd_title' => $this->oldTitle->getDBkey(), - ) , __METHOD__ ); + ), __METHOD__ ); } else { $hasRedirects = false; } @@ -206,6 +218,7 @@ class MovePageForm extends UnlistedSpecialPage { if ( count( $err ) == 1 ) { $errMsg = $err[0]; $errMsgName = array_shift( $errMsg ); + if ( $errMsgName == 'hookaborted' ) { $out->addHTML( "<p>{$errMsg[0]}</p>\n" ); } else { @@ -213,8 +226,9 @@ class MovePageForm extends UnlistedSpecialPage { } } else { $errStr = array(); - foreach( $err as $errMsg ) { - if( $errMsg[0] == 'hookaborted' ) { + + foreach ( $err as $errMsg ) { + if ( $errMsg[0] == 'hookaborted' ) { $errStr[] = $errMsg[1]; } else { $errMsgName = array_shift( $errMsg ); @@ -239,7 +253,13 @@ class MovePageForm extends UnlistedSpecialPage { } $out->addHTML( "<div class='mw-warning-with-logexcerpt'>\n" ); $out->addWikiMsg( $noticeMsg ); - LogEventsList::showLogExtract( $out, 'protect', $this->oldTitle, '', array( 'lim' => 1 ) ); + LogEventsList::showLogExtract( + $out, + 'protect', + $this->oldTitle, + '', + array( 'lim' => 1 ) + ); $out->addHTML( "</div>\n" ); } @@ -254,14 +274,23 @@ class MovePageForm extends UnlistedSpecialPage { } } + $handler = ContentHandler::getForTitle( $this->oldTitle ); + $out->addHTML( - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->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' ) ) . - "<tr> + Xml::openElement( + 'form', + array( + 'method' => 'post', + 'action' => $this->getTitle()->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' ) ) . + "<tr> <td class='mw-label'>" . - $this->msg( 'movearticle' )->escaped() . + $this->msg( 'movearticle' )->escaped() . "</td> <td class='mw-input'> <strong>{$oldTitleLink}</strong> @@ -269,53 +298,70 @@ class MovePageForm extends UnlistedSpecialPage { </tr> <tr> <td class='mw-label'>" . - Xml::label( $this->msg( 'newtitle' )->text(), 'wpNewTitleMain' ) . + 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( + 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() ) . + 'maxlength' => 255 + ) + ) . + Html::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) . "</td> </tr> <tr> <td class='mw-label'>" . - Xml::label( $this->msg( 'movereason' )->text(), 'wpReason' ) . + Xml::label( $this->msg( 'movereason' )->text(), 'wpReason' ) . "</td> <td class='mw-input'>" . - Html::element( 'textarea', array( 'name' => 'wpReason', 'id' => 'wpReason', 'cols' => 60, 'rows' => 2, - 'maxlength' => 200 ), $this->reason ) . + Xml::input( 'wpReason', 60, $this->reason, array( + 'type' => 'text', + 'id' => 'wpReason', + 'maxlength' => 200, + ) ) . "</td> </tr>" ); - if( $considerTalk ) { + if ( $considerTalk ) { $out->addHTML( " <tr> <td></td> <td class='mw-input'>" . - Xml::checkLabel( $this->msg( 'movetalk' )->text(), 'wpMovetalk', 'wpMovetalk', $this->moveTalk ) . + Xml::checkLabel( + $this->msg( 'movetalk' )->text(), + 'wpMovetalk', + 'wpMovetalk', + $this->moveTalk + ) . "</td> </tr>" ); } - if ( $user->isAllowed( 'suppressredirect' ) ) { + if ( $user->isAllowed( 'suppressredirect' ) && $handler->supportsRedirects() ) { $out->addHTML( " <tr> <td></td> <td class='mw-input' >" . - Xml::checkLabel( $this->msg( 'move-leave-redirect' )->text(), 'wpLeaveRedirect', - 'wpLeaveRedirect', $this->leaveRedirect ) . + Xml::checkLabel( + $this->msg( 'move-leave-redirect' )->text(), + 'wpLeaveRedirect', + 'wpLeaveRedirect', + $this->leaveRedirect + ) . "</td> </tr>" ); @@ -326,48 +372,57 @@ class MovePageForm extends UnlistedSpecialPage { <tr> <td></td> <td class='mw-input' >" . - Xml::checkLabel( $this->msg( 'fix-double-redirects' )->text(), 'wpFixRedirects', - 'wpFixRedirects', $this->fixRedirects ) . + Xml::checkLabel( + $this->msg( 'fix-double-redirects' )->text(), + 'wpFixRedirects', + 'wpFixRedirects', + $this->fixRedirects + ) . "</td> </tr>" ); } - if( $canMoveSubpage ) { + if ( $canMoveSubpage ) { $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' ) - ) . ' ' . - Xml::tags( 'label', array( 'for' => 'wpMovesubpages' ), - $this->msg( - ( $this->oldTitle->hasSubpages() - ? 'move-subpages' - : 'move-talk-subpages' ) + 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' ) + ) . ' ' . + Xml::tags( 'label', array( 'for' => 'wpMovesubpages' ), + $this->msg( + ( $this->oldTitle->hasSubpages() + ? 'move-subpages' + : 'move-talk-subpages' ) )->numParams( $wgMaximumMovedPages )->params( $wgMaximumMovedPages )->parse() - ) . + ) . "</td> </tr>" ); } - $watchChecked = $user->isLoggedIn() && ($this->watch || $user->getBoolOption( 'watchmoves' ) + $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() ) { + if ( $user->isLoggedIn() ) { $out->addHTML( " <tr> <td></td> <td class='mw-input'>" . - Xml::checkLabel( $this->msg( 'move-watch' )->text(), 'wpWatch', 'watch', $watchChecked ) . + Xml::checkLabel( + $this->msg( 'move-watch' )->text(), + 'wpWatch', + 'watch', + $watchChecked + ) . "</td> - </tr>"); + </tr>" ); } $out->addHTML( " @@ -375,19 +430,18 @@ class MovePageForm extends UnlistedSpecialPage { <tr> <td> </td> <td class='mw-submit'>" . - Xml::submitButton( $movepagebtn, array( 'name' => $submitVar ) ) . + Xml::submitButton( $movepagebtn, array( 'name' => $submitVar ) ) . "</td> </tr>" . - Xml::closeElement( 'table' ) . - Html::hidden( 'wpEditToken', $user->getEditToken() ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) . - "\n" + Xml::closeElement( 'table' ) . + Html::hidden( 'wpEditToken', $user->getEditToken() ) . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) . + "\n" ); $this->showLogFragment( $this->oldTitle ); $this->showSubpages( $this->oldTitle ); - } function doSubmit() { @@ -405,6 +459,7 @@ class MovePageForm extends UnlistedSpecialPage { # don't allow moving to pages with # in if ( !$nt || $nt->getFragment() != '' ) { $this->showForm( array( array( 'badtitletext' ) ) ); + return; } @@ -412,11 +467,11 @@ class MovePageForm extends UnlistedSpecialPage { if ( $nt->getNamespace() == NS_FILE && !( $this->moveOverShared && $user->isAllowed( 'reupload-shared' ) ) && !RepoGroup::singleton()->getLocalRepo()->findFile( $nt ) - && wfFindFile( $nt ) ) - { + && wfFindFile( $nt ) + ) { $this->showForm( array( array( 'file-exists-sharedrepo' ) ) ); - return; + return; } # Delete to make way if requested @@ -425,6 +480,7 @@ class MovePageForm extends UnlistedSpecialPage { if ( count( $permErrors ) ) { # Only show the first error $this->showForm( $permErrors ); + return; } @@ -443,11 +499,16 @@ class MovePageForm extends UnlistedSpecialPage { $deleteStatus = $page->doDeleteArticleReal( $reason, false, 0, true, $error, $user ); if ( !$deleteStatus->isGood() ) { $this->showForm( $deleteStatus->getErrorsArray() ); + return; } } - if ( $user->isAllowed( 'suppressredirect' ) ) { + $handler = ContentHandler::getForTitle( $ot ); + + if ( !$handler->supportsRedirects() ) { + $createRedirect = false; + } elseif ( $user->isAllowed( 'suppressredirect' ) ) { $createRedirect = $this->leaveRedirect; } else { $createRedirect = true; @@ -457,6 +518,7 @@ class MovePageForm extends UnlistedSpecialPage { $error = $ot->moveTo( $nt, true, $this->reason, $createRedirect ); if ( $error !== true ) { $this->showForm( $error ); + return; } @@ -464,8 +526,6 @@ class MovePageForm extends UnlistedSpecialPage { DoubleRedirectJob::fixRedirects( 'move', $ot, $nt ); } - wfRunHooks( 'SpecialMovepageAfterMove', array( &$this, &$ot, &$nt ) ); - $out = $this->getOutput(); $out->setPageTitle( $this->msg( 'pagemovedsub' ) ); @@ -479,15 +539,27 @@ class MovePageForm extends UnlistedSpecialPage { $oldText = $ot->getPrefixedText(); $newText = $nt->getPrefixedText(); - $msgName = $createRedirect ? 'movepage-moved-redirect' : 'movepage-moved-noredirect'; + if ( $ot->exists() ) { + //NOTE: we assume that if the old title exists, it's because it was re-created as + // a redirect to the new title. This is not safe, but what we did before was + // even worse: we just determined whether a redirect should have been created, + // and reported that it was created if it should have, without any checks. + // Also note that isRedirect() is unreliable because of bug 37209. + $msgName = 'movepage-moved-redirect'; + } else { + $msgName = 'movepage-moved-noredirect'; + } + $out->addHTML( $this->msg( 'movepage-moved' )->rawParams( $oldLink, $newLink )->params( $oldText, $newText )->parseAsBlock() ); $out->addWikiMsg( $msgName ); + wfRunHooks( 'SpecialMovepageAfterMove', array( &$this, &$ot, &$nt ) ); + # Now we move extra pages we've been asked to move: subpages and talk # pages. First, if the old page or the new page is a talk page, we # can't move any talk pages: cancel that. - if( $ot->isTalkPage() || $nt->isTalkPage() ) { + if ( $ot->isTalkPage() || $nt->isTalkPage() ) { $this->moveTalk = false; } @@ -509,24 +581,26 @@ class MovePageForm extends UnlistedSpecialPage { // @todo FIXME: Use Title::moveSubpages() here $dbr = wfGetDB( DB_MASTER ); - if( $this->moveSubpages && ( + if ( $this->moveSubpages && ( MWNamespace::hasSubpages( $nt->getNamespace() ) || ( $this->moveTalk && - MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() ) + MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() ) ) ) ) { $conds = array( 'page_title' . $dbr->buildLike( $ot->getDBkey() . '/', $dbr->anyString() ) - .' OR page_title = ' . $dbr->addQuotes( $ot->getDBkey() ) + . ' OR page_title = ' . $dbr->addQuotes( $ot->getDBkey() ) ); $conds['page_namespace'] = array(); - if( MWNamespace::hasSubpages( $nt->getNamespace() ) ) { - $conds['page_namespace'] []= $ot->getNamespace(); + if ( MWNamespace::hasSubpages( $nt->getNamespace() ) ) { + $conds['page_namespace'][] = $ot->getNamespace(); } - if( $this->moveTalk && MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() ) ) { - $conds['page_namespace'] []= $ot->getTalkPage()->getNamespace(); + if ( $this->moveTalk && + MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() ) + ) { + $conds['page_namespace'][] = $ot->getTalkPage()->getNamespace(); } - } elseif( $this->moveTalk ) { + } elseif ( $this->moveTalk ) { $conds = array( 'page_namespace' => $ot->getTalkPage()->getNamespace(), 'page_title' => $ot->getDBkey() @@ -537,7 +611,7 @@ class MovePageForm extends UnlistedSpecialPage { } $extraPages = array(); - if( !is_null( $conds ) ) { + if ( !is_null( $conds ) ) { $extraPages = TitleArray::newFromResult( $dbr->select( 'page', array( 'page_id', 'page_namespace', 'page_title' ), @@ -549,39 +623,42 @@ class MovePageForm extends UnlistedSpecialPage { $extraOutput = array(); $count = 1; - foreach( $extraPages as $oldSubpage ) { - if( $ot->equals( $oldSubpage ) ) { + foreach ( $extraPages as $oldSubpage ) { + if ( $ot->equals( $oldSubpage ) || $nt->equals( $oldSubpage ) ) { # Already did this one. continue; } $newPageName = preg_replace( - '#^'.preg_quote( $ot->getDBkey(), '#' ).'#', + '#^' . preg_quote( $ot->getDBkey(), '#' ) . '#', StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234 $oldSubpage->getDBkey() ); - if( $oldSubpage->isTalkPage() ) { + + if ( $oldSubpage->isTalkPage() ) { $newNs = $nt->getTalkPage()->getNamespace(); } else { $newNs = $nt->getSubjectPage()->getNamespace(); } + # Bug 14385: we need makeTitleSafe because the new page names may # be longer than 255 characters. $newSubpage = Title::makeTitleSafe( $newNs, $newPageName ); - if( !$newSubpage ) { + if ( !$newSubpage ) { $oldLink = Linker::linkKnown( $oldSubpage ); - $extraOutput []= $this->msg( 'movepage-page-unmoved' )->rawParams( $oldLink - )->params( Title::makeName( $newNs, $newPageName ) )->escaped(); + $extraOutput[] = $this->msg( 'movepage-page-unmoved' )->rawParams( $oldLink ) + ->params( Title::makeName( $newNs, $newPageName ) )->escaped(); continue; } # This was copy-pasted from Renameuser, bleh. if ( $newSubpage->exists() && !$oldSubpage->isValidMoveTarget( $newSubpage ) ) { $link = Linker::linkKnown( $newSubpage ); - $extraOutput []= $this->msg( 'movepage-page-exists' )->rawParams( $link )->escaped(); + $extraOutput[] = $this->msg( 'movepage-page-exists' )->rawParams( $link )->escaped(); } else { $success = $oldSubpage->moveTo( $newSubpage, true, $this->reason, $createRedirect ); - if( $success === true ) { + + if ( $success === true ) { if ( $this->fixRedirects ) { DoubleRedirectJob::fixRedirects( 'move', $oldSubpage, $newSubpage ); } @@ -591,41 +668,30 @@ class MovePageForm extends UnlistedSpecialPage { array(), array( 'redirect' => 'no' ) ); + $newLink = Linker::linkKnown( $newSubpage ); - $extraOutput []= $this->msg( 'movepage-page-moved' )->rawParams( $oldLink, $newLink )->escaped(); + $extraOutput[] = $this->msg( 'movepage-page-moved' )->rawParams( $oldLink, $newLink )->escaped(); ++$count; - if( $count >= $wgMaximumMovedPages ) { - $extraOutput []= $this->msg( 'movepage-max-pages' )->numParams( $wgMaximumMovedPages )->escaped(); + + if ( $count >= $wgMaximumMovedPages ) { + $extraOutput[] = $this->msg( 'movepage-max-pages' )->numParams( $wgMaximumMovedPages )->escaped(); break; } } else { $oldLink = Linker::linkKnown( $oldSubpage ); $newLink = Linker::link( $newSubpage ); - $extraOutput []= $this->msg( 'movepage-page-unmoved' )->rawParams( $oldLink, $newLink )->escaped(); + $extraOutput[] = $this->msg( 'movepage-page-unmoved' )->rawParams( $oldLink, $newLink )->escaped(); } } - } - if( $extraOutput !== array() ) { + if ( $extraOutput !== array() ) { $out->addHTML( "<ul>\n<li>" . implode( "</li>\n<li>", $extraOutput ) . "</li>\n</ul>" ); } # Deal with watches (we don't watch subpages) - if( $this->watch && $user->isLoggedIn() ) { - $user->addWatch( $ot ); - $user->addWatch( $nt ); - } else { - $user->removeWatch( $ot ); - $user->removeWatch( $nt ); - } - - # Re-clear the file redirect cache, which may have been polluted by - # parsing in messages above. See CR r56745. - # @todo FIXME: Needs a more robust solution inside FileRepo. - if( $ot->getNamespace() == NS_FILE ) { - RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $ot ); - } + WatchAction::doWatchOrUnwatch( $this->watch, $ot, $user ); + WatchAction::doWatchOrUnwatch( $this->watch, $nt, $user ); } function showLogFragment( $title ) { @@ -636,8 +702,9 @@ class MovePageForm extends UnlistedSpecialPage { } function showSubpages( $title ) { - if( !MWNamespace::hasSubpages( $title->getNamespace() ) ) + if ( !MWNamespace::hasSubpages( $title->getNamespace() ) ) { return; + } $subpages = $title->getSubpages(); $count = $subpages instanceof TitleArray ? $subpages->count() : 0; @@ -648,16 +715,21 @@ class MovePageForm extends UnlistedSpecialPage { # No subpages. if ( $count == 0 ) { $out->addWikiMsg( 'movenosubpage' ); + return; } $out->addWikiMsg( 'movesubpagetext', $this->getLanguage()->formatNum( $count ) ); $out->addHTML( "<ul>\n" ); - foreach( $subpages as $subpage ) { + foreach ( $subpages as $subpage ) { $link = Linker::link( $subpage ); $out->addHTML( "<li>$link</li>\n" ); } $out->addHTML( "</ul>\n" ); } + + protected function getGroupName() { + return 'pagetools'; + } } diff --git a/includes/specials/SpecialNewimages.php b/includes/specials/SpecialNewimages.php index 350aac63..37d29734 100644 --- a/includes/specials/SpecialNewimages.php +++ b/includes/specials/SpecialNewimages.php @@ -21,12 +21,11 @@ * @ingroup SpecialPage */ class SpecialNewFiles extends IncludableSpecialPage { - - public function __construct(){ + public function __construct() { parent::__construct( 'Newimages' ); } - public function execute( $par ){ + public function execute( $par ) { $this->setHeaders(); $this->outputHeader(); @@ -37,19 +36,22 @@ class SpecialNewFiles extends IncludableSpecialPage { $form->prepareForm(); $form->displayForm( '' ); } + $this->getOutput()->addHTML( $pager->getBody() ); if ( !$this->including() ) { $this->getOutput()->addHTML( $pager->getNavigationBar() ); } } -} + protected function getGroupName() { + return 'changes'; + } +} /** * @ingroup SpecialPage Pager */ class NewFilesPager extends ReverseChronologicalPager { - /** * @var ImageGallery */ @@ -57,7 +59,7 @@ class NewFilesPager extends ReverseChronologicalPager { function __construct( IContextSource $context, $par = null ) { $this->like = $context->getRequest()->getText( 'like' ); - $this->showbots = $context->getRequest()->getBool( 'showbots' , 0 ); + $this->showbots = $context->getRequest()->getBool( 'showbots', 0 ); if ( is_numeric( $par ) ) { $this->setLimit( $par ); } @@ -70,9 +72,10 @@ class NewFilesPager extends ReverseChronologicalPager { $conds = $jconds = array(); $tables = array( 'image' ); - if( !$this->showbots ) { + if ( !$this->showbots ) { $groupsWithBotPermission = User::getGroupsWithPermission( 'bot' ); - if( count( $groupsWithBotPermission ) ) { + + if ( count( $groupsWithBotPermission ) ) { $tables[] = 'user_groups'; $conds[] = 'ug_group IS NULL'; $jconds['user_groups'] = array( @@ -85,11 +88,15 @@ class NewFilesPager extends ReverseChronologicalPager { } } - if( !$wgMiserMode && $this->like !== null ){ + if ( !$wgMiserMode && $this->like !== null ) { $dbr = wfGetDB( DB_SLAVE ); $likeObj = Title::newFromURL( $this->like ); - if( $likeObj instanceof Title ){ - $like = $dbr->buildLike( $dbr->anyString(), strtolower( $likeObj->getDBkey() ), $dbr->anyString() ); + if ( $likeObj instanceof Title ) { + $like = $dbr->buildLike( + $dbr->anyString(), + strtolower( $likeObj->getDBkey() ), + $dbr->anyString() + ); $conds[] = "LOWER(img_name) $like"; } } @@ -104,18 +111,27 @@ class NewFilesPager extends ReverseChronologicalPager { return $query; } - function getIndexField(){ + function getIndexField() { return 'img_timestamp'; } - function getStartBody(){ + function getStartBody() { if ( !$this->gallery ) { - $this->gallery = new ImageGallery(); + // Note that null for mode is taken to mean use default. + $mode = $this->getRequest()->getVal( 'gallerymode', null ); + try { + $this->gallery = ImageGalleryBase::factory( $mode ); + } catch ( MWException $e ) { + // User specified something invalid, fallback to default. + $this->gallery = ImageGalleryBase::factory(); + } + $this->gallery->setContext( $this->getContext() ); } + return ''; } - function getEndBody(){ + function getEndBody() { return $this->gallery->toHTML(); } @@ -125,11 +141,12 @@ class NewFilesPager extends ReverseChronologicalPager { $title = Title::makeTitle( NS_FILE, $name ); $ul = Linker::link( $user->getUserpage(), $user->getName() ); + $time = $this->getLanguage()->userTimeAndDate( $row->img_timestamp, $this->getUser() ); $this->gallery->add( $title, "$ul<br />\n<i>" - . htmlspecialchars( $this->getLanguage()->userTimeAndDate( $row->img_timestamp, $this->getUser() ) ) + . htmlspecialchars( $time ) . "</i><br />\n" ); } @@ -147,7 +164,6 @@ class NewFilesPager extends ReverseChronologicalPager { 'type' => 'check', 'label' => $this->msg( 'showhidebots', $this->msg( 'show' )->plain() )->escaped(), 'name' => 'showbots', - # 'default' => $this->getRequest()->getBool( 'showbots', 0 ), ), 'limit' => array( 'type' => 'hidden', @@ -161,12 +177,13 @@ class NewFilesPager extends ReverseChronologicalPager { ), ); - if( $wgMiserMode ){ + if ( $wgMiserMode ) { unset( $fields['like'] ); } - $form = new HTMLForm( $fields, $this->getContext() ); - $form->setTitle( $this->getTitle() ); + $context = new DerivativeContext( $this->getContext() ); + $context->setTitle( $this->getTitle() ); // Remove subpage + $form = new HTMLForm( $fields, $context ); $form->setSubmitTextMsg( 'ilsubmit' ); $form->setMethod( 'get' ); $form->setWrapperLegendMsg( 'newimages-legend' ); diff --git a/includes/specials/SpecialNewpages.php b/includes/specials/SpecialNewpages.php index 8e15d554..43d48558 100644 --- a/includes/specials/SpecialNewpages.php +++ b/includes/specials/SpecialNewpages.php @@ -27,7 +27,6 @@ * @ingroup SpecialPage */ class SpecialNewpages extends IncludableSpecialPage { - // Stored objects /** @@ -53,26 +52,29 @@ class SpecialNewpages extends IncludableSpecialPage { $opts->add( 'hidepatrolled', $this->getUser()->getBoolOption( 'newpageshidepatrolled' ) ); $opts->add( 'hidebots', false ); $opts->add( 'hideredirs', true ); - $opts->add( 'limit', (int)$this->getUser()->getOption( 'rclimit' ) ); + $opts->add( 'limit', $this->getUser()->getIntOption( 'rclimit' ) ); $opts->add( 'offset', '' ); $opts->add( 'namespace', '0' ); $opts->add( 'username', '' ); $opts->add( 'feed', '' ); $opts->add( 'tagfilter', '' ); + $opts->add( 'invert', false ); $this->customFilters = array(); wfRunHooks( 'SpecialNewPagesFilters', array( $this, &$this->customFilters ) ); - foreach( $this->customFilters as $key => $params ) { + foreach ( $this->customFilters as $key => $params ) { $opts->add( $key, $params['default'] ); } // Set values $opts->fetchValuesFromRequest( $this->getRequest() ); - if ( $par ) $this->parseParams( $par ); + if ( $par ) { + $this->parseParams( $par ); + } // Validate $opts->validateIntBounds( 'limit', 0, 5000 ); - if( !$wgEnableNewpagesUserFilter ) { + if ( !$wgEnableNewpagesUserFilter ) { $opts->setValue( 'username', '' ); } } @@ -105,15 +107,15 @@ class SpecialNewpages extends IncludableSpecialPage { } // PG offsets not just digits! if ( preg_match( '/^offset=([^=]+)$/', $bit, $m ) ) { - $this->opts->setValue( 'offset', intval( $m[1] ) ); + $this->opts->setValue( 'offset', intval( $m[1] ) ); } if ( preg_match( '/^username=(.*)$/', $bit, $m ) ) { $this->opts->setValue( 'username', $m[1] ); } if ( preg_match( '/^namespace=(.*)$/', $bit, $m ) ) { $ns = $this->getLanguage()->getNsIndex( $m[1] ); - if( $ns !== false ) { - $this->opts->setValue( 'namespace', $ns ); + if ( $ns !== false ) { + $this->opts->setValue( 'namespace', $ns ); } } } @@ -134,25 +136,27 @@ class SpecialNewpages extends IncludableSpecialPage { $this->showNavigation = !$this->including(); // Maybe changed in setup $this->setup( $par ); - if( !$this->including() ) { + if ( !$this->including() ) { // Settings $this->form(); $feedType = $this->opts->getValue( 'feed' ); - if( $feedType ) { - return $this->feed( $feedType ); + if ( $feedType ) { + $this->feed( $feedType ); + + return; } $allValues = $this->opts->getAllValues(); unset( $allValues['feed'] ); - $out->setFeedAppendQuery( wfArrayToCGI( $allValues ) ); + $out->setFeedAppendQuery( wfArrayToCgi( $allValues ) ); } $pager = new NewPagesPager( $this, $this->opts ); $pager->mLimit = $this->opts->getValue( 'limit' ); $pager->mOffset = $this->opts->getValue( 'offset' ); - if( $pager->getNumRows() ) { + if ( $pager->getNumRows() ) { $navigation = ''; if ( $this->showNavigation ) { $navigation = $pager->getNavigationBar(); @@ -164,8 +168,6 @@ class SpecialNewpages extends IncludableSpecialPage { } protected function filterLinks() { - global $wgGroupPermissions; - // show/hide links $showhide = array( $this->msg( 'show' )->escaped(), $this->msg( 'hide' )->escaped() ); @@ -181,8 +183,7 @@ class SpecialNewpages extends IncludableSpecialPage { } // Disable some if needed - # @todo FIXME: Throws E_NOTICEs if not set; and doesn't obey hooks etc. - if ( $wgGroupPermissions['*']['createpage'] !== true ) { + if ( !User::groupHasPermission( '*', 'createpage' ) ) { unset( $filters['hideliu'] ); } if ( !$this->getUser()->useNPPatrol() ) { @@ -197,7 +198,7 @@ class SpecialNewpages extends IncludableSpecialPage { foreach ( $filters as $key => $msg ) { $onoff = 1 - $this->opts->getValue( $key ); $link = Linker::link( $self, $showhide[$onoff], array(), - array( $key => $onoff ) + $changed + array( $key => $onoff ) + $changed ); $links[$key] = $this->msg( $msg )->rawParams( $link )->escaped(); } @@ -213,6 +214,7 @@ class SpecialNewpages extends IncludableSpecialPage { $namespace = $this->opts->consumeValue( 'namespace' ); $username = $this->opts->consumeValue( 'username' ); $tagFilterVal = $this->opts->consumeValue( 'tagfilter' ); + $nsinvert = $this->opts->consumeValue( 'invert' ); // Check username input validity $ut = Title::makeTitleSafe( NS_USER, $username ); @@ -236,48 +238,55 @@ class SpecialNewpages extends IncludableSpecialPage { Xml::openElement( 'table', array( 'id' => 'mw-newpages-table' ) ) . '<tr> <td class="mw-label">' . - Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ) . - '</td> - <td class="mw-input">' . - Html::namespaceSelector( - array( - 'selected' => $namespace, - 'all' => 'all', - ), array( - 'name' => 'namespace', - 'id' => 'namespace', - 'class' => 'namespaceselector', - ) - ) . - '</td> + Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ) . + '</td> + <td class="mw-input">' . + Html::namespaceSelector( + array( + 'selected' => $namespace, + 'all' => 'all', + ), array( + 'name' => 'namespace', + 'id' => 'namespace', + 'class' => 'namespaceselector', + ) + ) . ' ' . + Xml::checkLabel( + $this->msg( 'invert' )->text(), + 'invert', + 'nsinvert', + $nsinvert, + array( 'title' => $this->msg( 'tooltip-invert' )->text() ) + ) . + '</td> </tr>' . ( $tagFilter ? ( '<tr> <td class="mw-label">' . - $tagFilterLabel . + $tagFilterLabel . '</td> <td class="mw-input">' . - $tagFilterSelector . + $tagFilterSelector . '</td> </tr>' ) : '' ) . ( $wgEnableNewpagesUserFilter ? - '<tr> + '<tr> <td class="mw-label">' . Xml::label( $this->msg( 'newpages-username' )->text(), 'mw-np-username' ) . - '</td> + '</td> <td class="mw-input">' . Xml::input( 'username', 30, $userText, array( 'id' => 'mw-np-username' ) ) . - '</td> + '</td> </tr>' : '' ) . '<tr> <td></td> <td class="mw-submit">' . - Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . - '</td> - </tr>' . + Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . + '</td> + </tr>' . '<tr> <td></td> <td class="mw-input">' . - $this->filterLinks() . - '</td> + $this->filterLinks() . + '</td> </tr>' . Xml::closeElement( 'table' ) . Xml::closeElement( 'fieldset' ) . @@ -288,9 +297,10 @@ class SpecialNewpages extends IncludableSpecialPage { } /** - * Format a row, providing the timestamp, links to the page/history, size, user links, and a comment + * Format a row, providing the timestamp, links to the page/history, + * size, user links, and a comment * - * @param $result Result row + * @param object $result Result row * @return String */ public function formatRow( $result ) { @@ -298,11 +308,11 @@ class SpecialNewpages extends IncludableSpecialPage { # Revision deletion works on revisions, so we should cast one $row = array( - 'comment' => $result->rc_comment, - 'deleted' => $result->rc_deleted, - 'user_text' => $result->rc_user_text, - 'user' => $result->rc_user, - ); + 'comment' => $result->rc_comment, + 'deleted' => $result->rc_deleted, + 'user_text' => $result->rc_user_text, + 'user' => $result->rc_user, + ); $rev = new Revision( $row ); $rev->setTitle( $title ); @@ -324,16 +334,14 @@ class SpecialNewpages extends IncludableSpecialPage { $query = array( 'redirect' => 'no' ); - if( $this->patrollable( $result ) ) { - $query['rcid'] = $result->rc_id; - } - - $plink = Linker::linkKnown( + // Linker::linkKnown() uses 'known' and 'noclasses' options. + // This breaks the colouration for stubs. + $plink = Linker::link( $title, null, array( 'class' => 'mw-newpages-pagename' ), $query, - array( 'known' ) // Set explicitly to avoid the default of 'known','noclasses'. This breaks the colouration for stubs + array( 'known' ) ); $histLink = Linker::linkKnown( $title, @@ -344,8 +352,12 @@ class SpecialNewpages extends IncludableSpecialPage { $hist = Html::rawElement( 'span', array( 'class' => 'mw-newpages-history' ), $this->msg( 'parentheses' )->rawParams( $histLink )->escaped() ); - $length = Html::element( 'span', array( 'class' => 'mw-newpages-length' ), - $this->msg( 'brackets' )->params( $this->msg( 'nbytes' )->numParams( $result->length )->text() ) + $length = Html::element( + 'span', + array( 'class' => 'mw-newpages-length' ), + $this->msg( 'brackets' )->params( $this->msg( 'nbytes' ) + ->numParams( $result->length )->text() + ) ); $ulink = Linker::revUserTools( $rev ); @@ -361,8 +373,11 @@ class SpecialNewpages extends IncludableSpecialPage { } # Tags, if any. - if( isset( $result->ts_tags ) ) { - list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow( $result->ts_tags, 'newpages' ); + if ( isset( $result->ts_tags ) ) { + list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow( + $result->ts_tags, + 'newpages' + ); $classes = array_merge( $classes, $newClasses ); } else { $tagDisplay = ''; @@ -373,8 +388,10 @@ class SpecialNewpages extends IncludableSpecialPage { # Display the old title if the namespace/title has been changed $oldTitleText = ''; $oldTitle = Title::makeTitle( $result->rc_namespace, $result->rc_title ); + if ( !$title->equals( $oldTitle ) ) { - $oldTitleText = $this->msg( 'rc-old-title' )->params( $oldTitle->getPrefixedText() )->escaped(); + $oldTitleText = $oldTitle->getPrefixedText(); + $oldTitleText = $this->msg( 'rc-old-title' )->params( $oldTitleText )->escaped(); } return "<li{$css}>{$time} {$dm}{$plink} {$hist} {$dm}{$length} {$dm}{$ulink} {$comment} {$tagDisplay} {$oldTitleText}</li>\n"; @@ -383,7 +400,7 @@ class SpecialNewpages extends IncludableSpecialPage { /** * Should a specific result row provide "patrollable" links? * - * @param $result Result row + * @param object $result Result row * @return Boolean */ protected function patrollable( $result ) { @@ -400,18 +417,20 @@ class SpecialNewpages extends IncludableSpecialPage { if ( !$wgFeed ) { $this->getOutput()->addWikiMsg( 'feed-unavailable' ); + return; } - if( !isset( $wgFeedClasses[$type] ) ) { + if ( !isset( $wgFeedClasses[$type] ) ) { $this->getOutput()->addWikiMsg( 'feed-invalid' ); + return; } $feed = new $wgFeedClasses[$type]( $this->feedTitle(), $this->msg( 'tagline' )->text(), - $this->getTitle()->getFullUrl() + $this->getTitle()->getFullURL() ); $pager = new NewPagesPager( $this, $this->opts ); @@ -419,7 +438,7 @@ class SpecialNewpages extends IncludableSpecialPage { $pager->mLimit = min( $limit, $wgFeedLimit ); $feed->outHeader(); - if( $pager->getNumRows() > 0 ) { + if ( $pager->getNumRows() > 0 ) { foreach ( $pager->mResult as $row ) { $feed->outItem( $this->feedItem( $row ) ); } @@ -430,12 +449,13 @@ class SpecialNewpages extends IncludableSpecialPage { protected function feedTitle() { global $wgLanguageCode, $wgSitename; $desc = $this->getDescription(); + return "$wgSitename - $desc [$wgLanguageCode]"; } protected function feedItem( $row ) { $title = Title::makeTitle( intval( $row->rc_namespace ), $row->rc_title ); - if( $title ) { + if ( $title ) { $date = $row->rc_timestamp; $comments = $title->getTalkPage()->getFullURL(); @@ -458,15 +478,21 @@ class SpecialNewpages extends IncludableSpecialPage { protected function feedItemDesc( $row ) { $revision = Revision::newFromId( $row->rev_id ); - if( $revision ) { + if ( $revision ) { + //XXX: include content model/type in feed item? return '<p>' . htmlspecialchars( $revision->getUserText() ) . $this->msg( 'colon-separator' )->inContentLanguage()->escaped() . htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) . "</p>\n<hr />\n<div>" . - nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>"; + nl2br( htmlspecialchars( $revision->getContent()->serialize() ) ) . "</div>"; } + return ''; } + + protected function getGroupName() { + return 'changes'; + } } /** @@ -488,7 +514,7 @@ class NewPagesPager extends ReverseChronologicalPager { } function getQueryInfo() { - global $wgEnableNewpagesUserFilter, $wgGroupPermissions; + global $wgEnableNewpagesUserFilter; $conds = array(); $conds['rc_new'] = 1; @@ -498,26 +524,34 @@ class NewPagesPager extends ReverseChronologicalPager { $username = $this->opts->getValue( 'username' ); $user = Title::makeTitleSafe( NS_USER, $username ); - if( $namespace !== false ) { - $conds['rc_namespace'] = $namespace; + if ( $namespace !== false ) { + if ( $this->opts->getValue( 'invert' ) ) { + $conds[] = 'rc_namespace != ' . $this->mDb->addQuotes( $namespace ); + } else { + $conds['rc_namespace'] = $namespace; + } $rcIndexes = array( 'new_name_timestamp' ); } else { $rcIndexes = array( 'rc_timestamp' ); } # $wgEnableNewpagesUserFilter - temp WMF hack - if( $wgEnableNewpagesUserFilter && $user ) { + if ( $wgEnableNewpagesUserFilter && $user ) { $conds['rc_user_text'] = $user->getText(); $rcIndexes = 'rc_user_text'; - # If anons cannot make new pages, don't "exclude logged in users"! - } elseif( $wgGroupPermissions['*']['createpage'] && $this->opts->getValue( 'hideliu' ) ) { + } elseif ( User::groupHasPermission( '*', 'createpage' ) && + $this->opts->getValue( 'hideliu' ) + ) { + # If anons cannot make new pages, don't "exclude logged in users"! $conds['rc_user'] = 0; } + # If this user cannot see patrolled edits or they are off, don't do dumb queries! - if( $this->opts->getValue( 'hidepatrolled' ) && $this->getUser()->useNPPatrol() ) { + if ( $this->opts->getValue( 'hidepatrolled' ) && $this->getUser()->useNPPatrol() ) { $conds['rc_patrolled'] = 0; } - if( $this->opts->getValue( 'hidebots' ) ) { + + if ( $this->opts->getValue( 'hidebots' ) ) { $conds['rc_bot'] = 0; } @@ -529,7 +563,7 @@ class NewPagesPager extends ReverseChronologicalPager { $tables = array( 'recentchanges', 'page' ); $fields = array( 'rc_namespace', 'rc_title', 'rc_cur_id', 'rc_user', 'rc_user_text', - 'rc_comment', 'rc_timestamp', 'rc_patrolled','rc_id', 'rc_deleted', + 'rc_comment', 'rc_timestamp', 'rc_patrolled', 'rc_id', 'rc_deleted', 'length' => 'page_len', 'rev_id' => 'page_latest', 'rc_this_oldid', 'page_namespace', 'page_title' ); @@ -539,10 +573,10 @@ class NewPagesPager extends ReverseChronologicalPager { array( &$this, $this->opts, &$conds, &$tables, &$fields, &$join_conds ) ); $info = array( - 'tables' => $tables, - 'fields' => $fields, - 'conds' => $conds, - 'options' => array( 'USE INDEX' => array( 'recentchanges' => $rcIndexes ) ), + 'tables' => $tables, + 'fields' => $fields, + 'conds' => $conds, + 'options' => array( 'USE INDEX' => array( 'recentchanges' => $rcIndexes ) ), 'join_conds' => $join_conds ); @@ -576,6 +610,7 @@ class NewPagesPager extends ReverseChronologicalPager { $linkBatch->add( $row->rc_namespace, $row->rc_title ); } $linkBatch->execute(); + return '<ul>'; } diff --git a/includes/specials/SpecialPagesWithProp.php b/includes/specials/SpecialPagesWithProp.php new file mode 100644 index 00000000..e22b42a3 --- /dev/null +++ b/includes/specials/SpecialPagesWithProp.php @@ -0,0 +1,156 @@ +<?php +/** + * Implements Special:PagesWithProp + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @since 1.21 + * @file + * @ingroup SpecialPage + * @author Brad Jorsch + */ + +/** + * Special:PagesWithProp to search the page_props table + * @ingroup SpecialPage + * @since 1.21 + */ +class SpecialPagesWithProp extends QueryPage { + private $propName = null; + + function __construct( $name = 'PagesWithProp' ) { + parent::__construct( $name ); + } + + function isCacheable() { + return false; + } + + function execute( $par ) { + $this->setHeaders(); + $this->outputHeader(); + $this->getOutput()->addModuleStyles( 'mediawiki.special.pagesWithProp' ); + + $request = $this->getRequest(); + $propname = $request->getVal( 'propname', $par ); + + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( + 'page_props', + 'pp_propname', + '', + __METHOD__, + array( 'DISTINCT', 'ORDER BY' => 'pp_propname' ) + ); + $propnames = array(); + foreach ( $res as $row ) { + $propnames[$row->pp_propname] = $row->pp_propname; + } + + $form = new HTMLForm( array( + 'propname' => array( + 'type' => 'selectorother', + 'name' => 'propname', + 'options' => $propnames, + 'default' => $propname, + 'label-message' => 'pageswithprop-prop', + 'required' => true, + ), + ), $this->getContext() ); + $form->setMethod( 'get' ); + $form->setSubmitCallback( array( $this, 'onSubmit' ) ); + $form->setWrapperLegendMsg( 'pageswithprop-legend' ); + $form->addHeaderText( $this->msg( 'pageswithprop-text' )->parseAsBlock() ); + $form->setSubmitTextMsg( 'pageswithprop-submit' ); + + $form->prepareForm(); + $form->displayForm( false ); + if ( $propname !== '' && $propname !== null ) { + $form->trySubmit(); + } + } + + public function onSubmit( $data, $form ) { + $this->propName = $data['propname']; + parent::execute( $data['propname'] ); + } + + /** + * Disable RSS/Atom feeds + * @return bool + */ + function isSyndicated() { + return false; + } + + function getQueryInfo() { + return array( + 'tables' => array( 'page_props', 'page' ), + 'fields' => array( + 'page_id' => 'pp_page', + 'page_namespace', + 'page_title', + 'page_len', + 'page_is_redirect', + 'page_latest', + 'pp_value', + ), + 'conds' => array( + 'page_id = pp_page', + 'pp_propname' => $this->propName, + ), + 'options' => array() + ); + } + + function getOrderFields() { + return array( 'page_id' ); + } + + /** + * @param Skin $skin + * @param object $result Result row + * @return string + */ + function formatResult( $skin, $result ) { + $title = Title::newFromRow( $result ); + $ret = Linker::link( $title, null, array(), array(), array( 'known' ) ); + if ( $result->pp_value !== '' ) { + // Do not show very long or binary values on the special page + $valueLength = strlen( $result->pp_value ); + $isBinary = strpos( $result->pp_value, "\0" ) !== false; + $isTooLong = $valueLength > 1024; + + if ( $isBinary || $isTooLong ) { + $message = $this + ->msg( $isBinary ? 'pageswithprop-prophidden-binary' : 'pageswithprop-prophidden-long' ) + ->params( $this->getLanguage()->formatSize( $valueLength ) ); + + $propValue = Html::element( 'span', array( 'class' => 'prop-value-hidden' ), $message->text() ); + } else { + $propValue = Html::element( 'span', array( 'class' => 'prop-value' ), $result->pp_value ); + } + + $ret .= $this->msg( 'colon-separator' )->escaped() . $propValue; + } + + return $ret; + } + + protected function getGroupName() { + return 'pages'; + } +} diff --git a/includes/specials/SpecialPasswordReset.php b/includes/specials/SpecialPasswordReset.php index efb57657..c486ba01 100644 --- a/includes/specials/SpecialPasswordReset.php +++ b/includes/specials/SpecialPasswordReset.php @@ -27,19 +27,23 @@ * @ingroup SpecialPage */ class SpecialPasswordReset extends FormSpecialPage { - /** * @var Message */ private $email; /** + * @var User + */ + private $firstUser; + + /** * @var Status */ private $result; public function __construct() { - parent::__construct( 'PasswordReset' ); + parent::__construct( 'PasswordReset', 'editmyprivateinfo' ); } public function userCanExecute( User $user ) { @@ -65,7 +69,8 @@ class SpecialPasswordReset extends FormSpecialPage { 'type' => 'text', 'label-message' => 'passwordreset-username', ); - if( $this->getUser()->isLoggedIn() ) { + + if ( $this->getUser()->isLoggedIn() ) { $a['Username']['default'] = $this->getUser()->getName(); } } @@ -86,7 +91,7 @@ class SpecialPasswordReset extends FormSpecialPage { ); } - if( $this->getUser()->isAllowed( 'passwordreset' ) ){ + if ( $this->getUser()->isAllowed( 'passwordreset' ) ) { $a['Capture'] = array( 'type' => 'check', 'label-message' => 'passwordreset-capture', @@ -98,11 +103,15 @@ class SpecialPasswordReset extends FormSpecialPage { } public function alterForm( HTMLForm $form ) { - $form->setSubmitTextMsg( 'mailmypassword' ); - } - - protected function preText() { global $wgPasswordResetRoutes; + + $form->setDisplayFormat( 'vform' ); + // Turn the old-school line around the form off. + // XXX This wouldn't be necessary here if we could set the format of + // the HTMLForm to 'vform' at its creation, but there's no way to do so + // from a FormSpecialPage class. + $form->setWrapperLegend( false ); + $i = 0; if ( isset( $wgPasswordResetRoutes['username'] ) && $wgPasswordResetRoutes['username'] ) { $i++; @@ -113,7 +122,11 @@ class SpecialPasswordReset extends FormSpecialPage { if ( isset( $wgPasswordResetRoutes['domain'] ) && $wgPasswordResetRoutes['domain'] ) { $i++; } - return $this->msg( 'passwordreset-pretext', $i )->parseAsBlock(); + + $message = ( $i > 1 ) ? 'passwordreset-text-many' : 'passwordreset-text-one'; + + $form->setHeaderText( $this->msg( $message, $i )->parseAsBlock() ); + $form->setSubmitTextMsg( 'mailmypassword' ); } /** @@ -121,6 +134,8 @@ class SpecialPasswordReset extends FormSpecialPage { * userCanExecute(), and if the data array contains 'Username', etc, then Username * resets are allowed. * @param $data array + * @throws MWException + * @throws ThrottledError|PermissionsError * @return Bool|Array */ public function onSubmit( array $data ) { @@ -134,8 +149,9 @@ class SpecialPasswordReset extends FormSpecialPage { } } - if( isset( $data['Capture'] ) && !$this->getUser()->isAllowed( 'passwordreset' ) ){ - // The user knows they don't have the passwordreset permission, but they tried to spoof the form. That's naughty + if ( isset( $data['Capture'] ) && !$this->getUser()->isAllowed( 'passwordreset' ) ) { + // The user knows they don't have the passwordreset permission, + // but they tried to spoof the form. That's naughty throw new PermissionsError( 'passwordreset' ); } @@ -149,8 +165,8 @@ class SpecialPasswordReset extends FormSpecialPage { $users = array( User::newFromName( $data['Username'] ) ); } elseif ( isset( $data['Email'] ) && $data['Email'] !== '' - && Sanitizer::validateEmail( $data['Email'] ) ) - { + && Sanitizer::validateEmail( $data['Email'] ) + ) { $method = 'email'; $res = wfGetDB( DB_SLAVE )->select( 'user', @@ -158,9 +174,11 @@ class SpecialPasswordReset extends FormSpecialPage { array( 'user_email' => $data['Email'] ), __METHOD__ ); + if ( $res ) { $users = array(); - foreach( $res as $row ){ + + foreach ( $res as $row ) { $users[] = User::newFromRow( $row ); } } else { @@ -178,8 +196,8 @@ class SpecialPasswordReset extends FormSpecialPage { return array( $error ); } - if( count( $users ) == 0 ){ - if( $method == 'email' ){ + if ( count( $users ) == 0 ) { + if ( $method == 'email' ) { // Don't reveal whether or not an email address is in use return true; } else { @@ -202,9 +220,13 @@ class SpecialPasswordReset extends FormSpecialPage { foreach ( $users as $user ) { if ( $user->isPasswordReminderThrottled() ) { global $wgPasswordReminderResendTime; + # Round the time in hours to 3 d.p., in case someone is specifying # minutes or seconds. - return array( array( 'throttled-mailpassword', round( $wgPasswordReminderResendTime, 3 ) ) ); + return array( array( + 'throttled-mailpassword', + round( $wgPasswordReminderResendTime, 3 ) + ) ); } } @@ -237,8 +259,8 @@ class SpecialPasswordReset extends FormSpecialPage { $password = $user->randomPassword(); $user->setNewpassword( $password ); $user->saveSettings(); - $passwords[] = $this->msg( 'passwordreset-emailelement', $user->getName(), $password - )->inLanguage( $userLanguage )->text(); // We'll escape the whole thing later + $passwords[] = $this->msg( 'passwordreset-emailelement', $user->getName(), $password ) + ->inLanguage( $userLanguage )->text(); // We'll escape the whole thing later } $passwordBlock = implode( "\n\n", $passwords ); @@ -247,39 +269,44 @@ class SpecialPasswordReset extends FormSpecialPage { $username, $passwordBlock, count( $passwords ), - Title::newMainPage()->getCanonicalUrl(), + '<' . Title::newMainPage()->getCanonicalURL() . '>', round( $wgNewPasswordExpiry / 86400 ) ); $title = $this->msg( 'passwordreset-emailtitle' ); - $this->result = $firstUser->sendMail( $title->escaped(), $this->email->escaped() ); + $this->result = $firstUser->sendMail( $title->escaped(), $this->email->text() ); - // Blank the email if the user is not supposed to see it - if( !isset( $data['Capture'] ) || !$data['Capture'] ) { + if ( isset( $data['Capture'] ) && $data['Capture'] ) { + // Save the user, will be used if an error occurs when sending the email + $this->firstUser = $firstUser; + } else { + // Blank the email if the user is not supposed to see it $this->email = null; } if ( $this->result->isGood() ) { return true; - } elseif( isset( $data['Capture'] ) && $data['Capture'] ){ + } elseif ( isset( $data['Capture'] ) && $data['Capture'] ) { // The email didn't send, but maybe they knew that and that's why they captured it return true; } else { - // @todo FIXME: The email didn't send, but we have already set the password throttle - // timestamp, so they won't be able to try again until it expires... :( + // @todo FIXME: The email wasn't sent, but we have already set + // the password throttle timestamp, so they won't be able to try + // again until it expires... :( return array( array( 'mailerror', $this->result->getMessage() ) ); } } public function onSuccess() { - if( $this->getUser()->isAllowed( 'passwordreset' ) && $this->email != null ){ - // @todo: Logging + if ( $this->getUser()->isAllowed( 'passwordreset' ) && $this->email != null ) { + // @todo Logging - if( $this->result->isGood() ){ + if ( $this->result->isGood() ) { $this->getOutput()->addWikiMsg( 'passwordreset-emailsent-capture' ); } else { - $this->getOutput()->addWikiMsg( 'passwordreset-emailerror-capture', $this->result->getMessage() ); + $this->getOutput()->addWikiMsg( 'passwordreset-emailerror-capture', + $this->result->getMessage(), $this->firstUser->getName() ); } $this->getOutput()->addHTML( Html::rawElement( 'pre', array(), $this->email->escaped() ) ); @@ -290,12 +317,12 @@ class SpecialPasswordReset extends FormSpecialPage { } protected function canChangePassword( User $user ) { - global $wgPasswordResetRoutes, $wgAuth; + global $wgPasswordResetRoutes, $wgEnableEmail, $wgAuth; // Maybe password resets are disabled, or there are no allowable routes if ( !is_array( $wgPasswordResetRoutes ) || - !in_array( true, array_values( $wgPasswordResetRoutes ) ) ) - { + !in_array( true, array_values( $wgPasswordResetRoutes ) ) + ) { return 'passwordreset-disabled'; } @@ -304,6 +331,11 @@ class SpecialPasswordReset extends FormSpecialPage { return 'resetpass_forbidden'; } + // Maybe email features have been disabled + if ( !$wgEnableEmail ) { + return 'passwordreset-emaildisabled'; + } + // Maybe the user is blocked (check this here rather than relying on the parent // method as we have a more specific error message to use here if ( $user->isBlocked() ) { @@ -324,4 +356,8 @@ class SpecialPasswordReset extends FormSpecialPage { return false; } + + protected function getGroupName() { + return 'users'; + } } diff --git a/includes/specials/SpecialPopularpages.php b/includes/specials/SpecialPopularpages.php index 448d1799..2a80f651 100644 --- a/includes/specials/SpecialPopularpages.php +++ b/includes/specials/SpecialPopularpages.php @@ -27,7 +27,6 @@ * @ingroup SpecialPage */ class PopularPagesPage extends QueryPage { - function __construct( $name = 'Popularpages' ) { parent::__construct( $name ); } @@ -37,30 +36,42 @@ class PopularPagesPage extends QueryPage { return true; } - function isSyndicated() { return false; } + function isSyndicated() { + return false; + } function getQueryInfo() { - return array ( + return array( 'tables' => array( 'page' ), - 'fields' => array( 'namespace' => 'page_namespace', - 'title' => 'page_title', - 'value' => 'page_counter'), - 'conds' => array( 'page_is_redirect' => 0, - 'page_namespace' => MWNamespace::getContentNamespaces() ) ); + 'fields' => array( + 'namespace' => 'page_namespace', + 'title' => 'page_title', + 'value' => 'page_counter' ), + 'conds' => array( + 'page_is_redirect' => 0, + 'page_namespace' => MWNamespace::getContentNamespaces() + ) + ); } /** - * @param $skin Skin - * @param $result + * @param Skin $skin + * @param object $result Result row * @return string */ function formatResult( $skin, $result ) { global $wgContLang; $title = Title::makeTitleSafe( $result->namespace, $result->title ); - if( !$title ) { - return Html::element( 'span', array( 'class' => 'mw-invalidtitle' ), - Linker::getInvalidTitleDescription( $this->getContext(), $result->namespace, $result->title ) ); + if ( !$title ) { + return Html::element( + 'span', + array( 'class' => 'mw-invalidtitle' ), + Linker::getInvalidTitleDescription( + $this->getContext(), + $result->namespace, + $result->title ) + ); } $link = Linker::linkKnown( @@ -68,6 +79,11 @@ class PopularPagesPage extends QueryPage { htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) ) ); $nv = $this->msg( 'nviews' )->numParams( $result->value )->escaped(); + return $this->getLanguage()->specialList( $link, $nv ); } + + protected function getGroupName() { + return 'wiki'; + } } diff --git a/includes/specials/SpecialPreferences.php b/includes/specials/SpecialPreferences.php index c6b2bb6b..ecee0bb7 100644 --- a/includes/specials/SpecialPreferences.php +++ b/includes/specials/SpecialPreferences.php @@ -35,16 +35,21 @@ class SpecialPreferences extends SpecialPage { $this->setHeaders(); $this->outputHeader(); $out = $this->getOutput(); - $out->disallowUserJs(); # Prevent hijacked user scripts from sniffing passwords etc. + $out->disallowUserJs(); # Prevent hijacked user scripts from sniffing passwords etc. $user = $this->getUser(); if ( $user->isAnon() ) { - throw new ErrorPageError( 'prefsnologin', 'prefsnologintext', array( $this->getTitle()->getPrefixedDBkey() ) ); + throw new ErrorPageError( + 'prefsnologin', + 'prefsnologintext', + array( $this->getTitle()->getPrefixedDBkey() ) + ); } $this->checkReadOnly(); if ( $par == 'reset' ) { $this->showResetForm(); + return; } @@ -52,7 +57,7 @@ class SpecialPreferences extends SpecialPage { if ( $this->getRequest()->getCheck( 'success' ) ) { $out->wrapWikiMsg( - "<div class=\"successbox\"><strong>\n$1\n</strong></div><div id=\"mw-pref-clear\"></div>", + "<div class=\"successbox\">\n$1\n</div>", 'savedprefs' ); } @@ -64,12 +69,17 @@ class SpecialPreferences extends SpecialPage { } private function showResetForm() { + if ( !$this->getUser()->isAllowed( 'editmyoptions' ) ) { + throw new PermissionsError( 'editmyoptions' ); + } + $this->getOutput()->addWikiMsg( 'prefs-reset-intro' ); - $htmlForm = new HTMLForm( array(), $this->getContext(), 'prefs-restore' ); + $context = new DerivativeContext( $this->getContext() ); + $context->setTitle( $this->getTitle( 'reset' ) ); // Reset subpage + $htmlForm = new HTMLForm( array(), $context, 'prefs-restore' ); $htmlForm->setSubmitTextMsg( 'restoreprefs' ); - $htmlForm->setTitle( $this->getTitle( 'reset' ) ); $htmlForm->setSubmitCallback( array( $this, 'submitReset' ) ); $htmlForm->suppressReset(); @@ -77,8 +87,12 @@ class SpecialPreferences extends SpecialPage { } public function submitReset( $formData ) { + if ( !$this->getUser()->isAllowed( 'editmyoptions' ) ) { + throw new PermissionsError( 'editmyoptions' ); + } + $user = $this->getUser(); - $user->resetOptions(); + $user->resetOptions( 'all', $this->getContext() ); $user->saveSettings(); $url = $this->getTitle()->getFullURL( 'success' ); @@ -87,4 +101,8 @@ class SpecialPreferences extends SpecialPage { return true; } + + protected function getGroupName() { + return 'users'; + } } diff --git a/includes/specials/SpecialPrefixindex.php b/includes/specials/SpecialPrefixindex.php index 7740b320..28d07ffc 100644 --- a/includes/specials/SpecialPrefixindex.php +++ b/includes/specials/SpecialPrefixindex.php @@ -27,15 +27,24 @@ * @ingroup SpecialPage */ class SpecialPrefixindex extends SpecialAllpages { + + /** + * Whether to remove the searched prefix from the displayed link. Useful + * for inclusion of a set of sub pages in a root page. + */ + protected $stripPrefix = false; + + protected $hideRedirects = false; + // Inherit $maxPerPage - function __construct(){ + function __construct() { parent::__construct( 'Prefixindex' ); } /** * Entry point : initialise variables and call subfunctions. - * @param $par String: becomes "FOO" when called like Special:Prefixindex/FOO (default null) + * @param string $par becomes "FOO" when called like Special:Prefixindex/FOO (default null) */ function execute( $par ) { global $wgContLang; @@ -52,7 +61,8 @@ class SpecialPrefixindex extends SpecialAllpages { $prefix = $request->getVal( 'prefix', '' ); $ns = $request->getIntOrNull( 'namespace' ); $namespace = (int)$ns; // if no namespace given, use 0 (NS_MAIN). - $hideredirects = $request->getBool( 'hideredirects', false ); + $this->hideRedirects = $request->getBool( 'hideredirects', $this->hideRedirects ); + $this->stripPrefix = $request->getBool( 'stripprefix', $this->stripPrefix ); $namespaces = $wgContLang->getNamespaces(); $out->setPageTitle( @@ -62,11 +72,11 @@ class SpecialPrefixindex extends SpecialAllpages { ); $showme = ''; - if( isset( $par ) ) { + if ( isset( $par ) ) { $showme = $par; - } elseif( $prefix != '' ) { + } elseif ( $prefix != '' ) { $showme = $prefix; - } elseif( $from != '' && $ns === null ) { + } elseif ( $from != '' && $ns === null ) { // For back-compat with Special:Allpages // Don't do this if namespace is passed, so paging works when doing NS views. $showme = $from; @@ -74,23 +84,22 @@ class SpecialPrefixindex extends SpecialAllpages { // Bug 27864: if transcluded, show all pages instead of the form. if ( $this->including() || $showme != '' || $ns !== null ) { - $this->showPrefixChunk( $namespace, $showme, $from, $hideredirects ); + $this->showPrefixChunk( $namespace, $showme, $from ); } else { - $out->addHTML( $this->namespacePrefixForm( $namespace, null, $hideredirects ) ); + $out->addHTML( $this->namespacePrefixForm( $namespace, null ) ); } } /** * HTML for the top form * @param $namespace Integer: a namespace constant (default NS_MAIN). - * @param $from String: dbKey we are starting listing at. - * @param $hideredirects Bool: hide redirects (default FALSE) + * @param string $from dbKey we are starting listing at. * @return string */ - function namespacePrefixForm( $namespace = NS_MAIN, $from = '', $hideredirects = false ) { + protected function namespacePrefixForm( $namespace = NS_MAIN, $from = '' ) { global $wgScript; - $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) ); + $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) ); $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); $out .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ); $out .= Xml::openElement( 'fieldset' ); @@ -98,55 +107,61 @@ class SpecialPrefixindex extends SpecialAllpages { $out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) ); $out .= "<tr> <td class='mw-label'>" . - Xml::label( $this->msg( 'allpagesprefix' )->text(), 'nsfrom' ) . - "</td> + Xml::label( $this->msg( 'allpagesprefix' )->text(), 'nsfrom' ) . + "</td> <td class='mw-input'>" . - Xml::input( 'prefix', 30, str_replace('_',' ',$from), array( 'id' => 'nsfrom' ) ) . - "</td> + Xml::input( 'prefix', 30, str_replace( '_', ' ', $from ), array( 'id' => 'nsfrom' ) ) . + "</td> </tr> <tr> - <td class='mw-label'>" . - Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ) . - "</td> + <td class='mw-label'>" . + Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ) . + "</td> <td class='mw-input'>" . - Html::namespaceSelector( array( - 'selected' => $namespace, - ), array( - 'name' => 'namespace', - 'id' => 'namespace', - 'class' => 'namespaceselector', - ) ) . - Xml::checkLabel( - $this->msg( 'allpages-hide-redirects' )->text(), - 'hideredirects', - 'hideredirects', - $hideredirects - ) . ' ' . - Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . - "</td> + Html::namespaceSelector( array( + 'selected' => $namespace, + ), array( + 'name' => 'namespace', + 'id' => 'namespace', + 'class' => 'namespaceselector', + ) ) . + Xml::checkLabel( + $this->msg( 'allpages-hide-redirects' )->text(), + 'hideredirects', + 'hideredirects', + $this->hideRedirects + ) . ' ' . + Xml::checkLabel( + $this->msg( 'prefixindex-strip' )->text(), + 'stripprefix', + 'stripprefix', + $this->stripPrefix + ) . ' ' . + Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . + "</td> </tr>"; $out .= Xml::closeElement( 'table' ); $out .= Xml::closeElement( 'fieldset' ); $out .= Xml::closeElement( 'form' ); $out .= Xml::closeElement( 'div' ); + return $out; } /** * @param $namespace Integer, default NS_MAIN * @param $prefix String - * @param $from String: list all pages from this name (default FALSE) - * @param $hideredirects Bool: hide redirects (default FALSE) + * @param string $from list all pages from this name (default FALSE) */ - function showPrefixChunk( $namespace = NS_MAIN, $prefix, $from = null, $hideredirects = false ) { + protected function showPrefixChunk( $namespace = NS_MAIN, $prefix, $from = null ) { global $wgContLang; if ( $from === null ) { $from = $prefix; } - $fromList = $this->getNamespaceKeyAndText($namespace, $from); - $prefixList = $this->getNamespaceKeyAndText($namespace, $prefix); + $fromList = $this->getNamespaceKeyAndText( $namespace, $from ); + $prefixList = $this->getNamespaceKeyAndText( $namespace, $prefix ); $namespaces = $wgContLang->getNamespaces(); if ( !$prefixList || !$fromList ) { @@ -169,7 +184,7 @@ class SpecialPrefixindex extends SpecialAllpages { 'page_title >= ' . $dbr->addQuotes( $fromKey ), ); - if ( $hideredirects ) { + if ( $this->hideRedirects ) { $conds['page_is_redirect'] = 0; } @@ -178,8 +193,8 @@ class SpecialPrefixindex extends SpecialAllpages { $conds, __METHOD__, array( - 'ORDER BY' => 'page_title', - 'LIMIT' => $this->maxPerPage + 1, + 'ORDER BY' => 'page_title', + 'LIMIT' => $this->maxPerPage + 1, 'USE INDEX' => 'name_title', ) ); @@ -187,34 +202,42 @@ class SpecialPrefixindex extends SpecialAllpages { ### @todo FIXME: Side link to previous $n = 0; - if( $res->numRows() > 0 ) { + if ( $res->numRows() > 0 ) { $out = Xml::openElement( 'table', array( 'id' => 'mw-prefixindex-list-table' ) ); - while( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) { + $prefixLength = strlen( $prefix ); + while ( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) { $t = Title::makeTitle( $s->page_namespace, $s->page_title ); - if( $t ) { - $link = ($s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) . + if ( $t ) { + $displayed = $t->getText(); + // Try not to generate unclickable links + if ( $this->stripPrefix && $prefixLength !== strlen( $displayed ) ) { + $displayed = substr( $displayed, $prefixLength ); + } + $link = ( $s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) . Linker::linkKnown( $t, - htmlspecialchars( $t->getText() ), + htmlspecialchars( $displayed ), $s->page_is_redirect ? array( 'class' => 'mw-redirect' ) : array() ) . - ($s->page_is_redirect ? '</div>' : '' ); + ( $s->page_is_redirect ? '</div>' : '' ); } else { $link = '[[' . htmlspecialchars( $s->page_title ) . ']]'; } - if( $n % 3 == 0 ) { + if ( $n % 3 == 0 ) { $out .= '<tr>'; } $out .= "<td>$link</td>"; $n++; - if( $n % 3 == 0 ) { + if ( $n % 3 == 0 ) { $out .= '</tr>'; } } - if( ($n % 3) != 0 ) { + + if ( $n % 3 != 0 ) { $out .= '</tr>'; } + $out .= Xml::closeElement( 'table' ); } else { $out = ''; @@ -225,37 +248,45 @@ class SpecialPrefixindex extends SpecialAllpages { if ( $this->including() ) { $out2 = ''; } else { - $nsForm = $this->namespacePrefixForm( $namespace, $prefix, $hideredirects ); + $nsForm = $this->namespacePrefixForm( $namespace, $prefix ); $self = $this->getTitle(); - $out2 = Xml::openElement( 'table', array( 'id' => 'mw-prefixindex-nav-table' ) ) . + $out2 = Xml::openElement( 'table', array( 'id' => 'mw-prefixindex-nav-table' ) ) . '<tr> <td>' . - $nsForm . - '</td> - <td id="mw-prefixindex-nav-form" class="mw-prefixindex-nav">'; + $nsForm . + '</td> + <td id="mw-prefixindex-nav-form" class="mw-prefixindex-nav">'; - if( isset( $res ) && $res && ( $n == $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) { + if ( isset( $res ) && $res && ( $n == $this->maxPerPage ) && + ( $s = $res->fetchObject() ) + ) { $query = array( 'from' => $s->page_title, 'prefix' => $prefix, - 'hideredirects' => $hideredirects, + 'hideredirects' => $this->hideRedirects, ); - if( $namespace || ($prefix == '')) { + if ( $namespace || $prefix == '' ) { // Keep the namespace even if it's 0 for empty prefixes. // This tells us we're not just a holdover from old links. $query['namespace'] = $namespace; } + $nextLink = Linker::linkKnown( - $self, - $this->msg( 'nextpage', str_replace( '_',' ', $s->page_title ) )->escaped(), - array(), - $query - ); + $self, + $this->msg( 'nextpage', str_replace( '_', ' ', $s->page_title ) )->escaped(), + array(), + $query + ); + $out2 .= $nextLink; - $footer = "\n" . Html::element( "hr" ) - . Html::rawElement( "div", array( "class" => "mw-prefixindex-nav" ), $nextLink ); + $footer = "\n" . Html::element( 'hr' ) . + Html::rawElement( + 'div', + array( 'class' => 'mw-prefixindex-nav' ), + $nextLink + ); } $out2 .= "</td></tr>" . Xml::closeElement( 'table' ); @@ -263,4 +294,8 @@ class SpecialPrefixindex extends SpecialAllpages { $this->getOutput()->addHTML( $out2 . $out . $footer ); } + + protected function getGroupName() { + return 'pages'; + } } diff --git a/includes/specials/SpecialProtectedpages.php b/includes/specials/SpecialProtectedpages.php index 74ed5378..3de6ea24 100644 --- a/includes/specials/SpecialProtectedpages.php +++ b/includes/specials/SpecialProtectedpages.php @@ -29,7 +29,7 @@ class SpecialProtectedpages extends SpecialPage { protected $IdLevel = 'level'; - protected $IdType = 'type'; + protected $IdType = 'type'; public function __construct() { parent::__construct( 'Protectedpages' ); @@ -40,7 +40,7 @@ class SpecialProtectedpages extends SpecialPage { $this->outputHeader(); // Purge expired entries on one in every 10 queries - if( !mt_rand( 0, 10 ) ) { + if ( !mt_rand( 0, 10 ) ) { Title::purgeExpiredRestrictions(); } @@ -49,19 +49,37 @@ class SpecialProtectedpages extends SpecialPage { $level = $request->getVal( $this->IdLevel ); $sizetype = $request->getVal( 'sizetype' ); $size = $request->getIntOrNull( 'size' ); - $NS = $request->getIntOrNull( 'namespace' ); + $ns = $request->getIntOrNull( 'namespace' ); $indefOnly = $request->getBool( 'indefonly' ) ? 1 : 0; - $cascadeOnly = $request->getBool('cascadeonly') ? 1 : 0; + $cascadeOnly = $request->getBool( 'cascadeonly' ) ? 1 : 0; - $pager = new ProtectedPagesPager( $this, array(), $type, $level, $NS, $sizetype, $size, $indefOnly, $cascadeOnly ); - - $this->getOutput()->addHTML( $this->showOptions( $NS, $type, $level, $sizetype, $size, $indefOnly, $cascadeOnly ) ); + $pager = new ProtectedPagesPager( + $this, + array(), + $type, + $level, + $ns, + $sizetype, + $size, + $indefOnly, + $cascadeOnly + ); - if( $pager->getNumRows() ) { + $this->getOutput()->addHTML( $this->showOptions( + $ns, + $type, + $level, + $sizetype, + $size, + $indefOnly, + $cascadeOnly + ) ); + + if ( $pager->getNumRows() ) { $this->getOutput()->addHTML( $pager->getNavigationBar() . - '<ul>' . $pager->getBody() . '</ul>' . - $pager->getNavigationBar() + '<ul>' . $pager->getBody() . '</ul>' . + $pager->getNavigationBar() ); } else { $this->getOutput()->addWikiMsg( 'protectedpagesempty' ); @@ -78,20 +96,39 @@ class SpecialProtectedpages extends SpecialPage { static $infinity = null; - if( is_null( $infinity ) ){ + if ( is_null( $infinity ) ) { $infinity = wfGetDB( DB_SLAVE )->getInfinity(); } $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title ); + if ( !$title ) { + wfProfileOut( __METHOD__ ); + + return Html::rawElement( + 'li', + array(), + Html::element( + 'span', + array( 'class' => 'mw-invalidtitle' ), + Linker::getInvalidTitleDescription( + $this->getContext(), + $row->page_namespace, + $row->page_title + ) + ) + ) . "\n"; + } + $link = Linker::link( $title ); - $description_items = array (); + $description_items = array(); + // Messages: restriction-level-sysop, restriction-level-autoconfirmed $protType = $this->msg( 'restriction-level-' . $row->pr_level )->escaped(); $description_items[] = $protType; - if( $row->pr_cascade ) { + if ( $row->pr_cascade ) { $description_items[] = $this->msg( 'protect-summary-cascade' )->text(); } @@ -99,7 +136,7 @@ class SpecialProtectedpages extends SpecialPage { $lang = $this->getLanguage(); $expiry = $lang->formatExpiry( $row->pr_expiry, TS_MW ); - if( $expiry != $infinity ) { + if ( $expiry != $infinity ) { $user = $this->getUser(); $description_items[] = $this->msg( 'protect-expiring-local', @@ -109,12 +146,13 @@ class SpecialProtectedpages extends SpecialPage { )->escaped(); } - if(!is_null($size = $row->page_len)) { + if ( !is_null( $size = $row->page_len ) ) { $stxt = $lang->getDirMark() . ' ' . Linker::formatRevisionSize( $size ); } - # Show a link to the change protection form for allowed users otherwise a link to the protection log - if( $this->getUser()->isAllowed( 'protect' ) ) { + // Show a link to the change protection form for allowed users otherwise + // a link to the protection log + if ( $this->getUser()->isAllowed( 'protect' ) ) { $changeProtection = Linker::linkKnown( $title, $this->msg( 'protect_change' )->escaped(), @@ -134,29 +172,36 @@ class SpecialProtectedpages extends SpecialPage { ); } - $changeProtection = ' ' . $this->msg( 'parentheses' )->rawParams( $changeProtection )->escaped(); + $changeProtection = ' ' . $this->msg( 'parentheses' )->rawParams( $changeProtection ) + ->escaped(); wfProfileOut( __METHOD__ ); return Html::rawElement( 'li', array(), - $lang->specialList( $link . $stxt, $lang->commaList( $description_items ), false ) . $changeProtection ) . "\n"; + $lang->specialList( $link . $stxt, $lang->commaList( $description_items ), false ) . + $changeProtection + ) . "\n"; } /** * @param $namespace Integer - * @param $type String: restriction type - * @param $level String: restriction level - * @param $sizetype String: "min" or "max" + * @param string $type restriction type + * @param string $level restriction level + * @param string $sizetype "min" or "max" * @param $size Integer * @param $indefOnly Boolean: only indefinie protection * @param $cascadeOnly Boolean: only cascading protection * @return String: input form */ - protected function showOptions( $namespace, $type='edit', $level, $sizetype, $size, $indefOnly, $cascadeOnly ) { + protected function showOptions( $namespace, $type = 'edit', $level, $sizetype, + $size, $indefOnly, $cascadeOnly + ) { global $wgScript; + $title = $this->getTitle(); + return Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . Xml::openElement( 'fieldset' ) . Xml::element( 'legend', array(), $this->msg( 'protectedpages' )->text() ) . @@ -190,8 +235,8 @@ class SpecialProtectedpages extends SpecialPage { 'all' => '', 'label' => $this->msg( 'namespace' )->text() ), array( - 'name' => 'namespace', - 'id' => 'namespace', + 'name' => 'namespace', + 'id' => 'namespace', 'class' => 'namespaceselector', ) ) @@ -199,31 +244,54 @@ class SpecialProtectedpages extends SpecialPage { } /** + * @param bool $indefOnly * @return string Formatted HTML */ protected function getExpiryCheck( $indefOnly ) { - return - Xml::checkLabel( $this->msg( 'protectedpages-indef' )->text(), 'indefonly', 'indefonly', $indefOnly ) . "\n"; + return Xml::checkLabel( + $this->msg( 'protectedpages-indef' )->text(), + 'indefonly', + 'indefonly', + $indefOnly + ) . "\n"; } /** + * @param bool $cascadeOnly * @return string Formatted HTML */ protected function getCascadeCheck( $cascadeOnly ) { - return - Xml::checkLabel( $this->msg( 'protectedpages-cascade' )->text(), 'cascadeonly', 'cascadeonly', $cascadeOnly ) . "\n"; + return Xml::checkLabel( + $this->msg( 'protectedpages-cascade' )->text(), + 'cascadeonly', + 'cascadeonly', + $cascadeOnly + ) . "\n"; } /** + * @param string $sizetype "min" or "max" + * @param mixed $size * @return string Formatted HTML */ protected function getSizeLimit( $sizetype, $size ) { $max = $sizetype === 'max'; - return - Xml::radioLabel( $this->msg( 'minimum-size' )->text(), 'sizetype', 'min', 'wpmin', !$max ) . + return Xml::radioLabel( + $this->msg( 'minimum-size' )->text(), + 'sizetype', + 'min', + 'wpmin', + !$max + ) . ' ' . - Xml::radioLabel( $this->msg( 'maximum-size' )->text(), 'sizetype', 'max', 'wpmax', $max ) . + Xml::radioLabel( + $this->msg( 'maximum-size' )->text(), + 'sizetype', + 'max', + 'wpmax', + $max + ) . ' ' . Xml::input( 'size', 9, $size, array( 'id' => 'wpsize' ) ) . ' ' . @@ -240,14 +308,15 @@ class SpecialProtectedpages extends SpecialPage { $options = array(); // First pass to load the log names - foreach( Title::getFilteredRestrictionTypes( true ) as $type ) { + foreach ( Title::getFilteredRestrictionTypes( true ) as $type ) { + // Messages: restriction-edit, restriction-move, restriction-create, restriction-upload $text = $this->msg( "restriction-$type" )->text(); $m[$text] = $type; } // Third pass generates sorted XHTML content - foreach( $m as $text => $type ) { - $selected = ($type == $pr_type ); + foreach ( $m as $text => $type ) { + $selected = ( $type == $pr_type ); $options[] = Xml::option( $text, $type, $selected ) . "\n"; } @@ -266,21 +335,22 @@ class SpecialProtectedpages extends SpecialPage { protected function getLevelMenu( $pr_level ) { global $wgRestrictionLevels; - $m = array( $this->msg( 'restriction-level-all' )->text() => 0 ); // Temporary array + // Temporary array + $m = array( $this->msg( 'restriction-level-all' )->text() => 0 ); $options = array(); // First pass to load the log names - foreach( $wgRestrictionLevels as $type ) { + foreach ( $wgRestrictionLevels as $type ) { // Messages used can be 'restriction-level-sysop' and 'restriction-level-autoconfirmed' - if( $type !='' && $type !='*') { + if ( $type != '' && $type != '*' ) { $text = $this->msg( "restriction-level-$type" )->text(); $m[$text] = $type; } } // Third pass generates sorted XHTML content - foreach( $m as $text => $type ) { - $selected = ($type == $pr_level ); + foreach ( $m as $text => $type ) { + $selected = ( $type == $pr_level ); $options[] = Xml::option( $text, $type, $selected ); } @@ -290,6 +360,10 @@ class SpecialProtectedpages extends SpecialPage { array( 'id' => $this->IdLevel, 'name' => $this->IdLevel ), implode( "\n", $options ) ) . "</span>"; } + + protected function getGroupName() { + return 'maintenance'; + } } /** @@ -300,16 +374,16 @@ class ProtectedPagesPager extends AlphabeticPager { public $mForm, $mConds; private $type, $level, $namespace, $sizetype, $size, $indefonly; - function __construct( $form, $conds = array(), $type, $level, $namespace, $sizetype='', $size=0, - $indefonly = false, $cascadeonly = false ) - { + function __construct( $form, $conds = array(), $type, $level, $namespace, + $sizetype = '', $size = 0, $indefonly = false, $cascadeonly = false + ) { $this->mForm = $form; $this->mConds = $conds; $this->type = ( $type ) ? $type : 'edit'; $this->level = $level; $this->namespace = $namespace; $this->sizetype = $sizetype; - $this->size = intval($size); + $this->size = intval( $size ); $this->indefonly = (bool)$indefonly; $this->cascadeonly = (bool)$cascadeonly; parent::__construct( $form->getContext() ); @@ -322,6 +396,7 @@ class ProtectedPagesPager extends AlphabeticPager { $lb->add( $row->page_namespace, $row->page_title ); } $lb->execute(); + return ''; } @@ -332,31 +407,35 @@ class ProtectedPagesPager extends AlphabeticPager { function getQueryInfo() { $conds = $this->mConds; $conds[] = '(pr_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() ) . - 'OR pr_expiry IS NULL)'; + 'OR pr_expiry IS NULL)'; $conds[] = 'page_id=pr_page'; $conds[] = 'pr_type=' . $this->mDb->addQuotes( $this->type ); - if( $this->sizetype=='min' ) { + if ( $this->sizetype == 'min' ) { $conds[] = 'page_len>=' . $this->size; - } elseif( $this->sizetype=='max' ) { + } elseif ( $this->sizetype == 'max' ) { $conds[] = 'page_len<=' . $this->size; } - if( $this->indefonly ) { - $db = wfGetDB( DB_SLAVE ); - $conds[] = "pr_expiry = {$db->addQuotes( $db->getInfinity() )} OR pr_expiry IS NULL"; + if ( $this->indefonly ) { + $infinity = $this->mDb->addQuotes( $this->mDb->getInfinity() ); + $conds[] = "pr_expiry = $infinity OR pr_expiry IS NULL"; } - if( $this->cascadeonly ) { - $conds[] = "pr_cascade = '1'"; + if ( $this->cascadeonly ) { + $conds[] = 'pr_cascade = 1'; } - if( $this->level ) + if ( $this->level ) { $conds[] = 'pr_level=' . $this->mDb->addQuotes( $this->level ); - if( !is_null($this->namespace) ) + } + if ( !is_null( $this->namespace ) ) { $conds[] = 'page_namespace=' . $this->mDb->addQuotes( $this->namespace ); + } + return array( 'tables' => array( 'page_restrictions', 'page' ), - 'fields' => 'pr_id,page_namespace,page_title,page_len,pr_type,pr_level,pr_expiry,pr_cascade', + 'fields' => array( 'pr_id', 'page_namespace', 'page_title', 'page_len', + 'pr_type', 'pr_level', 'pr_expiry', 'pr_cascade' ), 'conds' => $conds ); } diff --git a/includes/specials/SpecialProtectedtitles.php b/includes/specials/SpecialProtectedtitles.php index a80f0d0a..078e7b12 100644 --- a/includes/specials/SpecialProtectedtitles.php +++ b/includes/specials/SpecialProtectedtitles.php @@ -27,9 +27,8 @@ * @ingroup SpecialPage */ class SpecialProtectedtitles extends SpecialPage { - protected $IdLevel = 'level'; - protected $IdType = 'type'; + protected $IdType = 'type'; public function __construct() { parent::__construct( 'Protectedtitles' ); @@ -58,8 +57,8 @@ class SpecialProtectedtitles extends SpecialPage { if ( $pager->getNumRows() ) { $this->getOutput()->addHTML( $pager->getNavigationBar() . - '<ul>' . $pager->getBody() . '</ul>' . - $pager->getNavigationBar() + '<ul>' . $pager->getBody() . '</ul>' . + $pager->getNavigationBar() ); } else { $this->getOutput()->addWikiMsg( 'protectedtitlesempty' ); @@ -69,6 +68,7 @@ class SpecialProtectedtitles extends SpecialPage { /** * Callback function to output a restriction * + * @param object $row Database row * @return string */ function formatRow( $row ) { @@ -76,22 +76,40 @@ class SpecialProtectedtitles extends SpecialPage { static $infinity = null; - if( is_null( $infinity ) ){ + if ( is_null( $infinity ) ) { $infinity = wfGetDB( DB_SLAVE )->getInfinity(); } $title = Title::makeTitleSafe( $row->pt_namespace, $row->pt_title ); - $link = Linker::link( $title ); - - $description_items = array (); + if ( !$title ) { + wfProfileOut( __METHOD__ ); + + return Html::rawElement( + 'li', + array(), + Html::element( + 'span', + array( 'class' => 'mw-invalidtitle' ), + Linker::getInvalidTitleDescription( + $this->getContext(), + $row->pt_namespace, + $row->pt_title + ) + ) + ) . "\n"; + } + $link = Linker::link( $title ); + $description_items = array(); + // Messages: restriction-level-sysop, restriction-level-autoconfirmed $protType = $this->msg( 'restriction-level-' . $row->pt_create_perm )->escaped(); - $description_items[] = $protType; - $lang = $this->getLanguage(); - $expiry = strlen( $row->pt_expiry ) ? $lang->formatExpiry( $row->pt_expiry, TS_MW ) : $infinity; - if( $expiry != $infinity ) { + $expiry = strlen( $row->pt_expiry ) ? + $lang->formatExpiry( $row->pt_expiry, TS_MW ) : + $infinity; + + if ( $expiry != $infinity ) { $user = $this->getUser(); $description_items[] = $this->msg( 'protect-expiring-local', @@ -103,6 +121,7 @@ class SpecialProtectedtitles extends SpecialPage { wfProfileOut( __METHOD__ ); + // @todo i18n: This should use a comma separator instead of a hard coded comma, right? return '<li>' . $lang->specialList( $link, implode( $description_items, ', ' ) ) . "</li>\n"; } @@ -113,11 +132,12 @@ class SpecialProtectedtitles extends SpecialPage { * @return string * @private */ - function showOptions( $namespace, $type='edit', $level ) { + function showOptions( $namespace, $type = 'edit', $level ) { global $wgScript; $action = htmlspecialchars( $wgScript ); $title = $this->getTitle(); $special = htmlspecialchars( $title->getPrefixedDBkey() ); + return "<form action=\"$action\" method=\"get\">\n" . '<fieldset>' . Xml::element( 'legend', array(), $this->msg( 'protectedtitles' )->text() ) . @@ -142,46 +162,53 @@ class SpecialProtectedtitles extends SpecialPage { 'all' => '', 'label' => $this->msg( 'namespace' )->text() ), array( - 'name' => 'namespace', - 'id' => 'namespace', + 'name' => 'namespace', + 'id' => 'namespace', 'class' => 'namespaceselector', ) ); } /** + * @param string $pr_level Determines which option is selected as default * @return string Formatted HTML * @private */ function getLevelMenu( $pr_level ) { global $wgRestrictionLevels; - $m = array( $this->msg( 'restriction-level-all' )->text() => 0 ); // Temporary array + // Temporary array + $m = array( $this->msg( 'restriction-level-all' )->text() => 0 ); $options = array(); // First pass to load the log names - foreach( $wgRestrictionLevels as $type ) { - if ( $type !='' && $type !='*') { + foreach ( $wgRestrictionLevels as $type ) { + if ( $type != '' && $type != '*' ) { + // Messages: restriction-level-sysop, restriction-level-autoconfirmed $text = $this->msg( "restriction-level-$type" )->text(); $m[$text] = $type; } } + // Is there only one level (aside from "all")? - if( count($m) <= 2 ) { + if ( count( $m ) <= 2 ) { return ''; } // Third pass generates sorted XHTML content - foreach( $m as $text => $type ) { - $selected = ($type == $pr_level ); + foreach ( $m as $text => $type ) { + $selected = ( $type == $pr_level ); $options[] = Xml::option( $text, $type, $selected ); } - return - Xml::label( $this->msg( 'restriction-level' )->text(), $this->IdLevel ) . ' ' . + return Xml::label( $this->msg( 'restriction-level' )->text(), $this->IdLevel ) . ' ' . Xml::tags( 'select', array( 'id' => $this->IdLevel, 'name' => $this->IdLevel ), implode( "\n", $options ) ); } + + protected function getGroupName() { + return 'maintenance'; + } } /** @@ -191,12 +218,14 @@ class SpecialProtectedtitles extends SpecialPage { class ProtectedTitlesPager extends AlphabeticPager { public $mForm, $mConds; - function __construct( $form, $conds = array(), $type, $level, $namespace, $sizetype='', $size=0 ) { + function __construct( $form, $conds = array(), $type, $level, $namespace, + $sizetype = '', $size = 0 + ) { $this->mForm = $form; $this->mConds = $conds; $this->level = $level; $this->namespace = $namespace; - $this->size = intval($size); + $this->size = intval( $size ); parent::__construct( $form->getContext() ); } @@ -212,6 +241,7 @@ class ProtectedTitlesPager extends AlphabeticPager { $lb->execute(); wfProfileOut( __METHOD__ ); + return ''; } @@ -232,13 +262,18 @@ class ProtectedTitlesPager extends AlphabeticPager { function getQueryInfo() { $conds = $this->mConds; $conds[] = 'pt_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() ); - if( $this->level ) + if ( $this->level ) { $conds['pt_create_perm'] = $this->level; - if( !is_null($this->namespace) ) + } + + if ( !is_null( $this->namespace ) ) { $conds[] = 'pt_namespace=' . $this->mDb->addQuotes( $this->namespace ); + } + return array( 'tables' => 'protected_titles', - 'fields' => 'pt_namespace,pt_title,pt_create_perm,pt_expiry,pt_timestamp', + 'fields' => array( 'pt_namespace', 'pt_title', 'pt_create_perm', + 'pt_expiry', 'pt_timestamp' ), 'conds' => $conds ); } @@ -247,4 +282,3 @@ class ProtectedTitlesPager extends AlphabeticPager { return 'pt_timestamp'; } } - diff --git a/includes/specials/SpecialRandomInCategory.php b/includes/specials/SpecialRandomInCategory.php new file mode 100644 index 00000000..0e022bfa --- /dev/null +++ b/includes/specials/SpecialRandomInCategory.php @@ -0,0 +1,291 @@ +<?php +/** + * Implements Special:RandomInCategory + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage + * @author Brian Wolff + */ + +/** + * Special page to direct the user to a random page + * + * @note The method used here is rather biased. It is assumed that + * the use of this page will be people wanting to get a random page + * out of a maintenance category, to fix it up. The method used by + * this page should return different pages in an unpredictable fashion + * which is hoped to be sufficient, even if some pages are selected + * more often than others. + * + * A more unbiased method could be achieved by adding a cl_random field + * to the categorylinks table. + * + * The method used here is as follows: + * * Find the smallest and largest timestamp in the category + * * Pick a random timestamp in between + * * Pick an offset between 0 and 30 + * * Get the offset'ed page that is newer than the timestamp selected + * The offset is meant to counter the fact the timestamps aren't usually + * uniformly distributed, so if things are very non-uniform at least we + * won't have the same page selected 99% of the time. + * + * @ingroup SpecialPage + */ +class SpecialRandomInCategory extends SpecialPage { + protected $extra = array(); // Extra SQL statements + protected $category = false; // Title object of category + protected $maxOffset = 30; // Max amount to fudge randomness by. + private $maxTimestamp = null; + private $minTimestamp = null; + + public function __construct( $name = 'RandomInCategory' ) { + parent::__construct( $name ); + } + + /** + * Set which category to use. + * @param Title $cat + */ + public function setCategory( Title $cat ) { + $this->category = $cat; + $this->maxTimestamp = null; + $this->minTimestamp = null; + } + + public function execute( $par ) { + global $wgScript; + + $cat = false; + + $categoryStr = $this->getRequest()->getText( 'category', $par ); + + if ( $categoryStr ) { + $cat = Title::newFromText( $categoryStr, NS_CATEGORY ); + } + + if ( $cat && $cat->getNamespace() !== NS_CATEGORY ) { + // Someone searching for something like "Wikipedia:Foo" + $cat = Title::makeTitleSafe( NS_CATEGORY, $categoryStr ); + } + + if ( $cat ) { + $this->setCategory( $cat ); + } + + + if ( !$this->category && $categoryStr ) { + $this->setHeaders(); + $this->getOutput()->addWikiMsg( 'randomincategory-invalidcategory', + wfEscapeWikiText( $categoryStr ) ); + + return; + } elseif ( !$this->category ) { + $this->setHeaders(); + $input = Html::input( 'category' ); + $submitText = $this->msg( 'randomincategory-selectcategory-submit' )->text(); + $submit = Html::input( '', $submitText, 'submit' ); + + $msg = $this->msg( 'randomincategory-selectcategory' ); + $form = Html::rawElement( 'form', array( 'action' => $wgScript ), + Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . + $msg->rawParams( $input, $submit )->parse() + ); + $this->getOutput()->addHtml( $form ); + + return; + } + + $title = $this->getRandomTitle(); + + if ( is_null( $title ) ) { + $this->setHeaders(); + $this->getOutput()->addWikiMsg( 'randomincategory-nopages', + $this->category->getText() ); + + return; + } + + $query = $this->getRequest()->getValues(); + unset( $query['title'] ); + unset( $query['category'] ); + $this->getOutput()->redirect( $title->getFullURL( $query ) ); + } + + /** + * Choose a random title. + * @return Title object (or null if nothing to choose from) + */ + public function getRandomTitle() { + // Convert to float, since we do math with the random number. + $rand = (float)wfRandom(); + $title = null; + + // Given that timestamps are rather unevenly distributed, we also + // use an offset between 0 and 30 to make any biases less noticeable. + $offset = mt_rand( 0, $this->maxOffset ); + + if ( mt_rand( 0, 1 ) ) { + $up = true; + } else { + $up = false; + } + + $row = $this->selectRandomPageFromDB( $rand, $offset, $up ); + + // Try again without the timestamp offset (wrap around the end) + if ( !$row ) { + $row = $this->selectRandomPageFromDB( false, $offset, $up ); + } + + // Maybe the category is really small and offset too high + if ( !$row ) { + $row = $this->selectRandomPageFromDB( $rand, 0, $up ); + } + + // Just get the first entry. + if ( !$row ) { + $row = $this->selectRandomPageFromDB( false, 0, true ); + } + + if ( $row ) { + return Title::makeTitle( $row->page_namespace, $row->page_title ); + } + + return null; + } + + /** + * @param float $rand Random number between 0 and 1 + * @param int $offset Extra offset to fudge randomness + * @param bool $up True to get the result above the random number, false for below + * + * @note The $up parameter is supposed to counteract what would happen if there + * was a large gap in the distribution of cl_timestamp values. This way instead + * of things to the right of the gap being favoured, both sides of the gap + * are favoured. + * @return Array Query information. + */ + protected function getQueryInfo( $rand, $offset, $up ) { + $op = $up ? '>=' : '<='; + $dir = $up ? 'ASC' : 'DESC'; + if ( !$this->category instanceof Title ) { + throw new MWException( 'No category set' ); + } + $qi = array( + 'tables' => array( 'categorylinks', 'page' ), + 'fields' => array( 'page_title', 'page_namespace' ), + 'conds' => array_merge( array( + 'cl_to' => $this->category->getDBKey(), + ), $this->extra ), + 'options' => array( + 'ORDER BY' => 'cl_timestamp ' . $dir, + 'LIMIT' => 1, + 'OFFSET' => $offset + ), + 'join_conds' => array( + 'page' => array( 'INNER JOIN', 'cl_from = page_id' ) + ) + ); + + $dbr = wfGetDB( DB_SLAVE ); + $minClTime = $this->getTimestampOffset( $rand ); + if ( $minClTime ) { + $qi['conds'][] = 'cl_timestamp ' . $op . ' ' . + $dbr->addQuotes( $dbr->timestamp( $minClTime ) ); + } + return $qi; + } + + /** + * @param float $rand Random number between 0 and 1 + * + * @return int|bool A random (unix) timestamp from the range of the category or false on failure + */ + protected function getTimestampOffset( $rand ) { + if ( $rand === false ) { + return false; + } + if ( !$this->minTimestamp || !$this->maxTimestamp ) { + try { + list( $this->minTimestamp, $this->maxTimestamp ) = $this->getMinAndMaxForCat( $this->category ); + } catch ( MWException $e ) { + // Possibly no entries in category. + return false; + } + } + + $ts = ( $this->maxTimestamp - $this->minTimestamp ) * $rand + $this->minTimestamp; + return intval( $ts ); + } + + /** + * Get the lowest and highest timestamp for a category. + * + * @param Title $category + * @return Array The lowest and highest timestamp + * @throws MWException if category has no entries. + */ + protected function getMinAndMaxForCat( Title $category ) { + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->selectRow( + 'categorylinks', + array( + 'low' => 'MIN( cl_timestamp )', + 'high' => 'MAX( cl_timestamp )' + ), + array( + 'cl_to' => $this->category->getDBKey(), + ), + __METHOD__, + array( + 'LIMIT' => 1 + ) + ); + if ( !$res ) { + throw new MWException( 'No entries in category' ); + } + return array( wfTimestamp( TS_UNIX, $res->low ), wfTimestamp( TS_UNIX, $res->high ) ); + } + + /** + * @param float $rand A random number that is converted to a random timestamp + * @param int $offset A small offset to make the result seem more "random" + * @param bool $up Get the result above the random value + * @param String $fname The name of the calling method + * @return Array Info for the title selected. + */ + private function selectRandomPageFromDB( $rand, $offset, $up, $fname = __METHOD__ ) { + $dbr = wfGetDB( DB_SLAVE ); + + $query = $this->getQueryInfo( $rand, $offset, $up ); + $res = $dbr->select( + $query['tables'], + $query['fields'], + $query['conds'], + $fname, + $query['options'], + $query['join_conds'] + ); + + return $res->fetchObject(); + } + + protected function getGroupName() { + return 'redirects'; + } +} diff --git a/includes/specials/SpecialRandompage.php b/includes/specials/SpecialRandompage.php index 307088ed..c94d2b35 100644 --- a/includes/specials/SpecialRandompage.php +++ b/includes/specials/SpecialRandompage.php @@ -28,11 +28,11 @@ * @ingroup SpecialPage */ class RandomPage extends SpecialPage { - private $namespaces; // namespaces to select pages from + private $namespaces; // namespaces to select pages from protected $isRedir = false; // should the result be a redirect? protected $extra = array(); // Extra SQL statements - public function __construct( $name = 'Randompage' ){ + public function __construct( $name = 'Randompage' ) { $this->namespaces = MWNamespace::getContentNamespaces(); parent::__construct( $name ); } @@ -41,15 +41,15 @@ class RandomPage extends SpecialPage { return $this->namespaces; } - public function setNamespace ( $ns ) { - if( !$ns || $ns < NS_MAIN ) { + public function setNamespace( $ns ) { + if ( !$ns || $ns < NS_MAIN ) { $ns = NS_MAIN; } $this->namespaces = array( $ns ); } // select redirects instead of normal pages? - public function isRedirect(){ + public function isRedirect() { return $this->isRedir; } @@ -62,17 +62,19 @@ class RandomPage extends SpecialPage { $title = $this->getRandomTitle(); - if( is_null( $title ) ) { + if ( is_null( $title ) ) { $this->setHeaders(); + // Message: randompage-nopages, randomredirect-nopages $this->getOutput()->addWikiMsg( strtolower( $this->getName() ) . '-nopages', $this->getNsList(), count( $this->namespaces ) ); + return; } $redirectParam = $this->isRedirect() ? array( 'redirect' => 'no' ) : array(); $query = array_merge( $this->getRequest()->getValues(), $redirectParam ); unset( $query['title'] ); - $this->getOutput()->redirect( $title->getFullUrl( $query ) ); + $this->getOutput()->redirect( $title->getFullURL( $query ) ); } /** @@ -83,13 +85,14 @@ class RandomPage extends SpecialPage { private function getNsList() { global $wgContLang; $nsNames = array(); - foreach( $this->namespaces as $n ) { - if( $n === NS_MAIN ) { + foreach ( $this->namespaces as $n ) { + if ( $n === NS_MAIN ) { $nsNames[] = $this->msg( 'blanknamespace' )->plain(); } else { $nsNames[] = $wgContLang->getNsText( $n ); } } + return $wgContLang->commaList( $nsNames ); } @@ -100,10 +103,14 @@ class RandomPage extends SpecialPage { public function getRandomTitle() { $randstr = wfRandom(); $title = null; - if ( !wfRunHooks( 'SpecialRandomGetRandomTitle', array( &$randstr, &$this->isRedir, &$this->namespaces, - &$this->extra, &$title ) ) ) { + + if ( !wfRunHooks( + 'SpecialRandomGetRandomTitle', + array( &$randstr, &$this->isRedir, &$this->namespaces, &$this->extra, &$title ) + ) ) { return $title; } + $row = $this->selectRandomPageFromDB( $randstr ); /* If we picked a value that was higher than any in @@ -113,15 +120,15 @@ class RandomPage extends SpecialPage { * any more bias than what the page_random scheme * causes anyway. Trust me, I'm a mathematician. :) */ - if( !$row ) { + if ( !$row ) { $row = $this->selectRandomPageFromDB( "0" ); } - if( $row ) { + if ( $row ) { return Title::makeTitleSafe( $row->page_namespace, $row->page_title ); - } else { - return null; } + + return null; } protected function getQueryInfo( $randstr ) { @@ -159,4 +166,8 @@ class RandomPage extends SpecialPage { return $dbr->fetchObject( $res ); } + + protected function getGroupName() { + return 'redirects'; + } } diff --git a/includes/specials/SpecialRandomredirect.php b/includes/specials/SpecialRandomredirect.php index 88c81b31..7c36a28a 100644 --- a/includes/specials/SpecialRandomredirect.php +++ b/includes/specials/SpecialRandomredirect.php @@ -28,9 +28,8 @@ * @ingroup SpecialPage */ class SpecialRandomredirect extends RandomPage { - function __construct(){ + function __construct() { parent::__construct( 'Randomredirect' ); $this->isRedir = true; } - } diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php index 2bd8b0a9..a42a2171 100644 --- a/includes/specials/SpecialRecentchanges.php +++ b/includes/specials/SpecialRecentchanges.php @@ -41,17 +41,18 @@ class SpecialRecentChanges extends IncludableSpecialPage { */ public function getDefaultOptions() { $opts = new FormOptions(); + $user = $this->getUser(); - $opts->add( 'days', (int)$this->getUser()->getOption( 'rcdays' ) ); - $opts->add( 'limit', (int)$this->getUser()->getOption( 'rclimit' ) ); + $opts->add( 'days', $user->getIntOption( 'rcdays' ) ); + $opts->add( 'limit', $user->getIntOption( 'rclimit' ) ); $opts->add( 'from', '' ); - $opts->add( 'hideminor', $this->getUser()->getBoolOption( 'hideminor' ) ); - $opts->add( 'hidebots', true ); - $opts->add( 'hideanons', false ); - $opts->add( 'hideliu', false ); - $opts->add( 'hidepatrolled', $this->getUser()->getBoolOption( 'hidepatrolled' ) ); - $opts->add( 'hidemyself', false ); + $opts->add( 'hideminor', $user->getBoolOption( 'hideminor' ) ); + $opts->add( 'hidebots', true ); + $opts->add( 'hideanons', false ); + $opts->add( 'hideliu', false ); + $opts->add( 'hidepatrolled', $user->getBoolOption( 'hidepatrolled' ) ); + $opts->add( 'hidemyself', false ); $opts->add( 'namespace', '', FormOptions::INTNULL ); $opts->add( 'invert', false ); @@ -60,44 +61,47 @@ class SpecialRecentChanges extends IncludableSpecialPage { $opts->add( 'categories', '' ); $opts->add( 'categories_any', false ); $opts->add( 'tagfilter', '' ); + return $opts; } /** * Create a FormOptions object with options as specified by the user * - * @param $parameters array + * @param array $parameters * * @return FormOptions */ public function setup( $parameters ) { $opts = $this->getDefaultOptions(); - foreach( $this->getCustomFilters() as $key => $params ) { + foreach ( $this->getCustomFilters() as $key => $params ) { $opts->add( $key, $params['default'] ); } $opts->fetchValuesFromRequest( $this->getRequest() ); // Give precedence to subpage syntax - if( $parameters !== null ) { + if ( $parameters !== null ) { $this->parseParameters( $parameters, $opts ); } $opts->validateIntBounds( 'limit', 0, 5000 ); + return $opts; } /** * Get custom show/hide filters * - * @return Array Map of filter URL param names to properties (msg/default) + * @return array Map of filter URL param names to properties (msg/default) */ protected function getCustomFilters() { if ( $this->customFilters === null ) { $this->customFilters = array(); wfRunHooks( 'SpecialRecentChangesFilters', array( $this, &$this->customFilters ) ); } + return $this->customFilters; } @@ -109,9 +113,9 @@ class SpecialRecentChanges extends IncludableSpecialPage { public function feedSetup() { global $wgFeedLimit; $opts = $this->getDefaultOptions(); - # Feed is cached on limit,hideminor,namespace; other params would randomly not work - $opts->fetchValuesFromRequest( $this->getRequest(), array( 'limit', 'hideminor', 'namespace' ) ); + $opts->fetchValuesFromRequest( $this->getRequest() ); $opts->validateIntBounds( 'limit', 0, $wgFeedLimit ); + return $opts; } @@ -127,14 +131,14 @@ class SpecialRecentChanges extends IncludableSpecialPage { } $this->rcOptions = $isFeed ? $this->feedSetup() : $this->setup( $this->rcSubpage ); } + return $this->rcOptions; } - /** * Main execution point * - * @param $subpage String + * @param string $subpage */ public function execute( $subpage ) { $this->rcSubpage = $subpage; @@ -144,36 +148,38 @@ class SpecialRecentChanges extends IncludableSpecialPage { $this->getOutput()->setSquidMaxage( 10 ); # Check if the client has a cached version $lastmod = $this->checkLastModified( $feedFormat ); - if( $lastmod === false ) { + if ( $lastmod === false ) { return; } $opts = $this->getOptions(); $this->setHeaders(); $this->outputHeader(); - $this->addRecentChangesJS(); + $this->addModules(); // Fetch results, prepare a batch link existence check query $conds = $this->buildMainQueryConds( $opts ); $rows = $this->doMainQuery( $conds, $opts ); - if( $rows === false ){ - if( !$this->including() ) { + if ( $rows === false ) { + if ( !$this->including() ) { $this->doHeader( $opts ); } + return; } - if( !$feedFormat ) { + if ( !$feedFormat ) { $batch = new LinkBatch; - foreach( $rows as $row ) { + foreach ( $rows as $row ) { $batch->add( NS_USER, $row->rc_user_text ); $batch->add( NS_USER_TALK, $row->rc_user_text ); $batch->add( $row->rc_namespace, $row->rc_title ); } $batch->execute(); } - if( $feedFormat ) { + if ( $feedFormat ) { list( $changesFeed, $formatter ) = $this->getFeedObject( $feedFormat ); + /** @var ChangesFeed $changesFeed */ $changesFeed->execute( $formatter, $rows, $lastmod, $opts ); } else { $this->webOutput( $rows, $opts ); @@ -185,15 +191,17 @@ class SpecialRecentChanges extends IncludableSpecialPage { /** * Return an array with a ChangesFeed object and ChannelFeed object * - * @return Array + * @param string $feedFormat Feed's format (either 'rss' or 'atom') + * @return array */ - public function getFeedObject( $feedFormat ){ + public function getFeedObject( $feedFormat ) { $changesFeed = new ChangesFeed( $feedFormat, 'rcfeed' ); $formatter = $changesFeed->getFeedObject( $this->msg( 'recentchanges' )->inContentLanguage()->text(), $this->msg( 'recentchanges-feed-description' )->inContentLanguage()->text(), $this->getTitle()->getFullURL() ); + return array( $changesFeed, $formatter ); } @@ -201,49 +209,49 @@ class SpecialRecentChanges extends IncludableSpecialPage { * Process $par and put options found if $opts * Mainly used when including the page * - * @param $par String - * @param $opts FormOptions + * @param string $par + * @param FormOptions $opts */ public function parseParameters( $par, FormOptions $opts ) { $bits = preg_split( '/\s*,\s*/', trim( $par ) ); - foreach( $bits as $bit ) { - if( 'hidebots' === $bit ) { + foreach ( $bits as $bit ) { + if ( 'hidebots' === $bit ) { $opts['hidebots'] = true; } - if( 'bots' === $bit ) { + if ( 'bots' === $bit ) { $opts['hidebots'] = false; } - if( 'hideminor' === $bit ) { + if ( 'hideminor' === $bit ) { $opts['hideminor'] = true; } - if( 'minor' === $bit ) { + if ( 'minor' === $bit ) { $opts['hideminor'] = false; } - if( 'hideliu' === $bit ) { + if ( 'hideliu' === $bit ) { $opts['hideliu'] = true; } - if( 'hidepatrolled' === $bit ) { + if ( 'hidepatrolled' === $bit ) { $opts['hidepatrolled'] = true; } - if( 'hideanons' === $bit ) { + if ( 'hideanons' === $bit ) { $opts['hideanons'] = true; } - if( 'hidemyself' === $bit ) { + if ( 'hidemyself' === $bit ) { $opts['hidemyself'] = true; } - if( is_numeric( $bit ) ) { - $opts['limit'] = $bit; + if ( is_numeric( $bit ) ) { + $opts['limit'] = $bit; } $m = array(); - if( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) { + if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) { $opts['limit'] = $m[1]; } - if( preg_match( '/^days=(\d+)$/', $bit, $m ) ) { + if ( preg_match( '/^days=(\d+)$/', $bit, $m ) ) { $opts['days'] = $m[1]; } - if( preg_match( '/^namespace=(\d+)$/', $bit, $m ) ) { + if ( preg_match( '/^namespace=(\d+)$/', $bit, $m ) ) { $opts['namespace'] = $m[1]; } } @@ -254,25 +262,26 @@ class SpecialRecentChanges extends IncludableSpecialPage { * Don't use this if we are using the patrol feature, patrol changes don't * update the timestamp * - * @param $feedFormat String - * @return String or false + * @param string $feedFormat + * @return string|bool */ public function checkLastModified( $feedFormat ) { $dbr = wfGetDB( DB_SLAVE ); $lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', false, __METHOD__ ); - if( $feedFormat || !$this->getUser()->useRCPatrol() ) { - if( $lastmod && $this->getOutput()->checkLastModified( $lastmod ) ) { + if ( $feedFormat || !$this->getUser()->useRCPatrol() ) { + if ( $lastmod && $this->getOutput()->checkLastModified( $lastmod ) ) { # Client cache fresh and headers sent, nothing more to do. return false; } } + return $lastmod; } /** * Return an array of conditions depending of options set in $opts * - * @param $opts FormOptions + * @param FormOptions $opts * @return array */ public function buildMainQueryConds( FormOptions $opts ) { @@ -282,9 +291,9 @@ class SpecialRecentChanges extends IncludableSpecialPage { # It makes no sense to hide both anons and logged-in users # Where this occurs, force anons to be shown $forcebot = false; - if( $opts['hideanons'] && $opts['hideliu'] ){ + if ( $opts['hideanons'] && $opts['hideliu'] ) { # Check if the user wants to show bots only - if( $opts['hidebots'] ){ + if ( $opts['hidebots'] ) { $opts['hideanons'] = false; } else { $forcebot = true; @@ -294,12 +303,12 @@ class SpecialRecentChanges extends IncludableSpecialPage { // Calculate cutoff $cutoff_unixtime = time() - ( $opts['days'] * 86400 ); - $cutoff_unixtime = $cutoff_unixtime - ($cutoff_unixtime % 86400); + $cutoff_unixtime = $cutoff_unixtime - ( $cutoff_unixtime % 86400 ); $cutoff = $dbr->timestamp( $cutoff_unixtime ); - $fromValid = preg_match('/^[0-9]{14}$/', $opts['from']); - if( $fromValid && $opts['from'] > wfTimestamp(TS_MW,$cutoff) ) { - $cutoff = $dbr->timestamp($opts['from']); + $fromValid = preg_match( '/^[0-9]{14}$/', $opts['from'] ); + if ( $fromValid && $opts['from'] > wfTimestamp( TS_MW, $cutoff ) ) { + $cutoff = $dbr->timestamp( $opts['from'] ); } else { $opts->reset( 'from' ); } @@ -310,27 +319,27 @@ class SpecialRecentChanges extends IncludableSpecialPage { $hideLoggedInUsers = $opts['hideliu'] && !$forcebot; $hideAnonymousUsers = $opts['hideanons'] && !$forcebot; - if( $opts['hideminor'] ) { + if ( $opts['hideminor'] ) { $conds['rc_minor'] = 0; } - if( $opts['hidebots'] ) { + if ( $opts['hidebots'] ) { $conds['rc_bot'] = 0; } - if( $hidePatrol ) { + if ( $hidePatrol ) { $conds['rc_patrolled'] = 0; } - if( $forcebot ) { + if ( $forcebot ) { $conds['rc_bot'] = 1; } - if( $hideLoggedInUsers ) { + if ( $hideLoggedInUsers ) { $conds[] = 'rc_user = 0'; } - if( $hideAnonymousUsers ) { + if ( $hideAnonymousUsers ) { $conds[] = 'rc_user != 0'; } - if( $opts['hidemyself'] ) { - if( $this->getUser()->getId() ) { + if ( $opts['hidemyself'] ) { + if ( $this->getUser()->getId() ) { $conds[] = 'rc_user != ' . $dbr->addQuotes( $this->getUser()->getId() ); } else { $conds[] = 'rc_user_text != ' . $dbr->addQuotes( $this->getUser()->getName() ); @@ -338,13 +347,13 @@ class SpecialRecentChanges extends IncludableSpecialPage { } # Namespace filtering - if( $opts['namespace'] !== '' ) { + if ( $opts['namespace'] !== '' ) { $selectedNS = $dbr->addQuotes( $opts['namespace'] ); - $operator = $opts['invert'] ? '!=' : '='; - $boolean = $opts['invert'] ? 'AND' : 'OR'; + $operator = $opts['invert'] ? '!=' : '='; + $boolean = $opts['invert'] ? 'AND' : 'OR'; # namespace association (bug 2429) - if( !$opts['associated'] ) { + if ( !$opts['associated'] ) { $condition = "rc_namespace $operator $selectedNS"; } else { # Also add the associated namespace @@ -352,21 +361,22 @@ class SpecialRecentChanges extends IncludableSpecialPage { MWNamespace::getAssociated( $opts['namespace'] ) ); $condition = "(rc_namespace $operator $selectedNS " - . $boolean - . " rc_namespace $operator $associatedNS)"; + . $boolean + . " rc_namespace $operator $associatedNS)"; } $conds[] = $condition; } + return $conds; } /** * Process the query * - * @param $conds Array - * @param $opts FormOptions - * @return bool|ResultWrapper result or false (for Recentchangeslinked only) + * @param array $conds + * @param FormOptions $opts + * @return bool|ResultWrapper Result or false (for Recentchangeslinked only) */ public function doMainQuery( $conds, $opts ) { $tables = array( 'recentchanges' ); @@ -382,19 +392,22 @@ class SpecialRecentChanges extends IncludableSpecialPage { $invert = $opts['invert']; $associated = $opts['associated']; - $fields = array( $dbr->tableName( 'recentchanges' ) . '.*' ); // all rc columns + $fields = RecentChange::selectFields(); // JOIN on watchlist for users - if ( $uid ) { + if ( $uid && $this->getUser()->isAllowed( 'viewmywatchlist' ) ) { $tables[] = 'watchlist'; $fields[] = 'wl_user'; $fields[] = 'wl_notificationtimestamp'; - $join_conds['watchlist'] = array('LEFT JOIN', - "wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace"); + $join_conds['watchlist'] = array( 'LEFT JOIN', array( + 'wl_user' => $uid, + 'wl_title=rc_title', + 'wl_namespace=rc_namespace' + ) ); } if ( $this->getUser()->isAllowed( 'rollback' ) ) { $tables[] = 'page'; $fields[] = 'page_latest'; - $join_conds['page'] = array('LEFT JOIN', 'rc_cur_id=page_id'); + $join_conds['page'] = array( 'LEFT JOIN', 'rc_cur_id=page_id' ); } // Tag stuff. ChangeTags::modifyDisplayQuery( @@ -407,86 +420,40 @@ class SpecialRecentChanges extends IncludableSpecialPage { ); if ( !wfRunHooks( 'SpecialRecentChangesQuery', - array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$fields ) ) ) - { + array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$fields ) ) + ) { return false; } - // Don't use the new_namespace_time timestamp index if: - // (a) "All namespaces" selected - // (b) We want pages in more than one namespace (inverted/associated) - // (c) There is a tag to filter on (use tag index instead) - // (d) UNION + sort/limit is not an option for the DBMS - if( $namespace === '' - || ( $invert || $associated ) - || $opts['tagfilter'] != '' - || !$dbr->unionSupportsOrderAndLimit() ) - { - $res = $dbr->select( $tables, $fields, $conds, __METHOD__, - array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit ) + - $query_options, - $join_conds ); - // We have a new_namespace_time index! UNION over new=(0,1) and sort result set! - } else { - // New pages - $sqlNew = $dbr->selectSQLText( - $tables, - $fields, - array( 'rc_new' => 1 ) + $conds, - __METHOD__, - array( - 'ORDER BY' => 'rc_timestamp DESC', - 'LIMIT' => $limit, - 'USE INDEX' => array( 'recentchanges' => 'new_name_timestamp' ) - ), - $join_conds - ); - // Old pages - $sqlOld = $dbr->selectSQLText( - $tables, - $fields, - array( 'rc_new' => 0 ) + $conds, - __METHOD__, - array( - 'ORDER BY' => 'rc_timestamp DESC', - 'LIMIT' => $limit, - 'USE INDEX' => array( 'recentchanges' => 'new_name_timestamp' ) - ), - $join_conds - ); - # Join the two fast queries, and sort the result set - $sql = $dbr->unionQueries( array( $sqlNew, $sqlOld ), false ) . - ' ORDER BY rc_timestamp DESC'; - $sql = $dbr->limitResult( $sql, $limit, false ); - $res = $dbr->query( $sql, __METHOD__ ); - } - - return $res; + // rc_new is not an ENUM, but adding a redundant rc_new IN (0,1) gives mysql enough + // knowledge to use an index merge if it wants (it may use some other index though). + return $dbr->select( + $tables, + $fields, + $conds + array( 'rc_new' => array( 0, 1 ) ), + __METHOD__, + array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit ) + $query_options, + $join_conds + ); } /** * Send output to the OutputPage object, only called if not used feeds * - * @param $rows Array of database rows - * @param $opts FormOptions + * @param array $rows Database rows + * @param FormOptions $opts */ public function webOutput( $rows, $opts ) { global $wgRCShowWatchingUsers, $wgShowUpdatedMarker, $wgAllowCategorizedRecentChanges; - $limit = $opts['limit']; - - if( !$this->including() ) { - // Output options box - $this->doHeader( $opts ); - } - - // And now for the content - $this->getOutput()->setFeedAppendQuery( $this->getFeedQuery() ); + // Build the final data - if( $wgAllowCategorizedRecentChanges ) { + if ( $wgAllowCategorizedRecentChanges ) { $this->filterByCategories( $rows, $opts ); } + $limit = $opts['limit']; + $showWatcherCount = $wgRCShowWatchingUsers && $this->getUser()->getOption( 'shownumberswatching' ); $watcherCache = array(); @@ -495,23 +462,23 @@ class SpecialRecentChanges extends IncludableSpecialPage { $counter = 1; $list = ChangesList::newFromContext( $this->getContext() ); - $s = $list->beginRecentChangesList(); - foreach( $rows as $obj ) { - if( $limit == 0 ) { + $rclistOutput = $list->beginRecentChangesList(); + foreach ( $rows as $obj ) { + if ( $limit == 0 ) { break; } $rc = RecentChange::newFromRow( $obj ); $rc->counter = $counter++; # Check if the page has been updated since the last visit - if( $wgShowUpdatedMarker && !empty( $obj->wl_notificationtimestamp ) ) { + if ( $wgShowUpdatedMarker && !empty( $obj->wl_notificationtimestamp ) ) { $rc->notificationtimestamp = ( $obj->rc_timestamp >= $obj->wl_notificationtimestamp ); } else { $rc->notificationtimestamp = false; // Default } # Check the number of users watching the page $rc->numberofWatchingusers = 0; // Default - if( $showWatcherCount && $obj->rc_namespace >= 0 ) { - if( !isset( $watcherCache[$obj->rc_namespace][$obj->rc_title] ) ) { + if ( $showWatcherCount && $obj->rc_namespace >= 0 ) { + if ( !isset( $watcherCache[$obj->rc_namespace][$obj->rc_title] ) ) { $watcherCache[$obj->rc_namespace][$obj->rc_title] = $dbr->selectField( 'watchlist', @@ -525,27 +492,66 @@ class SpecialRecentChanges extends IncludableSpecialPage { } $rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title]; } - $s .= $list->recentChangesLine( $rc, !empty( $obj->wl_user ), $counter ); - --$limit; + + $changeLine = $list->recentChangesLine( $rc, !empty( $obj->wl_user ), $counter ); + if ( $changeLine !== false ) { + $rclistOutput .= $changeLine; + --$limit; + } + } + $rclistOutput .= $list->endRecentChangesList(); + + // Print things out + + if ( !$this->including() ) { + // Output options box + $this->doHeader( $opts ); + } + + // And now for the content + $feedQuery = $this->getFeedQuery(); + if ( $feedQuery !== '' ) { + $this->getOutput()->setFeedAppendQuery( $feedQuery ); + } else { + $this->getOutput()->setFeedAppendQuery( false ); + } + + if ( $rows->numRows() === 0 ) { + $this->getOutput()->wrapWikiMsg( + "<div class='mw-changeslist-empty'>\n$1\n</div>", 'recentchanges-noresult' + ); + } else { + $this->getOutput()->addHTML( $rclistOutput ); } - $s .= $list->endRecentChangesList(); - $this->getOutput()->addHTML( $s ); } /** * Get the query string to append to feed link URLs. - * This is overridden by RCL to add the target parameter - * @return bool + * + * @return string */ public function getFeedQuery() { - return false; + global $wgFeedLimit; + + $this->getOptions()->validateIntBounds( 'limit', 0, $wgFeedLimit ); + $options = $this->getOptions()->getChangedValues(); + + // wfArrayToCgi() omits options set to null or false + foreach ( $options as &$value ) { + if ( $value === false ) { + $value = '0'; + } + } + unset( $value ); + + return wfArrayToCgi( $options ); } /** * Return the text to be displayed above the changes * - * @param $opts FormOptions - * @return String: XHTML + * @param FormOptions $opts + * @return string XHTML */ public function doHeader( $opts ) { global $wgScript; @@ -554,10 +560,6 @@ class SpecialRecentChanges extends IncludableSpecialPage { $defaults = $opts->getAllValues(); $nondefaults = $opts->getChangedValues(); - $opts->consumeValues( array( - 'namespace', 'invert', 'associated', 'tagfilter', - 'categories', 'categories_any' - ) ); $panel = array(); $panel[] = $this->optionsPanel( $defaults, $nondefaults ); @@ -569,24 +571,36 @@ class SpecialRecentChanges extends IncludableSpecialPage { $submit = ' ' . Xml::submitbutton( $this->msg( 'allpagessubmit' )->text() ); $out = Xml::openElement( 'table', array( 'class' => 'mw-recentchanges-table' ) ); - foreach( $extraOpts as $name => $optionRow ) { + foreach ( $extraOpts as $name => $optionRow ) { # Add submit button to the last row only ++$count; $addSubmit = ( $count === $extraOptsCount ) ? $submit : ''; $out .= Xml::openElement( 'tr' ); - if( is_array( $optionRow ) ) { - $out .= Xml::tags( 'td', array( 'class' => 'mw-label mw-' . $name . '-label' ), $optionRow[0] ); - $out .= Xml::tags( 'td', array( 'class' => 'mw-input' ), $optionRow[1] . $addSubmit ); + if ( is_array( $optionRow ) ) { + $out .= Xml::tags( + 'td', + array( 'class' => 'mw-label mw-' . $name . '-label' ), + $optionRow[0] + ); + $out .= Xml::tags( + 'td', + array( 'class' => 'mw-input' ), + $optionRow[1] . $addSubmit + ); } else { - $out .= Xml::tags( 'td', array( 'class' => 'mw-input', 'colspan' => 2 ), $optionRow . $addSubmit ); + $out .= Xml::tags( + 'td', + array( 'class' => 'mw-input', 'colspan' => 2 ), + $optionRow . $addSubmit + ); } $out .= Xml::closeElement( 'tr' ); } $out .= Xml::closeElement( 'table' ); $unconsumed = $opts->getUnconsumedValues(); - foreach( $unconsumed as $key => $value ) { + foreach ( $unconsumed as $key => $value ) { $out .= Html::hidden( $key, $value ); } @@ -597,7 +611,11 @@ class SpecialRecentChanges extends IncludableSpecialPage { $panelString = implode( "\n", $panel ); $this->getOutput()->addHTML( - Xml::fieldset( $this->msg( 'recentchanges-legend' )->text(), $panelString, array( 'class' => 'rcoptions' ) ) + Xml::fieldset( + $this->msg( 'recentchanges-legend' )->text(), + $panelString, + array( 'class' => 'rcoptions' ) + ) ); $this->setBottomText( $opts ); @@ -606,15 +624,19 @@ class SpecialRecentChanges extends IncludableSpecialPage { /** * Get options to be displayed in a form * - * @param $opts FormOptions - * @return Array + * @param FormOptions $opts + * @return array */ function getExtraOptions( $opts ) { + $opts->consumeValues( array( + 'namespace', 'invert', 'associated', 'tagfilter', 'categories', 'categories_any' + ) ); + $extraOpts = array(); $extraOpts['namespace'] = $this->namespaceFilterForm( $opts ); global $wgAllowCategorizedRecentChanges; - if( $wgAllowCategorizedRecentChanges ) { + if ( $wgAllowCategorizedRecentChanges ) { $extraOpts['category'] = $this->categoryFilterForm( $opts ); } @@ -623,14 +645,18 @@ class SpecialRecentChanges extends IncludableSpecialPage { $extraOpts['tagfilter'] = $tagFilter; } - wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) ); + // Don't fire the hook for subclasses. (Or should we?) + if ( $this->getName() === 'Recentchanges' ) { + wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) ); + } + return $extraOpts; } /** * Send the text to be displayed above the options * - * @param $opts FormOptions + * @param FormOptions $opts Unused */ function setTopText( FormOptions $opts ) { global $wgContLang; @@ -649,19 +675,19 @@ class SpecialRecentChanges extends IncludableSpecialPage { } /** - * Send the text to be displayed after the options, for use in - * Recentchangeslinked + * Send the text to be displayed after the options, for use in subclasses. * - * @param $opts FormOptions + * @param FormOptions $opts */ - function setBottomText( FormOptions $opts ) {} + function setBottomText( FormOptions $opts ) { + } /** * Creates the choose namespace selection * * @todo Uses radio buttons (HASHAR) - * @param $opts FormOptions - * @return String + * @param FormOptions $opts + * @return string */ protected function namespaceFilterForm( FormOptions $opts ) { $nsSelect = Html::namespaceSelector( @@ -679,14 +705,15 @@ class SpecialRecentChanges extends IncludableSpecialPage { $opts['associated'], array( 'title' => $this->msg( 'tooltip-namespace_association' )->text() ) ); + return array( $nsLabel, "$nsSelect $invert $associated" ); } /** * Create a input to filter changes by categories * - * @param $opts FormOptions - * @return Array + * @param FormOptions $opts + * @return array */ protected function categoryFilterForm( FormOptions $opts ) { list( $label, $input ) = Xml::inputLabelSep( $this->msg( 'rc_categories' )->text(), @@ -701,21 +728,21 @@ class SpecialRecentChanges extends IncludableSpecialPage { /** * Filter $rows by categories set in $opts * - * @param $rows Array of database rows - * @param $opts FormOptions + * @param array $rows Database rows + * @param FormOptions $opts */ function filterByCategories( &$rows, FormOptions $opts ) { - $categories = array_map( 'trim', explode( '|' , $opts['categories'] ) ); + $categories = array_map( 'trim', explode( '|', $opts['categories'] ) ); - if( !count( $categories ) ) { + if ( !count( $categories ) ) { return; } # Filter categories $cats = array(); - foreach( $categories as $cat ) { + foreach ( $categories as $cat ) { $cat = trim( $cat ); - if( $cat == '' ) { + if ( $cat == '' ) { continue; } $cats[] = $cat; @@ -725,16 +752,16 @@ class SpecialRecentChanges extends IncludableSpecialPage { $articles = array(); $a2r = array(); $rowsarr = array(); - foreach( $rows as $k => $r ) { + foreach ( $rows as $k => $r ) { $nt = Title::makeTitle( $r->rc_namespace, $r->rc_title ); $id = $nt->getArticleID(); - if( $id == 0 ) { + if ( $id == 0 ) { continue; # Page might have been deleted... } - if( !in_array( $id, $articles ) ) { + if ( !in_array( $id, $articles ) ) { $articles[] = $id; } - if( !isset( $a2r[$id] ) ) { + if ( !isset( $a2r[$id] ) ) { $a2r[$id] = array(); } $a2r[$id][] = $k; @@ -742,7 +769,7 @@ class SpecialRecentChanges extends IncludableSpecialPage { } # Shortcut? - if( !count( $articles ) || !count( $cats ) ) { + if ( !count( $articles ) || !count( $cats ) ) { return; } @@ -753,8 +780,8 @@ class SpecialRecentChanges extends IncludableSpecialPage { # Filter $newrows = array(); - foreach( $match as $id ) { - foreach( $a2r[$id] as $rev ) { + foreach ( $match as $id ) { + foreach ( $a2r[$id] as $rev ) { $k = $rev; $newrows[$k] = $rowsarr[$k]; } @@ -765,10 +792,10 @@ class SpecialRecentChanges extends IncludableSpecialPage { /** * Makes change an option link which carries all the other options * - * @param $title Title - * @param $override Array: options to override - * @param $options Array: current options - * @param $active Boolean: whether to show the link in bold + * @param string $title Title + * @param array $override Options to override + * @param array $options Current options + * @param bool $active Whether to show the link in bold * @return string */ function makeOptionsLink( $title, $override, $options, $active = false ) { @@ -787,14 +814,15 @@ class SpecialRecentChanges extends IncludableSpecialPage { if ( $active ) { $text = '<strong>' . $text . '</strong>'; } + return Linker::linkKnown( $this->getTitle(), $text, array(), $params ); } /** * Creates the options panel. * - * @param $defaults Array - * @param $nondefaults Array + * @param array $defaults + * @param array $nondefaults * @return string */ function optionsPanel( $defaults, $nondefaults ) { @@ -804,13 +832,13 @@ class SpecialRecentChanges extends IncludableSpecialPage { $note = ''; $msg = $this->msg( 'rclegend' ); - if( !$msg->isDisabled() ) { + if ( !$msg->isDisabled() ) { $note .= '<div class="mw-rclegend">' . $msg->parse() . "</div>\n"; } $lang = $this->getLanguage(); $user = $this->getUser(); - if( $options['from'] ) { + if ( $options['from'] ) { $note .= $this->msg( 'rcnotefrom' )->numParams( $options['limit'] )->params( $lang->userTimeAndDate( $options['from'], $user ), $lang->userDate( $options['from'], $user ), @@ -818,37 +846,41 @@ class SpecialRecentChanges extends IncludableSpecialPage { } # Sort data for display and make sure it's unique after we've added user data. - $wgRCLinkLimits[] = $options['limit']; - $wgRCLinkDays[] = $options['days']; - sort( $wgRCLinkLimits ); - sort( $wgRCLinkDays ); - $wgRCLinkLimits = array_unique( $wgRCLinkLimits ); - $wgRCLinkDays = array_unique( $wgRCLinkDays ); + $linkLimits = $wgRCLinkLimits; + $linkLimits[] = $options['limit']; + sort( $linkLimits ); + $linkLimits = array_unique( $linkLimits ); + + $linkDays = $wgRCLinkDays; + $linkDays[] = $options['days']; + sort( $linkDays ); + $linkDays = array_unique( $linkDays ); // limit links - foreach( $wgRCLinkLimits as $value ) { + $cl = array(); + foreach ( $linkLimits as $value ) { $cl[] = $this->makeOptionsLink( $lang->formatNum( $value ), array( 'limit' => $value ), $nondefaults, $value == $options['limit'] ); } $cl = $lang->pipeList( $cl ); // day links, reset 'from' to none - foreach( $wgRCLinkDays as $value ) { + $dl = array(); + foreach ( $linkDays as $value ) { $dl[] = $this->makeOptionsLink( $lang->formatNum( $value ), array( 'days' => $value, 'from' => '' ), $nondefaults, $value == $options['days'] ); } $dl = $lang->pipeList( $dl ); - // show/hide links $showhide = array( $this->msg( 'show' )->text(), $this->msg( 'hide' )->text() ); $filters = array( - 'hideminor' => 'rcshowhideminor', - 'hidebots' => 'rcshowhidebots', - 'hideanons' => 'rcshowhideanons', - 'hideliu' => 'rcshowhideliu', + 'hideminor' => 'rcshowhideminor', + 'hidebots' => 'rcshowhidebots', + 'hideanons' => 'rcshowhideanons', + 'hideliu' => 'rcshowhideliu', 'hidepatrolled' => 'rcshowhidepatr', - 'hidemyself' => 'rcshowhidemine' + 'hidemyself' => 'rcshowhidemine' ); foreach ( $this->getCustomFilters() as $key => $params ) { $filters[$key] = $params['msg']; @@ -861,7 +893,7 @@ class SpecialRecentChanges extends IncludableSpecialPage { $links = array(); foreach ( $filters as $key => $msg ) { $link = $this->makeOptionsLink( $showhide[1 - $options[$key]], - array( $key => 1-$options[$key] ), $nondefaults ); + array( $key => 1 - $options[$key] ), $nondefaults ); $links[] = $this->msg( $msg )->rawParams( $link )->escaped(); } @@ -872,17 +904,23 @@ class SpecialRecentChanges extends IncludableSpecialPage { $now, array( 'from' => $timestamp ), $nondefaults ); - $rclinks = $this->msg( 'rclinks' )->rawParams( $cl, $dl, $lang->pipeList( $links ) )->parse(); + $rclinks = $this->msg( 'rclinks' )->rawParams( $cl, $dl, $lang->pipeList( $links ) ) + ->parse(); $rclistfrom = $this->msg( 'rclistfrom' )->rawParams( $tl )->parse(); + return "{$note}$rclinks<br />$rclistfrom"; } /** - * add javascript specific to the [[Special:RecentChanges]] page + * Add page-specific modules. */ - function addRecentChangesJS() { + protected function addModules() { $this->getOutput()->addModules( array( 'mediawiki.special.recentchanges', ) ); } + + protected function getGroupName() { + return 'changes'; + } } diff --git a/includes/specials/SpecialRecentchangeslinked.php b/includes/specials/SpecialRecentchangeslinked.php index 862736d3..a8447046 100644 --- a/includes/specials/SpecialRecentchangeslinked.php +++ b/includes/specials/SpecialRecentchangeslinked.php @@ -29,7 +29,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { var $rclTargetTitle; - function __construct(){ + function __construct() { parent::__construct( 'Recentchangeslinked' ); } @@ -37,7 +37,6 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { $opts = parent::getDefaultOptions(); $opts->add( 'target', '' ); $opts->add( 'showlinkedto', false ); - $opts->add( 'tagfilter', '' ); return $opts; } @@ -51,13 +50,13 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { return $opts; } - public function getFeedObject( $feedFormat ){ + public function getFeedObject( $feedFormat ) { $feed = new ChangesFeed( $feedFormat, false ); $feedObj = $feed->getFeedObject( $this->msg( 'recentchangeslinked-title', $this->getTargetTitle()->getPrefixedText() ) ->inContentLanguage()->text(), $this->msg( 'recentchangeslinked-feed' )->inContentLanguage()->text(), - $this->getTitle()->getFullUrl() + $this->getTitle()->getFullURL() ); return array( $feed, $feedObj ); } @@ -72,8 +71,8 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { } $outputPage = $this->getOutput(); $title = Title::newFromURL( $target ); - if( !$title || $title->getInterwiki() != '' ){ - $outputPage->wrapWikiMsg( "<div class=\"errorbox\">\n$1\n</div><br style=\"clear: both\" />", 'allpagesbadtitle' ); + if ( !$title || $title->getInterwiki() != '' ) { + $outputPage->wrapWikiMsg( "<div class=\"errorbox\">\n$1\n</div>", 'allpagesbadtitle' ); return false; } @@ -94,20 +93,24 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { $dbkey = $title->getDBkey(); $tables = array( 'recentchanges' ); - $select = array( $dbr->tableName( 'recentchanges' ) . '.*' ); + $select = RecentChange::selectFields(); $join_conds = array(); $query_options = array(); // left join with watchlist table to highlight watched rows $uid = $this->getUser()->getId(); - if( $uid ) { + if ( $uid && $this->getUser()->isAllowed( 'viewmywatchlist' ) ) { $tables[] = 'watchlist'; $select[] = 'wl_user'; - $join_conds['watchlist'] = array( 'LEFT JOIN', "wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace" ); + $join_conds['watchlist'] = array( 'LEFT JOIN', array( + 'wl_user' => $uid, + 'wl_title=rc_title', + 'wl_namespace=rc_namespace' + )); } if ( $this->getUser()->isAllowed( 'rollback' ) ) { $tables[] = 'page'; - $join_conds['page'] = array('LEFT JOIN', 'rc_cur_id=page_id'); + $join_conds['page'] = array( 'LEFT JOIN', 'rc_cur_id=page_id' ); $select[] = 'page_latest'; } ChangeTags::modifyDisplayQuery( @@ -123,21 +126,21 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { return false; } - if( $ns == NS_CATEGORY && !$showlinkedto ) { + if ( $ns == NS_CATEGORY && !$showlinkedto ) { // special handling for categories - // XXX: should try to make this less klugy + // XXX: should try to make this less kludgy $link_tables = array( 'categorylinks' ); $showlinkedto = true; } else { // for now, always join on these tables; really should be configurable as in whatlinkshere $link_tables = array( 'pagelinks', 'templatelinks' ); // imagelinks only contains links to pages in NS_FILE - if( $ns == NS_FILE || !$showlinkedto ) { + if ( $ns == NS_FILE || !$showlinkedto ) { $link_tables[] = 'imagelinks'; } } - if( $id == 0 && !$showlinkedto ) { + if ( $id == 0 && !$showlinkedto ) { return false; // nonexistent pages can't link to any pages } @@ -146,22 +149,22 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { $subsql = array(); // SELECT statements to combine with UNION - foreach( $link_tables as $link_table ) { + foreach ( $link_tables as $link_table ) { $pfx = $prefix[$link_table]; // imagelinks and categorylinks tables have no xx_namespace field, and have xx_to instead of xx_title - if( $link_table == 'imagelinks' ) { + if ( $link_table == 'imagelinks' ) { $link_ns = NS_FILE; - } elseif( $link_table == 'categorylinks' ) { + } elseif ( $link_table == 'categorylinks' ) { $link_ns = NS_CATEGORY; } else { $link_ns = 0; } - if( $showlinkedto ) { + if ( $showlinkedto ) { // find changes to pages linking to this page - if( $link_ns ) { - if( $ns != $link_ns ) { + if ( $link_ns ) { + if ( $ns != $link_ns ) { continue; } // should never happen, but check anyway $subconds = array( "{$pfx}_to" => $dbkey ); @@ -172,15 +175,15 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { } else { // find changes to pages linked from this page $subconds = array( "{$pfx}_from" => $id ); - if( $link_table == 'imagelinks' || $link_table == 'categorylinks' ) { + if ( $link_table == 'imagelinks' || $link_table == 'categorylinks' ) { $subconds["rc_namespace"] = $link_ns; $subjoin = "rc_title = {$pfx}_to"; } else { - $subjoin = "rc_namespace = {$pfx}_namespace AND rc_title = {$pfx}_title"; + $subjoin = array( "rc_namespace = {$pfx}_namespace", "rc_title = {$pfx}_title" ); } } - if( $dbr->unionSupportsOrderAndLimit()) { + if ( $dbr->unionSupportsOrderAndLimit() ) { $order = array( 'ORDER BY' => 'rc_timestamp DESC' ); } else { $order = array(); @@ -195,26 +198,27 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { $join_conds + array( $link_table => array( 'INNER JOIN', $subjoin ) ) ); - if( $dbr->unionSupportsOrderAndLimit()) + if ( $dbr->unionSupportsOrderAndLimit() ) { $query = $dbr->limitResult( $query, $limit ); + } $subsql[] = $query; } - if( count($subsql) == 0 ) { + if ( count( $subsql ) == 0 ) { return false; // should never happen } - if( count($subsql) == 1 && $dbr->unionSupportsOrderAndLimit() ) { + if ( count( $subsql ) == 1 && $dbr->unionSupportsOrderAndLimit() ) { $sql = $subsql[0]; } else { // need to resort and relimit after union - $sql = $dbr->unionQueries($subsql, false).' ORDER BY rc_timestamp DESC'; - $sql = $dbr->limitResult($sql, $limit, false); + $sql = $dbr->unionQueries( $subsql, false ) . ' ORDER BY rc_timestamp DESC'; + $sql = $dbr->limitResult( $sql, $limit, false ); } $res = $dbr->query( $sql, __METHOD__ ); - if( $res->numRows() == 0 ) { + if ( $res->numRows() == 0 ) { $this->mResultEmpty = true; } @@ -222,21 +226,21 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { } /** - * @param $opts FormOptions + * Get options to be displayed in a form + * + * @param FormOptions $opts * @return array */ - function getExtraOptions( $opts ){ - $opts->consumeValues( array( 'showlinkedto', 'target', 'tagfilter' ) ); - $extraOpts = array(); - $extraOpts['namespace'] = $this->namespaceFilterForm( $opts ); + function getExtraOptions( $opts ) { + $extraOpts = parent::getExtraOptions( $opts ); + + $opts->consumeValues( array( 'showlinkedto', 'target' ) ); + $extraOpts['target'] = array( $this->msg( 'recentchangeslinked-page' )->escaped(), - Xml::input( 'target', 40, str_replace('_',' ',$opts['target']) ) . - Xml::check( 'showlinkedto', $opts['showlinkedto'], array('id' => 'showlinkedto') ) . ' ' . + Xml::input( 'target', 40, str_replace( '_', ' ', $opts['target'] ) ) . + Xml::check( 'showlinkedto', $opts['showlinkedto'], array( 'id' => 'showlinkedto' ) ) . ' ' . Xml::label( $this->msg( 'recentchangeslinked-to' )->text(), 'showlinkedto' ) ); - $tagFilter = ChangeTags::buildTagFilterSelector( $opts['tagfilter'] ); - if ($tagFilter) { - $extraOpts['tagfilter'] = $tagFilter; - } + return $extraOpts; } @@ -257,23 +261,8 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { function setTopText( FormOptions $opts ) { $target = $this->getTargetTitle(); - if( $target ) { + if ( $target ) { $this->getOutput()->addBacklinkSubtitle( $target ); } } - - public function getFeedQuery() { - $target = $this->getTargetTitle(); - if( $target ) { - return "target=" . urlencode( $target->getPrefixedDBkey() ); - } else { - return false; - } - } - - function setBottomText( FormOptions $opts ) { - if( isset( $this->mResultEmpty ) && $this->mResultEmpty ) { - $this->getOutput()->addWikiMsg( 'recentchangeslinked-noresult' ); - } - } } diff --git a/includes/specials/SpecialRedirect.php b/includes/specials/SpecialRedirect.php new file mode 100644 index 00000000..f05dacbc --- /dev/null +++ b/includes/specials/SpecialRedirect.php @@ -0,0 +1,235 @@ +<?php +/** + * Implements Special:Redirect + * + * @section LICENSE + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage + */ + +/** + * A special page that redirects to: the user for a numeric user id, + * the file for a given filename, or the page for a given revision id. + * + * @ingroup SpecialPage + * @since 1.22 + */ +class SpecialRedirect extends FormSpecialPage { + + /** + * The type of the redirect (user/file/revision) + * + * @var string $mType + * @example 'user' + */ + protected $mType; + + /** + * The identifier/value for the redirect (which id, which file) + * + * @var string $mValue + * @example '42' + */ + protected $mValue; + + function __construct() { + parent::__construct( 'Redirect' ); + $this->mType = null; + $this->mValue = null; + } + + /** + * Set $mType and $mValue based on parsed value of $subpage. + */ + function setParameter( $subpage ) { + // parse $subpage to pull out the parts + $parts = explode( '/', $subpage, 2 ); + $this->mType = count( $parts ) > 0 ? $parts[0] : null; + $this->mValue = count( $parts ) > 1 ? $parts[1] : null; + } + + /** + * Handle Special:Redirect/user/xxxx (by redirecting to User:YYYY) + * + * @return string|null url to redirect to, or null if $mValue is invalid. + */ + function dispatchUser() { + if ( !ctype_digit( $this->mValue ) ) { + return null; + } + $user = User::newFromId( (int)$this->mValue ); + $username = $user->getName(); // load User as side-effect + if ( $user->isAnon() ) { + return null; + } + $userpage = Title::makeTitle( NS_USER, $username ); + return $userpage->getFullURL( '', false, PROTO_CURRENT ); + } + + /** + * Handle Special:Redirect/file/xxxx + * + * @return string|null url to redirect to, or null if $mValue is not found. + */ + function dispatchFile() { + $title = Title::makeTitleSafe( NS_FILE, $this->mValue ); + + if ( ! $title instanceof Title ) { + return null; + } + $file = wfFindFile( $title ); + + if ( !$file || !$file->exists() ) { + return null; + } + // Default behavior: Use the direct link to the file. + $url = $file->getURL(); + $request = $this->getRequest(); + $width = $request->getInt( 'width', -1 ); + $height = $request->getInt( 'height', -1 ); + + // If a width is requested... + if ( $width != -1 ) { + $mto = $file->transform( array( 'width' => $width, 'height' => $height ) ); + // ... and we can + if ( $mto && !$mto->isError() ) { + // ... change the URL to point to a thumbnail. + $url = $mto->getURL(); + } + } + return $url; + } + + /** + * Handle Special:Redirect/revision/xxx + * (by redirecting to index.php?oldid=xxx) + * + * @return string|null url to redirect to, or null if $mValue is invalid. + */ + function dispatchRevision() { + $oldid = $this->mValue; + if ( !ctype_digit( $oldid ) ) { + return null; + } + $oldid = (int)$oldid; + if ( $oldid === 0 ) { + return null; + } + return wfAppendQuery( wfScript( 'index' ), array( + 'oldid' => $oldid + ) ); + } + + /** + * Use appropriate dispatch* method to obtain a redirection URL, + * and either: redirect, set a 404 error code and error message, + * or do nothing (if $mValue wasn't set) allowing the form to be + * displayed. + * + * @return bool true if a redirect was successfully handled. + */ + function dispatch() { + // the various namespaces supported by Special:Redirect + switch ( $this->mType ) { + case 'user': + $url = $this->dispatchUser(); + break; + case 'file': + $url = $this->dispatchFile(); + break; + case 'revision': + $url = $this->dispatchRevision(); + break; + default: + $this->getOutput()->setStatusCode( 404 ); + $url = null; + break; + } + if ( $url ) { + $this->getOutput()->redirect( $url ); + return true; + } + if ( !is_null( $this->mValue ) ) { + $this->getOutput()->setStatusCode( 404 ); + // Message: redirect-not-exists + $msg = $this->getMessagePrefix() . '-not-exists'; + return Status::newFatal( $msg ); + } + return false; + } + + protected function getFormFields() { + $mp = $this->getMessagePrefix(); + $ns = array( + // subpage => message + // Messages: redirect-user, redirect-revision, redirect-file + 'user' => $mp . '-user', + 'revision' => $mp . '-revision', + 'file' => $mp . '-file', + ); + $a = array(); + $a['type'] = array( + 'type' => 'select', + 'label-message' => $mp . '-lookup', // Message: redirect-lookup + 'options' => array(), + 'default' => current( array_keys( $ns ) ), + ); + foreach ( $ns as $n => $m ) { + $m = $this->msg( $m )->text(); + $a['type']['options'][$m] = $n; + } + $a['value'] = array( + 'type' => 'text', + 'label-message' => $mp . '-value' // Message: redirect-value + ); + // set the defaults according to the parsed subpage path + if ( !empty( $this->mType ) ) { + $a['type']['default'] = $this->mType; + } + if ( !empty( $this->mValue ) ) { + $a['value']['default'] = $this->mValue; + } + return $a; + } + + public function onSubmit( array $data ) { + if ( !empty( $data['type'] ) && !empty( $data['value'] ) ) { + $this->setParameter( $data['type'] . '/' . $data['value'] ); + } + /* if this returns false, will show the form */ + return $this->dispatch(); + } + + public function onSuccess() { + /* do nothing, we redirect in $this->dispatch if successful. */ + } + + protected function alterForm( HTMLForm $form ) { + /* display summary at top of page */ + $this->outputHeader(); + // tweak label on submit button + // Message: redirect-submit + $form->setSubmitTextMsg( $this->getMessagePrefix() . '-submit' ); + /* submit form every time */ + $form->setMethod( 'get' ); + } + + protected function getGroupName() { + return 'redirects'; + } +} diff --git a/includes/specials/SpecialResetTokens.php b/includes/specials/SpecialResetTokens.php new file mode 100644 index 00000000..ef2a45da --- /dev/null +++ b/includes/specials/SpecialResetTokens.php @@ -0,0 +1,145 @@ +<?php +/** + * Implements Special:ResetTokens + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage + */ + +/** + * Let users reset tokens like the watchlist token. + * + * @ingroup SpecialPage + */ +class SpecialResetTokens extends FormSpecialPage { + private $tokensList; + + public function __construct() { + parent::__construct( 'ResetTokens' ); + } + + /** + * Returns the token information list for this page after running + * the hook and filtering out disabled preferences. + * + * @return array + */ + protected function getTokensList() { + global $wgHiddenPrefs; + + if ( !isset( $this->tokensList ) ) { + $tokens = array( + array( 'preference' => 'watchlisttoken', 'label-message' => 'resettokens-watchlist-token' ), + ); + wfRunHooks( 'SpecialResetTokensTokens', array( &$tokens ) ); + + $tokens = array_filter( $tokens, function ( $tok ) use ( $wgHiddenPrefs ) { + return !in_array( $tok['preference'], $wgHiddenPrefs ); + } ); + + $this->tokensList = $tokens; + } + + return $this->tokensList; + } + + public function execute( $par ) { + // This is a preferences page, so no user JS for y'all. + $this->getOutput()->disallowUserJs(); + + parent::execute( $par ); + + $this->getOutput()->addReturnTo( SpecialPage::getTitleFor( 'Preferences' ) ); + } + + public function onSuccess() { + $this->getOutput()->wrapWikiMsg( + "<div class='successbox'>\n$1\n</div>", + 'resettokens-done' + ); + } + + /** + * Display appropriate message if there's nothing to do. + * The submit button is also suppressed in this case (see alterForm()). + */ + protected function getFormFields() { + $user = $this->getUser(); + $tokens = $this->getTokensList(); + + if ( $tokens ) { + $tokensForForm = array(); + foreach ( $tokens as $tok ) { + $label = $this->msg( 'resettokens-token-label' ) + ->rawParams( $this->msg( $tok['label-message'] )->parse() ) + ->params( $user->getTokenFromOption( $tok['preference'] ) ) + ->escaped(); + $tokensForForm[ $label ] = $tok['preference']; + } + + $desc = array( + 'label-message' => 'resettokens-tokens', + 'type' => 'multiselect', + 'options' => $tokensForForm, + ); + } else { + $desc = array( + 'label-message' => 'resettokens-no-tokens', + 'type' => 'info', + ); + } + + return array( + 'tokens' => $desc, + ); + } + + /** + * Suppress the submit button if there's nothing to do; + * provide additional message on it otherwise. + */ + protected function alterForm( HTMLForm $form ) { + if ( $this->getTokensList() ) { + $form->setSubmitTextMsg( 'resettokens-resetbutton' ); + } else { + $form->suppressDefaultSubmit(); + } + } + + public function onSubmit( array $formData ) { + if ( $formData['tokens'] ) { + $user = $this->getUser(); + foreach ( $formData['tokens'] as $tokenPref ) { + $user->resetTokenFromOption( $tokenPref ); + } + $user->saveSettings(); + + return true; + } + + return false; + } + + protected function getGroupName() { + return 'users'; + } + + public function isListed() { + return (bool)$this->getTokensList(); + } +} diff --git a/includes/specials/SpecialRevisiondelete.php b/includes/specials/SpecialRevisiondelete.php index aba90cf8..825be6c4 100644 --- a/includes/specials/SpecialRevisiondelete.php +++ b/includes/specials/SpecialRevisiondelete.php @@ -49,68 +49,43 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { /** Array of checkbox specs (message, name, deletion bits) */ var $checks; - /** Information about the current type */ - var $typeInfo; + /** UI Labels about the current type */ + var $typeLabels; /** The RevDel_List object, storing the list of items to be deleted/undeleted */ var $list; /** - * Assorted information about each type, needed by the special page. - * TODO Move some of this to the list class + * UI labels for each type. */ - static $allowedTypes = array( + static $UILabels = array( 'revision' => array( 'check-label' => 'revdelete-hide-text', - 'deletion-bits' => Revision::DELETED_TEXT, 'success' => 'revdelete-success', 'failure' => 'revdelete-failure', - 'list-class' => 'RevDel_RevisionList', - 'permission' => 'deleterevision', ), 'archive' => array( 'check-label' => 'revdelete-hide-text', - 'deletion-bits' => Revision::DELETED_TEXT, 'success' => 'revdelete-success', 'failure' => 'revdelete-failure', - 'list-class' => 'RevDel_ArchiveList', - 'permission' => 'deleterevision', ), - 'oldimage'=> array( + 'oldimage' => array( 'check-label' => 'revdelete-hide-image', - 'deletion-bits' => File::DELETED_FILE, 'success' => 'revdelete-success', 'failure' => 'revdelete-failure', - 'list-class' => 'RevDel_FileList', - 'permission' => 'deleterevision', ), 'filearchive' => array( 'check-label' => 'revdelete-hide-image', - 'deletion-bits' => File::DELETED_FILE, 'success' => 'revdelete-success', 'failure' => 'revdelete-failure', - 'list-class' => 'RevDel_ArchivedFileList', - 'permission' => 'deleterevision', ), 'logging' => array( 'check-label' => 'revdelete-hide-name', - 'deletion-bits' => LogPage::DELETED_ACTION, 'success' => 'logdelete-success', 'failure' => 'logdelete-failure', - 'list-class' => 'RevDel_LogList', - 'permission' => 'deletelogentry', ), ); - /** Type map to support old log entries */ - static $deprecatedTypeMap = array( - 'oldid' => 'revision', - 'artimestamp' => 'archive', - 'oldimage' => 'oldimage', - 'fileid' => 'filearchive', - 'logid' => 'logging', - ); - public function __construct() { parent::__construct( 'Revisiondelete', 'deletedhistory' ); } @@ -133,7 +108,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { $this->ids = explode( ',', $ids ); } else { # Array input - $this->ids = array_keys( $request->getArray('ids',array()) ); + $this->ids = array_keys( $request->getArray( 'ids', array() ) ); } // $this->ids = array_map( 'intval', $this->ids ); $this->ids = array_unique( array_filter( $this->ids ) ); @@ -147,24 +122,6 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { } else { $this->typeName = $request->getVal( 'type' ); $this->targetObj = Title::newFromText( $request->getText( 'target' ) ); - if ( $this->targetObj->isSpecial( 'Log' ) ) { - $result = wfGetDB( DB_SLAVE )->select( 'logging', - 'log_type', - array( 'log_id' => $this->ids ), - __METHOD__, - array( 'DISTINCT' ) - ); - - $logTypes = array(); - foreach ( $result as $row ) { - $logTypes[] = $row->log_type; - } - - if ( count( $logTypes ) == 1 ) { - // If there's only one type, the target can be set to include it. - $this->targetObj = SpecialPage::getTitleFor( 'Log', $logTypes[0] ); - } - } } # For reviewing deleted files... @@ -175,28 +132,21 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { return; } - if ( isset( self::$deprecatedTypeMap[$this->typeName] ) ) { - $this->typeName = self::$deprecatedTypeMap[$this->typeName]; - } + $this->typeName = RevisionDeleter::getCanonicalTypeName( $this->typeName ); # No targets? - if( !isset( self::$allowedTypes[$this->typeName] ) || count( $this->ids ) == 0 ) { + if ( !$this->typeName || count( $this->ids ) == 0 ) { throw new ErrorPageError( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); } - $this->typeInfo = self::$allowedTypes[$this->typeName]; - $this->mIsAllowed = $user->isAllowed( $this->typeInfo['permission'] ); - - # If we have revisions, get the title from the first one - # since they should all be from the same page. This allows - # for more flexibility with page moves... - if( $this->typeName == 'revision' ) { - $rev = Revision::newFromId( $this->ids[0] ); - $this->targetObj = $rev ? $rev->getTitle() : $this->targetObj; - } + $this->typeLabels = self::$UILabels[$this->typeName]; + $this->mIsAllowed = $user->isAllowed( RevisionDeleter::getRestriction( $this->typeName ) ); + + # Allow the list type to adjust the passed target + $this->targetObj = RevisionDeleter::suggestTarget( $this->typeName, $this->targetObj, $this->ids ); $this->otherReason = $request->getVal( 'wpReason' ); # We need a target page! - if( is_null($this->targetObj) ) { + if ( is_null( $this->targetObj ) ) { $output->addWikiMsg( 'undelete-header' ); return; } @@ -205,17 +155,19 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { # Initialise checkboxes $this->checks = array( - array( $this->typeInfo['check-label'], 'wpHidePrimary', $this->typeInfo['deletion-bits'] ), + array( $this->typeLabels['check-label'], 'wpHidePrimary', + RevisionDeleter::getRevdelConstant( $this->typeName ) + ), array( 'revdelete-hide-comment', 'wpHideComment', Revision::DELETED_COMMENT ), array( 'revdelete-hide-user', 'wpHideUser', Revision::DELETED_USER ) ); - if( $user->isAllowed('suppressrevision') ) { + if ( $user->isAllowed( 'suppressrevision' ) ) { $this->checks[] = array( 'revdelete-hide-restricted', 'wpHideRestricted', Revision::DELETED_RESTRICTED ); } # Either submit or create our form - if( $this->mIsAllowed && $this->submitClicked ) { + if ( $this->mIsAllowed && $this->submitClicked ) { $this->submit( $request ); } else { $this->showForm(); @@ -228,9 +180,9 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { LogEventsList::showLogExtract( $output, 'delete', $this->targetObj, '', array( 'lim' => 25, 'conds' => $qc ) ); # Show relevant lines from the suppression log - if( $user->isAllowed( 'suppressionlog' ) ) { + if ( $user->isAllowed( 'suppressionlog' ) ) { $suppressLogPage = new LogPage( 'suppress' ); - $output->addHTML( "<h2>" . $suppressLogPage->getName()->escaped() . "</h2>\n" ); + $output->addHTML( "<h2>" . $suppressLogPage->getName()->escaped() . "</h2>\n" ); LogEventsList::showLogExtract( $output, 'suppress', $this->targetObj, '', array( 'lim' => 25, 'conds' => $qc ) ); } @@ -241,7 +193,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { */ protected function showConvenienceLinks() { # Give a link to the logs/hist for this page - if( $this->targetObj ) { + if ( $this->targetObj ) { $links = array(); $links[] = Linker::linkKnown( SpecialPage::getTitleFor( 'Log' ), @@ -258,7 +210,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { array( 'action' => 'history' ) ); # Link to deleted edits - if( $this->getUser()->isAllowed('undelete') ) { + if ( $this->getUser()->isAllowed( 'undelete' ) ) { $undelete = SpecialPage::getTitleFor( 'Undelete' ); $links[] = Linker::linkKnown( $undelete, @@ -301,8 +253,8 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { return; } $user = $this->getUser(); - if( !$oimage->userCan( File::DELETED_FILE, $user ) ) { - if( $oimage->isDeleted( File::DELETED_RESTRICTED ) ) { + if ( !$oimage->userCan( File::DELETED_FILE, $user ) ) { + if ( $oimage->isDeleted( File::DELETED_RESTRICTED ) ) { throw new PermissionsError( 'suppressrevision' ); } else { throw new PermissionsError( 'deletedtext' ); @@ -317,10 +269,11 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { $this->getOutput()->addHTML( Xml::openElement( 'form', array( 'method' => 'POST', - 'action' => $this->getTitle()->getLocalUrl( - 'target=' . urlencode( $this->targetObj->getPrefixedDBkey() ) . - '&file=' . urlencode( $archiveName ) . - '&token=' . urlencode( $user->getEditToken( $archiveName ) ) ) + 'action' => $this->getTitle()->getLocalURL( array( + 'target' => $this->targetObj->getPrefixedDBkey(), + 'file' => $archiveName, + 'token' => $user->getEditToken( $archiveName ), + ) ) ) ) . Xml::submitButton( $this->msg( 'revdelete-show-file-submit' )->text() ) . @@ -347,8 +300,9 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { */ protected function getList() { if ( is_null( $this->list ) ) { - $class = $this->typeInfo['list-class']; - $this->list = new $class( $this->getContext(), $this->targetObj, $this->ids ); + $this->list = RevisionDeleter::createList( + $this->typeName, $this->getContext(), $this->targetObj, $this->ids + ); } return $this->list; } @@ -361,7 +315,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { $UserAllowed = true; if ( $this->typeName == 'logging' ) { - $this->getOutput()->addWikiMsg( 'logdelete-selected', $this->getLanguage()->formatNum( count($this->ids) ) ); + $this->getOutput()->addWikiMsg( 'logdelete-selected', $this->getLanguage()->formatNum( count( $this->ids ) ) ); } else { $this->getOutput()->addWikiMsg( 'revdelete-selected', $this->targetObj->getPrefixedText(), count( $this->ids ) ); @@ -375,7 +329,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { for ( $list->reset(); $list->current(); $list->next() ) { $item = $list->current(); if ( !$item->canView() ) { - if( !$this->submitClicked ) { + if ( !$this->submitClicked ) { throw new PermissionsError( 'suppressrevision' ); } $UserAllowed = false; @@ -384,7 +338,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { $this->getOutput()->addHTML( $item->getHTML() ); } - if( !$numRevisions ) { + if ( !$numRevisions ) { throw new ErrorPageError( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); } @@ -393,12 +347,14 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { $this->addUsageText(); // Normal sysops can always see what they did, but can't always change it - if( !$UserAllowed ) return; + if ( !$UserAllowed ) { + return; + } // Show form if the user can submit - if( $this->mIsAllowed ) { + if ( $this->mIsAllowed ) { $out = Xml::openElement( 'form', array( 'method' => 'post', - 'action' => $this->getTitle()->getLocalUrl( array( 'action' => 'submit' ) ), + 'action' => $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) ), 'id' => 'mw-revdel-form-revisions' ) ) . Xml::fieldset( $this->msg( 'revdelete-legend' )->text() ) . $this->buildCheckBoxes() . @@ -411,7 +367,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { Xml::listDropDown( 'wpRevDeleteReasonList', $this->msg( 'revdelete-reason-dropdown' )->inContentLanguage()->text(), $this->msg( 'revdelete-reasonotherlist' )->inContentLanguage()->text(), - '', 'wpReasonDropDown', 1 + $this->getRequest()->getText( 'wpRevDeleteReasonList', 'other' ), 'wpReasonDropDown', 1 ) . '</td>' . "</tr><tr>\n" . @@ -437,10 +393,10 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { } else { $out = ''; } - if( $this->mIsAllowed ) { + if ( $this->mIsAllowed ) { $out .= Xml::closeElement( 'form' ) . "\n"; // Show link to edit the dropdown reasons - if( $this->getUser()->isAllowed( 'editinterface' ) ) { + if ( $this->getUser()->isAllowed( 'editinterface' ) ) { $title = Title::makeTitle( NS_MEDIAWIKI, 'Revdelete-reason-dropdown' ); $link = Linker::link( $title, @@ -460,32 +416,33 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { */ protected function addUsageText() { $this->getOutput()->addWikiMsg( 'revdelete-text' ); - if( $this->getUser()->isAllowed( 'suppressrevision' ) ) { + if ( $this->getUser()->isAllowed( 'suppressrevision' ) ) { $this->getOutput()->addWikiMsg( 'revdelete-suppress-text' ); } - if( $this->mIsAllowed ) { + if ( $this->mIsAllowed ) { $this->getOutput()->addWikiMsg( 'revdelete-confirm' ); } } /** - * @return String: HTML - */ + * @return String: HTML + */ protected function buildCheckBoxes() { $html = '<table>'; // If there is just one item, use checkboxes $list = $this->getList(); - if( $list->length() == 1 ) { + if ( $list->length() == 1 ) { $list->reset(); $bitfield = $list->current()->getBits(); // existing field - if( $this->submitClicked ) { + if ( $this->submitClicked ) { $bitfield = $this->extractBitfield( $this->extractBitParams(), $bitfield ); } - foreach( $this->checks as $item ) { + foreach ( $this->checks as $item ) { list( $message, $name, $field ) = $item; $innerHTML = Xml::checkLabel( $this->msg( $message )->text(), $name, $name, $bitfield & $field ); - if( $field == Revision::DELETED_RESTRICTED ) + if ( $field == Revision::DELETED_RESTRICTED ) { $innerHTML = "<b>$innerHTML</b>"; + } $line = Xml::tags( 'td', array( 'class' => 'mw-input' ), $innerHTML ); $html .= "<tr>$line</tr>\n"; } @@ -496,10 +453,10 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { $html .= '<th class="mw-revdel-checkbox">' . $this->msg( 'revdelete-radio-unset' )->escaped() . '</th>'; $html .= '<th class="mw-revdel-checkbox">' . $this->msg( 'revdelete-radio-set' )->escaped() . '</th>'; $html .= "<th></th></tr>\n"; - foreach( $this->checks as $item ) { + foreach ( $this->checks as $item ) { list( $message, $name, $field ) = $item; // If there are several items, use third state by default... - if( $this->submitClicked ) { + if ( $this->submitClicked ) { $selected = $this->getRequest()->getInt( $name, 0 /* unchecked */ ); } else { $selected = -1; // use existing field @@ -508,7 +465,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { $line .= '<td class="mw-revdel-checkbox">' . Xml::radio( $name, 0, $selected == 0 ) . '</td>'; $line .= '<td class="mw-revdel-checkbox">' . Xml::radio( $name, 1, $selected == 1 ) . '</td>'; $label = $this->msg( $message )->escaped(); - if( $field == Revision::DELETED_RESTRICTED ) { + if ( $field == Revision::DELETED_RESTRICTED ) { $label = "<b>$label</b>"; } $line .= "<td>$label</td>"; @@ -522,26 +479,27 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { /** * UI entry point for form submission. + * @throws PermissionsError * @return bool */ protected function submit() { # Check edit token on submission - $token = $this->getRequest()->getVal('wpEditToken'); - if( $this->submitClicked && !$this->getUser()->matchEditToken( $token ) ) { + $token = $this->getRequest()->getVal( 'wpEditToken' ); + if ( $this->submitClicked && !$this->getUser()->matchEditToken( $token ) ) { $this->getOutput()->addWikiMsg( 'sessionfailure' ); return false; } $bitParams = $this->extractBitParams(); $listReason = $this->getRequest()->getText( 'wpRevDeleteReasonList', 'other' ); // from dropdown $comment = $listReason; - if( $comment != 'other' && $this->otherReason != '' ) { + if ( $comment != 'other' && $this->otherReason != '' ) { // Entry from drop down menu + additional comment $comment .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $this->otherReason; - } elseif( $comment == 'other' ) { + } elseif ( $comment == 'other' ) { $comment = $this->otherReason; } # Can the user set this field? - if( $bitParams[Revision::DELETED_RESTRICTED]==1 && !$this->getUser()->isAllowed('suppressrevision') ) { + if ( $bitParams[Revision::DELETED_RESTRICTED] == 1 && !$this->getUser()->isAllowed( 'suppressrevision' ) ) { throw new PermissionsError( 'suppressrevision' ); } # If the save went through, go to success message... @@ -561,7 +519,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { */ protected function success() { $this->getOutput()->setPageTitle( $this->msg( 'actioncomplete' ) ); - $this->getOutput()->wrapWikiMsg( "<span class=\"success\">\n$1\n</span>", $this->typeInfo['success'] ); + $this->getOutput()->wrapWikiMsg( "<span class=\"success\">\n$1\n</span>", $this->typeLabels['success'] ); $this->list->reloadFromMaster(); $this->showForm(); } @@ -571,7 +529,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { */ protected function failure( $status ) { $this->getOutput()->setPageTitle( $this->msg( 'actionfailed' ) ); - $this->getOutput()->addWikiText( $status->getWikiText( $this->typeInfo['failure'] ) ); + $this->getOutput()->addWikiText( $status->getWikiText( $this->typeLabels['failure'] ) ); $this->showForm(); } @@ -582,15 +540,15 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { */ protected function extractBitParams() { $bitfield = array(); - foreach( $this->checks as $item ) { - list( /* message */ , $name, $field ) = $item; + foreach ( $this->checks as $item ) { + list( /* message */, $name, $field ) = $item; $val = $this->getRequest()->getInt( $name, 0 /* unchecked */ ); - if( $val < -1 || $val > 1) { + if ( $val < -1 || $val > 1 ) { $val = -1; // -1 for existing value } $bitfield[$field] = $val; } - if( !isset($bitfield[Revision::DELETED_RESTRICTED]) ) { + if ( !isset( $bitfield[Revision::DELETED_RESTRICTED] ) ) { $bitfield[Revision::DELETED_RESTRICTED] = 0; } return $bitfield; @@ -598,21 +556,13 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { /** * Put together a rev_deleted bitfield - * @param $bitPars array extractBitParams() params - * @param $oldfield int current bitfield + * @deprecated since 1.22, use RevisionDeleter::extractBitfield instead + * @param array $bitPars extractBitParams() params + * @param int $oldfield current bitfield * @return array */ public static function extractBitfield( $bitPars, $oldfield ) { - // Build the actual new rev_deleted bitfield - $newBits = 0; - foreach( $bitPars as $const => $val ) { - if( $val == 1 ) { - $newBits |= $const; // $const is the *_deleted const - } elseif( $val == -1 ) { - $newBits |= ($oldfield & $const); // use existing - } - } - return $newBits; + return RevisionDeleter::extractBitfield( $bitPars, $oldfield ); } /** @@ -627,5 +577,8 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { array( 'value' => $bitfield, 'comment' => $reason ) ); } -} + protected function getGroupName() { + return 'pagetools'; + } +} diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php index 5f5b6b4d..8609c740 100644 --- a/includes/specials/SpecialSearch.php +++ b/includes/specials/SpecialSearch.php @@ -42,6 +42,9 @@ class SpecialSearch extends SpecialPage { /// Search engine protected $searchEngine; + /// Search engine type, if not default + protected $searchEngineType; + /// For links protected $extraParams = array(); @@ -78,7 +81,7 @@ class SpecialSearch extends SpecialPage { /** * Entry point * - * @param $par String or null + * @param string $par or null */ public function execute( $par ) { $this->setHeaders(); @@ -98,6 +101,8 @@ class SpecialSearch extends SpecialPage { $this->load(); + $this->searchEngineType = $request->getVal( 'srbackend' ); + if ( $request->getVal( 'fulltext' ) || !is_null( $request->getVal( 'offset' ) ) || !is_null( $request->getVal( 'searchx' ) ) ) @@ -137,8 +142,8 @@ class SpecialSearch extends SpecialPage { if ( $profile === null ) { // BC with old request format $profile = 'advanced'; - foreach( $profiles as $key => $data ) { - if ( $nslist === $data['namespaces'] && $key !== 'advanced') { + foreach ( $profiles as $key => $data ) { + if ( $nslist === $data['namespaces'] && $key !== 'advanced' ) { $profile = $key; } } @@ -159,7 +164,7 @@ class SpecialSearch extends SpecialPage { $default = $request->getBool( 'profile' ) ? 0 : 1; $this->searchRedirects = $request->getBool( 'redirs', $default ) ? 1 : 0; $this->didYouMeanHtml = ''; # html of did you mean... link - $this->fulltext = $request->getVal('fulltext'); + $this->fulltext = $request->getVal( 'fulltext' ); $this->profile = $profile; } @@ -173,7 +178,7 @@ class SpecialSearch extends SpecialPage { # Try to go to page as entered. $t = Title::newFromText( $term ); # If the string cannot be used to create a title - if( is_null( $t ) ) { + if ( is_null( $t ) ) { $this->showResults( $term ); return; } @@ -185,19 +190,19 @@ class SpecialSearch extends SpecialPage { return; } - if( !is_null( $t ) ) { + if ( !is_null( $t ) ) { $this->getOutput()->redirect( $t->getFullURL() ); return; } # No match, generate an edit URL $t = Title::newFromText( $term ); - if( !is_null( $t ) ) { + if ( !is_null( $t ) ) { global $wgGoToEdit; wfRunHooks( 'SpecialSearchNogomatch', array( &$t ) ); wfDebugLog( 'nogomatch', $t->getText(), false ); # If the feature is enabled, go straight to the edit page - if( $wgGoToEdit ) { + if ( $wgGoToEdit ) { $this->getOutput()->redirect( $t->getFullURL( array( 'action' => 'edit' ) ) ); return; } @@ -218,7 +223,7 @@ class SpecialSearch extends SpecialPage { $search->showRedirects = $this->searchRedirects; // BC $search->setFeatureData( 'list-redirects', $this->searchRedirects ); $search->prefix = $this->mPrefix; - $term = $search->transformSearchTerm($term); + $term = $search->transformSearchTerm( $term ); wfRunHooks( 'SpecialSearchSetupEngine', array( $this, $this->profile, $search ) ); @@ -250,21 +255,27 @@ class SpecialSearch extends SpecialPage { $t = Title::newFromText( $term ); // fetch search results - $rewritten = $search->replacePrefixes($term); + $rewritten = $search->replacePrefixes( $term ); $titleMatches = $search->searchTitle( $rewritten ); - if( !( $titleMatches instanceof SearchResultTooMany ) ) { + if ( !( $titleMatches instanceof SearchResultTooMany ) ) { $textMatches = $search->searchText( $rewritten ); } + $textStatus = null; + if ( $textMatches instanceof Status ) { + $textStatus = $textMatches; + $textMatches = null; + } + // did you mean... suggestions - if( $textMatches && $textMatches->hasSuggestion() ) { + if ( $textMatches && !$textStatus && $textMatches->hasSuggestion() ) { $st = SpecialPage::getTitleFor( 'Search' ); - # mirror Go/Search behaviour of original request .. + # mirror Go/Search behavior of original request .. $didYouMeanParams = array( 'search' => $textMatches->getSuggestionQuery() ); - if( $this->fulltext != null ) { + if ( $this->fulltext != null ) { $didYouMeanParams['fulltext'] = $this->fulltext; } @@ -275,7 +286,7 @@ class SpecialSearch extends SpecialPage { $suggestionSnippet = $textMatches->getSuggestionSnippet(); - if( $suggestionSnippet == '' ) { + if ( $suggestionSnippet == '' ) { $suggestionSnippet = null; } @@ -288,6 +299,13 @@ class SpecialSearch extends SpecialPage { $this->didYouMeanHtml = '<div class="searchdidyoumean">' . $this->msg( 'search-suggest' )->rawParams( $suggestLink )->text() . '</div>'; } + + if ( !wfRunHooks( 'SpecialSearchResultsPrepend', array( $this, $out, $term ) ) ) { + # Hook requested termination + wfProfileOut( __METHOD__ ); + return; + } + // start rendering the page $out->addHtml( Xml::openElement( @@ -304,20 +322,20 @@ class SpecialSearch extends SpecialPage { Xml::openElement( 'tr' ) . Xml::openElement( 'td' ) . "\n" . $this->shortDialog( $term ) . - Xml::closeElement('td') . - Xml::closeElement('tr') . - Xml::closeElement('table') + Xml::closeElement( 'td' ) . + Xml::closeElement( 'tr' ) . + Xml::closeElement( 'table' ) ); // Sometimes the search engine knows there are too many hits - if( $titleMatches instanceof SearchResultTooMany ) { + if ( $titleMatches instanceof SearchResultTooMany ) { $out->wrapWikiMsg( "==$1==\n", 'toomanymatches' ); wfProfileOut( __METHOD__ ); return; } - $filePrefix = $wgContLang->getFormattedNsText(NS_FILE).':'; - if( trim( $term ) === '' || $filePrefix === trim( $term ) ) { + $filePrefix = $wgContLang->getFormattedNsText( NS_FILE ) . ':'; + if ( trim( $term ) === '' || $filePrefix === trim( $term ) ) { $out->addHTML( $this->formHeader( $term, 0, 0 ) ); $out->addHtml( $this->getProfileForm( $this->profile, $term ) ); $out->addHTML( '</form>' ); @@ -340,21 +358,22 @@ class SpecialSearch extends SpecialPage { // get total number of results if backend can calculate it $totalRes = 0; - if($titleMatches && !is_null( $titleMatches->getTotalHits() ) ) + if ( $titleMatches && !is_null( $titleMatches->getTotalHits() ) ) { $totalRes += $titleMatches->getTotalHits(); - if($textMatches && !is_null( $textMatches->getTotalHits() )) + } + if ( $textMatches && !is_null( $textMatches->getTotalHits() ) ) { $totalRes += $textMatches->getTotalHits(); + } // show number of results and current offset $out->addHTML( $this->formHeader( $term, $num, $totalRes ) ); $out->addHtml( $this->getProfileForm( $this->profile, $term ) ); - $out->addHtml( Xml::closeElement( 'form' ) ); $out->addHtml( "<div class='searchresults'>" ); // prev/next links - if( $num || $this->offset ) { + if ( $num || $this->offset ) { // Show the create link ahead $this->showCreateLink( $t ); $prevnext = $this->getLanguage()->viewPrevNext( $this->getTitle(), $this->offset, $this->limit, @@ -368,42 +387,49 @@ class SpecialSearch extends SpecialPage { } $out->parserOptions()->setEditSection( false ); - if( $titleMatches ) { - if( $numTitleMatches > 0 ) { + if ( $titleMatches ) { + if ( $numTitleMatches > 0 ) { $out->wrapWikiMsg( "==$1==\n", 'titlematches' ); $out->addHTML( $this->showMatches( $titleMatches ) ); } $titleMatches->free(); } - if( $textMatches ) { + if ( $textMatches && !$textStatus ) { // output appropriate heading - if( $numTextMatches > 0 && $numTitleMatches > 0 ) { + if ( $numTextMatches > 0 && $numTitleMatches > 0 ) { // if no title matches the heading is redundant $out->wrapWikiMsg( "==$1==\n", 'textmatches' ); - } elseif( $totalRes == 0 ) { + } elseif ( $totalRes == 0 ) { # Don't show the 'no text matches' if we received title matches # $out->wrapWikiMsg( "==$1==\n", 'notextmatches' ); } // show interwiki results if any - if( $textMatches->hasInterwikiResults() ) { + if ( $textMatches->hasInterwikiResults() ) { $out->addHTML( $this->showInterwiki( $textMatches->getInterwikiResults(), $term ) ); } // show results - if( $numTextMatches > 0 ) { + if ( $numTextMatches > 0 ) { $out->addHTML( $this->showMatches( $textMatches ) ); } $textMatches->free(); } - if( $num === 0 ) { - $out->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>", array( 'search-nonefound', wfEscapeWikiText( $term ) ) ); - $this->showCreateLink( $t ); + if ( $num === 0 ) { + if ( $textStatus ) { + $out->addHTML( '<div class="error">' . + htmlspecialchars( $textStatus->getWikiText( 'search-error' ) ) . '</div>' ); + } else { + $out->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>", + array( 'search-nonefound', wfEscapeWikiText( $term ) ) ); + $this->showCreateLink( $t ); + } } $out->addHtml( "</div>" ); - if( $num || $this->offset ) { + if ( $num || $this->offset ) { $out->addHTML( "<p class='mw-search-pager-bottom'>{$prevnext}</p>\n" ); } + wfRunHooks( 'SpecialSearchResultsAppend', array( $this, $out, $term ) ); wfProfileOut( __METHOD__ ); } @@ -414,16 +440,16 @@ class SpecialSearch extends SpecialPage { // show direct page/create link if applicable // Check DBkey !== '' in case of fragment link only. - if( is_null( $t ) || $t->getDBkey() === '' ) { + if ( is_null( $t ) || $t->getDBkey() === '' ) { // invalid title // preserve the paragraph for margins etc... $this->getOutput()->addHtml( '<p></p>' ); return; } - if( $t->isKnown() ) { + if ( $t->isKnown() ) { $messageName = 'searchmenu-exists'; - } elseif( $t->userCan( 'create' ) ) { + } elseif ( $t->userCan( 'create', $this->getUser() ) ) { $messageName = 'searchmenu-new'; } else { $messageName = 'searchmenu-new-nocreate'; @@ -432,7 +458,7 @@ class SpecialSearch extends SpecialPage { wfRunHooks( 'SpecialSearchCreateLink', array( $t, &$params ) ); // Extensions using the hook might still return an empty $messageName - if( $messageName ) { + if ( $messageName ) { $this->getOutput()->wrapWikiMsg( "<p class=\"mw-search-createlink\">\n$1</p>", $params ); } else { // preserve the paragraph for margins etc... @@ -445,11 +471,13 @@ class SpecialSearch extends SpecialPage { */ protected function setupPage( $term ) { # Should advanced UI be used? - $this->searchAdvanced = ($this->profile === 'advanced'); + $this->searchAdvanced = ( $this->profile === 'advanced' ); $out = $this->getOutput(); - if( strval( $term ) !== '' ) { + if ( strval( $term ) !== '' ) { $out->setPageTitle( $this->msg( 'searchresults' ) ); - $out->setHTMLTitle( $this->msg( 'pagetitle', $this->msg( 'searchresults-title', $term )->plain() ) ); + $out->setHTMLTitle( $this->msg( 'pagetitle' )->rawParams( + $this->msg( 'searchresults-title' )->rawParams( $term )->text() + ) ); } // add javascript specific to special:search $out->addModules( 'mediawiki.special.search' ); @@ -464,8 +492,8 @@ class SpecialSearch extends SpecialPage { */ protected function powerSearch( &$request ) { $arr = array(); - foreach( SearchEngine::searchableNamespaces() as $ns => $name ) { - if( $request->getCheck( 'ns' . $ns ) ) { + foreach ( SearchEngine::searchableNamespaces() as $ns => $name ) { + if ( $request->getCheck( 'ns' . $ns ) ) { $arr[] = $ns; } } @@ -481,10 +509,10 @@ class SpecialSearch extends SpecialPage { protected function powerSearchOptions() { $opt = array(); $opt['redirs'] = $this->searchRedirects ? 1 : 0; - if( $this->profile !== 'advanced' ) { + if ( $this->profile !== 'advanced' ) { $opt['profile'] = $this->profile; } else { - foreach( $this->namespaces as $n ) { + foreach ( $this->namespaces as $n ) { $opt['ns' . $n] = 1; } } @@ -506,12 +534,12 @@ class SpecialSearch extends SpecialPage { $out = ""; $infoLine = $matches->getInfo(); - if( !is_null($infoLine) ) { + if ( !is_null( $infoLine ) ) { $out .= "\n<!-- {$infoLine} -->\n"; } $out .= "<ul class='mw-search-results'>\n"; $result = $matches->next(); - while( $result ) { + while ( $result ) { $out .= $this->showHit( $result, $terms ); $result = $matches->next(); } @@ -527,24 +555,25 @@ class SpecialSearch extends SpecialPage { * Format a single hit result * * @param $result SearchResult - * @param $terms Array: terms to highlight + * @param array $terms terms to highlight * * @return string */ protected function showHit( $result, $terms ) { wfProfileIn( __METHOD__ ); - if( $result->isBrokenTitle() ) { + if ( $result->isBrokenTitle() ) { wfProfileOut( __METHOD__ ); return "<!-- Broken link in search result -->\n"; } $t = $result->getTitle(); - $titleSnippet = $result->getTitleSnippet($terms); + $titleSnippet = $result->getTitleSnippet( $terms ); - if( $titleSnippet == '' ) + if ( $titleSnippet == '' ) { $titleSnippet = null; + } $link_t = clone $t; @@ -559,7 +588,7 @@ class SpecialSearch extends SpecialPage { //If page content is not readable, just return the title. //This is not quite safe, but better than showing excerpts from non-readable pages //Note that hiding the entry entirely would screw up paging. - if( !$t->userCan( 'read' ) ) { + if ( !$t->userCan( 'read', $this->getUser() ) ) { wfProfileOut( __METHOD__ ); return "<li>{$link}</li>\n"; } @@ -567,21 +596,22 @@ class SpecialSearch extends SpecialPage { // If the page doesn't *exist*... our search index is out of date. // The least confusing at this point is to drop the result. // You may get less results, but... oh well. :P - if( $result->isMissingRevision() ) { + if ( $result->isMissingRevision() ) { wfProfileOut( __METHOD__ ); return "<!-- missing page " . htmlspecialchars( $t->getPrefixedText() ) . "-->\n"; } // format redirects / relevant sections $redirectTitle = $result->getRedirectTitle(); - $redirectText = $result->getRedirectSnippet($terms); + $redirectText = $result->getRedirectSnippet( $terms ); $sectionTitle = $result->getSectionTitle(); - $sectionText = $result->getSectionSnippet($terms); + $sectionText = $result->getSectionSnippet( $terms ); $redirect = ''; - if( !is_null($redirectTitle) ) { - if( $redirectText == '' ) + if ( !is_null( $redirectTitle ) ) { + if ( $redirectText == '' ) { $redirectText = null; + } $redirect = "<span class='searchalttitle'>" . $this->msg( 'search-redirect' )->rawParams( @@ -591,9 +621,10 @@ class SpecialSearch extends SpecialPage { $section = ''; - if( !is_null($sectionTitle) ) { - if( $sectionText == '' ) + if ( !is_null( $sectionTitle ) ) { + if ( $sectionText == '' ) { $sectionText = null; + } $section = "<span class='searchalttitle'>" . $this->msg( 'search-section' )->rawParams( @@ -602,12 +633,12 @@ class SpecialSearch extends SpecialPage { } // format text extract - $extract = "<div class='searchresult'>".$result->getTextSnippet($terms)."</div>"; + $extract = "<div class='searchresult'>" . $result->getTextSnippet( $terms ) . "</div>"; $lang = $this->getLanguage(); // format score - if( is_null( $result->getScore() ) ) { + if ( is_null( $result->getScore() ) ) { // Search engine doesn't report scoring info $score = ''; } else { @@ -623,7 +654,7 @@ class SpecialSearch extends SpecialPage { $size = $this->msg( 'search-result-size', $lang->formatSize( $byteSize ) ) ->numParams( $wordCount )->escaped(); - if( $t->getNamespace() == NS_CATEGORY ) { + if ( $t->getNamespace() == NS_CATEGORY ) { $cat = Category::newFromTitle( $t ); $size = $this->msg( 'search-result-category-size' ) ->numParams( $cat->getPageCount(), $cat->getSubcatCount(), $cat->getFileCount() ) @@ -634,7 +665,7 @@ class SpecialSearch extends SpecialPage { // link to related articles if supported $related = ''; - if( $result->hasRelated() ) { + if ( $result->hasRelated() ) { $st = SpecialPage::getTitleFor( 'Search' ); $stParams = array_merge( $this->powerSearchOptions(), @@ -654,11 +685,11 @@ class SpecialSearch extends SpecialPage { } // Include a thumbnail for media files... - if( $t->getNamespace() == NS_FILE ) { + if ( $t->getNamespace() == NS_FILE ) { $img = wfFindFile( $t ); - if( $img ) { + if ( $img ) { $thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) ); - if( $thumb ) { + if ( $thumb ) { $desc = $this->msg( 'parentheses' )->rawParams( $img->getShortDesc() )->escaped(); wfProfileOut( __METHOD__ ); // Float doesn't seem to interact well with the bullets. @@ -667,7 +698,7 @@ class SpecialSearch extends SpecialPage { return "<li>" . '<table class="searchResultImage">' . '<tr>' . - '<td width="120" style="text-align: center; vertical-align: top;">' . + '<td style="width: 120px; text-align: center; vertical-align: top;">' . $thumb->toHtml( array( 'desc-link' => true ) ) . '</td>' . '<td style="vertical-align: top;">' . @@ -682,11 +713,21 @@ class SpecialSearch extends SpecialPage { } } - wfProfileOut( __METHOD__ ); - return "<li><div class='mw-search-result-heading'>{$link} {$redirect} {$section}</div> {$extract}\n" . - "<div class='mw-search-result-data'>{$score}{$size} - {$date}{$related}</div>" . - "</li>\n"; + $html = null; + + if ( wfRunHooks( 'ShowSearchHit', array( + $this, $result, $terms, + &$link, &$redirect, &$section, &$extract, + &$score, &$size, &$date, &$related, + &$html + ) ) ) { + $html = "<li><div class='mw-search-result-heading'>{$link} {$redirect} {$section}</div> {$extract}\n" . + "<div class='mw-search-result-data'>{$score}{$size} - {$date}{$related}</div>" . + "</li>\n"; + } + wfProfileOut( __METHOD__ ); + return $html; } /** @@ -702,22 +743,23 @@ class SpecialSearch extends SpecialPage { wfProfileIn( __METHOD__ ); $terms = $wgContLang->convertForSearchResult( $matches->termMatches() ); - $out = "<div id='mw-search-interwiki'><div id='mw-search-interwiki-caption'>". - $this->msg( 'search-interwiki-caption' )->text() . "</div>\n"; + $out = "<div id='mw-search-interwiki'><div id='mw-search-interwiki-caption'>" . + $this->msg( 'search-interwiki-caption' )->text() . "</div>\n"; $out .= "<ul class='mw-search-iwresults'>\n"; // work out custom project captions $customCaptions = array(); $customLines = explode( "\n", $this->msg( 'search-interwiki-custom' )->text() ); // format per line <iwprefix>:<caption> - foreach($customLines as $line) { - $parts = explode(":",$line,2); - if(count($parts) == 2) // validate line + foreach ( $customLines as $line ) { + $parts = explode( ":", $line, 2 ); + if ( count( $parts ) == 2 ) { // validate line $customCaptions[$parts[0]] = $parts[1]; + } } $prev = null; $result = $matches->next(); - while( $result ) { + while ( $result ) { $out .= $this->showInterwikiHit( $result, $prev, $terms, $query, $customCaptions ); $prev = $result->getInterwikiPrefix(); $result = $matches->next(); @@ -738,24 +780,25 @@ class SpecialSearch extends SpecialPage { * @param $lastInterwiki String * @param $terms Array * @param $query String - * @param $customCaptions Array: iw prefix -> caption + * @param array $customCaptions iw prefix -> caption * * @return string */ - protected function showInterwikiHit( $result, $lastInterwiki, $terms, $query, $customCaptions) { + protected function showInterwikiHit( $result, $lastInterwiki, $terms, $query, $customCaptions ) { wfProfileIn( __METHOD__ ); - if( $result->isBrokenTitle() ) { + if ( $result->isBrokenTitle() ) { wfProfileOut( __METHOD__ ); return "<!-- Broken link in search result -->\n"; } $t = $result->getTitle(); - $titleSnippet = $result->getTitleSnippet($terms); + $titleSnippet = $result->getTitleSnippet( $terms ); - if( $titleSnippet == '' ) + if ( $titleSnippet == '' ) { $titleSnippet = null; + } $link = Linker::linkKnown( $t, @@ -764,11 +807,12 @@ class SpecialSearch extends SpecialPage { // format redirect if any $redirectTitle = $result->getRedirectTitle(); - $redirectText = $result->getRedirectSnippet($terms); + $redirectText = $result->getRedirectSnippet( $terms ); $redirect = ''; - if( !is_null($redirectTitle) ) { - if( $redirectText == '' ) + if ( !is_null( $redirectTitle ) ) { + if ( $redirectText == '' ) { $redirectText = null; + } $redirect = "<span class='searchalttitle'>" . $this->msg( 'search-redirect' )->rawParams( @@ -778,8 +822,8 @@ class SpecialSearch extends SpecialPage { $out = ""; // display project name - if(is_null($lastInterwiki) || $lastInterwiki != $t->getInterwiki()) { - if( array_key_exists($t->getInterwiki(),$customCaptions) ) { + if ( is_null( $lastInterwiki ) || $lastInterwiki != $t->getInterwiki() ) { + if ( array_key_exists( $t->getInterwiki(), $customCaptions ) ) { // captions from 'search-interwiki-custom' $caption = $customCaptions[$t->getInterwiki()]; } else { @@ -789,7 +833,7 @@ class SpecialSearch extends SpecialPage { $caption = $this->msg( 'search-interwiki-default', $parsed['host'] )->text(); } // "more results" link (special page stuff could be localized, but we might not know target lang) - $searchTitle = Title::newFromText($t->getInterwiki().":Special:Search"); + $searchTitle = Title::newFromText( $t->getInterwiki() . ":Special:Search" ); $searchLink = Linker::linkKnown( $searchTitle, $this->msg( 'search-interwiki-more' )->text(), @@ -831,22 +875,26 @@ class SpecialSearch extends SpecialPage { /** * Generates the power search box at [[Special:Search]] * - * @param $term String: search term + * @param string $term search term * @param $opts array * @return String: HTML form */ protected function powerSearchBox( $term, $opts ) { + global $wgContLang; + // Groups namespaces into rows according to subject $rows = array(); - foreach( SearchEngine::searchableNamespaces() as $namespace => $name ) { + foreach ( SearchEngine::searchableNamespaces() as $namespace => $name ) { $subject = MWNamespace::getSubject( $namespace ); - if( !array_key_exists( $subject, $rows ) ) { + if ( !array_key_exists( $subject, $rows ) ) { $rows[$subject] = ""; } - $name = str_replace( '_', ' ', $name ); - if( $name == '' ) { + + $name = $wgContLang->getConverter()->convertNamespace( $namespace ); + if ( $name == '' ) { $name = $this->msg( 'blanknamespace' )->text(); } + $rows[$subject] .= Xml::openElement( 'td', array( 'style' => 'white-space: nowrap' ) @@ -859,27 +907,30 @@ class SpecialSearch extends SpecialPage { ) . Xml::closeElement( 'td' ); } + $rows = array_values( $rows ); $numRows = count( $rows ); // Lays out namespaces in multiple floating two-column tables so they'll // be arranged nicely while still accommodating different screen widths $namespaceTables = ''; - for( $i = 0; $i < $numRows; $i += 4 ) { + for ( $i = 0; $i < $numRows; $i += 4 ) { $namespaceTables .= Xml::openElement( 'table', array( 'cellpadding' => 0, 'cellspacing' => 0 ) ); - for( $j = $i; $j < $i + 4 && $j < $numRows; $j++ ) { + + for ( $j = $i; $j < $i + 4 && $j < $numRows; $j++ ) { $namespaceTables .= Xml::tags( 'tr', null, $rows[$j] ); } + $namespaceTables .= Xml::closeElement( 'table' ); } $showSections = array( 'namespaceTables' => $namespaceTables ); // Show redirects check only if backend supports it - if( $this->getSearchEngine()->supports( 'list-redirects' ) ) { + if ( $this->getSearchEngine()->supports( 'list-redirects' ) ) { $showSections['redirects'] = Xml::checkLabel( $this->msg( 'powersearch-redir' )->text(), 'redirs', 'redirs', $this->searchRedirects ); } @@ -888,16 +939,15 @@ class SpecialSearch extends SpecialPage { $hidden = ''; unset( $opts['redirs'] ); - foreach( $opts as $key => $value ) { + foreach ( $opts as $key => $value ) { $hidden .= Html::hidden( $key, $value ); } // Return final output - return - Xml::openElement( + return Xml::openElement( 'fieldset', array( 'id' => 'mw-searchoptions', 'style' => 'margin:0em;' ) ) . - Xml::element( 'legend', null, $this->msg('powersearch-legend' )->text() ) . + Xml::element( 'legend', null, $this->msg( 'powersearch-legend' )->text() ) . Xml::tags( 'h4', null, $this->msg( 'powersearch-ns' )->parse() ) . Html::element( 'div', array( 'id' => 'mw-search-togglebox' ) ) . Xml::element( 'div', array( 'class' => 'divider' ), '', false ) . @@ -949,8 +999,10 @@ class SpecialSearch extends SpecialPage { wfRunHooks( 'SpecialSearchProfiles', array( &$profiles ) ); - foreach( $profiles as &$data ) { - if ( !is_array( $data['namespaces'] ) ) continue; + foreach ( $profiles as &$data ) { + if ( !is_array( $data['namespaces'] ) ) { + continue; + } sort( $data['namespaces'] ); } @@ -964,10 +1016,10 @@ class SpecialSearch extends SpecialPage { * @return string */ protected function formHeader( $term, $resultsShown, $totalNum ) { - $out = Xml::openElement('div', array( 'class' => 'mw-search-formheader' ) ); + $out = Xml::openElement( 'div', array( 'class' => 'mw-search-formheader' ) ); $bareterm = $term; - if( $this->startsWithImage( $term ) ) { + if ( $this->startsWithImage( $term ) ) { // Deletes prefixes $bareterm = substr( $term, strpos( $term, ':' ) + 1 ); } @@ -1001,11 +1053,11 @@ class SpecialSearch extends SpecialPage { ); } $out .= Xml::closeElement( 'ul' ); - $out .= Xml::closeElement('div') ; + $out .= Xml::closeElement( 'div' ); // Results-info if ( $resultsShown > 0 ) { - if ( $totalNum > 0 ){ + if ( $totalNum > 0 ) { $top = $this->msg( 'showingresultsheader' ) ->numParams( $this->offset + 1, $this->offset + $resultsShown, $totalNum ) ->params( wfEscapeWikiText( $term ) ) @@ -1026,7 +1078,7 @@ class SpecialSearch extends SpecialPage { } $out .= Xml::element( 'div', array( 'style' => 'clear:both' ), '', false ); - $out .= Xml::closeElement('div'); + $out .= Xml::closeElement( 'div' ); return $out; } @@ -1053,15 +1105,15 @@ class SpecialSearch extends SpecialPage { * Make a search link with some target namespaces * * @param $term String - * @param $namespaces Array ignored - * @param $label String: link's text - * @param $tooltip String: link's tooltip - * @param $params Array: query string parameters + * @param array $namespaces ignored + * @param string $label link's text + * @param string $tooltip link's tooltip + * @param array $params query string parameters * @return String: HTML fragment */ protected function makeSearchLink( $term, $namespaces, $label, $tooltip, $params = array() ) { $opt = $params; - foreach( $namespaces as $n ) { + foreach ( $namespaces as $n ) { $opt['ns' . $n] = 1; } $opt['redirs'] = $this->searchRedirects; @@ -1078,7 +1130,8 @@ class SpecialSearch extends SpecialPage { 'a', array( 'href' => $this->getTitle()->getLocalURL( $stParams ), - 'title' => $tooltip), + 'title' => $tooltip + ), $label ); } @@ -1086,14 +1139,14 @@ class SpecialSearch extends SpecialPage { /** * Check if query starts with image: prefix * - * @param $term String: the string to check + * @param string $term the string to check * @return Boolean */ protected function startsWithImage( $term ) { global $wgContLang; $p = explode( ':', $term ); - if( count( $p ) > 1 ) { + if ( count( $p ) > 1 ) { return $wgContLang->getNsIndex( $p[0] ) == NS_FILE; } return false; @@ -1102,7 +1155,7 @@ class SpecialSearch extends SpecialPage { /** * Check if query starts with all: prefix * - * @param $term String: the string to check + * @param string $term the string to check * @return Boolean */ protected function startsWithAll( $term ) { @@ -1110,8 +1163,8 @@ class SpecialSearch extends SpecialPage { $allkeyword = $this->msg( 'searchall' )->inContentLanguage()->text(); $p = explode( ':', $term ); - if( count( $p ) > 1 ) { - return $p[0] == $allkeyword; + if ( count( $p ) > 1 ) { + return $p[0] == $allkeyword; } return false; } @@ -1123,7 +1176,8 @@ class SpecialSearch extends SpecialPage { */ public function getSearchEngine() { if ( $this->searchEngine === null ) { - $this->searchEngine = SearchEngine::create(); + $this->searchEngine = $this->searchEngineType ? + SearchEngine::create( $this->searchEngineType ) : SearchEngine::create(); } return $this->searchEngine; } @@ -1141,4 +1195,7 @@ class SpecialSearch extends SpecialPage { $this->extraParams[$key] = $value; } + protected function getGroupName() { + return 'pages'; + } } diff --git a/includes/specials/SpecialShortpages.php b/includes/specials/SpecialShortpages.php index 5a4e8f03..9b50875a 100644 --- a/includes/specials/SpecialShortpages.php +++ b/includes/specials/SpecialShortpages.php @@ -38,15 +38,15 @@ class ShortPagesPage extends QueryPage { } function getQueryInfo() { - return array ( - 'tables' => array ( 'page' ), - 'fields' => array ( 'namespace' => 'page_namespace', + return array( + 'tables' => array( 'page' ), + 'fields' => array( 'namespace' => 'page_namespace', 'title' => 'page_title', 'value' => 'page_len' ), - 'conds' => array ( 'page_namespace' => + 'conds' => array( 'page_namespace' => MWNamespace::getContentNamespaces(), 'page_is_redirect' => 0 ), - 'options' => array ( 'USE INDEX' => 'page_redirect_namespace_len' ) + 'options' => array( 'USE INDEX' => 'page_redirect_namespace_len' ) ); } @@ -56,8 +56,7 @@ class ShortPagesPage extends QueryPage { /** * @param $db DatabaseBase - * @param $res - * @return void + * @param ResultWrapper $res */ function preprocessResults( $db, $res ) { # There's no point doing a batch check if we aren't caching results; @@ -79,6 +78,11 @@ class ShortPagesPage extends QueryPage { return false; } + /** + * @param Skin $skin + * @param object $result Result row + * @return string + */ function formatResult( $skin, $result ) { $dm = $this->getLanguage()->getDirMark(); @@ -110,4 +114,8 @@ class ShortPagesPage extends QueryPage { ? "${hlinkInParentheses} {$dm}{$plink} {$dm}[{$size}]" : "<del>${hlinkInParentheses} {$dm}{$plink} {$dm}[{$size}]</del>"; } + + protected function getGroupName() { + return 'maintenance'; + } } diff --git a/includes/specials/SpecialSpecialpages.php b/includes/specials/SpecialSpecialpages.php index e973ddc8..47c89d0d 100644 --- a/includes/specials/SpecialSpecialpages.php +++ b/includes/specials/SpecialSpecialpages.php @@ -53,32 +53,37 @@ class SpecialSpecialpages extends UnlistedSpecialPage { $pages = SpecialPageFactory::getUsablePages( $this->getUser() ); - if( !count( $pages ) ) { + if ( !count( $pages ) ) { # Yeah, that was pointless. Thanks for coming. return false; } /** Put them into a sortable array */ $groups = array(); + /** @var SpecialPage $page */ foreach ( $pages as $page ) { if ( $page->isListed() ) { - $group = SpecialPageFactory::getGroup( $page ); - if( !isset( $groups[$group] ) ) { + $group = $page->getFinalGroupName(); + if ( !isset( $groups[$group] ) ) { $groups[$group] = array(); } - $groups[$group][$page->getDescription()] = array( $page->getTitle(), $page->isRestricted(), $page->isExpensive() ); + $groups[$group][$page->getDescription()] = array( + $page->getTitle(), + $page->isRestricted(), + $page->isCached() + ); } } /** Sort */ if ( $wgSortSpecialPages ) { - foreach( $groups as $group => $sortedPages ) { + foreach ( $groups as $group => $sortedPages ) { ksort( $groups[$group] ); } } /** Always move "other" to end */ - if( array_key_exists( 'other', $groups ) ) { + if ( array_key_exists( 'other', $groups ) ) { $other = $groups['other']; unset( $groups['other'] ); $groups['other'] = $other; @@ -88,43 +93,42 @@ class SpecialSpecialpages extends UnlistedSpecialPage { } private function outputPageList( $groups ) { - global $wgMiserMode; $out = $this->getOutput(); $includesRestrictedPages = false; $includesCachedPages = false; foreach ( $groups as $group => $sortedPages ) { - $middle = ceil( count( $sortedPages )/2 ); $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( '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" ); - foreach( $sortedPages as $desc => $specialpage ) { - list( $title, $restricted, $expensive) = $specialpage; + foreach ( $sortedPages as $desc => $specialpage ) { + list( $title, $restricted, $cached ) = $specialpage; $pageClasses = array(); - if ( $expensive && $wgMiserMode ){ + if ( $cached ) { $includesCachedPages = true; $pageClasses[] = 'mw-specialpagecached'; } - if( $restricted ) { + if ( $restricted ) { $includesRestrictedPages = true; $pageClasses[] = 'mw-specialpagerestricted'; } - $link = Linker::linkKnown( $title , htmlspecialchars( $desc ) ); + $link = Linker::linkKnown( $title, htmlspecialchars( $desc ) ); $out->addHTML( Html::rawElement( 'li', array( 'class' => implode( ' ', $pageClasses ) ), $link ) . "\n" ); # Split up the larger groups $count++; - if( $total > 3 && $count == $middle ) { + if ( $total > 3 && $count == $middle ) { $out->addHTML( Html::closeElement( 'ul' ) . Html::closeElement( 'td' ) . Html::element( 'td', array( 'style' => 'width:10%' ), '' ) . diff --git a/includes/specials/SpecialStatistics.php b/includes/specials/SpecialStatistics.php index 46881ec4..f26d1a60 100644 --- a/includes/specials/SpecialStatistics.php +++ b/includes/specials/SpecialStatistics.php @@ -53,18 +53,18 @@ class SpecialStatistics extends SpecialPage { # Staticic - views $viewsStats = ''; - if( !$wgDisableCounters ) { + if ( !$wgDisableCounters ) { $viewsStats = $this->getViewsStats(); } # Set active user count - if( !$wgMiserMode ) { + if ( !$wgMiserMode ) { $key = wfMemcKey( 'sitestats', 'activeusers-updated' ); // Re-calculate the count if the last tally is old... - if( !$wgMemc->get($key) ) { + if ( !$wgMemc->get( $key ) ) { $dbw = wfGetDB( DB_MASTER ); SiteStatsUpdate::cacheUpdate( $dbw ); - $wgMemc->set( $key, '1', 24*3600 ); // don't update for 1 day + $wgMemc->set( $key, '1', 24 * 3600 ); // don't update for 1 day } } @@ -84,13 +84,13 @@ class SpecialStatistics extends SpecialPage { $text .= $viewsStats; # Statistic - popular pages - if( !$wgDisableCounters && !$wgMiserMode ) { + if ( !$wgDisableCounters && !$wgMiserMode ) { $text .= $this->getMostViewedPages(); } # Statistic - other $extraStats = array(); - if( wfRunHooks( 'SpecialStatsAddExtra', array( &$extraStats ) ) ) { + if ( wfRunHooks( 'SpecialStatsAddExtra', array( &$extraStats ) ) ) { $text .= $this->getOtherStats( $extraStats ); } @@ -111,15 +111,15 @@ class SpecialStatistics extends SpecialPage { * @param $number Float: a statistical number * @param $trExtraParams Array: params to table row, see Html::elememt * @param $descMsg String: message key - * @param $descMsgParam Array: message params + * @param array|string $descMsgParam Message parameters * @return string table row in HTML format */ private function formatRow( $text, $number, $trExtraParams = array(), $descMsg = '', $descMsgParam = '' ) { - if( $descMsg ) { + 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'), + $text .= "<br />" . Xml::element( 'small', array( 'class' => 'mw-statistic-desc' ), " $descriptionText" ); } } @@ -185,7 +185,7 @@ class SpecialStatistics extends SpecialPage { private function getGroupStats() { global $wgGroupPermissions, $wgImplicitGroups; $text = ''; - foreach( $wgGroupPermissions as $group => $permissions ) { + foreach ( $wgGroupPermissions as $group => $permissions ) { # Skip generic * and implicit groups if ( in_array( $group, $wgImplicitGroups ) || $group == '*' ) { continue; @@ -217,12 +217,12 @@ class SpecialStatistics extends SpecialPage { # Add a class when a usergroup contains no members to allow hiding these rows $classZero = ''; $countUsers = SiteStats::numberingroup( $groupname ); - if( $countUsers == 0 ) { + if ( $countUsers == 0 ) { $classZero = ' statistics-group-zero'; } $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; } @@ -233,11 +233,11 @@ class SpecialStatistics extends SpecialPage { Xml::closeElement( 'tr' ) . $this->formatRow( $this->msg( 'statistics-views-total' )->parse(), $this->getLanguage()->formatNum( $this->views ), - array ( 'class' => 'mw-statistics-views-total' ), 'statistics-views-total-desc' ) . + array( 'class' => 'mw-statistics-views-total' ), 'statistics-views-total-desc' ) . $this->formatRow( $this->msg( 'statistics-views-peredit' )->parse(), $this->getLanguage()->formatNum( sprintf( '%.2f', $this->edits ? $this->views / $this->edits : 0 ) ), - array ( 'class' => 'mw-statistics-views-peredit' ) ); + array( 'class' => 'mw-statistics-views-peredit' ) ); } private function getMostViewedPages() { @@ -260,13 +260,13 @@ class SpecialStatistics extends SpecialPage { 'LIMIT' => 10, ) ); - if( $res->numRows() > 0 ) { + if ( $res->numRows() > 0 ) { $text .= Xml::openElement( 'tr' ); $text .= Xml::tags( 'th', array( 'colspan' => '2' ), $this->msg( 'statistics-mostpopular' )->parse() ); $text .= Xml::closeElement( 'tr' ); foreach ( $res as $row ) { $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title ); - if( $title instanceof Title ) { + if ( $title instanceof Title ) { $text .= $this->formatRow( Linker::link( $title ), $this->getLanguage()->formatNum( $row->page_counter ) ); @@ -277,21 +277,59 @@ class SpecialStatistics extends SpecialPage { return $text; } - private function getOtherStats( $stats ) { - if ( !count( $stats ) ) - return ''; + /** + * Conversion of external statistics into an internal representation + * Following a ([<header-message>][<item-message>] = number) pattern + * + * @param array $stats + * @return string + */ + private function getOtherStats( array $stats ) { + $return = ''; - $return = Xml::openElement( 'tr' ) . - Xml::tags( 'th', array( 'colspan' => '2' ), $this->msg( 'statistics-header-hooks' )->parse() ) . - Xml::closeElement( 'tr' ); + foreach ( $stats as $header => $items ) { + // Identify the structure used + if ( is_array( $items ) ) { + + // Ignore headers that are recursively set as legacy header + if ( $header !== 'statistics-header-hooks' ) { + $return .= $this->formatRowHeader( $header ); + } + + // Collect all items that belong to the same header + foreach ( $items as $key => $value ) { + $name = $this->msg( $key )->parse(); + $number = htmlspecialchars( $value ); - foreach( $stats as $name => $number ) { - $name = htmlspecialchars( $name ); - $number = htmlspecialchars( $number ); + $return .= $this->formatRow( $name, $this->getLanguage()->formatNum( $number ), array( 'class' => 'mw-statistics-hook', 'id' => 'mw-' . $key ) ); + } + } else { + // Create the legacy header only once + if ( $return === '' ) { + $return .= $this->formatRowHeader( 'statistics-header-hooks' ); + } - $return .= $this->formatRow( $name, $this->getLanguage()->formatNum( $number ), array( 'class' => 'mw-statistics-hook' ) ); + // Recursively remap the legacy structure + $return .= $this->getOtherStats( array( 'statistics-header-hooks' => array( $header => $items ) ) ); + } } return $return; } + + /** + * Format row header + * + * @param string $header + * @return string + */ + private function formatRowHeader( $header ) { + return Xml::openElement( 'tr' ) . + Xml::tags( 'th', array( 'colspan' => '2' ), $this->msg( $header )->parse() ) . + Xml::closeElement( 'tr' ); + } + + protected function getGroupName() { + return 'wiki'; + } } diff --git a/includes/specials/SpecialTags.php b/includes/specials/SpecialTags.php index 4036ebb2..077e7cbc 100644 --- a/includes/specials/SpecialTags.php +++ b/includes/specials/SpecialTags.php @@ -27,6 +27,10 @@ * @ingroup SpecialPage */ class SpecialTags extends SpecialPage { + /** + * @var array List of defined tags + */ + public $definedTags; function __construct() { parent::__construct( 'Tags' ); @@ -44,52 +48,71 @@ class SpecialTags extends SpecialPage { $html = Xml::tags( 'tr', null, Xml::tags( 'th', null, $this->msg( 'tags-tag' )->parse() ) . Xml::tags( 'th', null, $this->msg( 'tags-display-header' )->parse() ) . Xml::tags( 'th', null, $this->msg( 'tags-description-header' )->parse() ) . + Xml::tags( 'th', null, $this->msg( 'tags-active-header' )->parse() ) . Xml::tags( 'th', null, $this->msg( 'tags-hitcount-header' )->parse() ) ); - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'change_tag', array( 'ct_tag', 'hitcount' => 'count(*)' ), - array(), __METHOD__, array( 'GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC' ) ); - foreach ( $res as $row ) { - $html .= $this->doTagRow( $row->ct_tag, $row->hitcount ); - } + // Used in #doTagRow() + $this->definedTags = array_fill_keys( ChangeTags::listDefinedTags(), true ); - foreach( ChangeTags::listDefinedTags() as $tag ) { - $html .= $this->doTagRow( $tag, 0 ); + foreach ( ChangeTags::tagUsageStatistics() as $tag => $hitcount ) { + $html .= $this->doTagRow( $tag, $hitcount ); } - $out->addHTML( Xml::tags( 'table', array( 'class' => 'wikitable mw-tags-table' ), $html ) ); + $out->addHTML( Xml::tags( + 'table', + array( 'class' => 'wikitable sortable mw-tags-table' ), + $html + ) ); } function doTagRow( $tag, $hitcount ) { - static $doneTags = array(); - - if ( in_array( $tag, $doneTags ) ) { - return ''; - } - + $user = $this->getUser(); $newRow = ''; $newRow .= Xml::tags( 'td', null, Xml::element( 'code', null, $tag ) ); $disp = ChangeTags::tagDescription( $tag ); - $disp .= ' '; - $editLink = Linker::link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag" ), $this->msg( 'tags-edit' )->escaped() ); - $disp .= $this->msg( 'parentheses' )->rawParams( $editLink )->escaped(); + if ( $user->isAllowed( 'editinterface' ) ) { + $disp .= ' '; + $editLink = Linker::link( + Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag" ), + $this->msg( 'tags-edit' )->escaped() + ); + $disp .= $this->msg( 'parentheses' )->rawParams( $editLink )->escaped(); + } $newRow .= Xml::tags( 'td', null, $disp ); $msg = $this->msg( "tag-$tag-description" ); $desc = !$msg->exists() ? '' : $msg->parse(); - $desc .= ' '; - $editDescLink = Linker::link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag-description" ), $this->msg( 'tags-edit' )->escaped() ); - $desc .= $this->msg( 'parentheses' )->rawParams( $editDescLink )->escaped(); + if ( $user->isAllowed( 'editinterface' ) ) { + $desc .= ' '; + $editDescLink = Linker::link( + Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag-description" ), + $this->msg( 'tags-edit' )->escaped() + ); + $desc .= $this->msg( 'parentheses' )->rawParams( $editDescLink )->escaped(); + } $newRow .= Xml::tags( 'td', null, $desc ); - $hitcount = $this->msg( 'tags-hitcount' )->numParams( $hitcount )->escaped(); - $hitcount = Linker::link( SpecialPage::getTitleFor( 'Recentchanges' ), $hitcount, array(), array( 'tagfilter' => $tag ) ); - $newRow .= Xml::tags( 'td', null, $hitcount ); + $active = isset( $this->definedTags[$tag] ) ? 'tags-active-yes' : 'tags-active-no'; + $active = $this->msg( $active )->escaped(); + $newRow .= Xml::tags( 'td', null, $active ); + + $hitcountLabel = $this->msg( 'tags-hitcount' )->numParams( $hitcount )->escaped(); + $hitcountLink = Linker::link( + SpecialPage::getTitleFor( 'Recentchanges' ), + $hitcountLabel, + array(), + array( 'tagfilter' => $tag ) + ); - $doneTags[] = $tag; + // add raw $hitcount for sorting, because tags-hitcount contains numbers and letters + $newRow .= Xml::tags( 'td', array( 'data-sort-value' => $hitcount ), $hitcountLink ); return Xml::tags( 'tr', null, $newRow ) . "\n"; } + + protected function getGroupName() { + return 'changes'; + } } diff --git a/includes/specials/SpecialUnblock.php b/includes/specials/SpecialUnblock.php index fb2005b5..ca93b6d1 100644 --- a/includes/specials/SpecialUnblock.php +++ b/includes/specials/SpecialUnblock.php @@ -32,11 +32,11 @@ class SpecialUnblock extends SpecialPage { protected $type; protected $block; - public function __construct(){ + public function __construct() { parent::__construct( 'Unblock', 'block' ); } - public function execute( $par ){ + public function execute( $par ) { $this->checkPermissions(); $this->checkReadOnly(); @@ -56,8 +56,8 @@ class SpecialUnblock extends SpecialPage { $form->setSubmitTextMsg( 'ipusubmit' ); $form->addPreText( $this->msg( 'unblockiptext' )->parseAsBlock() ); - if( $form->show() ){ - switch( $this->type ){ + if ( $form->show() ) { + switch ( $this->type ) { case Block::TYPE_USER: case Block::TYPE_IP: $out->addWikiMsg( 'unblocked', wfEscapeWikiText( $this->target ) ); @@ -73,7 +73,7 @@ class SpecialUnblock extends SpecialPage { } } - protected function getFields(){ + protected function getFields() { $fields = array( 'Target' => array( 'type' => 'text', @@ -92,21 +92,21 @@ class SpecialUnblock extends SpecialPage { ) ); - if( $this->block instanceof Block ){ + if ( $this->block instanceof Block ) { list( $target, $type ) = $this->block->getTargetAndType(); # Autoblocks are logged as "autoblock #123 because the IP was recently used by # User:Foo, and we've just got any block, auto or not, that applies to a target # the user has specified. Someone could be fishing to connect IPs to autoblocks, # so don't show any distinction between unblocked IPs and autoblocked IPs - if( $type == Block::TYPE_AUTO && $this->type == Block::TYPE_IP ){ + if ( $type == Block::TYPE_AUTO && $this->type == Block::TYPE_IP ) { $fields['Target']['default'] = $this->target; unset( $fields['Name'] ); } else { $fields['Target']['default'] = $target; $fields['Target']['type'] = 'hidden'; - switch( $type ){ + switch ( $type ) { case Block::TYPE_USER: case Block::TYPE_IP: $fields['Name']['default'] = Linker::link( @@ -138,6 +138,8 @@ class SpecialUnblock extends SpecialPage { /** * Submit callback for an HTMLForm object + * @param array $data + * @param HTMLForm $form * @return Array( Array(message key, parameters) */ public static function processUIUnblock( array $data, HTMLForm $form ) { @@ -149,14 +151,15 @@ class SpecialUnblock extends SpecialPage { * * @param $data Array * @param $context IContextSource + * @throws ErrorPageError * @return Array( Array(message key, parameters) ) on failure, True on success */ - public static function processUnblock( array $data, IContextSource $context ){ + public static function processUnblock( array $data, IContextSource $context ) { $performer = $context->getUser(); $target = $data['Target']; $block = Block::newFromTarget( $data['Target'] ); - if( !$block instanceof Block ){ + if ( !$block instanceof Block ) { return array( array( 'ipb_cant_unblock', $target ) ); } @@ -171,14 +174,14 @@ class SpecialUnblock extends SpecialPage { # If the specified IP is a single address, and the block is a range block, don't # unblock the whole range. list( $target, $type ) = SpecialBlock::getTargetAndType( $target ); - if( $block->getType() == Block::TYPE_RANGE && $type == Block::TYPE_IP ) { - $range = $block->getTarget(); - return array( array( 'ipb_blocked_as_range', $target, $range ) ); + if ( $block->getType() == Block::TYPE_RANGE && $type == Block::TYPE_IP ) { + $range = $block->getTarget(); + return array( array( 'ipb_blocked_as_range', $target, $range ) ); } # If the name was hidden and the blocking user cannot hide # names, then don't allow any block removals... - if( !$performer->isAllowed( 'hideuser' ) && $block->mHideName ) { + if ( !$performer->isAllowed( 'hideuser' ) && $block->mHideName ) { return array( 'unblock-hideuser' ); } @@ -188,7 +191,7 @@ class SpecialUnblock extends SpecialPage { } # Unset _deleted fields as needed - if( $block->mHideName ) { + if ( $block->mHideName ) { # Something is deeply FUBAR if this is not a User object, but who knows? $id = $block->getTarget() instanceof User ? $block->getTarget()->getID() @@ -208,8 +211,12 @@ class SpecialUnblock extends SpecialPage { # Make log entry $log = new LogPage( 'block' ); - $log->addEntry( 'unblock', $page, $data['Reason'] ); + $log->addEntry( 'unblock', $page, $data['Reason'], array(), $performer ); return true; } + + protected function getGroupName() { + return 'users'; + } } diff --git a/includes/specials/SpecialUncategorizedcategories.php b/includes/specials/SpecialUncategorizedcategories.php index 70d98df9..1cc40a9e 100644 --- a/includes/specials/SpecialUncategorizedcategories.php +++ b/includes/specials/SpecialUncategorizedcategories.php @@ -31,4 +31,17 @@ class UncategorizedCategoriesPage extends UncategorizedPagesPage { parent::__construct( $name ); $this->requestedNamespace = NS_CATEGORY; } + + /** + * Formats the result + * @param Skin $skin The current skin + * @param object $result The query result + * @return string The category link + */ + function formatResult( $skin, $result ) { + $title = Title::makeTitle( NS_CATEGORY, $result->title ); + $text = $title->getText(); + + return Linker::linkKnown( $title, htmlspecialchars( $text ) ); + } } diff --git a/includes/specials/SpecialUncategorizedimages.php b/includes/specials/SpecialUncategorizedimages.php index 5865bf62..3bfcedec 100644 --- a/includes/specials/SpecialUncategorizedimages.php +++ b/includes/specials/SpecialUncategorizedimages.php @@ -47,7 +47,7 @@ class UncategorizedImagesPage extends ImageQueryPage { } function getQueryInfo() { - return array ( + return array( 'tables' => array( 'page', 'categorylinks' ), 'fields' => array( 'namespace' => 'page_namespace', 'title' => 'page_title', @@ -60,4 +60,7 @@ class UncategorizedImagesPage extends ImageQueryPage { ); } + protected function getGroupName() { + return 'maintenance'; + } } diff --git a/includes/specials/SpecialUncategorizedpages.php b/includes/specials/SpecialUncategorizedpages.php index 1226a6ca..8bc9e489 100644 --- a/includes/specials/SpecialUncategorizedpages.php +++ b/includes/specials/SpecialUncategorizedpages.php @@ -41,20 +41,23 @@ class UncategorizedPagesPage extends PageQueryPage { function isExpensive() { return true; } - function isSyndicated() { return false; } + + function isSyndicated() { + return false; + } function getQueryInfo() { - return array ( - 'tables' => array ( 'page', 'categorylinks' ), - 'fields' => array ( 'namespace' => 'page_namespace', + return array( + 'tables' => array( 'page', 'categorylinks' ), + 'fields' => array( 'namespace' => 'page_namespace', 'title' => 'page_title', 'value' => 'page_title' ), // default for page_namespace is all content namespaces (if requestedNamespace is false) // otherwise, page_namespace is requestedNamespace - 'conds' => array ( 'cl_from IS NULL', - 'page_namespace' => ( $this->requestedNamespace!==false ? $this->requestedNamespace : MWNamespace::getContentNamespaces() ), + 'conds' => array( 'cl_from IS NULL', + 'page_namespace' => ( $this->requestedNamespace !== false ? $this->requestedNamespace : MWNamespace::getContentNamespaces() ), 'page_is_redirect' => 0 ), - 'join_conds' => array ( 'categorylinks' => array ( + 'join_conds' => array( 'categorylinks' => array( 'LEFT JOIN', 'cl_from = page_id' ) ) ); } @@ -62,8 +65,13 @@ class UncategorizedPagesPage extends PageQueryPage { function getOrderFields() { // For some crazy reason ordering by a constant // causes a filesort - if( $this->requestedNamespace === false && count( MWNamespace::getContentNamespaces() ) > 1 ) + if ( $this->requestedNamespace === false && count( MWNamespace::getContentNamespaces() ) > 1 ) { return array( 'page_namespace', 'page_title' ); + } return array( 'page_title' ); } + + protected function getGroupName() { + return 'maintenance'; + } } diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php index d8e0b97c..d4aed113 100644 --- a/includes/specials/SpecialUndelete.php +++ b/includes/specials/SpecialUndelete.php @@ -27,15 +27,23 @@ * @ingroup SpecialPage */ class PageArchive { - /** * @var Title */ protected $title; - var $fileStatus; + + /** + * @var Status + */ + protected $fileStatus; + + /** + * @var Status + */ + protected $revisionStatus; function __construct( $title ) { - if( is_null( $title ) ) { + if ( is_null( $title ) ) { throw new MWException( __METHOD__ . ' given a null title.' ); } $this->title = $title; @@ -58,14 +66,14 @@ class PageArchive { * given title prefix. * Returns result wrapper with (ar_namespace, ar_title, count) fields. * - * @param $prefix String: title prefix + * @param string $prefix Title prefix * @return ResultWrapper */ public static function listPagesByPrefix( $prefix ) { $dbr = wfGetDB( DB_SLAVE ); $title = Title::newFromText( $prefix ); - if( $title ) { + if ( $title ) { $ns = $title->getNamespace(); $prefix = $title->getDBkey(); } else { @@ -73,36 +81,36 @@ class PageArchive { // @todo handle bare namespace names cleanly? $ns = 0; } + $conds = array( 'ar_namespace' => $ns, 'ar_title' . $dbr->buildLike( $prefix, $dbr->anyString() ), ); + return self::listPages( $dbr, $conds ); } /** - * @param $dbr DatabaseBase - * @param $condition + * @param DatabaseBase $dbr + * @param string|array $condition * @return bool|ResultWrapper */ protected static function listPages( $dbr, $condition ) { - return $dbr->resultObject( - $dbr->select( - array( 'archive' ), - array( - 'ar_namespace', - 'ar_title', - 'count' => 'COUNT(*)' - ), - $condition, - __METHOD__, - array( - 'GROUP BY' => array( 'ar_namespace', 'ar_title' ), - 'ORDER BY' => array( 'ar_namespace', 'ar_title' ), - 'LIMIT' => 100, - ) + return $dbr->resultObject( $dbr->select( + array( 'archive' ), + array( + 'ar_namespace', + 'ar_title', + 'count' => 'COUNT(*)' + ), + $condition, + __METHOD__, + array( + 'GROUP BY' => array( 'ar_namespace', 'ar_title' ), + 'ORDER BY' => array( 'ar_namespace', 'ar_title' ), + 'LIMIT' => 100, ) - ); + ) ); } /** @@ -112,18 +120,28 @@ class PageArchive { * @return ResultWrapper */ function listRevisions() { + global $wgContentHandlerUseDB; + $dbr = wfGetDB( DB_SLAVE ); + + $fields = array( + 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text', + 'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1', + ); + + if ( $wgContentHandlerUseDB ) { + $fields[] = 'ar_content_format'; + $fields[] = 'ar_content_model'; + } + $res = $dbr->select( 'archive', - array( - 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text', - 'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1' - ), + $fields, array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey() ), + 'ar_title' => $this->title->getDBkey() ), __METHOD__, array( 'ORDER BY' => 'ar_timestamp DESC' ) ); - $ret = $dbr->resultObject( $res ); - return $ret; + + return $dbr->resultObject( $res ); } /** @@ -135,70 +153,66 @@ class PageArchive { * @todo Does this belong in Image for fuller encapsulation? */ function listFiles() { - if( $this->title->getNamespace() == NS_FILE ) { - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'filearchive', - array( - 'fa_id', - 'fa_name', - 'fa_archive_name', - 'fa_storage_key', - 'fa_storage_group', - 'fa_size', - 'fa_width', - 'fa_height', - 'fa_bits', - 'fa_metadata', - 'fa_media_type', - 'fa_major_mime', - 'fa_minor_mime', - 'fa_description', - 'fa_user', - 'fa_user_text', - 'fa_timestamp', - 'fa_deleted' ), - array( 'fa_name' => $this->title->getDBkey() ), - __METHOD__, - array( 'ORDER BY' => 'fa_timestamp DESC' ) ); - $ret = $dbr->resultObject( $res ); - return $ret; + if ( $this->title->getNamespace() != NS_FILE ) { + return null; } - return null; + + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( + 'filearchive', + ArchivedFile::selectFields(), + array( 'fa_name' => $this->title->getDBkey() ), + __METHOD__, + array( 'ORDER BY' => 'fa_timestamp DESC' ) + ); + + return $dbr->resultObject( $res ); } /** * Return a Revision object containing data for the deleted revision. * Note that the result *may* or *may not* have a null page ID. * - * @param $timestamp String - * @return Revision + * @param string $timestamp + * @return Revision|null */ function getRevision( $timestamp ) { + global $wgContentHandlerUseDB; + $dbr = wfGetDB( DB_SLAVE ); + + $fields = array( + 'ar_rev_id', + 'ar_text', + 'ar_comment', + 'ar_user', + 'ar_user_text', + 'ar_timestamp', + 'ar_minor_edit', + 'ar_flags', + 'ar_text_id', + 'ar_deleted', + 'ar_len', + 'ar_sha1', + ); + + if ( $wgContentHandlerUseDB ) { + $fields[] = 'ar_content_format'; + $fields[] = 'ar_content_model'; + } + $row = $dbr->selectRow( 'archive', - array( - 'ar_rev_id', - 'ar_text', - 'ar_comment', - 'ar_user', - 'ar_user_text', - 'ar_timestamp', - 'ar_minor_edit', - 'ar_flags', - 'ar_text_id', - 'ar_deleted', - 'ar_len', - 'ar_sha1', - ), + $fields, array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey(), - 'ar_timestamp' => $dbr->timestamp( $timestamp ) ), + 'ar_title' => $this->title->getDBkey(), + 'ar_timestamp' => $dbr->timestamp( $timestamp ) ), __METHOD__ ); - if( $row ) { - return Revision::newFromArchiveRow( $row, array( 'page' => $this->title->getArticleID() ) ); - } else { - return null; + + if ( $row ) { + return Revision::newFromArchiveRow( $row, array( 'title' => $this->title ) ); } + + return null; } /** @@ -208,8 +222,8 @@ class PageArchive { * May produce unexpected results in case of history merges or other * unusual time issues. * - * @param $timestamp String - * @return Revision or null + * @param string $timestamp + * @return Revision|null Null when there is no previous revision */ function getPreviousRevision( $timestamp ) { $dbr = wfGetDB( DB_SLAVE ); @@ -218,9 +232,9 @@ class PageArchive { $row = $dbr->selectRow( 'archive', 'ar_timestamp', array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey(), - 'ar_timestamp < ' . - $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ), + 'ar_title' => $this->title->getDBkey(), + 'ar_timestamp < ' . + $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ), __METHOD__, array( 'ORDER BY' => 'ar_timestamp DESC', @@ -234,7 +248,7 @@ class PageArchive { 'page_title' => $this->title->getDBkey(), 'page_id = rev_page', 'rev_timestamp < ' . - $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ), + $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ), __METHOD__, array( 'ORDER BY' => 'rev_timestamp DESC', @@ -242,38 +256,39 @@ class PageArchive { $prevLive = $row ? wfTimestamp( TS_MW, $row->rev_timestamp ) : false; $prevLiveId = $row ? intval( $row->rev_id ) : null; - if( $prevLive && $prevLive > $prevDeleted ) { + if ( $prevLive && $prevLive > $prevDeleted ) { // Most prior revision was live return Revision::newFromId( $prevLiveId ); - } elseif( $prevDeleted ) { + } elseif ( $prevDeleted ) { // Most prior revision was deleted return $this->getRevision( $prevDeleted ); - } else { - // No prior revision on this page. - return null; } + + // No prior revision on this page. + return null; } /** * Get the text from an archive row containing ar_text, ar_flags and ar_text_id * - * @param $row Object: database row - * @return Revision + * @param object $row Database row + * @return string */ function getTextFromRow( $row ) { - if( is_null( $row->ar_text_id ) ) { + if ( is_null( $row->ar_text_id ) ) { // An old row from MediaWiki 1.4 or previous. // Text is embedded in this row in classic compression format. return Revision::getRevisionText( $row, 'ar_' ); - } else { - // New-style: keyed to the text storage backend. - $dbr = wfGetDB( DB_SLAVE ); - $text = $dbr->selectRow( 'text', - array( 'old_text', 'old_flags' ), - array( 'old_id' => $row->ar_text_id ), - __METHOD__ ); - return Revision::getRevisionText( $text ); } + + // New-style: keyed to the text storage backend. + $dbr = wfGetDB( DB_SLAVE ); + $text = $dbr->selectRow( 'text', + array( 'old_text', 'old_flags' ), + array( 'old_id' => $row->ar_text_id ), + __METHOD__ ); + + return Revision::getRevisionText( $text ); } /** @@ -282,35 +297,37 @@ class PageArchive { * * If there are no archived revisions for the page, returns NULL. * - * @return String + * @return string|null */ function getLastRevisionText() { $dbr = wfGetDB( DB_SLAVE ); $row = $dbr->selectRow( 'archive', array( 'ar_text', 'ar_flags', 'ar_text_id' ), array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey() ), + 'ar_title' => $this->title->getDBkey() ), __METHOD__, array( 'ORDER BY' => 'ar_timestamp DESC' ) ); - if( $row ) { + + if ( $row ) { return $this->getTextFromRow( $row ); - } else { - return null; } + + return null; } /** * Quick check if any archived revisions are present for the page. * - * @return Boolean + * @return boolean */ function isDeleted() { $dbr = wfGetDB( DB_SLAVE ); $n = $dbr->selectField( 'archive', 'COUNT(ar_title)', array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey() ), + 'ar_title' => $this->title->getDBkey() ), __METHOD__ ); + return ( $n > 0 ); } @@ -319,18 +336,16 @@ class PageArchive { * Once restored, the items will be removed from the archive tables. * The deletion log will be updated with an undeletion notice. * - * @param $timestamps Array: pass an empty array to restore all revisions, otherwise list the ones to undelete. - * @param $comment String - * @param $fileVersions Array - * @param $unsuppress Boolean - * @param $user User doing the action, or null to use $wgUser + * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete. + * @param string $comment + * @param array $fileVersions + * @param bool $unsuppress + * @param User $user User performing the action, or null to use $wgUser * * @return array(number of file revisions restored, number of image revisions restored, log message) * on success, false on failure */ function undelete( $timestamps, $comment = '', $fileVersions = array(), $unsuppress = false, User $user = null ) { - global $wgUser; - // If both the set of text revisions and file revisions are empty, // restore everything. Otherwise, just restore the requested items. $restoreAll = empty( $timestamps ) && empty( $fileVersions ); @@ -338,10 +353,10 @@ class PageArchive { $restoreText = $restoreAll || !empty( $timestamps ); $restoreFiles = $restoreAll || !empty( $fileVersions ); - if( $restoreFiles && $this->title->getNamespace() == NS_FILE ) { + if ( $restoreFiles && $this->title->getNamespace() == NS_FILE ) { $img = wfLocalFile( $this->title ); $this->fileStatus = $img->restore( $fileVersions, $unsuppress ); - if ( !$this->fileStatus->isOk() ) { + if ( !$this->fileStatus->isOK() ) { return false; } $filesRestored = $this->fileStatus->successCount; @@ -349,24 +364,26 @@ class PageArchive { $filesRestored = 0; } - if( $restoreText ) { - $textRestored = $this->undeleteRevisions( $timestamps, $unsuppress, $comment ); - if( $textRestored === false ) { // It must be one of UNDELETE_* + if ( $restoreText ) { + $this->revisionStatus = $this->undeleteRevisions( $timestamps, $unsuppress, $comment ); + if ( !$this->revisionStatus->isOK() ) { return false; } + + $textRestored = $this->revisionStatus->getValue(); } else { $textRestored = 0; } // Touch the log! - if( $textRestored && $filesRestored ) { + if ( $textRestored && $filesRestored ) { $reason = wfMessage( 'undeletedrevisions-files' ) ->numParams( $textRestored, $filesRestored )->inContentLanguage()->text(); - } elseif( $textRestored ) { + } elseif ( $textRestored ) { $reason = wfMessage( 'undeletedrevisions' )->numParams( $textRestored ) ->inContentLanguage()->text(); - } elseif( $filesRestored ) { + } elseif ( $filesRestored ) { $reason = wfMessage( 'undeletedfiles' )->numParams( $filesRestored ) ->inContentLanguage()->text(); } else { @@ -374,11 +391,12 @@ class PageArchive { return false; } - if( trim( $comment ) != '' ) { + if ( trim( $comment ) != '' ) { $reason .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $comment; } if ( $user === null ) { + global $wgUser; $user = $wgUser; } @@ -386,6 +404,9 @@ class PageArchive { $logEntry->setPerformer( $user ); $logEntry->setTarget( $this->title ); $logEntry->setComment( $reason ); + + wfRunHooks( 'ArticleUndeleteLogEntry', array( $this, &$logEntry, $user ) ); + $logid = $logEntry->insert(); $logEntry->publish( $logid ); @@ -397,18 +418,20 @@ class PageArchive { * to the cur/old tables. If the page currently exists, all revisions will * be stuffed into old, otherwise the most recent will go into cur. * - * @param $timestamps Array: pass an empty array to restore all revisions, otherwise list the ones to undelete. - * @param $comment String - * @param $unsuppress Boolean: remove all ar_deleted/fa_deleted restrictions of seletected revs - * - * @return Mixed: number of revisions restored or false on failure + * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete. + * @param bool $unsuppress Remove all ar_deleted/fa_deleted restrictions of seletected revs + * @param string $comment + * @throws ReadOnlyError + * @return Status Object containing the number of revisions restored on success */ private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) { + global $wgContentHandlerUseDB; + if ( wfReadOnly() ) { - return false; + throw new ReadOnlyError(); } - $restoreAll = empty( $timestamps ); + $restoreAll = empty( $timestamps ); $dbw = wfGetDB( DB_MASTER ); # Does this page already exist? We'll have to update it... @@ -420,24 +443,30 @@ class PageArchive { $page = $dbw->selectRow( 'page', array( 'page_id', 'page_latest' ), array( 'page_namespace' => $this->title->getNamespace(), - 'page_title' => $this->title->getDBkey() ), + 'page_title' => $this->title->getDBkey() ), __METHOD__, array( 'FOR UPDATE' ) // lock page ); - if( $page ) { + + if ( $page ) { $makepage = false; # Page already exists. Import the history, and if necessary # we'll update the latest revision field in the record. - $newid = 0; - $pageId = $page->page_id; - $previousRevId = $page->page_latest; + + $previousRevId = $page->page_latest; + # Get the time span of this page $previousTimestamp = $dbw->selectField( 'revision', 'rev_timestamp', array( 'rev_id' => $previousRevId ), __METHOD__ ); - if( $previousTimestamp === false ) { - wfDebug( __METHOD__.": existing page refers to a page_latest that does not exist\n" ); - return 0; + + if ( $previousTimestamp === false ) { + wfDebug( __METHOD__ . ": existing page refers to a page_latest that does not exist\n" ); + + $status = Status::newGood( 0 ); + $status->warning( 'undeleterevision-missing' ); + + return $status; } } else { # Have to create a new article... @@ -446,7 +475,7 @@ class PageArchive { $previousTimestamp = 0; } - if( $restoreAll ) { + if ( $restoreAll ) { $oldones = '1 = 1'; # All revisions... } else { $oldts = implode( ',', @@ -457,58 +486,89 @@ class PageArchive { $oldones = "ar_timestamp IN ( {$oldts} )"; } + $fields = array( + 'ar_rev_id', + 'ar_text', + 'ar_comment', + 'ar_user', + 'ar_user_text', + 'ar_timestamp', + 'ar_minor_edit', + 'ar_flags', + 'ar_text_id', + 'ar_deleted', + 'ar_page_id', + 'ar_len', + 'ar_sha1' + ); + + if ( $wgContentHandlerUseDB ) { + $fields[] = 'ar_content_format'; + $fields[] = 'ar_content_model'; + } + /** * Select each archived revision... */ $result = $dbw->select( 'archive', - /* fields */ array( - 'ar_rev_id', - 'ar_text', - 'ar_comment', - 'ar_user', - 'ar_user_text', - 'ar_timestamp', - 'ar_minor_edit', - 'ar_flags', - 'ar_text_id', - 'ar_deleted', - 'ar_page_id', - 'ar_len', - 'ar_sha1' ), + $fields, /* WHERE */ array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey(), + 'ar_title' => $this->title->getDBkey(), $oldones ), __METHOD__, /* options */ array( 'ORDER BY' => 'ar_timestamp' ) ); $ret = $dbw->resultObject( $result ); $rev_count = $dbw->numRows( $result ); - if( !$rev_count ) { + + if ( !$rev_count ) { wfDebug( __METHOD__ . ": no revisions to restore\n" ); - return false; // ??? + + $status = Status::newGood( 0 ); + $status->warning( "undelete-no-results" ); + return $status; } $ret->seek( $rev_count - 1 ); // move to last $row = $ret->fetchObject(); // get newest archived rev $ret->seek( 0 ); // move back - if( $makepage ) { + // grab the content to check consistency with global state before restoring the page. + $revision = Revision::newFromArchiveRow( $row, + array( + 'title' => $article->getTitle(), // used to derive default content model + ) + ); + $user = User::newFromName( $revision->getRawUserText(), false ); + $content = $revision->getContent( Revision::RAW ); + + //NOTE: article ID may not be known yet. prepareSave() should not modify the database. + $status = $content->prepareSave( $article, 0, -1, $user ); + + if ( !$status->isOK() ) { + return $status; + } + + if ( $makepage ) { // Check the state of the newest to-be version... - if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) { - return false; // we can't leave the current revision like this! + if ( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) { + return Status::newFatal( "undeleterevdel" ); } // Safe to insert now... - $newid = $article->insertOn( $dbw ); + $newid = $article->insertOn( $dbw ); $pageId = $newid; } else { // Check if a deleted revision will become the current revision... - if( $row->ar_timestamp > $previousTimestamp ) { + if ( $row->ar_timestamp > $previousTimestamp ) { // Check the state of the newest to-be version... - if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) { - return false; // we can't leave the current revision like this! + if ( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) { + return Status::newFatal( "undeleterevdel" ); } } + + $newid = false; + $pageId = $article->getId(); } $revision = null; @@ -516,10 +576,10 @@ class PageArchive { foreach ( $ret as $row ) { // Check for key dupes due to shitty archive integrity. - if( $row->ar_rev_id ) { + if ( $row->ar_rev_id ) { $exists = $dbw->selectField( 'revision', '1', array( 'rev_id' => $row->ar_rev_id ), __METHOD__ ); - if( $exists ) { + if ( $exists ) { continue; // don't throw DB errors } } @@ -528,6 +588,7 @@ class PageArchive { $revision = Revision::newFromArchiveRow( $row, array( 'page' => $pageId, + 'title' => $this->title, 'deleted' => $unsuppress ? 0 : $row->ar_deleted ) ); @@ -546,7 +607,7 @@ class PageArchive { // Was anything restored at all? if ( $restored == 0 ) { - return 0; + return Status::newGood( 0 ); } $created = (bool)$newid; @@ -561,18 +622,27 @@ class PageArchive { wfRunHooks( 'ArticleUndelete', array( &$this->title, $created, $comment ) ); - if( $this->title->getNamespace() == NS_FILE ) { + if ( $this->title->getNamespace() == NS_FILE ) { $update = new HTMLCacheUpdate( $this->title, 'imagelinks' ); $update->doUpdate(); } - return $restored; + return Status::newGood( $restored ); } /** * @return Status */ - function getFileStatus() { return $this->fileStatus; } + function getFileStatus() { + return $this->fileStatus; + } + + /** + * @return Status + */ + function getRevisionStatus() { + return $this->revisionStatus; + } } /** @@ -604,10 +674,13 @@ class SpecialUndelete extends SpecialPage { } else { $this->mTarget = $request->getVal( 'target' ); } + $this->mTargetObj = null; + if ( $this->mTarget !== null && $this->mTarget !== '' ) { $this->mTargetObj = Title::newFromURL( $this->mTarget ); } + $this->mSearchPrefix = $request->getText( 'prefix' ); $time = $request->getVal( 'timestamp' ); $this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : ''; @@ -638,16 +711,16 @@ class SpecialUndelete extends SpecialPage { $this->mRestore = false; } - if( $this->mRestore || $this->mInvert ) { + if ( $this->mRestore || $this->mInvert ) { $timestamps = array(); $this->mFileVersions = array(); - foreach( $request->getValues() as $key => $val ) { + foreach ( $request->getValues() as $key => $val ) { $matches = array(); - if( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) { + if ( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) { array_push( $timestamps, $matches[1] ); } - if( preg_match( '/^fileid(\d+)$/', $key, $matches ) ) { + if ( preg_match( '/^fileid(\d+)$/', $key, $matches ) ) { $this->mFileVersions[] = intval( $matches[1] ); } } @@ -687,13 +760,13 @@ class SpecialUndelete extends SpecialPage { if ( $this->mTimestamp !== '' ) { $this->showRevision( $this->mTimestamp ); - } elseif ( $this->mFilename !== null ) { + } elseif ( $this->mFilename !== null && $this->mTargetObj->inNamespace( NS_FILE ) ) { $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename ); // Check if user is allowed to see this file if ( !$file->exists() ) { $out->addWikiMsg( 'filedelete-nofile', $this->mFilename ); } elseif ( !$file->userCan( File::DELETED_FILE, $user ) ) { - if( $file->isDeleted( File::DELETED_RESTRICTED ) ) { + if ( $file->isDeleted( File::DELETED_RESTRICTED ) ) { throw new PermissionsError( 'suppressrevision' ); } else { throw new PermissionsError( 'deletedtext' ); @@ -716,22 +789,27 @@ class SpecialUndelete extends SpecialPage { $out = $this->getOutput(); $out->setPageTitle( $this->msg( 'undelete-search-title' ) ); $out->addHTML( - Xml::openElement( 'form', array( - 'method' => 'get', - 'action' => $wgScript ) ) . - Xml::fieldset( $this->msg( 'undelete-search-box' )->text() ) . - Html::hidden( 'title', - $this->getTitle()->getPrefixedDbKey() ) . - Xml::inputLabel( $this->msg( 'undelete-search-prefix' )->text(), - 'prefix', 'prefix', 20, - $this->mSearchPrefix ) . ' ' . - Xml::submitButton( $this->msg( 'undelete-search-submit' )->text() ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) + Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . + Xml::fieldset( $this->msg( 'undelete-search-box' )->text() ) . + Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . + Html::rawElement( + 'label', + array( 'for' => 'prefix' ), + $this->msg( 'undelete-search-prefix' )->parse() + ) . + Xml::input( + 'prefix', + 20, + $this->mSearchPrefix, + array( 'id' => 'prefix', 'autofocus' => true ) + ) . ' ' . + Xml::submitButton( $this->msg( 'undelete-search-submit' )->text() ) . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) ); # List undeletable articles - if( $this->mSearchPrefix ) { + if ( $this->mSearchPrefix ) { $result = PageArchive::listPagesByPrefix( $this->mSearchPrefix ); $this->showList( $result ); } @@ -740,15 +818,15 @@ class SpecialUndelete extends SpecialPage { /** * Generic list of deleted pages * - * @param $result ResultWrapper + * @param ResultWrapper $result * @return bool */ private function showList( $result ) { $out = $this->getOutput(); - if( $result->numRows() == 0 ) { + if ( $result->numRows() == 0 ) { $out->addWikiMsg( 'undelete-no-results' ); - return; + return false; } $out->addWikiMsg( 'undeletepagetext', $this->getLanguage()->formatNum( $result->numRows() ) ); @@ -766,8 +844,15 @@ class SpecialUndelete extends SpecialPage { ); } else { // The title is no longer valid, show as text - $item = Html::element( 'span', array( 'class' => 'mw-invalidtitle' ), - Linker::getInvalidTitleDescription( $this->getContext(), $row->ar_namespace, $row->ar_title ) ); + $item = Html::element( + 'span', + array( 'class' => 'mw-invalidtitle' ), + Linker::getInvalidTitleDescription( + $this->getContext(), + $row->ar_namespace, + $row->ar_title + ) + ); } $revs = $this->msg( 'undeleterevisions' )->numParams( $row->count )->parse(); $out->addHTML( "<li>{$item} ({$revs})</li>\n" ); @@ -779,42 +864,50 @@ class SpecialUndelete extends SpecialPage { } private function showRevision( $timestamp ) { - if( !preg_match( '/[0-9]{14}/', $timestamp ) ) { - return 0; + if ( !preg_match( '/[0-9]{14}/', $timestamp ) ) { + return; } $archive = new PageArchive( $this->mTargetObj ); - wfRunHooks( 'UndeleteForm::showRevision', array( &$archive, $this->mTargetObj ) ); + if ( !wfRunHooks( 'UndeleteForm::showRevision', array( &$archive, $this->mTargetObj ) ) ) { + return; + } $rev = $archive->getRevision( $timestamp ); $out = $this->getOutput(); $user = $this->getUser(); - if( !$rev ) { + if ( !$rev ) { $out->addWikiMsg( 'undeleterevision-missing' ); return; } - if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { - if( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) { - $out->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' ); + if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) { + if ( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) { + $out->wrapWikiMsg( + "<div class='mw-warning plainlinks'>\n$1\n</div>\n", + 'rev-deleted-text-permission' + ); return; - } else { - $out->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' ); - $out->addHTML( '<br />' ); - // and we are allowed to see... } + + $out->wrapWikiMsg( + "<div class='mw-warning plainlinks'>\n$1\n</div>\n", + 'rev-deleted-text-view' + ); + $out->addHTML( '<br />' ); + // and we are allowed to see... } - if( $this->mDiff ) { + if ( $this->mDiff ) { $previousRev = $archive->getPreviousRevision( $timestamp ); - if( $previousRev ) { + if ( $previousRev ) { $this->showDiff( $previousRev, $rev ); - if( $this->mDiffOnly ) { + if ( $this->mDiffOnly ) { return; - } else { - $out->addHTML( '<hr />' ); } + + $out->addHTML( '<hr />' ); } else { $out->addWikiMsg( 'undelete-nodiff' ); } @@ -834,7 +927,11 @@ class SpecialUndelete extends SpecialPage { $t = $lang->userTime( $timestamp, $user ); $userLink = Linker::revUserTools( $rev ); - if( $this->mPreview ) { + $content = $rev->getContent( Revision::FOR_THIS_USER, $user ); + + $isText = ( $content instanceof TextContent ); + + if ( $this->mPreview || $isText ) { $openDiv = '<div id="mw-undelete-revision" class="mw-warning">'; } else { $openDiv = '<div id="mw-undelete-revision">'; @@ -851,92 +948,120 @@ class SpecialUndelete extends SpecialPage { $out->addHTML( $this->msg( 'undelete-revision' )->rawParams( $link )->params( $time )->rawParams( $userLink )->params( $d, $t )->parse() . '</div>' ); - wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) ); - if( $this->mPreview ) { + if ( !wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) ) ) { + return; + } + + if ( $this->mPreview || !$isText ) { + // NOTE: non-text content has no source view, so always use rendered preview + // Hide [edit]s $popts = $out->parserOptions(); $popts->setEditSection( false ); - $out->parserOptions( $popts ); - $out->addWikiTextTitleTidy( $rev->getText( Revision::FOR_THIS_USER, $user ), $this->mTargetObj, true ); + + $pout = $content->getParserOutput( $this->mTargetObj, $rev->getId(), $popts, true ); + $out->addParserOutput( $pout ); } - $out->addHTML( - Xml::element( 'textarea', array( + if ( $isText ) { + // source view for textual content + $sourceView = Xml::element( + 'textarea', + array( 'readonly' => 'readonly', - 'cols' => intval( $user->getOption( 'cols' ) ), - 'rows' => intval( $user->getOption( 'rows' ) ) ), - $rev->getText( Revision::FOR_THIS_USER, $user ) . "\n" ) . - Xml::openElement( 'div' ) . - Xml::openElement( 'form', array( - 'method' => 'post', - 'action' => $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) ) ) ) . - Xml::element( 'input', array( - 'type' => 'hidden', - 'name' => 'target', - 'value' => $this->mTargetObj->getPrefixedDbKey() ) ) . - Xml::element( 'input', array( - 'type' => 'hidden', - 'name' => 'timestamp', - 'value' => $timestamp ) ) . - Xml::element( 'input', array( - 'type' => 'hidden', - 'name' => 'wpEditToken', - 'value' => $user->getEditToken() ) ) . - Xml::element( 'input', array( + 'cols' => $user->getIntOption( 'cols' ), + 'rows' => $user->getIntOption( 'rows' ) + ), + $content->getNativeData() . "\n" + ); + + $previewButton = Xml::element( 'input', array( 'type' => 'submit', 'name' => 'preview', - 'value' => $this->msg( 'showpreview' )->text() ) ) . - Xml::element( 'input', array( - 'name' => 'diff', - 'type' => 'submit', - 'value' => $this->msg( 'showdiff' )->text() ) ) . - Xml::closeElement( 'form' ) . - Xml::closeElement( 'div' ) ); + 'value' => $this->msg( 'showpreview' )->text() + ) ); + } else { + $sourceView = ''; + $previewButton = ''; + } + + $diffButton = Xml::element( 'input', array( + 'name' => 'diff', + 'type' => 'submit', + 'value' => $this->msg( 'showdiff' )->text() ) ); + + $out->addHTML( + $sourceView . + Xml::openElement( 'div', array( + 'style' => 'clear: both' ) ) . + Xml::openElement( 'form', array( + 'method' => 'post', + 'action' => $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) ) ) ) . + Xml::element( 'input', array( + 'type' => 'hidden', + 'name' => 'target', + 'value' => $this->mTargetObj->getPrefixedDBkey() ) ) . + Xml::element( 'input', array( + 'type' => 'hidden', + 'name' => 'timestamp', + 'value' => $timestamp ) ) . + Xml::element( 'input', array( + 'type' => 'hidden', + 'name' => 'wpEditToken', + 'value' => $user->getEditToken() ) ) . + $previewButton . + $diffButton . + Xml::closeElement( 'form' ) . + Xml::closeElement( 'div' ) + ); } /** * Build a diff display between this and the previous either deleted * or non-deleted edit. * - * @param $previousRev Revision - * @param $currentRev Revision - * @return String: HTML + * @param Revision $previousRev + * @param Revision $currentRev + * @return string HTML */ function showDiff( $previousRev, $currentRev ) { - $diffEngine = new DifferenceEngine( $this->getContext() ); + $diffContext = clone $this->getContext(); + $diffContext->setTitle( $currentRev->getTitle() ); + $diffContext->setWikiPage( WikiPage::factory( $currentRev->getTitle() ) ); + + $diffEngine = $currentRev->getContentHandler()->createDifferenceEngine( $diffContext ); $diffEngine->showDiffStyle(); - $this->getOutput()->addHTML( - "<div>" . - "<table width='98%' cellpadding='0' cellspacing='4' class='diff'>" . + $this->getOutput()->addHTML( "<div>" . + "<table style='width: 98%;' cellpadding='0' cellspacing='4' class='diff'>" . "<col class='diff-marker' />" . "<col class='diff-content' />" . "<col class='diff-marker' />" . "<col class='diff-content' />" . "<tr>" . - "<td colspan='2' width='50%' style='text-align: center' class='diff-otitle'>" . - $this->diffHeader( $previousRev, 'o' ) . - "</td>\n" . - "<td colspan='2' width='50%' style='text-align: center' class='diff-ntitle'>" . - $this->diffHeader( $currentRev, 'n' ) . - "</td>\n" . + "<td colspan='2' style='width: 50%; text-align: center' class='diff-otitle'>" . + $this->diffHeader( $previousRev, 'o' ) . + "</td>\n" . + "<td colspan='2' style='width: 50%; text-align: center' class='diff-ntitle'>" . + $this->diffHeader( $currentRev, 'n' ) . + "</td>\n" . "</tr>" . - $diffEngine->generateDiffBody( - $previousRev->getText( Revision::FOR_THIS_USER, $this->getUser() ), - $currentRev->getText( Revision::FOR_THIS_USER, $this->getUser() ) ) . + $diffEngine->generateContentDiffBody( + $previousRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ), + $currentRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ) ) . "</table>" . "</div>\n" ); } /** - * @param $rev Revision - * @param $prefix + * @param Revision $rev + * @param string $prefix * @return string */ private function diffHeader( $rev, $prefix ) { $isDeleted = !( $rev->getId() && $rev->getTitle() ); - if( $isDeleted ) { + if ( $isDeleted ) { /// @todo FIXME: $rev->getTitle() is null for deleted revs...? $targetPage = $this->getTitle(); $targetQuery = array( @@ -948,30 +1073,34 @@ class SpecialUndelete extends SpecialPage { $targetPage = $rev->getTitle(); $targetQuery = array( 'oldid' => $rev->getId() ); } + // Add show/hide deletion links if available $user = $this->getUser(); $lang = $this->getLanguage(); $rdel = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj ); - if ( $rdel ) $rdel = " $rdel"; - return - '<div id="mw-diff-' . $prefix . 'title1"><strong>' . - Linker::link( - $targetPage, - $this->msg( - 'revisionasof', - $lang->userTimeAndDate( $rev->getTimestamp(), $user ), - $lang->userDate( $rev->getTimestamp(), $user ), - $lang->userTime( $rev->getTimestamp(), $user ) - )->escaped(), - array(), - $targetQuery - ) . + + if ( $rdel ) { + $rdel = " $rdel"; + } + + return '<div id="mw-diff-' . $prefix . 'title1"><strong>' . + Linker::link( + $targetPage, + $this->msg( + 'revisionasof', + $lang->userTimeAndDate( $rev->getTimestamp(), $user ), + $lang->userDate( $rev->getTimestamp(), $user ), + $lang->userTime( $rev->getTimestamp(), $user ) + )->escaped(), + array(), + $targetQuery + ) . '</strong></div>' . - '<div id="mw-diff-'.$prefix.'title2">' . - Linker::revUserTools( $rev ) . '<br />' . + '<div id="mw-diff-' . $prefix . 'title2">' . + Linker::revUserTools( $rev ) . '<br />' . '</div>' . - '<div id="mw-diff-'.$prefix.'title3">' . - Linker::revComment( $rev ) . $rdel . '<br />' . + '<div id="mw-diff-' . $prefix . 'title3">' . + Linker::revComment( $rev ) . $rdel . '<br />' . '</div>'; } @@ -989,15 +1118,16 @@ class SpecialUndelete extends SpecialPage { $lang->userTime( $file->getTimestamp(), $user ) ); $out->addHTML( Xml::openElement( 'form', array( - 'method' => 'POST', - 'action' => $this->getTitle()->getLocalURL( - 'target=' . urlencode( $this->mTarget ) . - '&file=' . urlencode( $key ) . - '&token=' . urlencode( $user->getEditToken( $key ) ) ) + 'method' => 'POST', + 'action' => $this->getTitle()->getLocalURL( array( + 'target' => $this->mTarget, + 'file' => $key, + 'token' => $user->getEditToken( $key ), + ) ), ) ) . - Xml::submitButton( $this->msg( 'undelete-show-file-submit' )->text() ) . - '</form>' + Xml::submitButton( $this->msg( 'undelete-show-file-submit' )->text() ) . + '</form>' ); } @@ -1023,7 +1153,7 @@ class SpecialUndelete extends SpecialPage { private function showHistory() { $out = $this->getOutput(); - if( $this->mAllowed ) { + if ( $this->mAllowed ) { $out->addModules( 'mediawiki.special.undelete' ); } $out->wrapWikiMsg( @@ -1057,7 +1187,7 @@ class SpecialUndelete extends SpecialPage { $haveFiles = $files && $files->numRows() > 0; # Batch existence check on user and talk pages - if( $haveRevisions ) { + if ( $haveRevisions ) { $batch = new LinkBatch(); foreach ( $revisions as $row ) { $batch->addObj( Title::makeTitleSafe( NS_USER, $row->ar_user_text ) ); @@ -1066,7 +1196,7 @@ class SpecialUndelete extends SpecialPage { $batch->execute(); $revisions->seek( 0 ); } - if( $haveFiles ) { + if ( $haveFiles ) { $batch = new LinkBatch(); foreach ( $files as $row ) { $batch->addObj( Title::makeTitleSafe( NS_USER, $row->fa_user_text ) ); @@ -1079,7 +1209,10 @@ class SpecialUndelete extends SpecialPage { if ( $this->mAllowed ) { $action = $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) ); # Start the form here - $top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'undelete' ) ); + $top = Xml::openElement( + 'form', + array( 'method' => 'post', 'action' => $action, 'id' => 'undelete' ) + ); $out->addHTML( $top ); } @@ -1089,59 +1222,60 @@ class SpecialUndelete extends SpecialPage { LogEventsList::showLogExtract( $out, 'delete', $this->mTargetObj ); # Show relevant lines from the suppression log: $suppressLogPage = new LogPage( 'suppress' ); - if( $this->getUser()->isAllowed( 'suppressionlog' ) ) { + if ( $this->getUser()->isAllowed( 'suppressionlog' ) ) { $out->addHTML( Xml::element( 'h2', null, $suppressLogPage->getName()->text() ) . "\n" ); LogEventsList::showLogExtract( $out, 'suppress', $this->mTargetObj ); } - if( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) { + if ( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) { # Format the user-visible controls (comment field, submission button) # in a nice little table - if( $this->getUser()->isAllowed( 'suppressrevision' ) ) { + if ( $this->getUser()->isAllowed( 'suppressrevision' ) ) { $unsuppressBox = "<tr> <td> </td> <td class='mw-input'>" . - Xml::checkLabel( $this->msg( 'revdelete-unsuppress' )->text(), - 'wpUnsuppress', 'mw-undelete-unsuppress', $this->mUnsuppress ). + Xml::checkLabel( $this->msg( 'revdelete-unsuppress' )->text(), + 'wpUnsuppress', 'mw-undelete-unsuppress', $this->mUnsuppress ) . "</td> </tr>"; } else { $unsuppressBox = ''; } + $table = Xml::fieldset( $this->msg( 'undelete-fieldset-title' )->text() ) . - Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) . + Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) . "<tr> <td colspan='2' class='mw-undelete-extrahelp'>" . - $this->msg( 'undeleteextrahelp' )->parseAsBlock() . - "</td> - </tr> - <tr> - <td class='mw-label'>" . - Xml::label( $this->msg( 'undeletecomment' )->text(), 'wpComment' ) . - "</td> - <td class='mw-input'>" . - Xml::input( 'wpComment', 50, $this->mComment, array( 'id' => 'wpComment' ) ) . - "</td> - </tr> - <tr> - <td> </td> - <td class='mw-submit'>" . - Xml::submitButton( $this->msg( 'undeletebtn' )->text(), array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) ) . ' ' . - Xml::submitButton( $this->msg( 'undeleteinvert' )->text(), array( 'name' => 'invert', 'id' => 'mw-undelete-invert' ) ) . - "</td> - </tr>" . + $this->msg( 'undeleteextrahelp' )->parseAsBlock() . + "</td> + </tr> + <tr> + <td class='mw-label'>" . + Xml::label( $this->msg( 'undeletecomment' )->text(), 'wpComment' ) . + "</td> + <td class='mw-input'>" . + Xml::input( 'wpComment', 50, $this->mComment, array( 'id' => 'wpComment', 'autofocus' => true ) ) . + "</td> + </tr> + <tr> + <td> </td> + <td class='mw-submit'>" . + Xml::submitButton( $this->msg( 'undeletebtn' )->text(), array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) ) . ' ' . + Xml::submitButton( $this->msg( 'undeleteinvert' )->text(), array( 'name' => 'invert', 'id' => 'mw-undelete-invert' ) ) . + "</td> + </tr>" . $unsuppressBox . - Xml::closeElement( 'table' ) . - Xml::closeElement( 'fieldset' ); + Xml::closeElement( 'table' ) . + Xml::closeElement( 'fieldset' ); $out->addHTML( $table ); } $out->addHTML( Xml::element( 'h2', null, $this->msg( 'history' )->text() ) . "\n" ); - if( $haveRevisions ) { + if ( $haveRevisions ) { # The page's stored (deleted) history: $out->addHTML( '<ul>' ); $remaining = $revisions->numRows(); @@ -1157,7 +1291,7 @@ class SpecialUndelete extends SpecialPage { $out->addWikiMsg( 'nohistory' ); } - if( $haveFiles ) { + if ( $haveFiles ) { $out->addHTML( Xml::element( 'h2', null, $this->msg( 'filehist' )->text() ) . "\n" ); $out->addHTML( '<ul>' ); foreach ( $files as $row ) { @@ -1169,7 +1303,7 @@ class SpecialUndelete extends SpecialPage { if ( $this->mAllowed ) { # Slip in the hidden controls here - $misc = Html::hidden( 'target', $this->mTarget ); + $misc = Html::hidden( 'target', $this->mTarget ); $misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() ); $misc .= Xml::closeElement( 'form' ); $out->addHTML( $misc ); @@ -1180,13 +1314,16 @@ class SpecialUndelete extends SpecialPage { private function formatRevisionRow( $row, $earliestLiveTime, $remaining ) { $rev = Revision::newFromArchiveRow( $row, - array( 'page' => $this->mTargetObj->getArticleID() ) ); + array( + 'title' => $this->mTargetObj + ) ); + $revTextSize = ''; $ts = wfTimestamp( TS_MW, $row->ar_timestamp ); // Build checkboxen... - if( $this->mAllowed ) { - if( $this->mInvert ) { - if( in_array( $ts, $this->mTargetTimestamp ) ) { + if ( $this->mAllowed ) { + if ( $this->mInvert ) { + if ( in_array( $ts, $this->mTargetTimestamp ) ) { $checkBox = Xml::check( "ts$ts" ); } else { $checkBox = Xml::check( "ts$ts", true ); @@ -1197,15 +1334,16 @@ class SpecialUndelete extends SpecialPage { } else { $checkBox = ''; } - $user = $this->getUser(); + // Build page & diff links... - if( $this->mCanView ) { + $user = $this->getUser(); + if ( $this->mCanView ) { $titleObj = $this->getTitle(); # Last link - if( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) { + if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) { $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) ); $last = $this->msg( 'diff' )->escaped(); - } elseif( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) { + } elseif ( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) { $pageLink = $this->getPageLink( $rev, $titleObj, $ts ); $last = Linker::linkKnown( $titleObj, @@ -1225,28 +1363,35 @@ class SpecialUndelete extends SpecialPage { $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) ); $last = $this->msg( 'diff' )->escaped(); } + // User links $userLink = Linker::revUserTools( $rev ); + // Revision text size $size = $row->ar_len; - if( !is_null( $size ) ) { + if ( !is_null( $size ) ) { $revTextSize = Linker::formatRevisionSize( $size ); } + // Edit summary $comment = Linker::revComment( $rev ); + // Revision delete links $revdlink = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj ); - $revisionRow = $this->msg( 'undelete-revisionrow' )->rawParams( $checkBox, $revdlink, $last, $pageLink , $userLink, $revTextSize, $comment )->escaped(); + $revisionRow = $this->msg( 'undelete-revisionrow' ) + ->rawParams( $checkBox, $revdlink, $last, $pageLink, $userLink, $revTextSize, $comment ) + ->escaped(); + return "<li>$revisionRow</li>"; } private function formatFileRow( $row ) { $file = ArchivedFile::newFromRow( $row ); - $ts = wfTimestamp( TS_MW, $row->fa_timestamp ); $user = $this->getUser(); - if( $this->mAllowed && $row->fa_storage_key ) { + + if ( $this->mAllowed && $row->fa_storage_key ) { $checkBox = Xml::check( 'fileid' . $row->fa_id ); $key = urlencode( $row->fa_storage_key ); $pageLink = $this->getFileLink( $file, $this->getTitle(), $ts, $key ); @@ -1256,15 +1401,18 @@ class SpecialUndelete extends SpecialPage { } $userLink = $this->getFileUser( $file ); $data = $this->msg( 'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text(); - $bytes = $this->msg( 'parentheses' )->rawParams( $this->msg( 'nbytes' )->numParams( $row->fa_size )->text() )->plain(); + $bytes = $this->msg( 'parentheses' ) + ->rawParams( $this->msg( 'nbytes' )->numParams( $row->fa_size )->text() ) + ->plain(); $data = htmlspecialchars( $data . ' ' . $bytes ); $comment = $this->getFileComment( $file ); // Add show/hide deletion links if available $canHide = $user->isAllowed( 'deleterevision' ); - if( $canHide || ( $file->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) ) { - if( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) { - $revdlink = Linker::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops + if ( $canHide || ( $file->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) ) { + if ( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) { + // Revision was hidden from sysops + $revdlink = Linker::revDeleteLinkDisabled( $canHide ); } else { $query = array( 'type' => 'filearchive', @@ -1284,104 +1432,114 @@ class SpecialUndelete extends SpecialPage { /** * Fetch revision text link if it's available to all users * - * @param $rev Revision - * @param $titleObj Title - * @param $ts string Timestamp + * @param Revision $rev + * @param Title $titleObj + * @param string $ts Timestamp * @return string */ function getPageLink( $rev, $titleObj, $ts ) { $user = $this->getUser(); $time = $this->getLanguage()->userTimeAndDate( $ts, $user ); - if( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) { + if ( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) { return '<span class="history-deleted">' . $time . '</span>'; - } else { - $link = Linker::linkKnown( - $titleObj, - htmlspecialchars( $time ), - array(), - array( - 'target' => $this->mTargetObj->getPrefixedText(), - 'timestamp' => $ts - ) - ); - if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { - $link = '<span class="history-deleted">' . $link . '</span>'; - } - return $link; } + + $link = Linker::linkKnown( + $titleObj, + htmlspecialchars( $time ), + array(), + array( + 'target' => $this->mTargetObj->getPrefixedText(), + 'timestamp' => $ts + ) + ); + + if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) { + $link = '<span class="history-deleted">' . $link . '</span>'; + } + + return $link; } /** * Fetch image view link if it's available to all users * - * @param $file File - * @param $titleObj Title - * @param $ts string A timestamp - * @param $key String: a storage key + * @param File|ArchivedFile $file + * @param Title $titleObj + * @param string $ts A timestamp + * @param string $key a storage key * - * @return String: HTML fragment + * @return string HTML fragment */ function getFileLink( $file, $titleObj, $ts, $key ) { $user = $this->getUser(); $time = $this->getLanguage()->userTimeAndDate( $ts, $user ); - if( !$file->userCan( File::DELETED_FILE, $user ) ) { + if ( !$file->userCan( File::DELETED_FILE, $user ) ) { return '<span class="history-deleted">' . $time . '</span>'; - } else { - $link = Linker::linkKnown( - $titleObj, - htmlspecialchars( $time ), - array(), - array( - 'target' => $this->mTargetObj->getPrefixedText(), - 'file' => $key, - 'token' => $user->getEditToken( $key ) - ) - ); - if( $file->isDeleted( File::DELETED_FILE ) ) { - $link = '<span class="history-deleted">' . $link . '</span>'; - } - return $link; } + + $link = Linker::linkKnown( + $titleObj, + htmlspecialchars( $time ), + array(), + array( + 'target' => $this->mTargetObj->getPrefixedText(), + 'file' => $key, + 'token' => $user->getEditToken( $key ) + ) + ); + + if ( $file->isDeleted( File::DELETED_FILE ) ) { + $link = '<span class="history-deleted">' . $link . '</span>'; + } + + return $link; } /** * Fetch file's user id if it's available to this user * - * @param $file File - * @return String: HTML fragment + * @param File|ArchivedFile $file + * @return string HTML fragment */ function getFileUser( $file ) { - if( !$file->userCan( File::DELETED_USER, $this->getUser() ) ) { - return '<span class="history-deleted">' . $this->msg( 'rev-deleted-user' )->escaped() . '</span>'; - } else { - $link = Linker::userLink( $file->getRawUser(), $file->getRawUserText() ) . - Linker::userToolLinks( $file->getRawUser(), $file->getRawUserText() ); - if( $file->isDeleted( File::DELETED_USER ) ) { - $link = '<span class="history-deleted">' . $link . '</span>'; - } - return $link; + if ( !$file->userCan( File::DELETED_USER, $this->getUser() ) ) { + return '<span class="history-deleted">' . + $this->msg( 'rev-deleted-user' )->escaped() . + '</span>'; } + + $link = Linker::userLink( $file->getRawUser(), $file->getRawUserText() ) . + Linker::userToolLinks( $file->getRawUser(), $file->getRawUserText() ); + + if ( $file->isDeleted( File::DELETED_USER ) ) { + $link = '<span class="history-deleted">' . $link . '</span>'; + } + + return $link; } /** * Fetch file upload comment if it's available to this user * - * @param $file File - * @return String: HTML fragment + * @param File|ArchivedFile $file + * @return string HTML fragment */ function getFileComment( $file ) { - if( !$file->userCan( File::DELETED_COMMENT, $this->getUser() ) ) { + if ( !$file->userCan( File::DELETED_COMMENT, $this->getUser() ) ) { return '<span class="history-deleted"><span class="comment">' . $this->msg( 'rev-deleted-comment' )->escaped() . '</span></span>'; - } else { - $link = Linker::commentBlock( $file->getRawDescription() ); - if( $file->isDeleted( File::DELETED_COMMENT ) ) { - $link = '<span class="history-deleted">' . $link . '</span>'; - } - return $link; } + + $link = Linker::commentBlock( $file->getRawDescription() ); + + if ( $file->isDeleted( File::DELETED_COMMENT ) ) { + $link = '<span class="history-deleted">' . $link . '</span>'; + } + + return $link; } function undelete() { @@ -1406,7 +1564,7 @@ class SpecialUndelete extends SpecialPage { $this->getUser() ); - if( is_array( $ok ) ) { + if ( is_array( $ok ) ) { if ( $ok[1] ) { // Undeleted file count wfRunHooks( 'FileUndeleteComplete', array( $this->mTargetObj, $this->mFileVersions, @@ -1417,14 +1575,22 @@ class SpecialUndelete extends SpecialPage { $out->addHTML( $this->msg( 'undeletedpage' )->rawParams( $link )->parse() ); } else { $out->setPageTitle( $this->msg( 'undelete-error' ) ); - $out->addWikiMsg( 'cannotundelete' ); - $out->addWikiMsg( 'undeleterevdel' ); } - // Show file deletion warnings and errors + // Show revision undeletion warnings and errors + $status = $archive->getRevisionStatus(); + if ( $status && !$status->isGood() ) { + $out->addWikiText( '<div class="error">' . $status->getWikiText( 'cannotundelete', 'cannotundelete' ) . '</div>' ); + } + + // Show file undeletion warnings and errors $status = $archive->getFileStatus(); - if( $status && !$status->isGood() ) { + if ( $status && !$status->isGood() ) { $out->addWikiText( '<div class="error">' . $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) . '</div>' ); } } + + protected function getGroupName() { + return 'pagetools'; + } } diff --git a/includes/specials/SpecialUnlockdb.php b/includes/specials/SpecialUnlockdb.php index 2e772540..35141d80 100644 --- a/includes/specials/SpecialUnlockdb.php +++ b/includes/specials/SpecialUnlockdb.php @@ -84,4 +84,8 @@ class SpecialUnlockdb extends FormSpecialPage { $out->addSubtitle( $this->msg( 'unlockdbsuccesssub' ) ); $out->addWikiMsg( 'unlockdbsuccesstext' ); } + + protected function getGroupName() { + return 'wiki'; + } } diff --git a/includes/specials/SpecialUnusedcategories.php b/includes/specials/SpecialUnusedcategories.php index 1bd38e17..b686a5b5 100644 --- a/includes/specials/SpecialUnusedcategories.php +++ b/includes/specials/SpecialUnusedcategories.php @@ -26,7 +26,9 @@ */ class UnusedCategoriesPage extends QueryPage { - function isExpensive() { return true; } + function isExpensive() { + return true; + } function __construct( $name = 'Unusedcategories' ) { parent::__construct( $name ); @@ -37,15 +39,15 @@ class UnusedCategoriesPage extends QueryPage { } function getQueryInfo() { - return array ( - 'tables' => array ( 'page', 'categorylinks' ), - 'fields' => array ( 'namespace' => 'page_namespace', + return array( + 'tables' => array( 'page', 'categorylinks' ), + 'fields' => array( 'namespace' => 'page_namespace', 'title' => 'page_title', 'value' => 'page_title' ), - 'conds' => array ( 'cl_from IS NULL', + 'conds' => array( 'cl_from IS NULL', 'page_namespace' => NS_CATEGORY, 'page_is_redirect' => 0 ), - 'join_conds' => array ( 'categorylinks' => array ( + 'join_conds' => array( 'categorylinks' => array( 'LEFT JOIN', 'cl_to = page_title' ) ) ); } @@ -58,8 +60,17 @@ class UnusedCategoriesPage extends QueryPage { return false; } + /** + * @param Skin $skin + * @param object $result Result row + * @return string + */ function formatResult( $skin, $result ) { $title = Title::makeTitle( NS_CATEGORY, $result->title ); return Linker::link( $title, htmlspecialchars( $title->getText() ) ); } + + protected function getGroupName() { + return 'maintenance'; + } } diff --git a/includes/specials/SpecialUnusedimages.php b/includes/specials/SpecialUnusedimages.php index cdab557e..d332db75 100644 --- a/includes/specials/SpecialUnusedimages.php +++ b/includes/specials/SpecialUnusedimages.php @@ -45,28 +45,28 @@ class UnusedimagesPage extends ImageQueryPage { function getQueryInfo() { global $wgCountCategorizedImagesAsUsed; - $retval = array ( - 'tables' => array ( 'image', 'imagelinks' ), - 'fields' => array ( 'namespace' => NS_FILE, + $retval = array( + 'tables' => array( 'image', 'imagelinks' ), + 'fields' => array( 'namespace' => NS_FILE, 'title' => 'img_name', 'value' => 'img_timestamp', 'img_user', 'img_user_text', 'img_description' ), - 'conds' => array ( 'il_to IS NULL' ), - 'join_conds' => array ( 'imagelinks' => array ( + 'conds' => array( 'il_to IS NULL' ), + 'join_conds' => array( 'imagelinks' => array( 'LEFT JOIN', 'il_to = img_name' ) ) ); if ( $wgCountCategorizedImagesAsUsed ) { // Order is significant - $retval['tables'] = array ( 'image', 'page', 'categorylinks', + $retval['tables'] = array( 'image', 'page', 'categorylinks', 'imagelinks' ); $retval['conds']['page_namespace'] = NS_FILE; $retval['conds'][] = 'cl_from IS NULL'; $retval['conds'][] = 'img_name = page_title'; - $retval['join_conds']['categorylinks'] = array ( + $retval['join_conds']['categorylinks'] = array( 'LEFT JOIN', 'cl_from = page_id' ); - $retval['join_conds']['imagelinks'] = array ( + $retval['join_conds']['imagelinks'] = array( 'LEFT JOIN', 'il_to = page_title' ); } return $retval; @@ -80,4 +80,7 @@ class UnusedimagesPage extends ImageQueryPage { return $this->msg( 'unusedimagestext' )->parseAsBlock(); } + protected function getGroupName() { + return 'maintenance'; + } } diff --git a/includes/specials/SpecialUnusedtemplates.php b/includes/specials/SpecialUnusedtemplates.php index 06077d1f..1dc9f420 100644 --- a/includes/specials/SpecialUnusedtemplates.php +++ b/includes/specials/SpecialUnusedtemplates.php @@ -35,28 +35,36 @@ class UnusedtemplatesPage extends QueryPage { parent::__construct( $name ); } - function isExpensive() { return true; } - function isSyndicated() { return false; } - function sortDescending() { return false; } + function isExpensive() { + return true; + } + + function isSyndicated() { + return false; + } + + function sortDescending() { + return false; + } function getQueryInfo() { - return array ( - 'tables' => array ( 'page', 'templatelinks' ), - 'fields' => array ( 'namespace' => 'page_namespace', + return array( + 'tables' => array( 'page', 'templatelinks' ), + 'fields' => array( 'namespace' => 'page_namespace', 'title' => 'page_title', 'value' => 'page_title' ), - 'conds' => array ( 'page_namespace' => NS_TEMPLATE, + 'conds' => array( 'page_namespace' => NS_TEMPLATE, 'tl_from IS NULL', 'page_is_redirect' => 0 ), - 'join_conds' => array ( 'templatelinks' => array ( - 'LEFT JOIN', array ( 'tl_title = page_title', + 'join_conds' => array( 'templatelinks' => array( + 'LEFT JOIN', array( 'tl_title = page_title', 'tl_namespace = page_namespace' ) ) ) ); } /** - * @param $skin Skin - * @param $result + * @param Skin $skin + * @param object $result Result row * @return string */ function formatResult( $skin, $result ) { @@ -77,4 +85,8 @@ class UnusedtemplatesPage extends QueryPage { function getPageHeader() { return $this->msg( 'unusedtemplatestext' )->parseAsBlock(); } + + protected function getGroupName() { + return 'maintenance'; + } } diff --git a/includes/specials/SpecialUnwatchedpages.php b/includes/specials/SpecialUnwatchedpages.php index e5a79413..954e3ffe 100644 --- a/includes/specials/SpecialUnwatchedpages.php +++ b/includes/specials/SpecialUnwatchedpages.php @@ -35,34 +35,41 @@ class UnwatchedpagesPage extends QueryPage { parent::__construct( $name, 'unwatchedpages' ); } - function isExpensive() { return true; } - function isSyndicated() { return false; } + function isExpensive() { + return true; + } + + function isSyndicated() { + return false; + } function getQueryInfo() { - return array ( - 'tables' => array ( 'page', 'watchlist' ), - 'fields' => array ( 'namespace' => 'page_namespace', + return array( + 'tables' => array( 'page', 'watchlist' ), + 'fields' => array( 'namespace' => 'page_namespace', 'title' => 'page_title', 'value' => 'page_namespace' ), - 'conds' => array ( 'wl_title IS NULL', + 'conds' => array( 'wl_title IS NULL', 'page_is_redirect' => 0, "page_namespace != '" . NS_MEDIAWIKI . "'" ), - 'join_conds' => array ( 'watchlist' => array ( - 'LEFT JOIN', array ( 'wl_title = page_title', + 'join_conds' => array( 'watchlist' => array( + 'LEFT JOIN', array( 'wl_title = page_title', 'wl_namespace = page_namespace' ) ) ) ); } - function sortDescending() { return false; } + function sortDescending() { + return false; + } function getOrderFields() { return array( 'page_namespace', 'page_title' ); } /** - * @param $skin Skin - * @param $result + * @param Skin $skin + * @param object $result Result row * @return string */ function formatResult( $skin, $result ) { @@ -87,4 +94,8 @@ class UnwatchedpagesPage extends QueryPage { return $this->getLanguage()->specialList( $plink, $wlink ); } + + protected function getGroupName() { + return 'maintenance'; + } } diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php index 43ea345b..09facf4f 100644 --- a/includes/specials/SpecialUpload.php +++ b/includes/specials/SpecialUpload.php @@ -39,7 +39,7 @@ class SpecialUpload extends SpecialPage { } /** Misc variables **/ - public $mRequest; // The WebRequest or FauxRequest this form is supposed to handle + public $mRequest; // The WebRequest or FauxRequest this form is supposed to handle public $mSourceType; /** @@ -54,7 +54,7 @@ class SpecialUpload extends SpecialPage { public $mUploadClicked; /** User input variables from the "description" section **/ - public $mDesiredDestName; // The requested target file name + public $mDesiredDestName; // The requested target file name public $mComment; public $mLicense; @@ -66,10 +66,10 @@ class SpecialUpload extends SpecialPage { /** Hidden variables **/ public $mDestWarningAck; - public $mForReUpload; // The user followed an "overwrite this file" link - public $mCancelUpload; // The user clicked "Cancel and return to upload form" button + public $mForReUpload; // The user followed an "overwrite this file" link + public $mCancelUpload; // The user clicked "Cancel and return to upload form" button public $mTokenOk; - public $mUploadSuccessful = false; // Subclasses can use this to determine whether a file was uploaded + public $mUploadSuccessful = false; // Subclasses can use this to determine whether a file was uploaded /** Text injection points for hooks not using HTMLForm **/ public $uploadFormTextTop; @@ -82,32 +82,37 @@ class SpecialUpload extends SpecialPage { */ protected function loadRequest() { $this->mRequest = $request = $this->getRequest(); - $this->mSourceType = $request->getVal( 'wpSourceType', 'file' ); - $this->mUpload = UploadBase::createFromRequest( $request ); - $this->mUploadClicked = $request->wasPosted() + $this->mSourceType = $request->getVal( 'wpSourceType', 'file' ); + $this->mUpload = UploadBase::createFromRequest( $request ); + $this->mUploadClicked = $request->wasPosted() && ( $request->getCheck( 'wpUpload' ) || $request->getCheck( 'wpUploadIgnoreWarning' ) ); // Guess the desired name from the filename if not provided - $this->mDesiredDestName = $request->getText( 'wpDestFile' ); - if( !$this->mDesiredDestName && $request->getFileName( 'wpUploadFile' ) !== null ) { + $this->mDesiredDestName = $request->getText( 'wpDestFile' ); + if ( !$this->mDesiredDestName && $request->getFileName( 'wpUploadFile' ) !== null ) { $this->mDesiredDestName = $request->getFileName( 'wpUploadFile' ); } - $this->mComment = $request->getText( 'wpUploadDescription' ); - $this->mLicense = $request->getText( 'wpLicense' ); + $this->mLicense = $request->getText( 'wpLicense' ); - - $this->mDestWarningAck = $request->getText( 'wpDestFileWarningAck' ); - $this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' ) + $this->mDestWarningAck = $request->getText( 'wpDestFileWarningAck' ); + $this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' ) || $request->getCheck( 'wpUploadIgnoreWarning' ); - $this->mWatchthis = $request->getBool( 'wpWatchthis' ) && $this->getUser()->isLoggedIn(); - $this->mCopyrightStatus = $request->getText( 'wpUploadCopyStatus' ); - $this->mCopyrightSource = $request->getText( 'wpUploadSource' ); + $this->mWatchthis = $request->getBool( 'wpWatchthis' ) && $this->getUser()->isLoggedIn(); + $this->mCopyrightStatus = $request->getText( 'wpUploadCopyStatus' ); + $this->mCopyrightSource = $request->getText( 'wpUploadSource' ); + + $this->mForReUpload = $request->getBool( 'wpForReUpload' ); // updating a file + $commentDefault = ''; + $commentMsg = wfMessage( 'upload-default-description' )->inContentLanguage(); + if ( !$this->mForReUpload && !$commentMsg->isDisabled() ) { + $commentDefault = $commentMsg->plain(); + } + $this->mComment = $request->getText( 'wpUploadDescription', $commentDefault ); - $this->mForReUpload = $request->getBool( 'wpForReUpload' ); // updating a file - $this->mCancelUpload = $request->getCheck( 'wpCancelUpload' ) - || $request->getCheck( 'wpReUpload' ); // b/w compat + $this->mCancelUpload = $request->getCheck( 'wpCancelUpload' ) + || $request->getCheck( 'wpReUpload' ); // b/w compat // If it was posted check for the token (no remote POST'ing with user credentials) $token = $request->getVal( 'wpEditToken' ); @@ -137,19 +142,19 @@ class SpecialUpload extends SpecialPage { $this->outputHeader(); # Check uploading enabled - if( !UploadBase::isEnabled() ) { + if ( !UploadBase::isEnabled() ) { throw new ErrorPageError( 'uploaddisabled', 'uploaddisabledtext' ); } # Check permissions $user = $this->getUser(); $permissionRequired = UploadBase::isAllowed( $user ); - if( $permissionRequired !== true ) { + if ( $permissionRequired !== true ) { throw new PermissionsError( $permissionRequired ); } # Check blocks - if( $user->isBlocked() ) { + if ( $user->isBlocked() ) { throw new UserBlockedError( $user->getBlock() ); } @@ -174,7 +179,7 @@ class SpecialUpload extends SpecialPage { $this->processUpload(); } else { # Backwards compatibility hook - if( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) ) { + if ( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) ) { wfDebug( "Hook 'UploadForm:initial' broke output of the upload form" ); return; } @@ -209,13 +214,15 @@ class SpecialUpload extends SpecialPage { /** * Get an UploadForm instance with title and text properly set. * - * @param $message String: HTML string to add to the form - * @param $sessionKey String: session key in case this is a stashed upload + * @param string $message HTML string to add to the form + * @param string $sessionKey session key in case this is a stashed upload * @param $hideIgnoreWarning Boolean: whether to hide "ignore warning" check box * @return UploadForm */ protected function getUploadForm( $message = '', $sessionKey = '', $hideIgnoreWarning = false ) { # Initialize form + $context = new DerivativeContext( $this->getContext() ); + $context->setTitle( $this->getTitle() ); // Remove subpage $form = new UploadForm( array( 'watch' => $this->getWatchCheck(), 'forreupload' => $this->mForReUpload, @@ -227,11 +234,10 @@ class SpecialUpload extends SpecialPage { 'texttop' => $this->uploadFormTextTop, 'textaftersummary' => $this->uploadFormTextAfterSummary, 'destfile' => $this->mDesiredDestName, - ), $this->getContext() ); - $form->setTitle( $this->getTitle() ); + ), $context ); # Check the token, but only if necessary - if( + if ( !$this->mTokenOk && !$this->mCancelUpload && ( $this->mUpload && $this->mUploadClicked ) ) { @@ -246,9 +252,9 @@ class SpecialUpload extends SpecialPage { LogEventsList::showLogExtract( $delNotice, array( 'delete', 'move' ), $desiredTitleObj, '', array( 'lim' => 10, - 'conds' => array( "log_action != 'revision'" ), - 'showIfEmpty' => false, - 'msgKey' => array( 'upload-recreate-warning' ) ) + 'conds' => array( "log_action != 'revision'" ), + 'showIfEmpty' => false, + 'msgKey' => array( 'upload-recreate-warning' ) ) ); } $form->addPreText( $delNotice ); @@ -277,7 +283,7 @@ class SpecialUpload extends SpecialPage { $title = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName ); $user = $this->getUser(); // Show a subtitle link to deleted revisions (to sysops et al only) - if( $title instanceof Title ) { + if ( $title instanceof Title ) { $count = $title->isDeleted(); if ( $count > 0 && $user->isAllowed( 'deletedhistory' ) ) { $restorelink = Linker::linkKnown( @@ -300,7 +306,7 @@ class SpecialUpload extends SpecialPage { * essentially means that UploadBase::VERIFICATION_ERROR and * UploadBase::EMPTY_FILE should not be passed here. * - * @param $message String: HTML message to be passed to mainUploadForm + * @param string $message HTML message to be passed to mainUploadForm */ protected function showRecoverableUploadError( $message ) { $sessionKey = $this->mUpload->stashSession(); @@ -312,12 +318,12 @@ class SpecialUpload extends SpecialPage { $this->showUploadForm( $form ); } /** - * Stashes the upload, shows the main form, but adds an "continue anyway button". + * Stashes the upload, shows the main form, but adds a "continue anyway button". * Also checks whether there are actually warnings to display. * * @param $warnings Array * @return boolean true if warnings were displayed, false if there are no - * warnings and the should continue processing like there was no warning + * warnings and it should continue processing */ protected function showUploadWarning( $warnings ) { # If there are no warnings, or warnings we can ignore, return early. @@ -335,12 +341,15 @@ class SpecialUpload extends SpecialPage { $warningHtml = '<h2>' . $this->msg( 'uploadwarning' )->escaped() . "</h2>\n" . '<ul class="warning">'; - foreach( $warnings as $warning => $args ) { - if( $warning == 'exists' ) { + foreach ( $warnings as $warning => $args ) { + if ( $warning == 'badfilename' ) { + $this->mDesiredDestName = Title::makeTitle( NS_FILE, $args )->getText(); + } + if ( $warning == 'exists' ) { $msg = "\t<li>" . self::getExistsWarning( $args ) . "</li>\n"; - } elseif( $warning == 'duplicate' ) { - $msg = self::getDupeWarning( $args ); - } elseif( $warning == 'duplicate-archive' ) { + } elseif ( $warning == 'duplicate' ) { + $msg = $this->getDupeWarning( $args ); + } elseif ( $warning == 'duplicate-archive' ) { $msg = "\t<li>" . $this->msg( 'file-deleted-duplicate', Title::makeTitle( NS_FILE, $args )->getPrefixedText() )->parse() . "</li>\n"; @@ -371,7 +380,7 @@ class SpecialUpload extends SpecialPage { /** * Show the upload form with error message, but do not stash the file. * - * @param $message string HTML string + * @param string $message HTML string */ protected function showUploadError( $message ) { $message = '<h2>' . $this->msg( 'uploadwarning' )->escaped() . "</h2>\n" . @@ -386,12 +395,12 @@ class SpecialUpload extends SpecialPage { protected function processUpload() { // Fetch the file if required $status = $this->mUpload->fetchFile(); - if( !$status->isOK() ) { + if ( !$status->isOK() ) { $this->showUploadError( $this->getOutput()->parse( $status->getWikiText() ) ); return; } - if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) ) { + if ( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) ) { wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file.\n" ); // This code path is deprecated. If you want to break upload processing // do so by hooking into the appropriate hooks in UploadBase::verifyUpload @@ -410,7 +419,7 @@ class SpecialUpload extends SpecialPage { // Verify permissions for this title $permErrors = $this->mUpload->verifyTitlePermissions( $this->getUser() ); - if( $permErrors !== true ) { + if ( $permErrors !== true ) { $code = array_shift( $permErrors[0] ); $this->showRecoverableUploadError( $this->msg( $code, $permErrors[0] )->parse() ); return; @@ -419,15 +428,15 @@ class SpecialUpload extends SpecialPage { $this->mLocalFile = $this->mUpload->getLocalFile(); // Check warnings if necessary - if( !$this->mIgnoreWarning ) { + if ( !$this->mIgnoreWarning ) { $warnings = $this->mUpload->checkWarnings(); - if( $this->showUploadWarning( $warnings ) ) { + if ( $this->showUploadWarning( $warnings ) ) { return; } } // Get the page text if this is not a reupload - if( !$this->mForReUpload ) { + if ( !$this->mForReUpload ) { $pageText = self::getInitialPageText( $this->mComment, $this->mLicense, $this->mCopyrightStatus, $this->mCopyrightSource ); } else { @@ -455,15 +464,14 @@ class SpecialUpload extends SpecialPage { */ public static function getInitialPageText( $comment = '', $license = '', $copyStatus = '', $source = '' ) { global $wgUseCopyrightUpload, $wgForceUIMsgAsContentMsg; - $wgForceUIMsgAsContentMsg = (array) $wgForceUIMsgAsContentMsg; $msg = array(); /* These messages are transcluded into the actual text of the description page. * Thus, forcing them as content messages makes the upload to produce an int: template * instead of hardcoding it there in the uploader language. */ - foreach( array( 'license-header', 'filedesc', 'filestatus', 'filesource' ) as $msgName ) { - if ( in_array( $msgName, $wgForceUIMsgAsContentMsg ) ) { + foreach ( array( 'license-header', 'filedesc', 'filestatus', 'filesource' ) as $msgName ) { + if ( in_array( $msgName, (array)$wgForceUIMsgAsContentMsg ) ) { $msg[$msgName] = "{{int:$msgName}}"; } else { $msg[$msgName] = wfMessage( $msgName )->inContentLanguage()->text(); @@ -473,17 +481,17 @@ class SpecialUpload extends SpecialPage { if ( $wgUseCopyrightUpload ) { $licensetxt = ''; if ( $license != '' ) { - $licensetxt = '== ' . $msg[ 'license-header' ] . " ==\n" . '{{' . $license . '}}' . "\n"; + $licensetxt = '== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n"; } - $pageText = '== ' . $msg[ 'filedesc' ] . " ==\n" . $comment . "\n" . - '== ' . $msg[ 'filestatus' ] . " ==\n" . $copyStatus . "\n" . + $pageText = '== ' . $msg['filedesc'] . " ==\n" . $comment . "\n" . + '== ' . $msg['filestatus'] . " ==\n" . $copyStatus . "\n" . "$licensetxt" . - '== ' . $msg[ 'filesource' ] . " ==\n" . $source; + '== ' . $msg['filesource'] . " ==\n" . $source; } else { if ( $license != '' ) { - $filedesc = $comment == '' ? '' : '== ' . $msg[ 'filedesc' ] . " ==\n" . $comment . "\n"; + $filedesc = $comment == '' ? '' : '== ' . $msg['filedesc'] . " ==\n" . $comment . "\n"; $pageText = $filedesc . - '== ' . $msg[ 'license-header' ] . " ==\n" . '{{' . $license . '}}' . "\n"; + '== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n"; } else { $pageText = $comment; } @@ -504,13 +512,13 @@ class SpecialUpload extends SpecialPage { * @return Bool|String */ protected function getWatchCheck() { - if( $this->getUser()->getOption( 'watchdefault' ) ) { + if ( $this->getUser()->getOption( 'watchdefault' ) ) { // Watch all edits! return true; } $local = wfLocalFile( $this->mDesiredDestName ); - if( $local && $local->exists() ) { + if ( $local && $local->exists() ) { // We're uploading a new version of an existing file. // No creation, so don't watch it if we're not already. return $this->getUser()->isWatched( $local->getTitle() ); @@ -520,16 +528,16 @@ class SpecialUpload extends SpecialPage { } } - /** * Provides output to the user for a result of UploadBase::verifyUpload * - * @param $details Array: result of UploadBase::verifyUpload + * @param array $details result of UploadBase::verifyUpload + * @throws MWException */ protected function processVerificationError( $details ) { global $wgFileExtensions; - switch( $details['status'] ) { + switch ( $details['status'] ) { /** Statuses that only require name changing **/ case UploadBase::MIN_LENGTH_PARTNAME: @@ -563,8 +571,9 @@ class SpecialUpload extends SpecialPage { } else { $msg->params( $details['finalExt'] ); } - $msg->params( $this->getLanguage()->commaList( $wgFileExtensions ), - count( $wgFileExtensions ) ); + $extensions = array_unique( $wgFileExtensions ); + $msg->params( $this->getLanguage()->commaList( $extensions ), + count( $extensions ) ); // Add PLURAL support for the first parameter. This results // in a bit unlogical parameter sequence, but does not break @@ -622,7 +631,7 @@ class SpecialUpload extends SpecialPage { * Formats a result of UploadBase::getExistsWarning as HTML * This check is static and can be done pre-upload via AJAX * - * @param $exists Array: the result of UploadBase::getExistsWarning + * @param array $exists the result of UploadBase::getExistsWarning * @return String: empty string if there is no warning or an HTML fragment */ public static function getExistsWarning( $exists ) { @@ -634,10 +643,10 @@ class SpecialUpload extends SpecialPage { $filename = $file->getTitle()->getPrefixedText(); $warning = ''; - if( $exists['warning'] == 'exists' ) { + if ( $exists['warning'] == 'exists' ) { // Exact match $warning = wfMessage( 'fileexists', $filename )->parse(); - } elseif( $exists['warning'] == 'page-exists' ) { + } elseif ( $exists['warning'] == 'page-exists' ) { // Page exists but file does not $warning = wfMessage( 'filepageexists', $filename )->parse(); } elseif ( $exists['warning'] == 'exists-normalized' ) { @@ -645,7 +654,7 @@ class SpecialUpload extends SpecialPage { $exists['normalizedFile']->getTitle()->getPrefixedText() )->parse(); } elseif ( $exists['warning'] == 'thumb' ) { // Swapped argument order compared with other messages for backwards compatibility - $warning = wfMessage( 'fileexists-thumbnail-yes', + $warning = wfMessage( 'fileexists-thumbnail-yes', $exists['thumbFile']->getTitle()->getPrefixedText(), $filename )->parse(); } elseif ( $exists['warning'] == 'thumb-name' ) { // Image w/o '180px-' does not exists, but we do not like these filenames @@ -673,42 +682,19 @@ class SpecialUpload extends SpecialPage { } /** - * Get a list of warnings - * - * @param $filename String: local filename, e.g. 'file exists', 'non-descriptive filename' - * @return Array: list of warning messages - */ - public static function ajaxGetExistsWarning( $filename ) { - $file = wfFindFile( $filename ); - if( !$file ) { - // Force local file so we have an object to do further checks against - // if there isn't an exact match... - $file = wfLocalFile( $filename ); - } - $s = ' '; - if ( $file ) { - $exists = UploadBase::getExistsWarning( $file ); - $warning = self::getExistsWarning( $exists ); - if ( $warning !== '' ) { - $s = "<div>$warning</div>"; - } - } - return $s; - } - - /** * Construct a warning and a gallery from an array of duplicate files. * @param $dupes array * @return string */ - public static function getDupeWarning( $dupes ) { + public function getDupeWarning( $dupes ) { if ( !$dupes ) { return ''; } - $gallery = new ImageGallery; + $gallery = ImageGalleryBase::factory(); + $gallery->setContext( $this->getContext() ); $gallery->setShowBytes( false ); - foreach( $dupes as $file ) { + foreach ( $dupes as $file ) { $gallery->add( $file->getTitle() ); } return '<li>' . @@ -716,6 +702,9 @@ class SpecialUpload extends SpecialPage { $gallery->toHtml() . "</li>\n"; } + protected function getGroupName() { + return 'media'; + } } /** @@ -789,6 +778,8 @@ class UploadForm extends HTMLForm { * @return Array: descriptor array */ protected function getSourceSection() { + global $wgCopyUploadsFromSpecialUpload; + if ( $this->mSessionKey ) { return array( 'SessionKey' => array( @@ -802,7 +793,9 @@ class UploadForm extends HTMLForm { ); } - $canUploadByUrl = UploadFromUrl::isEnabled() && UploadFromUrl::isAllowed( $this->getUser() ); + $canUploadByUrl = UploadFromUrl::isEnabled() + && UploadFromUrl::isAllowed( $this->getUser() ) + && $wgCopyUploadsFromSpecialUpload; $radio = $canUploadByUrl; $selectedSourceType = strtolower( $this->getRequest()->getText( 'wpSourceType', 'File' ) ); @@ -835,10 +828,13 @@ class UploadForm extends HTMLForm { 'upload-type' => 'File', 'radio' => &$radio, 'help' => $this->msg( 'upload-maxfilesize', - $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['file'] ) - )->parse() . ' ' . $this->msg( 'upload_source_file' )->escaped(), + $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['file'] ) ) + ->parse() . + $this->msg( 'word-separator' )->escaped() . + $this->msg( 'upload_source_file' )->escaped(), 'checked' => $selectedSourceType == 'file', ); + if ( $canUploadByUrl ) { $this->mMaxUploadSize['url'] = UploadBase::getMaxUploadSize( 'url' ); $descriptor['UploadFileURL'] = array( @@ -849,8 +845,10 @@ class UploadForm extends HTMLForm { 'upload-type' => 'url', 'radio' => &$radio, 'help' => $this->msg( 'upload-maxfilesize', - $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['url'] ) - )->parse() . ' ' . $this->msg( 'upload_source_url' )->escaped(), + $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['url'] ) ) + ->parse() . + $this->msg( 'word-separator' )->escaped() . + $this->msg( 'upload_source_url' )->escaped(), 'checked' => $selectedSourceType == 'url', ); } @@ -876,21 +874,21 @@ class UploadForm extends HTMLForm { global $wgCheckFileExtensions, $wgStrictFileExtensions, $wgFileExtensions, $wgFileBlacklist; - if( $wgCheckFileExtensions ) { - if( $wgStrictFileExtensions ) { + if ( $wgCheckFileExtensions ) { + if ( $wgStrictFileExtensions ) { # Everything not permitted is banned $extensionsList = '<div id="mw-upload-permitted">' . - $this->msg( 'upload-permitted', $this->getContext()->getLanguage()->commaList( $wgFileExtensions ) )->parseAsBlock() . + $this->msg( 'upload-permitted', $this->getContext()->getLanguage()->commaList( array_unique( $wgFileExtensions ) ) )->parseAsBlock() . "</div>\n"; } else { # We have to list both preferred and prohibited $extensionsList = '<div id="mw-upload-preferred">' . - $this->msg( 'upload-preferred', $this->getContext()->getLanguage()->commaList( $wgFileExtensions ) )->parseAsBlock() . + $this->msg( 'upload-preferred', $this->getContext()->getLanguage()->commaList( array_unique( $wgFileExtensions ) ) )->parseAsBlock() . "</div>\n" . '<div id="mw-upload-prohibited">' . - $this->msg( 'upload-prohibited', $this->getContext()->getLanguage()->commaList( $wgFileBlacklist ) )->parseAsBlock() . + $this->msg( 'upload-prohibited', $this->getContext()->getLanguage()->commaList( array_unique( $wgFileBlacklist ) ) )->parseAsBlock() . "</div>\n"; } } else { @@ -946,7 +944,7 @@ class UploadForm extends HTMLForm { ? 'filereuploadsummary' : 'fileuploadsummary', 'default' => $this->mComment, - 'cols' => intval( $this->getUser()->getOption( 'cols' ) ), + 'cols' => $this->getUser()->getIntOption( 'cols' ), 'rows' => 8, ) ); @@ -1077,7 +1075,6 @@ class UploadForm extends HTMLForm { $out = $this->getOutput(); $out->addJsConfigVars( $scriptVars ); - $out->addModules( array( 'mediawiki.action.edit', // For <charinsert> support 'mediawiki.legacy.upload', // Old form stuff... @@ -1106,7 +1103,7 @@ class UploadSourceField extends HTMLTextField { * @return string */ function getLabelHtml( $cellAttributes = array() ) { - $id = "wpSourceType{$this->mParams['upload-type']}"; + $id = $this->mParams['id']; $label = Html::rawElement( 'label', array( 'for' => $id ), $this->mLabel ); if ( !empty( $this->mParams['radio'] ) ) { @@ -1134,4 +1131,3 @@ class UploadSourceField extends HTMLTextField { : 60; } } - diff --git a/includes/specials/SpecialUploadStash.php b/includes/specials/SpecialUploadStash.php index 1a00d731..1373df1a 100644 --- a/includes/specials/SpecialUploadStash.php +++ b/includes/specials/SpecialUploadStash.php @@ -57,7 +57,7 @@ class SpecialUploadStash extends UnlistedSpecialPage { /** * Execute page -- can output a file directly or show a listing of them. * - * @param $subPage String: subpage, e.g. in http://example.com/wiki/Special:UploadStash/foo.jpg, the "foo.jpg" part + * @param string $subPage subpage, e.g. in http://example.com/wiki/Special:UploadStash/foo.jpg, the "foo.jpg" part * @return Boolean: success */ public function execute( $subPage ) { @@ -73,7 +73,8 @@ class SpecialUploadStash extends UnlistedSpecialPage { * If file available in stash, cats it out to the client as a simple HTTP response. * n.b. Most sanity checking done in UploadStashLocalFile, so this is straightforward. * - * @param $key String: the key of a particular requested file + * @param string $key the key of a particular requested file + * @throws HttpError * @return bool */ public function showUpload( $key ) { @@ -87,19 +88,19 @@ class SpecialUploadStash extends UnlistedSpecialPage { } else { return $this->outputLocalFile( $params['file'] ); } - } catch( UploadStashFileNotFoundException $e ) { + } catch ( UploadStashFileNotFoundException $e ) { $code = 404; $message = $e->getMessage(); - } catch( UploadStashZeroLengthFileException $e ) { + } catch ( UploadStashZeroLengthFileException $e ) { $code = 500; $message = $e->getMessage(); - } catch( UploadStashBadPathException $e ) { + } catch ( UploadStashBadPathException $e ) { $code = 500; $message = $e->getMessage(); - } catch( SpecialUploadStashTooLargeException $e ) { + } catch ( SpecialUploadStashTooLargeException $e ) { $code = 500; $message = 'Cannot serve a file larger than ' . self::MAX_SERVE_BYTES . ' bytes. ' . $e->getMessage(); - } catch( Exception $e ) { + } catch ( Exception $e ) { $code = 500; $message = $e->getMessage(); } @@ -113,6 +114,7 @@ class SpecialUploadStash extends UnlistedSpecialPage { * application the transform parameters * * @param string $key + * @throws UploadStashBadPathException * @return array */ private function parseKey( $key ) { @@ -132,8 +134,13 @@ class SpecialUploadStash extends UnlistedSpecialPage { $paramString = substr( $thumbPart, 0, $srcNamePos - 1 ); $handler = $file->getHandler(); - $params = $handler->parseParamString( $paramString ); - return array( 'file' => $file, 'type' => $type, 'params' => $params ); + if ( $handler ) { + $params = $handler->parseParamString( $paramString ); + return array( 'file' => $file, 'type' => $type, 'params' => $params ); + } else { + throw new UploadStashBadPathException( 'No handler found for ' . + "mime {$file->getMimeType()} of file {$file->getPath()}" ); + } } return array( 'file' => $file, 'type' => $type ); @@ -164,10 +171,11 @@ class SpecialUploadStash extends UnlistedSpecialPage { /** * Scale a file (probably with a locally installed imagemagick, or similar) and output it to STDOUT. - * @param $file: File object - * @param $params: scaling parameters ( e.g. array( width => '50' ) ); - * @param $flags: scaling flags ( see File:: constants ) + * @param $file File + * @param array $params Scaling parameters ( e.g. array( width => '50' ) ); + * @param int $flags Scaling flags ( see File:: constants ) * @throws MWException + * @throws UploadStashFileNotFoundException * @return boolean success */ private function outputLocallyScaledThumb( $file, $params, $flags ) { @@ -189,7 +197,7 @@ class SpecialUploadStash extends UnlistedSpecialPage { // now we should construct a File, so we can get mime and other such info in a standard way // n.b. mimetype may be different from original (ogx original -> jpeg thumb) - $thumbFile = new UnregisteredLocalFile( false, + $thumbFile = new UnregisteredLocalFile( false, $this->stash->repo, $thumbnailImage->getStoragePath(), false ); if ( !$thumbFile ) { throw new UploadStashFileNotFoundException( "couldn't create local file object for thumbnail" ); @@ -219,7 +227,7 @@ class SpecialUploadStash extends UnlistedSpecialPage { global $wgUploadStashScalerBaseUrl; $scalerBaseUrl = $wgUploadStashScalerBaseUrl; - if( preg_match( '/^\/\//', $scalerBaseUrl ) ) { + if ( preg_match( '/^\/\//', $scalerBaseUrl ) ) { // this is apparently a protocol-relative URL, which makes no sense in this context, // since this is used for communication that's internal to the application. // default to http. @@ -258,6 +266,7 @@ class SpecialUploadStash extends UnlistedSpecialPage { * Side effect: writes HTTP response to STDOUT. * * @param $file File object with a local path (e.g. UnregisteredLocalFile, LocalFile. Oddly these don't share an ancestor!) + * @throws SpecialUploadStashTooLargeException * @return bool */ private function outputLocalFile( File $file ) { @@ -273,8 +282,9 @@ class SpecialUploadStash extends UnlistedSpecialPage { /** * Output HTTP response of raw content * Side effect: writes HTTP response to STDOUT. - * @param $content String content - * @param $contentType String mime type + * @param string $content content + * @param string $contentType mime type + * @throws SpecialUploadStashTooLargeException * @return bool */ private function outputContents( $content, $contentType ) { @@ -291,13 +301,15 @@ class SpecialUploadStash extends UnlistedSpecialPage { * Output headers for streaming * XXX unsure about encoding as binary; if we received from HTTP perhaps we should use that encoding, concatted with semicolon to mimeType as it usually is. * Side effect: preps PHP to write headers to STDOUT. - * @param String $contentType : string suitable for content-type header - * @param String $size: length in bytes + * @param string $contentType : string suitable for content-type header + * @param string $size: length in bytes */ private static function outputFileHeaders( $contentType, $size ) { header( "Content-Type: $contentType", true ); header( 'Content-Transfer-Encoding: binary', true ); header( 'Expires: Sun, 17-Jan-2038 19:14:07 GMT', true ); + // Bug 53032 - It shouldn't be a problem here, but let's be safe and not cache + header( 'Cache-Control: private' ); header( "Content-Length: $size", true ); } @@ -306,6 +318,7 @@ class SpecialUploadStash extends UnlistedSpecialPage { * Note the stash has to be recreated since this is being called in a static context. * This works, because there really is only one stash per logged-in user, despite appearances. * + * @param array $formData * @return Status */ public static function tryClearStashedUploads( $formData ) { @@ -322,14 +335,9 @@ class SpecialUploadStash extends UnlistedSpecialPage { /** * Default action when we don't have a subpage -- just show links to the uploads we have, * Also show a button to clear stashed files - * @param $status [optional] Status: the result of processRequest * @return bool */ - private function showUploads( $status = null ) { - if ( $status === null ) { - $status = Status::newGood(); - } - + private function showUploads() { // sets the title, etc. $this->setHeaders(); $this->outputHeader(); @@ -337,15 +345,16 @@ class SpecialUploadStash extends UnlistedSpecialPage { // create the form, which will also be used to execute a callback to process incoming form data // this design is extremely dubious, but supposedly HTMLForm is our standard now? + $context = new DerivativeContext( $this->getContext() ); + $context->setTitle( $this->getTitle() ); // Remove subpage $form = new HTMLForm( array( 'Clear' => array( 'type' => 'hidden', 'default' => true, 'name' => 'clear', ) - ), $this->getContext(), 'clearStashedUploads' ); - $form->setSubmitCallback( array( __CLASS__ , 'tryClearStashedUploads' ) ); - $form->setTitle( $this->getTitle() ); + ), $context, 'clearStashedUploads' ); + $form->setSubmitCallback( array( __CLASS__, 'tryClearStashedUploads' ) ); $form->setSubmitTextMsg( 'uploadstash-clear' ); $form->prepareForm(); diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php index 58da77da..5ac3e654 100644 --- a/includes/specials/SpecialUserlogin.php +++ b/includes/specials/SpecialUserlogin.php @@ -48,18 +48,21 @@ class LoginForm extends SpecialPage { var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage; var $mSkipCookieCheck, $mReturnToQuery, $mToken, $mStickHTTPS; var $mType, $mReason, $mRealName; - var $mAbortLoginErrorMsg = 'login-abort-generic'; + var $mAbortLoginErrorMsg = null; private $mLoaded = false; + private $mSecureLoginUrl; /** - * @var ExternalUser + * @ var WebRequest */ - private $mExtUser = null; + private $mOverrideRequest = null; /** - * @ var WebRequest + * Effective request; set at the beginning of load + * + * @var WebRequest $mRequest */ - private $mOverrideRequest = null; + private $mRequest = null; /** * @param WebRequest $request @@ -86,6 +89,7 @@ class LoginForm extends SpecialPage { } else { $request = $this->mOverrideRequest; } + $this->mRequest = $request; $this->mType = $request->getText( 'type' ); $this->mUsername = $request->getText( 'wpName' ); @@ -95,31 +99,32 @@ class LoginForm extends SpecialPage { $this->mReason = $request->getText( 'wpReason' ); $this->mCookieCheck = $request->getVal( 'wpCookieCheck' ); $this->mPosted = $request->wasPosted(); - $this->mCreateaccount = $request->getCheck( 'wpCreateaccount' ); $this->mCreateaccountMail = $request->getCheck( 'wpCreateaccountMail' ) && $wgEnableEmail; + $this->mCreateaccount = $request->getCheck( 'wpCreateaccount' ) && !$this->mCreateaccountMail; $this->mLoginattempt = $request->getCheck( 'wpLoginattempt' ); $this->mAction = $request->getVal( 'action' ); $this->mRemember = $request->getCheck( 'wpRemember' ); - $this->mStickHTTPS = $request->getCheck( 'wpStickHTTPS' ); + $this->mFromHTTP = $request->getBool( 'fromhttp', false ); + $this->mStickHTTPS = ( !$this->mFromHTTP && $request->detectProtocol() === 'https' ) || $request->getBool( 'wpForceHttps', false ); $this->mLanguage = $request->getText( 'uselang' ); $this->mSkipCookieCheck = $request->getCheck( 'wpSkipCookieCheck' ); $this->mToken = ( $this->mType == 'signup' ) ? $request->getVal( 'wpCreateaccountToken' ) : $request->getVal( 'wpLoginToken' ); $this->mReturnTo = $request->getVal( 'returnto', '' ); $this->mReturnToQuery = $request->getVal( 'returntoquery', '' ); - if( $wgEnableEmail ) { + if ( $wgEnableEmail ) { $this->mEmail = $request->getText( 'wpEmail' ); } else { $this->mEmail = ''; } - if( !in_array( 'realname', $wgHiddenPrefs ) ) { + if ( !in_array( 'realname', $wgHiddenPrefs ) ) { $this->mRealName = $request->getText( 'wpRealName' ); } else { $this->mRealName = ''; } - if( !$wgAuth->validDomain( $this->mDomain ) ) { + if ( !$wgAuth->validDomain( $this->mDomain ) ) { $this->mDomain = $wgAuth->getDomain(); } $wgAuth->setDomain( $this->mDomain ); @@ -128,7 +133,7 @@ class LoginForm extends SpecialPage { # 2. Do not return to PasswordReset after a successful password change # but goto Wiki start page (Main_Page) instead ( bug 33997 ) $returnToTitle = Title::newFromText( $this->mReturnTo ); - if( is_object( $returnToTitle ) && ( + if ( is_object( $returnToTitle ) && ( $returnToTitle->isSpecial( 'Userlogout' ) || $returnToTitle->isSpecial( 'PasswordReset' ) ) ) { $this->mReturnTo = ''; @@ -137,27 +142,61 @@ class LoginForm extends SpecialPage { } function getDescription() { - return $this->msg( $this->getUser()->isAllowed( 'createaccount' ) ? - 'userlogin' : 'userloginnocreate' )->text(); + if ( $this->mType === 'signup' ) { + return $this->msg( 'createaccount' )->text(); + } else { + return $this->msg( 'login' )->text(); + } } - public function execute( $par ) { + /* + * @param $subPage string|null + */ + public function execute( $subPage ) { if ( session_id() == '' ) { wfSetupSession(); } $this->load(); - $this->setHeaders(); - if ( $par == 'signup' ) { # Check for [[Special:Userlogin/signup]] + // Check for [[Special:Userlogin/signup]]. This affects form display and + // page title. + if ( $subPage == 'signup' ) { $this->mType = 'signup'; } + $this->setHeaders(); + + // If logging in and not on HTTPS, either redirect to it or offer a link. + global $wgSecureLogin; + if ( WebRequest::detectProtocol() !== 'https' ) { + $title = $this->getFullTitle(); + $query = array( + 'returnto' => $this->mReturnTo, + 'returntoquery' => $this->mReturnToQuery, + 'title' => null, + ) + $this->mRequest->getQueryValues(); + $url = $title->getFullURL( $query, false, PROTO_HTTPS ); + if ( $wgSecureLogin && wfCanIPUseHTTPS( $this->getRequest()->getIP() ) ) { + $url = wfAppendQuery( $url, 'fromhttp=1' ); + $this->getOutput()->redirect( $url ); + // Since we only do this redir to change proto, always vary + $this->getOutput()->addVaryHeader( 'X-Forwarded-Proto' ); + return; + } else { + // A wiki without HTTPS login support should set $wgServer to + // http://somehost, in which case the secure URL generated + // above won't actually start with https:// + if ( substr( $url, 0, 8 ) === 'https://' ) { + $this->mSecureLoginUrl = $url; + } + } + } if ( !is_null( $this->mCookieCheck ) ) { $this->onCookieRedirectCheck( $this->mCookieCheck ); return; - } elseif( $this->mPosted ) { - if( $this->mCreateaccount ) { + } elseif ( $this->mPosted ) { + if ( $this->mCreateaccount ) { $this->addNewAccount(); return; } elseif ( $this->mCreateaccountMail ) { @@ -180,24 +219,27 @@ class LoginForm extends SpecialPage { return; } - $u = $this->addNewaccountInternal(); - - if ( $u == null ) { + $status = $this->addNewaccountInternal(); + if ( !$status->isGood() ) { + $error = $status->getMessage(); + $this->mainLoginForm( $error->toString() ); return; } + $u = $status->getValue(); + // Wipe the initial password and mail a temporary one $u->setPassword( null ); $u->saveSettings(); $result = $this->mailPasswordInternal( $u, false, 'createaccount-title', 'createaccount-text' ); wfRunHooks( 'AddNewAccount', array( $u, true ) ); - $u->addNewUserLogEntry( true, $this->mReason ); + $u->addNewUserLogEntry( 'byemail', $this->mReason ); $out = $this->getOutput(); $out->setPageTitle( $this->msg( 'accmailtitle' ) ); - if( !$result->isGood() ) { + if ( !$result->isGood() ) { $this->mainLoginForm( $this->msg( 'mailerror', $result->getWikiText() )->text() ); } else { $out->addWikiMsg( 'accmailtext', $u->getName(), $u->getEmail() ); @@ -210,26 +252,42 @@ class LoginForm extends SpecialPage { * @return bool */ function addNewAccount() { - global $wgUser, $wgEmailAuthentication, $wgLoginLanguageSelector; + global $wgContLang, $wgUser, $wgEmailAuthentication, $wgLoginLanguageSelector; # Create the account and abort if there's a problem doing so - $u = $this->addNewAccountInternal(); - if( $u == null ) { + $status = $this->addNewAccountInternal(); + if ( !$status->isGood() ) { + $error = $status->getMessage(); + $this->mainLoginForm( $error->toString() ); return false; } - # If we showed up language selection links, and one was in use, be - # smart (and sensible) and save that language as the user's preference - if( $wgLoginLanguageSelector && $this->mLanguage ) { - $u->setOption( 'language', $this->mLanguage ); + $u = $status->getValue(); + + # Only save preferences if the user is not creating an account for someone else. + if ( $this->getUser()->isAnon() ) { + # If we showed up language selection links, and one was in use, be + # smart (and sensible) and save that language as the user's preference + if ( $wgLoginLanguageSelector && $this->mLanguage ) { + $u->setOption( 'language', $this->mLanguage ); + } else { + + # Otherwise the user's language preference defaults to $wgContLang, + # but it may be better to set it to their preferred $wgContLang variant, + # based on browser preferences or URL parameters. + $u->setOption( 'language', $wgContLang->getPreferredVariant() ); + } + if ( $wgContLang->hasVariants() ) { + $u->setOption( 'variant', $wgContLang->getPreferredVariant() ); + } } $out = $this->getOutput(); # Send out an email authentication message if needed - if( $wgEmailAuthentication && Sanitizer::validateEmail( $u->getEmail() ) ) { + if ( $wgEmailAuthentication && Sanitizer::validateEmail( $u->getEmail() ) ) { $status = $u->sendConfirmationMail(); - if( $status->isGood() ) { + if ( $status->isGood() ) { $out->addWikiMsg( 'confirmemail_oncreate' ); } else { $out->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) ); @@ -242,7 +300,7 @@ class LoginForm extends SpecialPage { # If not logged in, assume the new account as the current one and set # session cookies then show a "welcome" message or a "need cookies" # message as needed - if( $this->getUser()->isAnon() ) { + if ( $this->getUser()->isAnon() ) { $u->setCookies(); $wgUser = $u; // This should set it for OutputPage and the Skin @@ -250,8 +308,8 @@ class LoginForm extends SpecialPage { // wrong. $this->getContext()->setUser( $u ); wfRunHooks( 'AddNewAccount', array( $u, false ) ); - $u->addNewUserLogEntry(); - if( $this->hasSessionCookie() ) { + $u->addNewUserLogEntry( 'create' ); + if ( $this->hasSessionCookie() ) { $this->successfulCreation(); } else { $this->cookieRedirectCheck( 'new' ); @@ -262,23 +320,24 @@ class LoginForm extends SpecialPage { $out->addWikiMsg( 'accountcreatedtext', $u->getName() ); $out->addReturnTo( $this->getTitle() ); wfRunHooks( 'AddNewAccount', array( $u, false ) ); - $u->addNewUserLogEntry( false, $this->mReason ); + $u->addNewUserLogEntry( 'create2', $this->mReason ); } return true; } /** + * Make a new user account using the loaded data. * @private - * @return bool|User + * @throws PermissionsError|ReadOnlyError + * @return Status */ - function addNewAccountInternal() { + public function addNewAccountInternal() { global $wgAuth, $wgMemc, $wgAccountCreationThrottle, $wgMinimalPasswordLength, $wgEmailConfirmToEdit; // If the user passes an invalid domain, something is fishy - if( !$wgAuth->validDomain( $this->mDomain ) ) { - $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() ); - return false; + if ( !$wgAuth->validDomain( $this->mDomain ) ) { + return Status::newFatal( 'wrongpassword' ); } // If we are not allowing users to login locally, we should be checking @@ -286,11 +345,15 @@ class LoginForm extends SpecialPage { // cation server before they create an account (otherwise, they can // create a local account and login as any domain user). We only need // to check this for domains that aren't local. - if( 'local' != $this->mDomain && $this->mDomain != '' ) { - if( !$wgAuth->canCreateAccounts() && ( !$wgAuth->userExists( $this->mUsername ) - || !$wgAuth->authenticate( $this->mUsername, $this->mPassword ) ) ) { - $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() ); - return false; + if ( 'local' != $this->mDomain && $this->mDomain != '' ) { + if ( + !$wgAuth->canCreateAccounts() && + ( + !$wgAuth->userExists( $this->mUsername ) || + !$wgAuth->authenticate( $this->mUsername, $this->mPassword ) + ) + ) { + return Status::newFatal( 'wrongpassword' ); } } @@ -301,28 +364,28 @@ class LoginForm extends SpecialPage { # Request forgery checks. if ( !self::getCreateaccountToken() ) { self::setCreateaccountToken(); - $this->mainLoginForm( $this->msg( 'nocookiesfornew' )->parse() ); - return false; + return Status::newFatal( 'nocookiesfornew' ); } # The user didn't pass a createaccount token if ( !$this->mToken ) { - $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() ); - return false; + return Status::newFatal( 'sessionfailure' ); } # Validate the createaccount token if ( $this->mToken !== self::getCreateaccountToken() ) { - $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() ); - return false; + return Status::newFatal( 'sessionfailure' ); } # Check permissions $currentUser = $this->getUser(); + $creationBlock = $currentUser->isBlockedFromCreateAccount(); if ( !$currentUser->isAllowed( 'createaccount' ) ) { throw new PermissionsError( 'createaccount' ); - } elseif ( $currentUser->isBlockedFromCreateAccount() ) { - $this->userBlockedMessage( $currentUser->isBlockedFromCreateAccount() ); + } elseif ( $creationBlock instanceof Block ) { + // Throws an ErrorPageError. + $this->userBlockedMessage( $creationBlock ); + // This should never be reached. return false; } @@ -334,58 +397,45 @@ class LoginForm extends SpecialPage { $ip = $this->getRequest()->getIP(); if ( $currentUser->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) { - $this->mainLoginForm( $this->msg( 'sorbs_create_account_reason' )->text() . ' ' . $this->msg( 'parentheses', $ip )->escaped() ); - return false; + return Status::newFatal( 'sorbs_create_account_reason' ); } # Now create a dummy user ($u) and check if it is valid $name = trim( $this->mUsername ); $u = User::newFromName( $name, 'creatable' ); if ( !is_object( $u ) ) { - $this->mainLoginForm( $this->msg( 'noname' )->text() ); - return false; + return Status::newFatal( 'noname' ); + } elseif ( 0 != $u->idForName() ) { + return Status::newFatal( 'userexists' ); } - if ( 0 != $u->idForName() ) { - $this->mainLoginForm( $this->msg( 'userexists' )->text() ); - return false; - } - - if ( 0 != strcmp( $this->mPassword, $this->mRetype ) ) { - $this->mainLoginForm( $this->msg( 'badretype' )->text() ); - return false; - } + if ( $this->mCreateaccountMail ) { + # do not force a password for account creation by email + # set invalid password, it will be replaced later by a random generated password + $this->mPassword = null; + } else { + if ( $this->mPassword !== $this->mRetype ) { + return Status::newFatal( 'badretype' ); + } - # check for minimal password length - $valid = $u->getPasswordValidity( $this->mPassword ); - if ( $valid !== true ) { - if ( !$this->mCreateaccountMail ) { - if ( is_array( $valid ) ) { - $message = array_shift( $valid ); - $params = $valid; - } else { - $message = $valid; - $params = array( $wgMinimalPasswordLength ); + # check for minimal password length + $valid = $u->getPasswordValidity( $this->mPassword ); + if ( $valid !== true ) { + if ( !is_array( $valid ) ) { + $valid = array( $valid, $wgMinimalPasswordLength ); } - $this->mainLoginForm( $this->msg( $message, $params )->text() ); - return false; - } else { - # do not force a password for account creation by email - # set invalid password, it will be replaced later by a random generated password - $this->mPassword = null; + return call_user_func_array( 'Status::newFatal', $valid ); } } # if you need a confirmed email address to edit, then obviously you # need an email address. - if ( $wgEmailConfirmToEdit && empty( $this->mEmail ) ) { - $this->mainLoginForm( $this->msg( 'noemailtitle' )->text() ); - return false; + if ( $wgEmailConfirmToEdit && strval( $this->mEmail ) === '' ) { + return Status::newFatal( 'noemailtitle' ); } - if( !empty( $this->mEmail ) && !Sanitizer::validateEmail( $this->mEmail ) ) { - $this->mainLoginForm( $this->msg( 'invalidemailaddress' )->text() ); - return false; + if ( strval( $this->mEmail ) !== '' && !Sanitizer::validateEmail( $this->mEmail ) ) { + return Status::newFatal( 'invalidemailaddress' ); } # Set some additional data so the AbortNewAccount hook can be used for @@ -394,11 +444,12 @@ class LoginForm extends SpecialPage { $u->setRealName( $this->mRealName ); $abortError = ''; - if( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError ) ) ) { + if ( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError ) ) ) { // Hook point to add extra creation throttles and blocks wfDebug( "LoginForm::addNewAccountInternal: a hook blocked creation\n" ); - $this->mainLoginForm( $abortError ); - return false; + $abortError = new RawMessage( $abortError ); + $abortError->text(); + return Status::newFatal( $abortError ); } // Hook point to check for exempt from account creation throttle @@ -412,16 +463,14 @@ class LoginForm extends SpecialPage { $wgMemc->set( $key, 0, 86400 ); } if ( $value >= $wgAccountCreationThrottle ) { - $this->throttleHit( $wgAccountCreationThrottle ); - return false; + return Status::newFatal( 'acct_creation_throttle_hit', $wgAccountCreationThrottle ); } $wgMemc->incr( $key ); } } - if( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) { - $this->mainLoginForm( $this->msg( 'externaldberror' )->text() ); - return false; + if ( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) { + return Status::newFatal( 'externaldberror' ); } self::clearCreateaccountToken(); @@ -434,13 +483,16 @@ class LoginForm extends SpecialPage { * * @param $u User object. * @param $autocreate boolean -- true if this is an autocreation via auth plugin - * @return User object. + * @return Status object, with the User object in the value member on success * @private */ function initUser( $u, $autocreate ) { global $wgAuth; - $u->addToDatabase(); + $status = $u->addToDatabase(); + if ( !$status->isOK() ) { + return $status; + } if ( $wgAuth->allowPasswordChange() ) { $u->setPassword( $this->mPassword ); @@ -452,22 +504,13 @@ class LoginForm extends SpecialPage { $wgAuth->initUser( $u, $autocreate ); - if ( $this->mExtUser ) { - $this->mExtUser->linkToLocal( $u->getId() ); - $email = $this->mExtUser->getPref( 'emailaddress' ); - if ( $email && !$this->mEmail ) { - $u->setEmail( $email ); - } - } - $u->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 ); $u->saveSettings(); # Update user count - $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 ); - $ssUpdate->doUpdate(); + DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 0, 0, 0, 1 ) ); - return $u; + return Status::newGood( $u ); } /** @@ -523,17 +566,13 @@ class LoginForm extends SpecialPage { return self::SUCCESS; } - $this->mExtUser = ExternalUser::newFromName( $this->mUsername ); - - # TODO: Allow some magic here for invalid external names, e.g., let the - # user choose a different wiki name. $u = User::newFromName( $this->mUsername ); - if( !( $u instanceof User ) || !User::isUsableName( $u->getName() ) ) { + if ( !( $u instanceof User ) || !User::isUsableName( $u->getName() ) ) { return self::ILLEGAL; } $isAutoCreated = false; - if ( 0 == $u->getID() ) { + if ( $u->getID() == 0 ) { $status = $this->attemptAutoCreate( $u ); if ( $status !== self::SUCCESS ) { return $status; @@ -541,27 +580,20 @@ class LoginForm extends SpecialPage { $isAutoCreated = true; } } else { - global $wgExternalAuthType, $wgAutocreatePolicy; - if ( $wgExternalAuthType && $wgAutocreatePolicy != 'never' - && is_object( $this->mExtUser ) - && $this->mExtUser->authenticate( $this->mPassword ) ) { - # The external user and local user have the same name and - # password, so we assume they're the same. - $this->mExtUser->linkToLocal( $u->getID() ); - } - $u->load(); } // Give general extensions, such as a captcha, a chance to abort logins $abort = self::ABORTED; - if( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$this->mAbortLoginErrorMsg ) ) ) { + $msg = null; + if ( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$msg ) ) ) { + $this->mAbortLoginErrorMsg = $msg; return $abort; } global $wgBlockDisablesLogin; if ( !$u->checkPassword( $this->mPassword ) ) { - if( $u->checkTemporaryPassword( $this->mPassword ) ) { + if ( $u->checkTemporaryPassword( $this->mPassword ) ) { // The e-mailed temporary password should not be used for actu- // al logins; that's a very sloppy habit, and insecure if an // attacker has a few seconds to click "search" on someone's o- @@ -578,7 +610,7 @@ class LoginForm extends SpecialPage { // As a side-effect, we can authenticate the user's e-mail ad- // dress if it's not already done, since the temporary password // was sent via e-mail. - if( !$u->isEmailConfirmed() ) { + if ( !$u->isEmailConfirmed() ) { $u->confirmEmail(); $u->saveSettings(); } @@ -588,7 +620,7 @@ class LoginForm extends SpecialPage { // faces etc will probably just fail cleanly here. $retval = self::RESET_PASS; } else { - $retval = ( $this->mPassword == '' ) ? self::EMPTY_PASS : self::WRONG_PASS; + $retval = ( $this->mPassword == '' ) ? self::EMPTY_PASS : self::WRONG_PASS; } } elseif ( $wgBlockDisablesLogin && $u->isBlocked() ) { // If we've enabled it, make it so that a blocked user cannot login @@ -620,7 +652,7 @@ class LoginForm extends SpecialPage { /** * Increment the login attempt throttle hit count for the (username,current IP) * tuple unless the throttle was already reached. - * @param $username string The user name + * @param string $username The user name * @return Bool|Integer The integer hit count or True if it is already at the limit */ public static function incLoginThrottle( $username ) { @@ -648,7 +680,7 @@ class LoginForm extends SpecialPage { /** * Clear the login attempt throttle hit count for the (username,current IP) tuple. - * @param $username string The user name + * @param string $username The user name * @return void */ public static function clearLoginThrottle( $username ) { @@ -668,44 +700,26 @@ class LoginForm extends SpecialPage { * @return integer Status code */ function attemptAutoCreate( $user ) { - global $wgAuth, $wgAutocreatePolicy; + global $wgAuth; if ( $this->getUser()->isBlockedFromCreateAccount() ) { wfDebug( __METHOD__ . ": user is blocked from account creation\n" ); return self::CREATE_BLOCKED; } - - /** - * If the external authentication plugin allows it, automatically cre- - * ate a new account for users that are externally defined but have not - * yet logged in. - */ - if ( $this->mExtUser ) { - # mExtUser is neither null nor false, so use the new ExternalAuth - # system. - if ( $wgAutocreatePolicy == 'never' ) { - return self::NOT_EXISTS; - } - if ( !$this->mExtUser->authenticate( $this->mPassword ) ) { - return self::WRONG_PLUGIN_PASS; - } - } else { - # Old AuthPlugin. - if ( !$wgAuth->autoCreate() ) { - return self::NOT_EXISTS; - } - if ( !$wgAuth->userExists( $user->getName() ) ) { - wfDebug( __METHOD__ . ": user does not exist\n" ); - return self::NOT_EXISTS; - } - if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) { - wfDebug( __METHOD__ . ": \$wgAuth->authenticate() returned false, aborting\n" ); - return self::WRONG_PLUGIN_PASS; - } + if ( !$wgAuth->autoCreate() ) { + return self::NOT_EXISTS; + } + if ( !$wgAuth->userExists( $user->getName() ) ) { + wfDebug( __METHOD__ . ": user does not exist\n" ); + return self::NOT_EXISTS; + } + if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) { + wfDebug( __METHOD__ . ": \$wgAuth->authenticate() returned false, aborting\n" ); + return self::WRONG_PLUGIN_PASS; } $abortError = ''; - if( !wfRunHooks( 'AbortAutoAccount', array( $user, &$abortError ) ) ) { + if ( !wfRunHooks( 'AbortAutoAccount', array( $user, &$abortError ) ) ) { // Hook point to add extra creation throttles and blocks wfDebug( "LoginForm::attemptAutoCreate: a hook blocked creation: $abortError\n" ); $this->mAbortLoginErrorMsg = $abortError; @@ -713,24 +727,40 @@ class LoginForm extends SpecialPage { } wfDebug( __METHOD__ . ": creating account\n" ); - $this->initUser( $user, true ); + $status = $this->initUser( $user, true ); + + if ( !$status->isOK() ) { + $errors = $status->getErrorsByType( 'error' ); + $this->mAbortLoginErrorMsg = $errors[0]['message']; + return self::ABORTED; + } + return self::SUCCESS; } function processLogin() { - global $wgMemc, $wgLang; + global $wgMemc, $wgLang, $wgSecureLogin, $wgPasswordAttemptThrottle; switch ( $this->authenticateUserData() ) { case self::SUCCESS: # We've verified now, update the real record $user = $this->getUser(); - if( (bool)$this->mRemember != (bool)$user->getOption( 'rememberpassword' ) ) { + if ( (bool)$this->mRemember != $user->getBoolOption( 'rememberpassword' ) ) { $user->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 ); $user->saveSettings(); } else { $user->invalidateCache(); } - $user->setCookies(); + + if ( $user->requiresHTTPS() ) { + $this->mStickHTTPS = true; + } + + if ( $wgSecureLogin && !$this->mStickHTTPS ) { + $user->setCookies( null, false ); + } else { + $user->setCookies(); + } self::clearLoginToken(); // Reset the throttle @@ -738,7 +768,7 @@ class LoginForm extends SpecialPage { $key = wfMemcKey( 'password-throttle', $request->getIP(), md5( $this->mUsername ) ); $wgMemc->delete( $key ); - if( $this->hasSessionCookie() || $this->mSkipCookieCheck ) { + if ( $this->hasSessionCookie() || $this->mSkipCookieCheck ) { /* Replace the language object to provide user interface in * correct language immediately on this first page load. */ @@ -755,56 +785,73 @@ class LoginForm extends SpecialPage { break; case self::NEED_TOKEN: - $this->mainLoginForm( $this->msg( 'nocookiesforlogin' )->parse() ); + $error = $this->mAbortLoginErrorMsg ?: 'nocookiesforlogin'; + $this->mainLoginForm( $this->msg( $error )->parse() ); break; case self::WRONG_TOKEN: - $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() ); + $error = $this->mAbortLoginErrorMsg ?: 'sessionfailure'; + $this->mainLoginForm( $this->msg( $error )->text() ); break; case self::NO_NAME: case self::ILLEGAL: - $this->mainLoginForm( $this->msg( 'noname' )->text() ); + $error = $this->mAbortLoginErrorMsg ?: 'noname'; + $this->mainLoginForm( $this->msg( $error )->text() ); break; case self::WRONG_PLUGIN_PASS: - $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() ); + $error = $this->mAbortLoginErrorMsg ?: 'wrongpassword'; + $this->mainLoginForm( $this->msg( $error )->text() ); break; case self::NOT_EXISTS: - if( $this->getUser()->isAllowed( 'createaccount' ) ) { - $this->mainLoginForm( $this->msg( 'nosuchuser', + if ( $this->getUser()->isAllowed( 'createaccount' ) ) { + $error = $this->mAbortLoginErrorMsg ?: 'nosuchuser'; + $this->mainLoginForm( $this->msg( $error, wfEscapeWikiText( $this->mUsername ) )->parse() ); } else { - $this->mainLoginForm( $this->msg( 'nosuchusershort', + $error = $this->mAbortLoginErrorMsg ?: 'nosuchusershort'; + $this->mainLoginForm( $this->msg( $error, wfEscapeWikiText( $this->mUsername ) )->text() ); } break; case self::WRONG_PASS: - $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() ); + $error = $this->mAbortLoginErrorMsg ?: 'wrongpassword'; + $this->mainLoginForm( $this->msg( $error )->text() ); break; case self::EMPTY_PASS: - $this->mainLoginForm( $this->msg( 'wrongpasswordempty' )->text() ); + $error = $this->mAbortLoginErrorMsg ?: 'wrongpasswordempty'; + $this->mainLoginForm( $this->msg( $error )->text() ); break; case self::RESET_PASS: - $this->resetLoginForm( $this->msg( 'resetpass_announce' )->text() ); + $error = $this->mAbortLoginErrorMsg ?: 'resetpass_announce'; + $this->resetLoginForm( $this->msg( $error )->text() ); break; case self::CREATE_BLOCKED: - $this->userBlockedMessage( $this->getUser()->mBlock ); + $this->userBlockedMessage( $this->getUser()->isBlockedFromCreateAccount() ); break; case self::THROTTLED: - $this->mainLoginForm( $this->msg( 'login-throttled' )->text() ); + $error = $this->mAbortLoginErrorMsg ?: 'login-throttled'; + $this->mainLoginForm( $this->msg( $error ) + ->params ( $this->getLanguage()->formatDuration( $wgPasswordAttemptThrottle['seconds'] ) ) + ->text() + ); break; case self::USER_BLOCKED: - $this->mainLoginForm( $this->msg( 'login-userblocked', - $this->mUsername )->escaped() ); + $error = $this->mAbortLoginErrorMsg ?: 'login-userblocked'; + $this->mainLoginForm( $this->msg( $error, $this->mUsername )->escaped() ); break; case self::ABORTED: - $this->mainLoginForm( $this->msg( $this->mAbortLoginErrorMsg )->text() ); + $error = $this->mAbortLoginErrorMsg ?: 'login-abort-generic'; + $this->mainLoginForm( $this->msg( $error )->text() ); break; default: throw new MWException( 'Unhandled case value' ); } } + /** + * @param $error string + */ function resetLoginForm( $error ) { - $this->getOutput()->addHTML( Xml::element('p', array( 'class' => 'error' ), $error ) ); + $this->getOutput()->addHTML( Xml::element( 'p', array( 'class' => 'error' ), $error ) ); $reset = new SpecialChangePassword(); $reset->setContext( $this->getContext() ); $reset->execute( null ); @@ -813,18 +860,18 @@ class LoginForm extends SpecialPage { /** * @param $u User object * @param $throttle Boolean - * @param $emailTitle String: message name of email title - * @param $emailText String: message name of email text + * @param string $emailTitle message name of email title + * @param string $emailText message name of email text * @return Status object */ function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle', $emailText = 'passwordremindertext' ) { - global $wgServer, $wgScript, $wgNewPasswordExpiry; + global $wgCanonicalServer, $wgScript, $wgNewPasswordExpiry; if ( $u->getEmail() == '' ) { return Status::newFatal( 'noemail', $u->getName() ); } $ip = $this->getRequest()->getIP(); - if( !$ip ) { + if ( !$ip ) { return Status::newFatal( 'badipaddress' ); } @@ -835,14 +882,13 @@ class LoginForm extends SpecialPage { $u->setNewpassword( $np, $throttle ); $u->saveSettings(); $userLanguage = $u->getOption( 'language' ); - $m = $this->msg( $emailText, $ip, $u->getName(), $np, $wgServer . $wgScript, + $m = $this->msg( $emailText, $ip, $u->getName(), $np, '<' . $wgCanonicalServer . $wgScript . '>', round( $wgNewPasswordExpiry / 86400 ) )->inLanguage( $userLanguage )->text(); $result = $u->sendMail( $this->msg( $emailTitle )->inLanguage( $userLanguage )->text(), $m ); return $result; } - /** * Run any hooks registered for logins, then HTTP redirect to * $this->mReturnTo (or Main Page if that's undefined). Formerly we had a @@ -859,8 +905,9 @@ class LoginForm extends SpecialPage { $injected_html = ''; wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) ); - if( $injected_html !== '' ) { - $this->displaySuccessfulLogin( 'loginsuccess', $injected_html ); + if ( $injected_html !== '' ) { + $this->displaySuccessfulAction( $this->msg( 'loginsuccesstitle' ), + 'loginsuccess', $injected_html ); } else { $this->executeReturnTo( 'successredirect' ); } @@ -876,7 +923,7 @@ class LoginForm extends SpecialPage { # Run any hooks; display injected HTML $currentUser = $this->getUser(); $injected_html = ''; - $welcome_creation_msg = 'welcomecreation'; + $welcome_creation_msg = 'welcomecreation-msg'; wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) ); @@ -887,18 +934,21 @@ class LoginForm extends SpecialPage { */ wfRunHooks( 'BeforeWelcomeCreation', array( &$welcome_creation_msg, &$injected_html ) ); - $this->displaySuccessfulLogin( $welcome_creation_msg, $injected_html ); + $this->displaySuccessfulAction( $this->msg( 'welcomeuser', $this->getUser()->getName() ), + $welcome_creation_msg, $injected_html ); } /** - * Display a "login successful" page. + * Display an "successful action" page. + * + * @param string|Message $title page's title * @param $msgname string * @param $injected_html string */ - private function displaySuccessfulLogin( $msgname, $injected_html ) { + private function displaySuccessfulAction( $title, $msgname, $injected_html ) { $out = $this->getOutput(); - $out->setPageTitle( $this->msg( 'loginsuccesstitle' ) ); - if( $msgname ){ + $out->setPageTitle( $title ); + if ( $msgname ) { $out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) ); } @@ -913,6 +963,7 @@ class LoginForm extends SpecialPage { * User::isBlockedFromCreateAccount(), which gets this block, ignores the 'hardblock' * setting on blocks (bug 13611). * @param $block Block the block causing this error + * @throws ErrorPageError */ function userBlockedMessage( Block $block ) { # Let's be nice about this, it's likely that this feature will be used @@ -922,23 +973,38 @@ class LoginForm extends SpecialPage { # haven't bothered to log out before trying to create an account to # evade it, but we'll leave that to their guilty conscience to figure # out. - - $out = $this->getOutput(); - $out->setPageTitle( $this->msg( 'cantcreateaccounttitle' ) ); - - $block_reason = $block->mReason; - if ( strval( $block_reason ) === '' ) { - $block_reason = $this->msg( 'blockednoreason' )->text(); - } - - $out->addWikiMsg( + throw new ErrorPageError( + 'cantcreateaccounttitle', 'cantcreateaccount-text', - $block->getTarget(), - $block_reason, - $block->getByName() + array( + $block->getTarget(), + $block->mReason ? $block->mReason : $this->msg( 'blockednoreason' )->text(), + $block->getByName() + ) ); + } - $this->executeReturnTo( 'error' ); + /** + * Add a "return to" link or redirect to it. + * Extensions can use this to reuse the "return to" logic after + * inject steps (such as redirection) into the login process. + * + * @param $type string, one of the following: + * - error: display a return to link ignoring $wgRedirectOnLogin + * - success: display a return to link using $wgRedirectOnLogin if needed + * - successredirect: send an HTTP redirect using $wgRedirectOnLogin if needed + * @param string $returnTo + * @param array|string $returnToQuery + * @param bool $stickHTTPs Keep redirect link on HTTPs + * @since 1.22 + */ + public function showReturnToPage( + $type, $returnTo = '', $returnToQuery = '', $stickHTTPs = false + ) { + $this->mReturnTo = $returnTo; + $this->mReturnToQuery = $returnToQuery; + $this->mStickHTTPS = $stickHTTPs; + $this->executeReturnTo( $type ); } /** @@ -965,14 +1031,22 @@ class LoginForm extends SpecialPage { $returnToTitle = Title::newMainPage(); } + if ( $wgSecureLogin && !$this->mStickHTTPS ) { + $options = array( 'http' ); + $proto = PROTO_HTTP; + } elseif ( $wgSecureLogin ) { + $options = array( 'https' ); + $proto = PROTO_HTTPS; + } else { + $options = array(); + $proto = PROTO_RELATIVE; + } + if ( $type == 'successredirect' ) { - $redirectUrl = $returnToTitle->getFullURL( $returnToQuery ); - if( $wgSecureLogin && !$this->mStickHTTPS ) { - $redirectUrl = preg_replace( '/^https:/', 'http:', $redirectUrl ); - } + $redirectUrl = $returnToTitle->getFullURL( $returnToQuery, false, $proto ); $this->getOutput()->redirect( $redirectUrl ); } else { - $this->getOutput()->addReturnTo( $returnToTitle, $returnToQuery ); + $this->getOutput()->addReturnTo( $returnToTitle, $returnToQuery, null, $options ); } } @@ -987,6 +1061,7 @@ class LoginForm extends SpecialPage { $titleObj = $this->getTitle(); $user = $this->getUser(); + $out = $this->getOutput(); if ( $this->mType == 'signup' ) { // Block signup here if in readonly. Keeps user from @@ -1003,7 +1078,8 @@ class LoginForm extends SpecialPage { } } - if ( $this->mUsername == '' ) { + // Pre-fill username (if not creating an account, bug 44775). + if ( $this->mUsername == '' && $this->mType != 'signup' ) { if ( $user->isLoggedIn() ) { $this->mUsername = $user->getName(); } else { @@ -1013,14 +1089,33 @@ class LoginForm extends SpecialPage { if ( $this->mType == 'signup' ) { $template = new UsercreateTemplate(); + + $out->addModuleStyles( array( + 'mediawiki.ui', + 'mediawiki.special.createaccount' + ) ); + // XXX hack pending RL or JS parse() support for complex content messages + // https://bugzilla.wikimedia.org/show_bug.cgi?id=25349 + $out->addJsConfigVars( 'wgCreateacctImgcaptchaHelp', + $this->msg( 'createacct-imgcaptcha-help' )->parse() ); + $out->addModules( array( + 'mediawiki.special.createaccount.js' + ) ); + // Must match number of benefits defined in messages + $template->set( 'benefitCount', 3 ); + $q = 'action=submitlogin&type=signup'; $linkq = 'type=login'; - $linkmsg = 'gotaccount'; } else { $template = new UserloginTemplate(); + + $out->addModuleStyles( array( + 'mediawiki.ui', + 'mediawiki.special.userlogin' + ) ); + $q = 'action=submitlogin&type=login'; $linkq = 'type=signup'; - $linkmsg = 'nologin'; } if ( $this->mReturnTo !== '' ) { @@ -1033,16 +1128,14 @@ class LoginForm extends SpecialPage { $linkq .= $returnto; } - # Don't show a "create account" link if the user can't - if( $this->showCreateOrLoginLink( $user ) ) { + # Don't show a "create account" link if the user can't. + if ( $this->showCreateOrLoginLink( $user ) ) { # Pass any language selection on to the mode switch link - if( $wgLoginLanguageSelector && $this->mLanguage ) { + if ( $wgLoginLanguageSelector && $this->mLanguage ) { $linkq .= '&uselang=' . $this->mLanguage; } - $link = Html::element( 'a', array( 'href' => $titleObj->getLocalURL( $linkq ) ), - $this->msg( $linkmsg . 'link' )->text() ); # Calling either 'gotaccountlink' or 'nologinlink' - - $template->set( 'link', $this->msg( $linkmsg )->rawParams( $link )->parse() ); + // Supply URL, login template creates the button. + $template->set( 'createOrLoginHref', $titleObj->getLocalURL( $linkq ) ); } else { $template->set( 'link', '' ); } @@ -1052,9 +1145,11 @@ class LoginForm extends SpecialPage { : is_array( $wgPasswordResetRoutes ) && in_array( true, array_values( $wgPasswordResetRoutes ) ); $template->set( 'header', '' ); + $template->set( 'skin', $this->getSkin() ); $template->set( 'name', $this->mUsername ); $template->set( 'password', $this->mPassword ); $template->set( 'retype', $this->mRetype ); + $template->set( 'createemailset', $this->mCreateaccountMail ); $template->set( 'email', $this->mEmail ); $template->set( 'realname', $this->mRealName ); $template->set( 'domain', $this->mDomain ); @@ -1074,7 +1169,9 @@ class LoginForm extends SpecialPage { $template->set( 'usereason', $user->isLoggedIn() ); $template->set( 'remember', $user->getOption( 'rememberpassword' ) || $this->mRemember ); $template->set( 'cansecurelogin', ( $wgSecureLogin === true ) ); - $template->set( 'stickHTTPS', $this->mStickHTTPS ); + $template->set( 'stickhttps', (int)$this->mStickHTTPS ); + $template->set( 'loggedin', $user->isLoggedIn() ); + $template->set( 'loggedinuser', $user->getName() ); if ( $this->mType == 'signup' ) { if ( !self::getCreateaccountToken() ) { @@ -1089,15 +1186,16 @@ class LoginForm extends SpecialPage { } # Prepare language selection links as needed - if( $wgLoginLanguageSelector ) { + if ( $wgLoginLanguageSelector ) { $template->set( 'languages', $this->makeLanguageSelector() ); - if( $this->mLanguage ) { + if ( $this->mLanguage ) { $template->set( 'uselang', $this->mLanguage ); } } + $template->set( 'secureLoginUrl', $this->mSecureLoginUrl ); // Use loginend-https for HTTPS requests if it's not blank, loginend otherwise - // Ditto for signupend + // Ditto for signupend. New forms use neither. $usingHTTPS = WebRequest::detectProtocol() == 'https'; $loginendHTTPS = $this->msg( 'loginend-https' ); $signupendHTTPS = $this->msg( 'signupend-https' ); @@ -1120,22 +1218,21 @@ class LoginForm extends SpecialPage { wfRunHooks( 'UserLoginForm', array( &$template ) ); } - $out = $this->getOutput(); $out->disallowUserJs(); // just in case... $out->addTemplate( $template ); } /** - * @private + * Whether the login/create account form should display a link to the + * other form (in addition to whatever the skin provides). * * @param $user User - * - * @return Boolean + * @return bool */ - function showCreateOrLoginLink( &$user ) { - if( $this->mType == 'signup' ) { + private function showCreateOrLoginLink( &$user ) { + if ( $this->mType == 'signup' ) { return true; - } elseif( $user->isAllowed( 'createaccount' ) ) { + } elseif ( $user->isAllowed( 'createaccount' ) ) { return true; } else { return false; @@ -1213,17 +1310,12 @@ class LoginForm extends SpecialPage { * Renew the user's session id, using strong entropy */ private function renewSessionId() { - if ( wfCheckEntropy() ) { - session_regenerate_id( false ); - } else { - //If we don't trust PHP's entropy, we have to replace the session manually - $tmp = $_SESSION; - session_unset(); - session_write_close(); - session_id( MWCryptRand::generateHex( 32 ) ); - session_start(); - $_SESSION = $tmp; + global $wgSecureLogin, $wgCookieSecure; + if ( $wgSecureLogin && !$this->mStickHTTPS ) { + $wgCookieSecure = false; } + + wfResetSessionID(); } /** @@ -1260,13 +1352,6 @@ class LoginForm extends SpecialPage { } /** - * @private - */ - function throttleHit( $limit ) { - $this->mainLoginForm( $this->msg( 'acct_creation_throttle_hit' )->numParams( $limit )->parse() ); - } - - /** * Produce a bar of links which allow the user to select another language * during login/registration but retain "returnto" * @@ -1274,10 +1359,10 @@ class LoginForm extends SpecialPage { */ function makeLanguageSelector() { $msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage(); - if( !$msg->isBlank() ) { + if ( !$msg->isBlank() ) { $langs = explode( "\n", $msg->text() ); $links = array(); - foreach( $langs as $lang ) { + foreach ( $langs as $lang ) { $lang = trim( $lang, '* ' ); $parts = explode( '|', $lang ); if ( count( $parts ) >= 2 ) { @@ -1295,20 +1380,20 @@ class LoginForm extends SpecialPage { * Create a language selector link for a particular language * Links back to this page preserving type and returnto * - * @param $text Link text - * @param $lang Language code + * @param string $text Link text + * @param string $lang Language code * @return string */ function makeLanguageSelectorLink( $text, $lang ) { - if( $this->getLanguage()->getCode() == $lang ) { + if ( $this->getLanguage()->getCode() == $lang ) { // no link for currently used language return htmlspecialchars( $text ); } $query = array( 'uselang' => $lang ); - if( $this->mType == 'signup' ) { + if ( $this->mType == 'signup' ) { $query['type'] = 'signup'; } - if( $this->mReturnTo !== '' ) { + if ( $this->mReturnTo !== '' ) { $query['returnto'] = $this->mReturnTo; $query['returntoquery'] = $this->mReturnToQuery; } @@ -1324,4 +1409,8 @@ class LoginForm extends SpecialPage { $query ); } + + protected function getGroupName() { + return 'login'; + } } diff --git a/includes/specials/SpecialUserlogout.php b/includes/specials/SpecialUserlogout.php index ab2bf0ac..d957e875 100644 --- a/includes/specials/SpecialUserlogout.php +++ b/includes/specials/SpecialUserlogout.php @@ -49,8 +49,11 @@ class SpecialUserlogout extends UnlistedSpecialPage { $oldName = $user->getName(); $user->logout(); + $loginURL = SpecialPage::getTitleFor( 'Userlogin' )->getFullURL( + $this->getRequest()->getValues( 'returnto', 'returntoquery' ) ); + $out = $this->getOutput(); - $out->addWikiMsg( 'logouttext' ); + $out->addWikiMsg( 'logouttext', $loginURL ); // Hook. $injected_html = ''; @@ -59,4 +62,8 @@ class SpecialUserlogout extends UnlistedSpecialPage { $out->returnToMain(); } + + protected function getGroupName() { + return 'login'; + } } diff --git a/includes/specials/SpecialUserrights.php b/includes/specials/SpecialUserrights.php index 9f5a48a5..4501736f 100644 --- a/includes/specials/SpecialUserrights.php +++ b/includes/specials/SpecialUserrights.php @@ -45,6 +45,11 @@ class UserrightsPage extends SpecialPage { return $this->userCanChangeRights( $user, false ); } + /** + * @param User $user + * @param bool $checkIfSelf + * @return bool + */ public function userCanChangeRights( $user, $checkIfSelf = true ) { $available = $this->changeableGroups(); if ( $user->getId() == 0 ) { @@ -54,7 +59,7 @@ class UserrightsPage extends SpecialPage { || !empty( $available['remove'] ) || ( ( $this->isself || !$checkIfSelf ) && ( !empty( $available['add-self'] ) - || !empty( $available['remove-self'] ) ) ); + || !empty( $available['remove-self'] ) ) ); } /** @@ -62,6 +67,7 @@ class UserrightsPage extends SpecialPage { * Depending on the submit button used, call a form or a save function. * * @param $par Mixed: string if any subpage provided, else null + * @throws UserBlockedError|PermissionsError */ public function execute( $par ) { // If the visitor doesn't have permissions to assign or remove @@ -74,13 +80,13 @@ class UserrightsPage extends SpecialPage { * (e.g. they don't have the userrights permission), then don't * allow them to use Special:UserRights. */ - if( $user->isBlocked() && !$user->isAllowed( 'userrights' ) ) { + if ( $user->isBlocked() && !$user->isAllowed( 'userrights' ) ) { throw new UserBlockedError( $user->getBlock() ); } $request = $this->getRequest(); - if( $par !== null ) { + if ( $par !== null ) { $this->mTarget = $par; } else { $this->mTarget = $request->getVal( 'user' ); @@ -94,15 +100,27 @@ class UserrightsPage extends SpecialPage { * edit their own groups, automatically set them as the * target. */ - if ( !count( $available['add'] ) && !count( $available['remove'] ) ) + if ( !count( $available['add'] ) && !count( $available['remove'] ) ) { $this->mTarget = $user->getName(); + } } if ( User::getCanonicalName( $this->mTarget ) == $user->getName() ) { $this->isself = true; } - if( !$this->userCanChangeRights( $user, true ) ) { + if ( !$this->userCanChangeRights( $user, true ) ) { + if ( $this->isself && $request->getCheck( 'success' ) ) { + // bug 48609: if the user just removed its own rights, this would + // leads it in a "permissions error" page. In that case, show a + // message that it can't anymore use this page instead of an error + $this->setHeaders(); + $out = $this->getOutput(); + $out->wrapWikiMsg( "<div class=\"successbox\">\n$1\n</div>", 'userrights-removed-self' ); + $out->returnToMain(); + return; + } + // @todo FIXME: There may be intermediate groups we can mention. $msg = $user->isAnon() ? 'userrights-nologin' : 'userrights-notallowed'; throw new PermissionsError( null, array( array( $msg ) ) ); @@ -121,50 +139,54 @@ class UserrightsPage extends SpecialPage { $this->switchForm(); } - if( $request->wasPosted() ) { + if ( + $request->wasPosted() && + $request->getCheck( 'saveusergroups' ) && + $user->matchEditToken( $request->getVal( 'wpEditToken' ), $this->mTarget ) + ) { // save settings - if( $request->getCheck( 'saveusergroups' ) ) { - $reason = $request->getVal( 'user-reason' ); - $tok = $request->getVal( 'wpEditToken' ); - if( $user->matchEditToken( $tok, $this->mTarget ) ) { - $this->saveUserGroups( - $this->mTarget, - $reason - ); - - $out->redirect( $this->getSuccessURL() ); - return; - } + $status = $this->fetchUser( $this->mTarget ); + if ( !$status->isOK() ) { + $this->getOutput()->addWikiText( $status->getWikiText() ); + return; + } + + $targetUser = $status->value; + + if ( $request->getVal( 'conflictcheck-originalgroups' ) !== implode( ',', $targetUser->getGroups() ) ) { + $out->addWikiMsg( 'userrights-conflict' ); + } else { + $this->saveUserGroups( + $this->mTarget, + $request->getVal( 'user-reason' ), + $targetUser + ); + + $out->redirect( $this->getSuccessURL() ); + return; } } // show some more forms - if( $this->mTarget !== null ) { + if ( $this->mTarget !== null ) { $this->editUserGroupsForm( $this->mTarget ); } } function getSuccessURL() { - return $this->getTitle( $this->mTarget )->getFullURL(); + return $this->getTitle( $this->mTarget )->getFullURL( array( 'success' => 1 ) ); } /** * Save user groups changes in the database. * Data comes from the editUserGroupsForm() form function * - * @param $username String: username to apply changes to. - * @param $reason String: reason for group change + * @param string $username username to apply changes to. + * @param string $reason reason for group change + * @param User|UserRightsProxy $user Target user object. * @return null */ - function saveUserGroups( $username, $reason = '' ) { - $status = $this->fetchUser( $username ); - if( !$status->isOK() ) { - $this->getOutput()->addWikiText( $status->getWikiText() ); - return; - } else { - $user = $status->value; - } - + function saveUserGroups( $username, $reason, $user ) { $allgroups = $this->getAllGroups(); $addgroup = array(); $removegroup = array(); @@ -188,12 +210,14 @@ class UserrightsPage extends SpecialPage { * Save user groups changes in the database. * * @param $user User object - * @param $add Array of groups to add - * @param $remove Array of groups to remove - * @param $reason String: reason for group change + * @param array $add of groups to add + * @param array $remove of groups to remove + * @param string $reason reason for group change * @return Array: Tuple of added, then removed groups */ function doSaveUserGroups( $user, $add, $remove, $reason = '' ) { + global $wgAuth; + // Validate input set... $isself = ( $user->getName() == $this->getUser()->getName() ); $groups = $user->getGroups(); @@ -212,15 +236,15 @@ class UserrightsPage extends SpecialPage { $newGroups = $oldGroups; // remove then add groups - if( $remove ) { + if ( $remove ) { $newGroups = array_diff( $newGroups, $remove ); - foreach( $remove as $group ) { + foreach ( $remove as $group ) { $user->removeGroup( $group ); } } - if( $add ) { + if ( $add ) { $newGroups = array_merge( $newGroups, $add ); - foreach( $add as $group ) { + foreach ( $add as $group ) { $user->addGroup( $group ); } } @@ -229,40 +253,42 @@ class UserrightsPage extends SpecialPage { // Ensure that caches are cleared $user->invalidateCache(); + // update groups in external authentication database + $wgAuth->updateExternalDBGroups( $user, $add, $remove ); + wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) ); wfDebug( 'newGroups: ' . print_r( $newGroups, true ) ); wfRunHooks( 'UserRights', array( &$user, $add, $remove ) ); - if( $newGroups != $oldGroups ) { + if ( $newGroups != $oldGroups ) { $this->addLogEntry( $user, $oldGroups, $newGroups, $reason ); } return array( $add, $remove ); } - /** * Add a rights log entry for an action. */ function addLogEntry( $user, $oldGroups, $newGroups, $reason ) { - $log = new LogPage( 'rights' ); - - $log->addEntry( 'rights', - $user->getUserPage(), - $reason, - array( - $this->makeGroupNameListForLog( $oldGroups ), - $this->makeGroupNameListForLog( $newGroups ) - ) - ); + $logEntry = new ManualLogEntry( 'rights', 'rights' ); + $logEntry->setPerformer( $this->getUser() ); + $logEntry->setTarget( $user->getUserPage() ); + $logEntry->setComment( $reason ); + $logEntry->setParameters( array( + '4::oldgroups' => $oldGroups, + '5::newgroups' => $newGroups, + ) ); + $logid = $logEntry->insert(); + $logEntry->publish( $logid ); } /** * Edit user groups membership - * @param $username String: name of the user. + * @param string $username name of the user. */ function editUserGroupsForm( $username ) { $status = $this->fetchUser( $username ); - if( !$status->isOK() ) { + if ( !$status->isOK() ) { $this->getOutput()->addWikiText( $status->getWikiText() ); return; } else { @@ -283,63 +309,64 @@ class UserrightsPage extends SpecialPage { * return a user (or proxy) object for manipulating it. * * Side effects: error output for invalid access + * @param string $username * @return Status object */ public function fetchUser( $username ) { global $wgUserrightsInterwikiDelimiter; $parts = explode( $wgUserrightsInterwikiDelimiter, $username ); - if( count( $parts ) < 2 ) { + if ( count( $parts ) < 2 ) { $name = trim( $username ); $database = ''; } else { list( $name, $database ) = array_map( 'trim', $parts ); - if( $database == wfWikiID() ) { + if ( $database == wfWikiID() ) { $database = ''; } else { - if( !$this->getUser()->isAllowed( 'userrights-interwiki' ) ) { + if ( !$this->getUser()->isAllowed( 'userrights-interwiki' ) ) { return Status::newFatal( 'userrights-no-interwiki' ); } - if( !UserRightsProxy::validDatabase( $database ) ) { + if ( !UserRightsProxy::validDatabase( $database ) ) { return Status::newFatal( 'userrights-nodatabase', $database ); } } } - if( $name === '' ) { + if ( $name === '' ) { return Status::newFatal( 'nouserspecified' ); } - if( $name[0] == '#' ) { + if ( $name[0] == '#' ) { // Numeric ID can be specified... // We'll do a lookup for the name internally. $id = intval( substr( $name, 1 ) ); - if( $database == '' ) { + if ( $database == '' ) { $name = User::whoIs( $id ); } else { $name = UserRightsProxy::whoIs( $database, $id ); } - if( !$name ) { + if ( !$name ) { return Status::newFatal( 'noname' ); } } else { $name = User::getCanonicalName( $name ); - if( $name === false ) { + if ( $name === false ) { // invalid name return Status::newFatal( 'nosuchusershort', $username ); } } - if( $database == '' ) { + if ( $database == '' ) { $user = User::newFromName( $name ); } else { $user = UserRightsProxy::newFromName( $database, $name ); } - if( !$user || $user->isAnon() ) { + if ( !$user || $user->isAnon() ) { return Status::newFatal( 'nosuchusershort', $username ); } @@ -347,15 +374,24 @@ class UserrightsPage extends SpecialPage { } function makeGroupNameList( $ids ) { - if( empty( $ids ) ) { + if ( empty( $ids ) ) { return $this->msg( 'rightsnone' )->inContentLanguage()->text(); } else { return implode( ', ', $ids ); } } + /** + * Make a list of group names to be stored as parameter for log entries + * + * @deprecated in 1.21; use LogFormatter instead. + * @param $ids array + * @return string + */ function makeGroupNameListForLog( $ids ) { - if( empty( $ids ) ) { + wfDeprecated( __METHOD__, '1.21' ); + + if ( empty( $ids ) ) { return ''; } else { return $this->makeGroupNameList( $ids ); @@ -369,9 +405,9 @@ class UserrightsPage extends SpecialPage { global $wgScript; $this->getOutput()->addHTML( Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'name' => 'uluser', 'id' => 'mw-userrights-form1' ) ) . - Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . + Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . Xml::fieldset( $this->msg( 'userrights-lookup-user' )->text() ) . - Xml::inputLabel( $this->msg( 'userrights-user-editname' )->text(), 'user', 'username', 30, str_replace( '_', ' ', $this->mTarget ) ) . ' ' . + Xml::inputLabel( $this->msg( 'userrights-user-editname' )->text(), 'user', 'username', 30, str_replace( '_', ' ', $this->mTarget ), array( 'autofocus' => true ) ) . ' ' . Xml::submitButton( $this->msg( 'editusergroup' )->text() ) . Html::closeElement( 'fieldset' ) . Html::closeElement( 'form' ) . "\n" @@ -383,7 +419,7 @@ class UserrightsPage extends SpecialPage { * form will be able to manipulate based on the current user's system * permissions. * - * @param $groups Array: list of groups the given user is in + * @param array $groups list of groups the given user is in * @return Array: Tuple of addable, then removable groups */ protected function splitGroups( $groups ) { @@ -409,27 +445,41 @@ class UserrightsPage extends SpecialPage { */ protected function showEditUserGroupsForm( $user, $groups ) { $list = array(); - foreach( $groups as $group ) { + $membersList = array(); + foreach ( $groups as $group ) { $list[] = self::buildGroupLink( $group ); + $membersList[] = self::buildGroupMemberLink( $group ); } - $autolist = array(); + $autoList = array(); + $autoMembersList = array(); if ( $user instanceof User ) { - foreach( Autopromote::getAutopromoteGroups( $user ) as $group ) { - $autolist[] = self::buildGroupLink( $group ); + foreach ( Autopromote::getAutopromoteGroups( $user ) as $group ) { + $autoList[] = self::buildGroupLink( $group ); + $autoMembersList[] = self::buildGroupMemberLink( $group ); } } + $language = $this->getLanguage(); + $displayedList = $this->msg( 'userrights-groupsmember-type', + $language->listToText( $list ), + $language->listToText( $membersList ) + )->plain(); + $displayedAutolist = $this->msg( 'userrights-groupsmember-type', + $language->listToText( $autoList ), + $language->listToText( $autoMembersList ) + )->plain(); + $grouplist = ''; $count = count( $list ); - if( $count > 0 ) { + if ( $count > 0 ) { $grouplist = $this->msg( 'userrights-groupsmember', $count, $user->getName() )->parse(); - $grouplist = '<p>' . $grouplist . ' ' . $this->getLanguage()->listToText( $list ) . "</p>\n"; + $grouplist = '<p>' . $grouplist . ' ' . $displayedList . "</p>\n"; } - $count = count( $autolist ); - if( $count > 0 ) { + $count = count( $autoList ); + if ( $count > 0 ) { $autogrouplistintro = $this->msg( 'userrights-groupsmember-auto', $count, $user->getName() )->parse(); - $grouplist .= '<p>' . $autogrouplistintro . ' ' . $this->getLanguage()->listToText( $autolist ) . "</p>\n"; + $grouplist .= '<p>' . $autogrouplistintro . ' ' . $displayedAutolist . "</p>\n"; } $userToolLinks = Linker::userToolLinks( @@ -443,6 +493,7 @@ class UserrightsPage extends SpecialPage { Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL(), 'name' => 'editGroup', 'id' => 'mw-userrights-form2' ) ) . Html::hidden( 'user', $this->mTarget ) . Html::hidden( 'wpEditToken', $this->getUser()->getEditToken( $this->mTarget ) ) . + Html::hidden( 'conflictcheck-originalgroups', implode( ',', $user->getGroups() ) ) . // Conflict detection Xml::openElement( 'fieldset' ) . Xml::element( 'legend', array(), $this->msg( 'userrights-editusergroup', $user->getName() )->text() ) . $this->msg( 'editinguser' )->params( wfEscapeWikiText( $user->getName() ) )->rawParams( $userToolLinks )->parse() . @@ -479,10 +530,17 @@ class UserrightsPage extends SpecialPage { * @return string */ private static function buildGroupLink( $group ) { - static $cache = array(); - if( !isset( $cache[$group] ) ) - $cache[$group] = User::makeGroupLinkHtml( $group, htmlspecialchars( User::getGroupName( $group ) ) ); - return $cache[$group]; + return User::makeGroupLinkHtml( $group, User::getGroupName( $group ) ); + } + + /** + * Format a link to a group member description page + * + * @param $group string + * @return string + */ + private static function buildGroupMemberLink( $group ) { + return User::makeGroupLinkHtml( $group, User::getGroupMember( $group ) ); } /** @@ -497,7 +555,7 @@ class UserrightsPage extends SpecialPage { * Adds a table with checkboxes where you can select what groups to add/remove * * @todo Just pass the username string? - * @param $usergroups Array: groups the user belongs to + * @param array $usergroups groups the user belongs to * @param $user User a user object * @return string XHTML table element with checkboxes */ @@ -505,17 +563,17 @@ class UserrightsPage extends SpecialPage { $allgroups = $this->getAllGroups(); $ret = ''; - # Put all column info into an associative array so that extensions can - # more easily manage it. + // Put all column info into an associative array so that extensions can + // more easily manage it. $columns = array( 'unchangeable' => array(), 'changeable' => array() ); - foreach( $allgroups as $group ) { + foreach ( $allgroups as $group ) { $set = in_array( $group, $usergroups ); - # Should the checkbox be disabled? + // Should the checkbox be disabled? $disabled = !( ( $set && $this->canRemove( $group ) ) || ( !$set && $this->canAdd( $group ) ) ); - # Do we need to point out that this action is irreversible? + // Do we need to point out that this action is irreversible? $irreversible = !$disabled && ( ( $set && !$this->canAdd( $group ) ) || ( !$set && !$this->canRemove( $group ) ) ); @@ -526,27 +584,30 @@ class UserrightsPage extends SpecialPage { 'irreversible' => $irreversible ); - if( $disabled ) { + if ( $disabled ) { $columns['unchangeable'][$group] = $checkbox; } else { $columns['changeable'][$group] = $checkbox; } } - # Build the HTML table - $ret .= Xml::openElement( 'table', array( 'class' => 'mw-userrights-groups' ) ) . + // Build the HTML table + $ret .= Xml::openElement( 'table', array( 'class' => 'mw-userrights-groups' ) ) . "<tr>\n"; - foreach( $columns as $name => $column ) { - if( $column === array() ) + foreach ( $columns as $name => $column ) { + if ( $column === array() ) { continue; + } + // Messages: userrights-changeable-col, userrights-unchangeable-col $ret .= Xml::element( 'th', null, $this->msg( 'userrights-' . $name . '-col', count( $column ) )->text() ); } - $ret.= "</tr>\n<tr>\n"; - foreach( $columns as $column ) { - if( $column === array() ) + $ret .= "</tr>\n<tr>\n"; + foreach ( $columns as $column ) { + if ( $column === array() ) { continue; + } $ret .= "\t<td style='vertical-align:top;'>\n"; - foreach( $column as $group => $checkbox ) { + foreach ( $column as $group => $checkbox ) { $attr = $checkbox['disabled'] ? array( 'disabled' => 'disabled' ) : array(); $member = User::getGroupMember( $group, $user->getName() ); @@ -574,14 +635,13 @@ class UserrightsPage extends SpecialPage { * @return bool Can we remove the group? */ private function canRemove( $group ) { - // $this->changeableGroups()['remove'] doesn't work, of course. Thanks, - // PHP. + // $this->changeableGroups()['remove'] doesn't work, of course. Thanks, PHP. $groups = $this->changeableGroups(); return in_array( $group, $groups['remove'] ) || ( $this->isself && in_array( $group, $groups['remove-self'] ) ); } /** - * @param $group string: the name of the group to check + * @param string $group the name of the group to check * @return bool Can we add the group? */ private function canAdd( $group ) { @@ -592,7 +652,7 @@ class UserrightsPage extends SpecialPage { /** * Returns $this->getUser()->changeableGroups() * - * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) , 'add-self' => array( addablegroups to self), 'remove-self' => array( removable groups from self) ) + * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ), 'add-self' => array( addablegroups to self ), 'remove-self' => array( removable groups from self ) ) */ function changeableGroups() { return $this->getUser()->changeableGroups(); @@ -609,4 +669,8 @@ class UserrightsPage extends SpecialPage { $output->addHTML( Xml::element( 'h2', null, $rightsLogPage->getName()->text() ) ); LogEventsList::showLogExtract( $output, 'rights', $user->getUserPage() ); } + + protected function getGroupName() { + return 'users'; + } } diff --git a/includes/specials/SpecialVersion.php b/includes/specials/SpecialVersion.php index 4e5b6bf5..5ba785f5 100644 --- a/includes/specials/SpecialVersion.php +++ b/includes/specials/SpecialVersion.php @@ -40,7 +40,7 @@ class SpecialVersion extends SpecialPage { 'https://svn.wikimedia.org/svnroot/mediawiki' => 'https://svn.wikimedia.org/viewvc/mediawiki', ); - public function __construct(){ + public function __construct() { parent::__construct( 'Version' ); } @@ -48,29 +48,41 @@ class SpecialVersion extends SpecialPage { * main() */ public function execute( $par ) { - global $wgSpecialVersionShowHooks; + global $wgSpecialVersionShowHooks, $IP; $this->setHeaders(); $this->outputHeader(); $out = $this->getOutput(); $out->allowClickjacking(); - $text = - $this->getMediaWikiCredits() . - $this->softwareInformation() . - $this->getEntryPointInfo() . - $this->getExtensionCredits(); - if ( $wgSpecialVersionShowHooks ) { - $text .= $this->getWgHooks(); - } + if ( $par !== 'Credits' ) { + $text = + $this->getMediaWikiCredits() . + $this->softwareInformation() . + $this->getEntryPointInfo() . + $this->getExtensionCredits(); + if ( $wgSpecialVersionShowHooks ) { + $text .= $this->getWgHooks(); + } - $out->addWikiText( $text ); - $out->addHTML( $this->IPInfo() ); + $out->addWikiText( $text ); + $out->addHTML( $this->IPInfo() ); - if ( $this->getRequest()->getVal( 'easteregg' ) ) { - if ( $this->showEasterEgg() ) { + if ( $this->getRequest()->getVal( 'easteregg' ) ) { // TODO: put something interesting here } + } else { + // Credits sub page + + // Header + $out->addHTML( wfMessage( 'version-credits-summary' )->parseAsBlock() ); + + $wikiText = file_get_contents( $IP . '/CREDITS' ); + + // Take everything from the first section onwards, to remove the (not localized) header + $wikiText = substr( $wikiText, strpos( $wikiText, '==' ) ); + + $out->addWikiText( $wikiText ); } } @@ -100,6 +112,14 @@ class SpecialVersion extends SpecialPage { public static function getCopyrightAndAuthorList() { global $wgLang; + if ( defined( 'MEDIAWIKI_INSTALL' ) ) { + $othersLink = '[//www.mediawiki.org/wiki/Special:Version/Credits ' . wfMessage( 'version-poweredby-others' )->text() . ']'; + } else { + $othersLink = '[[Special:Version/Credits|' . wfMessage( 'version-poweredby-others' )->text() . ']]'; + } + + $translatorsLink = '[//translatewiki.net/wiki/Translating:MediaWiki/Credits ' . wfMessage( 'version-poweredby-translators' )->text() . ']'; + $authorList = array( 'Magnus Manske', 'Brion Vibber', 'Lee Daniel Crocker', 'Tim Starling', 'Erik Möller', 'Gabriel Wicke', 'Ævar Arnfjörð Bjarmason', @@ -108,13 +128,11 @@ class SpecialVersion extends SpecialPage { 'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe', 'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed', 'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso', - 'Timo Tijhof', - '[{{SERVER}}{{SCRIPTPATH}}/CREDITS ' . - wfMessage( 'version-poweredby-others' )->text() . - ']' + 'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', $othersLink, + $translatorsLink ); - return wfMessage( 'version-poweredby-credits', date( 'Y' ), + return wfMessage( 'version-poweredby-credits', MWTimestamp::getLocalInstance()->format( 'Y' ), $wgLang->listToText( $authorList ) )->text(); } @@ -131,20 +149,20 @@ class SpecialVersion extends SpecialPage { // wikimarkup can be used. $software = array(); $software['[https://www.mediawiki.org/ MediaWiki]'] = self::getVersionLinked(); - $software['[http://www.php.net/ PHP]'] = phpversion() . " (" . php_sapi_name() . ")"; + $software['[http://www.php.net/ PHP]'] = phpversion() . " (" . PHP_SAPI . ")"; $software[$dbr->getSoftwareLink()] = $dbr->getServerInfo(); // Allow a hook to add/remove items. wfRunHooks( 'SoftwareInfo', array( &$software ) ); $out = Xml::element( 'h2', array( 'id' => 'mw-version-software' ), wfMessage( 'version-software' )->text() ) . - Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-software' ) ) . + Xml::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-software' ) ) . "<tr> <th>" . wfMessage( 'version-software-product' )->text() . "</th> <th>" . wfMessage( 'version-software-version' )->text() . "</th> </tr>\n"; - foreach( $software as $name => $version ) { + foreach ( $software as $name => $version ) { $out .= "<tr> <td>" . $name . "</td> <td dir=\"ltr\">" . $version . "</td> @@ -203,11 +221,11 @@ class SpecialVersion extends SpecialPage { wfProfileIn( __METHOD__ ); $gitVersion = self::getVersionLinkedGit(); - if( $gitVersion ) { + if ( $gitVersion ) { $v = $gitVersion; } else { $svnVersion = self::getVersionLinkedSvn(); - if( $svnVersion ) { + if ( $svnVersion ) { $v = $svnVersion; } else { $v = $wgVersion; // fallback @@ -222,10 +240,10 @@ class SpecialVersion extends SpecialPage { * @return string wgVersion + a link to subversion revision of svn BASE */ private static function getVersionLinkedSvn() { - global $wgVersion, $IP; + global $IP; $info = self::getSvnInfo( $IP ); - if( !isset( $info['checkout-rev'] ) ) { + if ( !isset( $info['checkout-rev'] ) ) { return false; } @@ -236,32 +254,54 @@ class SpecialVersion extends SpecialPage { )->text(); if ( isset( $info['viewvc-url'] ) ) { - $version = "$wgVersion [{$info['viewvc-url']} $linkText]"; + $version = "[{$info['viewvc-url']} $linkText]"; } else { - $version = "$wgVersion $linkText"; + $version = $linkText; } - return $version; + return self::getwgVersionLinked() . " $version"; + } + + /** + * @return string + */ + private static function getwgVersionLinked() { + global $wgVersion; + $versionUrl = ""; + if ( wfRunHooks( 'SpecialVersionVersionUrl', array( $wgVersion, &$versionUrl ) ) ) { + $versionParts = array(); + preg_match( "/^(\d+\.\d+)/", $wgVersion, $versionParts ); + $versionUrl = "https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}"; + } + return "[$versionUrl $wgVersion]"; } /** - * @return bool|string wgVersion + HEAD sha1 stripped to the first 7 chars. False on failure + * @since 1.22 Returns the HEAD date in addition to the sha1 and link + * @return bool|string wgVersion + HEAD sha1 stripped to the first 7 chars with link and date, or false on failure */ private static function getVersionLinkedGit() { - global $wgVersion, $IP; + global $IP, $wgLang; $gitInfo = new GitInfo( $IP ); $headSHA1 = $gitInfo->getHeadSHA1(); - if( !$headSHA1 ) { + if ( !$headSHA1 ) { return false; } $shortSHA1 = '(' . substr( $headSHA1, 0, 7 ) . ')'; - $viewerUrl = $gitInfo->getHeadViewUrl(); - if ( $viewerUrl !== false ) { - $shortSHA1 = "[$viewerUrl $shortSHA1]"; + + $gitHeadUrl = $gitInfo->getHeadViewUrl(); + if ( $gitHeadUrl !== false ) { + $shortSHA1 = "[$gitHeadUrl $shortSHA1]"; } - return "$wgVersion $shortSHA1"; + + $gitHeadCommitDate = $gitInfo->getHeadCommitDate(); + if ( $gitHeadCommitDate ) { + $shortSHA1 .= "<br/>" . $wgLang->timeanddate( $gitHeadCommitDate, true ); + } + + return self::getwgVersionLinked() . " $shortSHA1"; } /** @@ -353,11 +393,6 @@ class SpecialVersion extends SpecialPage { // We want the 'other' type to be last in the list. $out .= $this->getExtensionCategory( 'other', $extensionTypes['other'] ); - if ( count( $wgExtensionFunctions ) ) { - $out .= $this->openExtType( $this->msg( 'version-extension-functions' )->text(), 'extension-functions' ); - $out .= '<tr><td colspan="4">' . $this->listToText( $wgExtensionFunctions ) . "</td></tr>\n"; - } - $tags = $wgParser->getTags(); $cnt = count( $tags ); @@ -366,11 +401,11 @@ class SpecialVersion extends SpecialPage { $tags[$i] = "<{$tags[$i]}>"; } $out .= $this->openExtType( $this->msg( 'version-parser-extensiontags' )->text(), 'parser-tags' ); - $out .= '<tr><td colspan="4">' . $this->listToText( $tags ). "</td></tr>\n"; + $out .= '<tr><td colspan="4">' . $this->listToText( $tags ) . "</td></tr>\n"; } $fhooks = $wgParser->getFunctionHooks(); - if( count( $fhooks ) ) { + if ( count( $fhooks ) ) { $out .= $this->openExtType( $this->msg( 'version-parser-function-hooks' )->text(), 'parser-function-hooks' ); $out .= '<tr><td colspan="4">' . $this->listToText( $fhooks ) . "</td></tr>\n"; } @@ -415,7 +450,7 @@ class SpecialVersion extends SpecialPage { * @return int */ function compare( $a, $b ) { - if( $a['name'] === $b['name'] ) { + if ( $a['name'] === $b['name'] ) { return 0; } else { return $this->getLanguage()->lc( $a['name'] ) > $this->getLanguage()->lc( $b['name'] ) @@ -432,6 +467,8 @@ class SpecialVersion extends SpecialPage { * @return string */ function getCreditsForExtension( array $extension ) { + global $wgLang; + $name = isset( $extension['name'] ) ? $extension['name'] : '[no name]'; $vcsText = false; @@ -445,6 +482,10 @@ class SpecialVersion extends SpecialPage { if ( $gitViewerUrl !== false ) { $vcsText = "[$gitViewerUrl $vcsText]"; } + $gitHeadCommitDate = $gitInfo->getHeadCommitDate(); + if ( $gitHeadCommitDate ) { + $vcsText .= "<br/>" . $wgLang->timeanddate( $gitHeadCommitDate, true ); + } } else { $svnInfo = self::getSvnInfo( dirname( $extension['path'] ) ); # Make subversion text/link. @@ -472,13 +513,13 @@ class SpecialVersion extends SpecialPage { } # Make description text. - $description = isset ( $extension['description'] ) ? $extension['description'] : ''; + $description = isset( $extension['description'] ) ? $extension['description'] : ''; - if( isset ( $extension['descriptionmsg'] ) ) { + if ( isset( $extension['descriptionmsg'] ) ) { # Look for a localized description. $descriptionMsg = $extension['descriptionmsg']; - if( is_array( $descriptionMsg ) ) { + if ( is_array( $descriptionMsg ) ) { $descriptionMsgKey = $descriptionMsg[0]; // Get the message key array_shift( $descriptionMsg ); // Shift out the message key to get the parameters only array_map( "htmlspecialchars", $descriptionMsg ); // For sanity @@ -497,7 +538,7 @@ class SpecialVersion extends SpecialPage { <td colspan=\"2\"><em>$mainLink $versionText</em></td>"; } - $author = isset ( $extension['author'] ) ? $extension['author'] : array(); + $author = isset( $extension['author'] ) ? $extension['author'] : array(); $extDescAuthor = "<td>$description</td> <td>" . $this->listAuthors( $author, false ) . "</td> </tr>\n"; @@ -533,21 +574,22 @@ class SpecialVersion extends SpecialPage { $ret .= Xml::closeElement( 'table' ); return $ret; - } else + } else { return ''; + } } private function openExtType( $text, $name = null ) { $opt = array( 'colspan' => 4 ); $out = ''; - if( $this->firstExtOpened ) { + if ( $this->firstExtOpened ) { // Insert a spacing line $out .= '<tr class="sv-space">' . Html::element( 'td', $opt ) . "</tr>\n"; } $this->firstExtOpened = true; - if( $name ) { + if ( $name ) { $opt['id'] = "sv-$name"; } @@ -562,9 +604,8 @@ class SpecialVersion extends SpecialPage { * @return String: HTML fragment */ private function IPInfo() { - $ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) ); - return "<!-- visited from $ip -->\n" . - "<span style='display:none'>visited from $ip</span>"; + $ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) ); + return "<!-- visited from $ip -->\n<span style='display:none'>visited from $ip</span>"; } /** @@ -575,9 +616,11 @@ class SpecialVersion extends SpecialPage { */ function listAuthors( $authors ) { $list = array(); - foreach( (array)$authors as $item ) { - if( $item == '...' ) { + foreach ( (array)$authors as $item ) { + if ( $item == '...' ) { $list[] = $this->msg( 'version-poweredby-others' )->text(); + } elseif ( substr( $item, -5 ) == ' ...]' ) { + $list[] = substr( $item, 0, -4 ) . $this->msg( 'version-poweredby-others' )->text() . "]"; } else { $list[] = $item; } @@ -588,7 +631,7 @@ class SpecialVersion extends SpecialPage { /** * Convert an array of items into a list for display. * - * @param $list Array of elements to display + * @param array $list of elements to display * @param $sort Boolean: whether to sort the items in $list * * @return String @@ -618,16 +661,16 @@ class SpecialVersion extends SpecialPage { * @return Mixed */ public static function arrayToString( $list ) { - if( is_array( $list ) && count( $list ) == 1 ) { + if ( is_array( $list ) && count( $list ) == 1 ) { $list = $list[0]; } - if( is_object( $list ) ) { + if ( is_object( $list ) ) { $class = wfMessage( 'parentheses' )->params( get_class( $list ) )->escaped(); return $class; } elseif ( !is_array( $list ) ) { return $list; } else { - if( is_object( $list[0] ) ) { + if ( is_object( $list[0] ) ) { $class = get_class( $list[0] ); } else { $class = $list[0]; @@ -656,7 +699,7 @@ class SpecialVersion extends SpecialPage { // http://svnbook.red-bean.com/nightly/en/svn.developer.insidewc.html $entries = $dir . '/.svn/entries'; - if( !file_exists( $entries ) ) { + if ( !file_exists( $entries ) ) { return false; } @@ -666,9 +709,9 @@ class SpecialVersion extends SpecialPage { } // check if file is xml (subversion release <= 1.3) or not (subversion release = 1.4) - if( preg_match( '/^<\?xml/', $lines[0] ) ) { + if ( preg_match( '/^<\?xml/', $lines[0] ) ) { // subversion is release <= 1.3 - if( !function_exists( 'simplexml_load_file' ) ) { + if ( !function_exists( 'simplexml_load_file' ) ) { // We could fall back to expat... YUCK return false; } @@ -678,11 +721,11 @@ class SpecialVersion extends SpecialPage { $xml = simplexml_load_file( $entries ); wfRestoreWarnings(); - if( $xml ) { - foreach( $xml->entry as $entry ) { - if( $xml->entry[0]['name'] == '' ) { + if ( $xml ) { + foreach ( $xml->entry as $entry ) { + if ( $xml->entry[0]['name'] == '' ) { // The directory entry should always have a revision marker. - if( $entry['revision'] ) { + if ( $entry['revision'] ) { return array( 'checkout-rev' => intval( $entry['revision'] ) ); } } @@ -722,7 +765,7 @@ class SpecialVersion extends SpecialPage { /** * Retrieve the revision number of a Subversion working directory. * - * @param $dir String: directory of the svn checkout + * @param string $dir directory of the svn checkout * * @return Integer: revision number as int */ @@ -739,7 +782,7 @@ class SpecialVersion extends SpecialPage { } /** - * @param $dir String: directory of the git checkout + * @param string $dir directory of the git checkout * @return bool|String sha1 of commit HEAD points to */ public static function getGitHeadSha1( $dir ) { @@ -753,19 +796,32 @@ class SpecialVersion extends SpecialPage { */ public function getEntryPointInfo() { global $wgArticlePath, $wgScriptPath; + $scriptPath = $wgScriptPath ? $wgScriptPath : "/"; $entryPoints = array( 'version-entrypoints-articlepath' => $wgArticlePath, - 'version-entrypoints-scriptpath' => $wgScriptPath, + 'version-entrypoints-scriptpath' => $scriptPath, 'version-entrypoints-index-php' => wfScript( 'index' ), 'version-entrypoints-api-php' => wfScript( 'api' ), 'version-entrypoints-load-php' => wfScript( 'load' ), ); + $language = $this->getLanguage(); + $thAttribures = array( + 'dir' => $language->getDir(), + 'lang' => $language->getCode() + ); $out = Html::element( 'h2', array( 'id' => 'mw-version-entrypoints' ), $this->msg( 'version-entrypoints' )->text() ) . - Html::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'mw-version-entrypoints-table' ) ) . + Html::openElement( 'table', + array( + 'class' => 'wikitable plainlinks', + 'id' => 'mw-version-entrypoints-table', + 'dir' => 'ltr', + 'lang' => 'en' + ) + ) . Html::openElement( 'tr' ) . - Html::element( 'th', array(), $this->msg( 'version-entrypoints-header-entrypoint' )->text() ) . - Html::element( 'th', array(), $this->msg( 'version-entrypoints-header-url' )->text() ) . + Html::element( 'th', $thAttribures, $this->msg( 'version-entrypoints-header-entrypoint' )->text() ) . + Html::element( 'th', $thAttribures, $this->msg( 'version-entrypoints-header-url' )->text() ) . Html::closeElement( 'tr' ); foreach ( $entryPoints as $message => $value ) { @@ -782,108 +838,8 @@ class SpecialVersion extends SpecialPage { return $out; } - function showEasterEgg() { - $rx = $rp = $xe = ''; - $alpha = array("", "kbQW", "\$\n()"); - $beta = implode( "', '", $alpha); - $juliet = 'echo $delta + strrev($foxtrot) - $alfa + $wgVersion . base64_decode($bravo) * $charlie'; - for ( $i = 1; $i <= 4; $i++ ) { - $rx .= '([^j]*)J'; - $rp .= "+(\\$i)"; - } - - $rx = "/$rx/Sei"; - $O = substr("$alpha')", 1); - for ( $i = 1; $i <= strlen( $rx ) / 3; $i++ ) { - $rx[$i-1] = strtolower( $rx[$i-1] ); - } - $ry = ".*?(.((.)(.))).{1,3}(.)(.{1,$i})(\\4.\\3)(.).*"; - $ry = "/$ry/Sei"; - $O = substr("$beta')", 1); - preg_match_all('/(?<=\$)[[:alnum:]]*/',substr($juliet, 0, $i<<1), $charlie); - foreach( $charlie[0] as $bravo ) { - $$bravo =& $xe; - } - $xe = 'xe=<<<mo/./hfromowoxv=<<<m -쵍潅旅왎캎𐺆ߨ趥䲀쫥Ꝍ螃䤎꤯溃櫅褡䞠⽬✡栠迤⾏쾃줏袏浣।궇䬃꼁꿤𘐧 -윥桯䦎䵎Ꞅ涁쭀讀撠蝠讄伣枮ⵇ𐡃𐭏沢𞴏⠤쳯蒣䮎컡豣ۅ⦇𐫁漅蛁꼤从楆 -⥀䡦沢⠬輁䲯좡梇䟇伄육较촅䥃要迯쟠꺃ⶥ栆궀撠満ꐣ좧𐠅𐠧讇輤亀➏欣첡쮧⽬ -氀쮧跧𐫥䪀⬬⾅ⵏ괬ত櫤䭀楦괥챣楀귧읠죯쒡ۅ䳄䤄괬躏譇䮄搥䯄津䶮⾅𐫅 -𐴂௧쮯궣輥ߡ亀氀诤⿅諃⫤䮣⦬죄椎貧ඇ쿇亏跤⦌术থۏ仆䛇枡䪄곁謠ⶏⶃ䞣 -궥螏蝁ꤣ⟬极涇𞴧伢ଅ즡⡌浣䯇쿃ⳇ궏ས⢃曦⦥蛧갠컡楧𘬧袏⦏⢠䳠챤⽧⬣⼀潧⭅椤 -軁종쵃䬆꤇溎楯곡⢡꾥첥쫧Ⱨ균檏辀䭮⡄𐞯쿁䱤𐠠柅迠웏⾅豠𐡀𐡅䱀轡⾯쥃⥁溆 -䢣䞮柄ꠌⶡ𐳣蛤椏✠귬ຄ䶃毥𞡯桥ꐥ❣쳀⡧𖥢꽧죄തޥ歠ແ위䯎撯쬁䮣浅 -쾇泮𐢁켄䦯꾯迡曎䢦쿣杦궯⡀䤦䷢𐭢쟁쯯⧤蟯䡏氇𞢣蝤궧ߢ𐭆䛃찃쭣沠 -䴃𐣣䣎𐺃ꥅ轃⣄蟧⦡蟃毣洇䞎Ҡ潄仆𐲃철䢤俎譯泠쮄␥栏쾯ⳏ짡⥡߂ކ澥䲀ⵀ -ⵡ✬輄䱀굡榏❡첄⦄ꡥⶣ𞡤⺁ݣ𐢅⤡꿄蝡ⴄ贁氃ޅ짣߁𐫄ۥ𐱅欤 -梢蝡柧䥏仏撣𐳣𞠅좇蒣䰤྅࿂ಇ濤䞦쮅沮潁좤澅杣棦ꤤ洯𐳃콅궧쭠 -桎䝆겡쭄겯䥂ⶀ⽬䠇쳄❬Ⰼ䐦⿌웃𒿠첏𐛡浣涆⢤অ䭎갣䴮⡃꤯죠䰀쬯༄䫏 -𐱂ꢅ䬦賧유辇➥佃仮귣젏⭅ꢡ각컄⒤⻠讁涅䠃跥袏佄𐳇泄껧棇满གྷ輤괅❥겠 -𒐧䣂ꤏ襃伎襡웅걯䳣켁쭄洠컥❅⿏亂쯅⢁𐠦诤꣏辀𖥢椯겇毣濢➠ -䮮浥겁沣졣䜦泇歏𐾄搯曯柆ۇۇ䞀泆武況꽌𐧢ꝅ軀⬠쾣𞡀榧𞣏Ⱡ䠦Ⲥ쿇䬄貃柅涢 -갏⼁𐿧ݏౠ𐿣褀涡༅䮄➇ꝣݥ䡏䯎梢輇ꤠ䫣䵀ण漂⢡軀௦襁쫇⾡濧沤 -䜇伢ۇ汧첏䤎잤䛯Ⰱ俇ꢧ殂궏榮ޣ涂氏滦즤蜀⠥𐺏쐣⾏껬콇漯Ꝡ柦櫇읁梠仇장滦⟠꿯 -쮁搥櫢𐫣ꠏ椥𐛤誅栮朥迣⺄ඇ⿏䬂쾏⫠⒧✏궇襤⡁濃Ⱐ歯䛠쮠𞟤컃𞢯⬣濡䦣 -衏貣柂森챏ಇ고蟄䤏젯⫯楀䞄䳣쮅궤轧껯𐪃潇ބ浣𐬀蝤⽧쐣쾇➣𐡦䮠䤣𐠄 -Ꝡ𐾁蠤䬦覯搦⥯쥏梂걯ⵁ೦챁躄轡䢦𐝂財䲧𐦁䬎첁棏␣౦잧棆젥襁젃䤏⢏榀ⵁ -螅赡𒿯ⶣ赧꾤濁涆𐴂ॡ䳦ߢ赁䯇䢃ꠌ泄柠泡찇𐛢䪂𐝢櫇漥⟤淣ഡ䳮த谀ཡ -➁血꽧蟧辧게⻣쳏ഡ䠄杮죃汦諤య毠蝅𐦄謄殯䳀ⳏ쟇ආ잏𐿡䳃ۂ䝇䦇⥌켏쥯춏 -𖽢𐳃𐿧𐝢䥦棇潡⥄歡찁朆⻠䤆𖤧漢ꡅ⽄쾠衏䤣অ䤣𐡡𐢏䞦ߣ裏 -ཅۄ춁䲃欆귬𐺀诀滁䝃챃첥꺏쫅䱮અ견Ф𐫁佣澢쿏⽅侮榅𐾄य쥏蜏䣣 -𐫏쵥➤跡殃䰣䯤읤ⴏ굄⥇줡걬০켃𜼧첣䜂찃궀谀Ɽ伎䢮ꤥ⾣𐭁沅䬇䧠𐱇 -沀濡ठ쟠𐺅ꐣ𐴂躄佇⦇毄计賀䢎澡䲄캀䟣褀蠤൯棏蜃澄❧⾥撦⽬ⶥ𐪄யބ躄 -䬎챯⽯䬎Ꞅ굥𐢂⠥䝧朄࿏웥꽬གྷ浅⦁❬𐺆侢栦⧠궠ඦ趤谥此𐲂𐬃軠𐞦 -蛄俧袥补榏읠⤁⠀豇俢쮯꤇➏𐴁ⶤ涮찣읁榠跣⦅ໃಆ䵣谠ꢯ⡧淯柤궡✠䮎괯❅朎 -⥅웣䯮첀꒤𐣠쭏洀蛡楆ൡ䮮ү氠𐜏濆䜢䷯潣歃䷯웁쭄椥䟂➅ૡༀ䭧ܣ죅ए軯䧣 -Ⱔ䐢⬥檂䠮⫤䛠꜡䛆讠✠꿏欣蠡켏豣譄𞣇춣䠃䰠撦朅䮄榦溃貀䶇⾁澡䲮榀 -𐪄䢆侄朦꜇ཏ췧꺁枠櫧桠괬枇ꜯ곇𐰂𘜧𐦄컡濦汥줠𞲡輀𐠣쥇⣃𞴏䳂⟤漇쯣껃𐾀衃 -쯇𐝄浥洄楠৯춥蒧⾯𐫆༂ꤌ毮䤆⺄༠०袀䢂죃ⴣ𐿯梇溄毦螄櫤쳃栅満걌毠ⱌ꒧䢆 -ꥁ泎仧궀辯諯웅津趃অ꿏伏캁⠃𐦂ꝣ䛂贤济杧𐝁撠䱤殥歡躇楄꒧꽧䡣쵧𐱆ꜯ위 -ཀ谠諃𐬃軅␥贠撣߅꽤⠥ಡ𐝀궥윁Ⰴܯ즡歎ⵅഏ蝁구ꝧ܅䱦껡䛦߅蒯俧콣梧䛠ꡇ -ݧ웥Т⬠䬦榀𐢂貤謣䱦⒡췧濇⧣⤀좯殧줤⣀楏楎굏ݤ滁ۇ𘐧䒯Ⰰҡ䰦椯❏ -趯𐣯豀쵅춀⳥䷠읡ۯ⺄ۅ䶏춤枂櫅ۅ𞥅䱃䭣汮澃𞢃谥ⵤ구콡曤𞣏ই߂읅蠠䞦ꞇⲏ諧 -趯첏䬎𐡏李겠⥇曢汥浆欠躅𐦁𞲯谡袧襃棧𞡡蟀侠찇챠쪇洠܀쯤䝇螏蜏俄⼀ལ -谥촯䲦⥁ඤ𐐧⤃궅༡褡䭏毆濆⧡蛣Ф蠏ݤ賯꜁溅⡡ߡ䮄榆䵄求謥𐐧Ꞁ쯏⧡貇䛇䐢撦袥 -쮇䫀দ굯⻤襇줅⬅ہఠ⻀쒠䫆𐡅梄梯輤䥣읏⤄ⶡ诃䮢譡ߤ枤櫥伦袠ꢃ쳀裣䰄 -槥淠䯃ඏ⒯𞠣椦泮汣赃潥ദ䰏쮡蝏毁䶂䦧档䪂쟀𐿯졇웄䳎汀𐫣 -漠ꐡଥ认꽡𐭏⦄梎આ枀䠦楇쒤ꞃꤡⴅꞅඅҡ氣즤裀櫁༦𐳃쳣𐡯桧权굁죁 -짤𖤧蟃澀𞲯ߏ⣣⬁Ⱔ졥潆ꐡ⽤웁浥𐫄棆갤濧⼣겅쬄൧젣此潆⻯䜃꤯궠쮥𘬧曀⿅譅槣䞂 -䝎ꡏ䰀梥⾬ܡ𞠥𞺃䢮આ䧮쮃誅櫆죯诠䵀䯀跥⻥䤆Ⰰ꜄棧枃⻇థ誃࿇贄𞡣欎⽡ -𞲄⬏杇𐠅𐱃𞢤➁𐢄꒥즏亀쭁漆첁殎쮁滠𐠥榯⡀䮆䣠준讥䶇⪅껃泃楀갠複撮 -✡𐭢ແ쫃⽤規䥇沁轁𐡅ಢ䧮椁⬇𐤁𞡯杅武楥歎䟄溇䯢迃䪎䳤满ଅⱇ쭀ಥ𞥄䥆⧥좃 -유栤༡𐰃俇Ⰵ殇蠄⽏⾠܇澄⡤䪎榮Я견濂賣쮠仠䝮䶢𐫆ݏ襅褥찯𞤤ݥ象侯쵇궥𞠃윀웧 -殀蛡⫥亃觯潥蠀补ⴄ觧𐡇𐾆ꐯ䡣췡潏⻯⾁諏య꿧䱠찥ꞅ⪃콄즯쳣覧Ⲅ쐯⬃ඤ겤 -ⵃ蟥谣轇䛂𐮄佀߁氣榡桇䷯觠椄챥ꠌ蒯꜌䭤➡侦䣤䲀쥁⒤𐦄Ꝭ䢮ꡌ歡䝯䢣괯⥀ -줣०殣⟄趥좠洦ꢬ装䠆曧➁𒿧椃䠀𞡅𖼧䳇ງ줄ধⰬ覠ꝃ殣涡䳠귥⫤覯𞲡༦ -䢦쥥줤ꡤড젃ಧꢥ諤ඥ枅줄躀ఏ䦎졯譄➇仄䰏蛏촡䞣춅涧⡄滀ଢ䮇每𘠧侇澀ꐡ杣 -槧߅䶠윥귡귧⤯ཆ裁毧⬤蝧첀⭁潤䝎池殤Ҡ䝯ཁ쟧氢귡𒿯ꥄ⭌䜇ۥ -ꝡ棄⣏ꤥ০쮁桧𐐧ⴤꠡ軅衄䠦ߤ܅ⲃଢ蛄溎椀𞠀䛃𞡣𞟣澅䧤⡇贤⫌쪄ށ朣 -⻏켅⼡𐲀잠௧𞥀౧䦤ས誇漎譠迄䦂䳇正계楧ޅ✬棅쭯诠枢䥮䭆楆컧ଆ -➬అ䤦誃𐠅𐿤䟀洀⡤滤𞥇즀𐠁⼃䰎溄꽅웇✡䲀⡏ܣ讣⼤覄䡇అ蝀⥌侧껄Ꝭ流贀 -漁쒤첧죏곡⣃趃賄撠।읠ⶌ⾥춧쒡쿀䵯毁涠⣡ꡄ䢀満棃䡯𐛣୯䳯ⵡୡ䥃❇⠅䣆杧𐳃 -귧覀漎𞴁𞤡ཇ䰦𞲣❃歆콣꿇朏𞢄Ꝍ𞡅賡曏꼃꼬ಇ𞴯资榎쮯輤ॡ䜎⦌𐠏⡃쳁࿀ -쯣껧쪃椃쐡⟤߇웅䱧䛣𐳤쮀䠏꽣⠣쟣𞢅ദ洅촥컇쵡ꞅ䠆⒥涯䐢ⴅ쮤꺅 -𞥇컠ⳁ漃𐲃윇诤겣𞥄伣䜠⻇𞡀修꜡䳎❄켇꽡쭄洂꜠Ⰳ쵅𐬂梀櫯䜯꜡䛣༏杇⪀캄⼌ -条𐳄没ⳅ➏첡❬侯캅检𞡧棡䥧𐳃𐝁ཧ謏𐫇讄枥첡쾀欎육웠𐭤୯濧譁챤䶢껤 -쒤𐾂辧褡⼣䳃␠豁ߡ櫦极ⶠઇꝠ𐭤沣棁柄𐳂䠯楅곅⼣⥃ༀ螡ߥ柤褣曠沧꒬ -𐴃䵂䲇蠀𐿧䲇ඦ⺁커謁컁漢䠀调ⲃ䢢ބ辅毡갯䤣椦𞲯१輯𘜧𐳅⽄䴆ଦ -䱠䒮諃ఏ𐠡桦谁𐡁쥡浣譀⫌쮥ꢅ컁曅ꥅଏ찀汅ೡ谠䬀𞴡䢠쳀⡏ߠߠඅ겧淤 -쥣每譄꼠쫁쭥讥ॡ쿇ஆ伃⫠汇䜢衯楥济俏极撮쬅蜏⧤蛥쮁⥃것ஃ줠䣇迅泆⤯𐧣 -萠泎ଡ蠄涣త⾏⻌䝧ༀ榮ү𐳃歂浅ꡥ첤⬇유讏欤俤잧⡌ⱁ춥氤𐠧修流쫤䵆𞠃܀웣 -곧萡ꠀ걁𞟠认쮀谥잡佮𞺏軡⾁쮯ߡ⧯쟡䰆⽀굇촤认䵄輥𞲇䡮侢朆쬣搢⽃濃⣧柁༢ -⼅ॠ軀浯ܡ컡谤ඤ曢⧠짠컠꿡𐺀곌濂ণ웧⾡栅䞠괬ܤ䦄伏曀了ཡ榧䭦⛃衧濠읥 -쵁𐛣⪅蜤𞤁装고쳅⻁ݣ䳆ৠ䐦ऄ⫏쿧䜎𐿣젡귧棥櫁쿣泯俣佦⾥朦潏ꢤꙧ𐺆ڦՈ췥 -췧䙭䶍澥쨯쵥Ⱕ쵥䗌쵍潅旅暬Ոⵤ旆줭젠ৡ쮠┢潧贮跣쓄䔭⽇𞴥ꔥ䓭 -₎챍澥엇곭贇Ԇ쬡쩯䘠䯃湁Ո꽤엇ꔭ₎谥䗌쳭䙭䟍◎쳭䙍侭쾇쵤蓄䕍췥췧䓭◎쳭 -䒭ߏ䓭亭è청䙭侭䷤擏䕍췤⽇䐍䕍ⵤ摆位ཧ暬è춍찤ⲥ䙭䔭è谥䗌첍䙭䟍◎䕍 -엎ߏ◎첍⒬䓭亭è效𐱅궤◄虬䶭侄䗌꾄쓅䕍췥췧╂旄◌첍旌藂꾄쓅䕍ⵤ檦첍旌暬è效 -꽤엇虬䕍𐱅궤⚤è챍澥엇춍찤ⲥ₎찭䙭侭쾇൧蓇䕍꽤엇暬೨藅䗌ⳇ查䗌찭䓭䙭䔭 -枅ද➥赏ⵯඏ춥쟅ⵅ쟥螥ⴅ춯䟏췯淯䴏ꗍ旌₆效ꡁ桁⪣꼭ⱅ졣쓀暬è -줭젠ৡ쮠┢꽠跮쵅䭀𞡀䗌è斈쳮𞴤侭ට潅暅汤津࿄𞴥ⶎ澥쑏肌惨澈漥쵤 -趤굄䶍澥쨯Ⱕ쵥䗌찭䓭䓭䐍è惨Э薎è擨₎ -mowoxf=<<<moDzk=hgs8GbPbqrcbvagDdJkbe zk=zk>0kssss?zk-0k10000:zk kbe zk=DDzk<<3&0kssssJ|Dzk>>13JJ^3658 kbe zk=pueDzk&0kssJ.pueDzk>>8JJ?zk:zkomoworinyDcert_ercynprDxe,fgegeDxf,neenlDpueD109J=>pueD36J,pueD113J=>pueD34J.pueD92J. 0 .pueD34JJJ,fgegeDxv,neenlDpueD13J=>snyfr,pueD10J=>snyfrJJJJwo'; - - $haystack = preg_replace($ry, "$1$2$5$1_$7$89$i$5$6$8$O", $juliet); - return preg_replace( $rx, $rp, $haystack ); + protected function getGroupName() { + return 'wiki'; } + } diff --git a/includes/specials/SpecialWantedcategories.php b/includes/specials/SpecialWantedcategories.php index 0b1fb251..d2ffdb94 100644 --- a/includes/specials/SpecialWantedcategories.php +++ b/includes/specials/SpecialWantedcategories.php @@ -35,22 +35,22 @@ class WantedCategoriesPage extends WantedQueryPage { } function getQueryInfo() { - return array ( - 'tables' => array ( 'categorylinks', 'page' ), - 'fields' => array ( 'namespace' => NS_CATEGORY, + return array( + 'tables' => array( 'categorylinks', 'page' ), + 'fields' => array( 'namespace' => NS_CATEGORY, 'title' => 'cl_to', 'value' => 'COUNT(*)' ), - 'conds' => array ( 'page_title IS NULL' ), - 'options' => array ( 'GROUP BY' => 'cl_to' ), - 'join_conds' => array ( 'page' => array ( 'LEFT JOIN', - array ( 'page_title = cl_to', + 'conds' => array( 'page_title IS NULL' ), + 'options' => array( 'GROUP BY' => 'cl_to' ), + 'join_conds' => array( 'page' => array( 'LEFT JOIN', + array( 'page_title = cl_to', 'page_namespace' => NS_CATEGORY ) ) ) ); } /** - * @param $skin Skin - * @param $result + * @param Skin $skin + * @param object $result Result row * @return string */ function formatResult( $skin, $result ) { @@ -72,4 +72,8 @@ class WantedCategoriesPage extends WantedQueryPage { $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped(); return $this->getLanguage()->specialList( $plink, $nlinks ); } + + protected function getGroupName() { + return 'maintenance'; + } } diff --git a/includes/specials/SpecialWantedfiles.php b/includes/specials/SpecialWantedfiles.php index f52f7bb9..b5c1fdbe 100644 --- a/includes/specials/SpecialWantedfiles.php +++ b/includes/specials/SpecialWantedfiles.php @@ -42,7 +42,7 @@ class WantedFilesPage extends WantedQueryPage { $catMessage = $this->msg( 'broken-file-category' ) ->title( Title::newFromText( "Wanted Files", NS_MAIN ) ) ->inContentLanguage(); - + if ( !$catMessage->isDisabled() ) { $category = Title::makeTitleSafe( NS_CATEGORY, $catMessage->text() ); } else { @@ -73,18 +73,22 @@ class WantedFilesPage extends WantedQueryPage { } function getQueryInfo() { - return array ( - 'tables' => array ( 'imagelinks', 'image' ), - 'fields' => array ( 'namespace' => NS_FILE, + return array( + 'tables' => array( 'imagelinks', 'image' ), + 'fields' => array( 'namespace' => NS_FILE, 'title' => 'il_to', 'value' => 'COUNT(*)' ), - 'conds' => array ( 'img_name IS NULL' ), - 'options' => array ( 'GROUP BY' => 'il_to' ), - 'join_conds' => array ( 'image' => - array ( 'LEFT JOIN', - array ( 'il_to = img_name' ) + 'conds' => array( 'img_name IS NULL' ), + 'options' => array( 'GROUP BY' => 'il_to' ), + 'join_conds' => array( 'image' => + array( 'LEFT JOIN', + array( 'il_to = img_name' ) ) ) ); } + + protected function getGroupName() { + return 'maintenance'; + } } diff --git a/includes/specials/SpecialWantedpages.php b/includes/specials/SpecialWantedpages.php index 7673305d..acec4ea4 100644 --- a/includes/specials/SpecialWantedpages.php +++ b/includes/specials/SpecialWantedpages.php @@ -27,10 +27,13 @@ * @ingroup SpecialPage */ class WantedPagesPage extends WantedQueryPage { - + function __construct( $name = 'Wantedpages' ) { parent::__construct( $name ); - $this->mIncludable = true; + } + + function isIncludable() { + return true; } function execute( $par ) { @@ -88,4 +91,8 @@ class WantedPagesPage extends WantedQueryPage { wfRunHooks( 'WantedPages::getQueryInfo', array( &$this, &$query ) ); return $query; } + + protected function getGroupName() { + return 'maintenance'; + } } diff --git a/includes/specials/SpecialWantedtemplates.php b/includes/specials/SpecialWantedtemplates.php index f3e33698..d13fa031 100644 --- a/includes/specials/SpecialWantedtemplates.php +++ b/includes/specials/SpecialWantedtemplates.php @@ -38,18 +38,22 @@ class WantedTemplatesPage extends WantedQueryPage { } function getQueryInfo() { - return array ( - 'tables' => array ( 'templatelinks', 'page' ), - 'fields' => array ( 'namespace' => 'tl_namespace', + return array( + 'tables' => array( 'templatelinks', 'page' ), + 'fields' => array( 'namespace' => 'tl_namespace', 'title' => 'tl_title', 'value' => 'COUNT(*)' ), - 'conds' => array ( 'page_title IS NULL', + 'conds' => array( 'page_title IS NULL', 'tl_namespace' => NS_TEMPLATE ), - 'options' => array ( + 'options' => array( 'GROUP BY' => array( 'tl_namespace', 'tl_title' ) ), - 'join_conds' => array ( 'page' => array ( 'LEFT JOIN', - array ( 'page_namespace = tl_namespace', + 'join_conds' => array( 'page' => array( 'LEFT JOIN', + array( 'page_namespace = tl_namespace', 'page_title = tl_title' ) ) ) ); } + + protected function getGroupName() { + return 'maintenance'; + } } diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php index 5dfc1133..55dc6efd 100644 --- a/includes/specials/SpecialWatchlist.php +++ b/includes/specials/SpecialWatchlist.php @@ -26,8 +26,8 @@ class SpecialWatchlist extends SpecialPage { /** * Constructor */ - public function __construct( $page = 'Watchlist' ){ - parent::__construct( $page ); + public function __construct( $page = 'Watchlist', $restriction = 'viewmywatchlist' ) { + parent::__construct( $page, $restriction ); } /** @@ -41,7 +41,7 @@ class SpecialWatchlist extends SpecialPage { $output = $this->getOutput(); # Anons don't get a watchlist - if( $user->isAnon() ) { + if ( $user->isAnon() ) { $output->setPageTitle( $this->msg( 'watchnologin' ) ); $output->setRobotPolicy( 'noindex,nofollow' ); $llink = Linker::linkKnown( @@ -54,17 +54,16 @@ class SpecialWatchlist extends SpecialPage { return; } + // Check permissions + $this->checkPermissions(); + // Add feed links - $wlToken = $user->getOption( 'watchlisttoken' ); - if ( !$wlToken ) { - $wlToken = MWCryptRand::generateHex( 40 ); - $user->setOption( 'watchlisttoken', $wlToken ); - $user->saveSettings(); + $wlToken = $user->getTokenFromOption( 'watchlisttoken' ); + if ( $wlToken ) { + $this->addFeedLinks( array( 'action' => 'feedwatchlist', 'allrev' => 'allrev', + 'wlowner' => $user->getName(), 'wltoken' => $wlToken ) ); } - $this->addFeedLinks( array( 'action' => 'feedwatchlist', 'allrev' => 'allrev', - 'wlowner' => $user->getName(), 'wltoken' => $wlToken ) ); - $this->setHeaders(); $this->outputHeader(); @@ -74,9 +73,9 @@ class SpecialWatchlist extends SpecialPage { $request = $this->getRequest(); $mode = SpecialEditWatchlist::getMode( $request, $par ); - if( $mode !== false ) { + if ( $mode !== false ) { # TODO: localise? - switch( $mode ){ + switch ( $mode ) { case SpecialEditWatchlist::EDIT_CLEAR: $mode = 'clear'; break; @@ -87,56 +86,51 @@ class SpecialWatchlist extends SpecialPage { $mode = null; } $title = SpecialPage::getTitleFor( 'EditWatchlist', $mode ); - $output->redirect( $title->getLocalUrl() ); + $output->redirect( $title->getLocalURL() ); return; } - $nitems = $this->countItems(); + $dbr = wfGetDB( DB_SLAVE, 'watchlist' ); + + $nitems = $this->countItems( $dbr ); if ( $nitems == 0 ) { $output->addWikiMsg( 'nowatchlist' ); return; } - // @TODO: use FormOptions! + // @todo use FormOptions! $defaults = array( - /* float */ 'days' => floatval( $user->getOption( 'watchlistdays' ) ), /* 3.0 or 0.5, watch further below */ + /* float */ 'days' => floatval( $user->getOption( 'watchlistdays' ) ), /* bool */ 'hideMinor' => (int)$user->getBoolOption( 'watchlisthideminor' ), - /* bool */ 'hideBots' => (int)$user->getBoolOption( 'watchlisthidebots' ), + /* bool */ 'hideBots' => (int)$user->getBoolOption( 'watchlisthidebots' ), /* bool */ 'hideAnons' => (int)$user->getBoolOption( 'watchlisthideanons' ), - /* bool */ 'hideLiu' => (int)$user->getBoolOption( 'watchlisthideliu' ), + /* bool */ 'hideLiu' => (int)$user->getBoolOption( 'watchlisthideliu' ), /* bool */ 'hidePatrolled' => (int)$user->getBoolOption( 'watchlisthidepatrolled' ), - /* bool */ 'hideOwn' => (int)$user->getBoolOption( 'watchlisthideown' ), - /* ? */ 'namespace' => 'all', - /* ? */ 'invert' => false, + /* bool */ 'hideOwn' => (int)$user->getBoolOption( 'watchlisthideown' ), + /* bool */ 'extended' => (int)$user->getBoolOption( 'extendwatchlist' ), + /* ? */ 'namespace' => '', //means all + /* ? */ 'invert' => false, /* bool */ 'associated' => false, ); $this->customFilters = array(); wfRunHooks( 'SpecialWatchlistFilters', array( $this, &$this->customFilters ) ); - foreach( $this->customFilters as $key => $params ) { - $defaults[$key] = $params['msg']; + foreach ( $this->customFilters as $key => $params ) { + $defaults[$key] = $params['default']; } # Extract variables from the request, falling back to user preferences or # other default values if these don't exist - $prefs['days'] = floatval( $user->getOption( 'watchlistdays' ) ); - $prefs['hideminor'] = $user->getBoolOption( 'watchlisthideminor' ); - $prefs['hidebots'] = $user->getBoolOption( 'watchlisthidebots' ); - $prefs['hideanons'] = $user->getBoolOption( 'watchlisthideanons' ); - $prefs['hideliu'] = $user->getBoolOption( 'watchlisthideliu' ); - $prefs['hideown' ] = $user->getBoolOption( 'watchlisthideown' ); - $prefs['hidepatrolled' ] = $user->getBoolOption( 'watchlisthidepatrolled' ); - - # Get query variables $values = array(); - $values['days'] = $request->getVal( 'days', $prefs['days'] ); - $values['hideMinor'] = (int)$request->getBool( 'hideMinor', $prefs['hideminor'] ); - $values['hideBots'] = (int)$request->getBool( 'hideBots' , $prefs['hidebots'] ); - $values['hideAnons'] = (int)$request->getBool( 'hideAnons', $prefs['hideanons'] ); - $values['hideLiu'] = (int)$request->getBool( 'hideLiu' , $prefs['hideliu'] ); - $values['hideOwn'] = (int)$request->getBool( 'hideOwn' , $prefs['hideown'] ); - $values['hidePatrolled'] = (int)$request->getBool( 'hidePatrolled', $prefs['hidepatrolled'] ); - foreach( $this->customFilters as $key => $params ) { - $values[$key] = (int)$request->getBool( $key ); + $values['days'] = floatval( $request->getVal( 'days', $defaults['days'] ) ); + $values['hideMinor'] = (int)$request->getBool( 'hideMinor', $defaults['hideMinor'] ); + $values['hideBots'] = (int)$request->getBool( 'hideBots', $defaults['hideBots'] ); + $values['hideAnons'] = (int)$request->getBool( 'hideAnons', $defaults['hideAnons'] ); + $values['hideLiu'] = (int)$request->getBool( 'hideLiu', $defaults['hideLiu'] ); + $values['hideOwn'] = (int)$request->getBool( 'hideOwn', $defaults['hideOwn'] ); + $values['hidePatrolled'] = (int)$request->getBool( 'hidePatrolled', $defaults['hidePatrolled'] ); + $values['extended'] = (int)$request->getBool( 'extended', $defaults['extended'] ); + foreach ( $this->customFilters as $key => $params ) { + $values[$key] = (int)$request->getBool( $key, $defaults[$key] ); } # Get namespace value, if supplied, and prepare a WHERE fragment @@ -164,64 +158,41 @@ class SpecialWatchlist extends SpecialPage { $values['invert'] = $invert; $values['associated'] = $associated; - if( is_null( $values['days'] ) || !is_numeric( $values['days'] ) ) { - $big = 1000; /* The magical big */ - if( $nitems > $big ) { - # Set default cutoff shorter - $values['days'] = $defaults['days'] = (12.0 / 24.0); # 12 hours... - } else { - $values['days'] = $defaults['days']; # default cutoff for shortlisters - } - } else { - $values['days'] = floatval( $values['days'] ); - } - // Dump everything here $nondefaults = array(); foreach ( $defaults as $name => $defValue ) { wfAppendToArrayIfNotDefault( $name, $values[$name], $defaults, $nondefaults ); } - if( ( $wgEnotifWatchlist || $wgShowUpdatedMarker ) && $request->getVal( 'reset' ) && + if ( ( $wgEnotifWatchlist || $wgShowUpdatedMarker ) && $request->getVal( 'reset' ) && $request->wasPosted() ) { $user->clearAllNotifications(); - $output->redirect( $this->getTitle()->getFullUrl( $nondefaults ) ); + $output->redirect( $this->getTitle()->getFullURL( $nondefaults ) ); return; } - $dbr = wfGetDB( DB_SLAVE, 'watchlist' ); - # Possible where conditions $conds = array(); - if( $values['days'] > 0 ) { - $conds[] = "rc_timestamp > '".$dbr->timestamp( time() - intval( $values['days'] * 86400 ) )."'"; + if ( $values['days'] > 0 ) { + $conds[] = 'rc_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( time() - intval( $values['days'] * 86400 ) ) ); } - # If the watchlist is relatively short, it's simplest to zip - # down its entirety and then sort the results. - - # If it's relatively long, it may be worth our while to zip - # through the time-sorted page list checking for watched items. - - # Up estimate of watched items by 15% to compensate for talk pages... - - # Toggles - if( $values['hideOwn'] ) { + if ( $values['hideOwn'] ) { $conds[] = 'rc_user != ' . $user->getId(); } - if( $values['hideBots'] ) { + if ( $values['hideBots'] ) { $conds[] = 'rc_bot = 0'; } - if( $values['hideMinor'] ) { + if ( $values['hideMinor'] ) { $conds[] = 'rc_minor = 0'; } - if( $values['hideLiu'] ) { + if ( $values['hideLiu'] ) { $conds[] = 'rc_user = 0'; } - if( $values['hideAnons'] ) { + if ( $values['hideAnons'] ) { $conds[] = 'rc_user != 0'; } if ( $user->useRCPatrol() && $values['hidePatrolled'] ) { @@ -232,47 +203,75 @@ class SpecialWatchlist extends SpecialPage { } # Toggle watchlist content (all recent edits or just the latest) - if( $user->getOption( 'extendwatchlist' ) ) { - $limitWatchlist = intval( $user->getOption( 'wllimit' ) ); + if ( $values['extended'] ) { + $limitWatchlist = $user->getIntOption( 'wllimit' ); $usePage = false; } else { # Top log Ids for a page are not stored - $conds[] = 'rc_this_oldid=page_latest OR rc_type=' . RC_LOG; + $nonRevisionTypes = array( RC_LOG ); + wfRunHooks( 'SpecialWatchlistGetNonRevisionTypes', array( &$nonRevisionTypes ) ); + if ( $nonRevisionTypes ) { + if ( count( $nonRevisionTypes ) === 1 ) { + // if only one use an equality instead of IN condition + $nonRevisionTypes = reset( $nonRevisionTypes ); + } + $conds[] = $dbr->makeList( + array( + 'rc_this_oldid=page_latest', + 'rc_type' => $nonRevisionTypes, + ), + LIST_OR + ); + } $limitWatchlist = 0; $usePage = true; } # Show a message about slave lag, if applicable $lag = wfGetLB()->safeGetLag( $dbr ); - if( $lag > 0 ) { + if ( $lag > 0 ) { $output->showLagWarning( $lag ); } - # Create output form - $form = Xml::fieldset( $this->msg( 'watchlist-options' )->text(), false, array( 'id' => 'mw-watchlist-options' ) ); + # Create output + $form = ''; # Show watchlist header - $form .= $this->msg( 'watchlist-details' )->numParams( $nitems )->parse(); - - if( $user->getOption( 'enotifwatchlistpages' ) && $wgEnotifWatchlist) { - $form .= $this->msg( 'wlheader-enotif' )->parseAsBlock() . "\n"; + $form .= "<p>"; + $form .= $this->msg( 'watchlist-details' )->numParams( $nitems )->parse() . "\n"; + if ( $wgEnotifWatchlist && $user->getOption( 'enotifwatchlistpages' ) ) { + $form .= $this->msg( 'wlheader-enotif' )->parse() . "\n"; + } + if ( $wgShowUpdatedMarker ) { + $form .= $this->msg( 'wlheader-showupdated' )->parse() . "\n"; } - if( $wgShowUpdatedMarker ) { + $form .= "</p>"; + + if ( $wgShowUpdatedMarker ) { $form .= Xml::openElement( 'form', array( 'method' => 'post', - 'action' => $this->getTitle()->getLocalUrl(), - 'id' => 'mw-watchlist-resetbutton' ) ) . - $this->msg( 'wlheader-showupdated' )->parse() . ' ' . - Xml::submitButton( $this->msg( 'enotif_reset' )->text(), array( 'name' => 'dummy' ) ) . - Html::hidden( 'reset', 'all' ); - foreach ( $nondefaults as $key => $value ) { - $form .= Html::hidden( $key, $value ); - } - $form .= Xml::closeElement( 'form' ); - } - $form .= '<hr />'; + 'action' => $this->getTitle()->getLocalURL(), + 'id' => 'mw-watchlist-resetbutton' ) ) . "\n" . + Xml::submitButton( $this->msg( 'enotif_reset' )->text(), array( 'name' => 'dummy' ) ) . "\n" . + Html::hidden( 'reset', 'all' ) . "\n"; + foreach ( $nondefaults as $key => $value ) { + $form .= Html::hidden( $key, $value ) . "\n"; + } + $form .= Xml::closeElement( 'form' ) . "\n"; + } + + $form .= Xml::openElement( 'form', array( + 'method' => 'post', + 'action' => $this->getTitle()->getLocalURL(), + 'id' => 'mw-watchlist-form' + ) ); + $form .= Xml::fieldset( + $this->msg( 'watchlist-options' )->text(), + false, + array( 'id' => 'mw-watchlist-options' ) + ); $tables = array( 'recentchanges', 'watchlist' ); - $fields = array( $dbr->tableName( 'recentchanges' ) . '.*' ); + $fields = RecentChange::selectFields(); $join_conds = array( 'watchlist' => array( 'INNER JOIN', @@ -284,24 +283,40 @@ class SpecialWatchlist extends SpecialPage { ), ); $options = array( 'ORDER BY' => 'rc_timestamp DESC' ); - if( $wgShowUpdatedMarker ) { + if ( $wgShowUpdatedMarker ) { $fields[] = 'wl_notificationtimestamp'; } - if( $limitWatchlist ) { + if ( $limitWatchlist ) { $options['LIMIT'] = $limitWatchlist; } - $rollbacker = $user->isAllowed('rollback'); + $rollbacker = $user->isAllowed( 'rollback' ); if ( $usePage || $rollbacker ) { $tables[] = 'page'; - $join_conds['page'] = array('LEFT JOIN','rc_cur_id=page_id'); + $join_conds['page'] = array( 'LEFT JOIN', 'rc_cur_id=page_id' ); if ( $rollbacker ) { $fields[] = 'page_latest'; } } + // Log entries with DELETED_ACTION must not show up unless the user has + // the necessary rights. + if ( !$user->isAllowed( 'deletedhistory' ) ) { + $bitmask = LogPage::DELETED_ACTION; + } elseif ( !$user->isAllowed( 'suppressrevision' ) ) { + $bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED; + } else { + $bitmask = 0; + } + if ( $bitmask ) { + $conds[] = $dbr->makeList( array( + 'rc_type != ' . RC_LOG, + $dbr->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask", + ), LIST_OR ); + } + ChangeTags::modifyDisplayQuery( $tables, $fields, $conds, $join_conds, $options, '' ); - wfRunHooks('SpecialWatchlistQuery', array(&$conds,&$tables,&$join_conds,&$fields) ); + wfRunHooks( 'SpecialWatchlistQuery', array( &$conds, &$tables, &$join_conds, &$fields, $values ) ); $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $join_conds ); $numRows = $res->numRows(); @@ -310,21 +325,21 @@ class SpecialWatchlist extends SpecialPage { $lang = $this->getLanguage(); $wlInfo = ''; - if( $values['days'] > 0 ) { + if ( $values['days'] > 0 ) { $timestamp = wfTimestampNow(); $wlInfo = $this->msg( 'wlnote' )->numParams( $numRows, round( $values['days'] * 24 ) )->params( - $lang->userDate( $timestamp, $user ), $lang->userTime( $timestamp, $user ) )->parse() . '<br />'; + $lang->userDate( $timestamp, $user ), $lang->userTime( $timestamp, $user ) )->parse() . "<br />\n"; } - $cutofflinks = "\n" . $this->cutoffLinks( $values['days'], $nondefaults ) . "<br />\n"; + $cutofflinks = $this->cutoffLinks( $values['days'], $nondefaults ) . "<br />\n"; # Spit out some control panel links $filters = array( - 'hideMinor' => 'rcshowhideminor', - 'hideBots' => 'rcshowhidebots', - 'hideAnons' => 'rcshowhideanons', - 'hideLiu' => 'rcshowhideliu', - 'hideOwn' => 'rcshowhidemine', + 'hideMinor' => 'rcshowhideminor', + 'hideBots' => 'rcshowhidebots', + 'hideAnons' => 'rcshowhideanons', + 'hideLiu' => 'rcshowhideliu', + 'hideOwn' => 'rcshowhidemine', 'hidePatrolled' => 'rcshowhidepatr' ); foreach ( $this->customFilters as $key => $params ) { @@ -336,24 +351,28 @@ class SpecialWatchlist extends SpecialPage { } $links = array(); - foreach( $filters as $name => $msg ) { + foreach ( $filters as $name => $msg ) { $links[] = $this->showHideLink( $nondefaults, $msg, $name, $values[$name] ); } + $hiddenFields = $nondefaults; + unset( $hiddenFields['namespace'] ); + unset( $hiddenFields['invert'] ); + unset( $hiddenFields['associated'] ); + # Namespace filter and put the whole form together. $form .= $wlInfo; $form .= $cutofflinks; - $form .= $lang->pipeList( $links ); - $form .= Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalUrl(), 'id' => 'mw-watchlist-form-namespaceselector' ) ); - $form .= '<hr /><p>'; + $form .= $lang->pipeList( $links ) . "\n"; + $form .= "<hr />\n<p>"; $form .= Html::namespaceSelector( array( 'selected' => $nameSpace, 'all' => '', 'label' => $this->msg( 'namespace' )->text() ), array( - 'name' => 'namespace', - 'id' => 'namespace', + 'name' => 'namespace', + 'id' => 'namespace', 'class' => 'namespaceselector', ) ) . ' '; @@ -371,20 +390,19 @@ class SpecialWatchlist extends SpecialPage { $associated, array( 'title' => $this->msg( 'tooltip-namespace_association' )->text() ) ) . ' '; - $form .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . '</p>'; - $form .= Html::hidden( 'days', $values['days'] ); - foreach ( $filters as $key => $msg ) { - if ( $values[$key] ) { - $form .= Html::hidden( $key, 1 ); - } + $form .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . "</p>\n"; + foreach ( $hiddenFields as $key => $value ) { + $form .= Html::hidden( $key, $value ) . "\n"; } - $form .= Xml::closeElement( 'form' ); - $form .= Xml::closeElement( 'fieldset' ); + $form .= Xml::closeElement( 'fieldset' ) . "\n"; + $form .= Xml::closeElement( 'form' ) . "\n"; $output->addHTML( $form ); # If there's nothing to show, stop here - if( $numRows == 0 ) { - $output->addWikiMsg( 'watchnochange' ); + if ( $numRows == 0 ) { + $output->wrapWikiMsg( + "<div class='mw-changeslist-empty'>\n$1\n</div>", 'recentchanges-noresult' + ); return; } @@ -432,7 +450,10 @@ class SpecialWatchlist extends SpecialPage { $rc->numberofWatchingusers = 0; } - $s .= $list->recentChangesLine( $rc, $updated, $counter ); + $changeLine = $list->recentChangesLine( $rc, $updated, $counter ); + if ( $changeLine !== false ) { + $s .= $changeLine; + } } $s .= $list->endRecentChangesList(); @@ -441,7 +462,7 @@ class SpecialWatchlist extends SpecialPage { protected function showHideLink( $options, $message, $name, $value ) { $label = $this->msg( $value ? 'show' : 'hide' )->escaped(); - $options[$name] = 1 - (int) $value; + $options[$name] = 1 - (int)$value; return $this->msg( $message )->rawParams( Linker::linkKnown( $this->getTitle(), $label, array(), $options ) )->escaped(); } @@ -472,17 +493,19 @@ class SpecialWatchlist extends SpecialPage { /** * Returns html * + * @param int $days This gets overwritten, so is not used + * @param array $options Query parameters for URL * @return string */ protected function cutoffLinks( $days, $options = array() ) { $hours = array( 1, 2, 6, 12 ); $days = array( 1, 3, 7 ); $i = 0; - foreach( $hours as $h ) { + foreach ( $hours as $h ) { $hours[$i++] = $this->hoursLink( $h, $options ); } $i = 0; - foreach( $days as $d ) { + foreach ( $days as $d ) { $days[$i++] = $this->daysLink( $d, $options ); } return $this->msg( 'wlshowlast' )->rawParams( @@ -494,11 +517,10 @@ class SpecialWatchlist extends SpecialPage { /** * Count the number of items on a user's watchlist * + * @param DatabaseBase $dbr A database connection * @return Integer */ - protected function countItems() { - $dbr = wfGetDB( DB_SLAVE, 'watchlist' ); - + protected function countItems( $dbr ) { # Fetch the raw count $res = $dbr->select( 'watchlist', array( 'count' => 'COUNT(*)' ), array( 'wl_user' => $this->getUser()->getId() ), __METHOD__ ); @@ -507,4 +529,8 @@ class SpecialWatchlist extends SpecialPage { return floor( $count / 2 ); } + + protected function getGroupName() { + return 'changes'; + } } diff --git a/includes/specials/SpecialWhatlinkshere.php b/includes/specials/SpecialWhatlinkshere.php index f1356493..05c7dd5f 100644 --- a/includes/specials/SpecialWhatlinkshere.php +++ b/includes/specials/SpecialWhatlinkshere.php @@ -69,7 +69,7 @@ class SpecialWhatLinksHere extends SpecialPage { $opts->validateIntBounds( 'limit', 0, 5000 ); // Give precedence to subpage syntax - if ( isset($par) ) { + if ( isset( $par ) ) { $opts->setValue( 'target', $par ); } @@ -77,7 +77,7 @@ class SpecialWhatLinksHere extends SpecialPage { $this->opts = $opts; $this->target = Title::newFromURL( $opts->getValue( 'target' ) ); - if( !$this->target ) { + if ( !$this->target ) { $out->addHTML( $this->whatlinkshereForm() ); return; } @@ -94,11 +94,11 @@ class SpecialWhatLinksHere extends SpecialPage { } /** - * @param $level int Recursion level - * @param $target Title Target title - * @param $limit int Number of entries to display - * @param $from Title Display from this article ID - * @param $back Title Display from this article ID at backwards scrolling + * @param int $level Recursion level + * @param Title $target Target title + * @param int $limit Number of entries to display + * @param int $from Display from this article ID (default: 0) + * @param int $back Display from this article ID at backwards scrolling (default: 0) */ function showIndirectLinks( $level, $target, $limit, $from = 0, $back = 0 ) { global $wgMaxRedirectLinksRetrieved; @@ -111,7 +111,7 @@ class SpecialWhatLinksHere extends SpecialPage { $hidetrans = $this->opts->getValue( 'hidetrans' ); $hideimages = $target->getNamespace() != NS_FILE || $this->opts->getValue( 'hideimages' ); - $fetchlinks = (!$hidelinks || !$hideredirs); + $fetchlinks = ( !$hidelinks || !$hideredirs ); // Make the query $plConds = array( @@ -119,9 +119,9 @@ class SpecialWhatLinksHere extends SpecialPage { 'pl_namespace' => $target->getNamespace(), 'pl_title' => $target->getDBkey(), ); - if( $hideredirs ) { + if ( $hideredirs ) { $plConds['rd_from'] = null; - } elseif( $hidelinks ) { + } elseif ( $hidelinks ) { $plConds[] = 'rd_from is NOT NULL'; } @@ -137,7 +137,7 @@ class SpecialWhatLinksHere extends SpecialPage { ); $namespace = $this->opts->getValue( 'namespace' ); - if ( is_int($namespace) ) { + if ( is_int( $namespace ) ) { $plConds['page_namespace'] = $namespace; $tlConds['page_namespace'] = $namespace; $ilConds['page_namespace'] = $namespace; @@ -166,36 +166,40 @@ class SpecialWhatLinksHere extends SpecialPage { 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL' ))); - if( $fetchlinks ) { + if ( $fetchlinks ) { $options['ORDER BY'] = 'pl_from'; $plRes = $dbr->select( array( 'pagelinks', 'page', 'redirect' ), $fields, $plConds, __METHOD__, $options, - $joinConds); + $joinConds + ); } - if( !$hidetrans ) { + if ( !$hidetrans ) { $options['ORDER BY'] = 'tl_from'; $tlRes = $dbr->select( array( 'templatelinks', 'page', 'redirect' ), $fields, $tlConds, __METHOD__, $options, - $joinConds); + $joinConds + ); } - if( !$hideimages ) { + if ( !$hideimages ) { $options['ORDER BY'] = 'il_from'; $ilRes = $dbr->select( array( 'imagelinks', 'page', 'redirect' ), $fields, $ilConds, __METHOD__, $options, - $joinConds); + $joinConds + ); } - if( ( !$fetchlinks || !$dbr->numRows($plRes) ) && ( $hidetrans || !$dbr->numRows($tlRes) ) && ( $hideimages || !$dbr->numRows($ilRes) ) ) { + if ( ( !$fetchlinks || !$plRes->numRows() ) && ( $hidetrans || !$tlRes->numRows() ) && ( $hideimages || !$ilRes->numRows() ) ) { if ( 0 == $level ) { $out->addHTML( $this->whatlinkshereForm() ); // Show filters only if there are links - if( $hidelinks || $hidetrans || $hideredirs || $hideimages ) + if ( $hidelinks || $hidetrans || $hideredirs || $hideimages ) { $out->addHTML( $this->getFilterPanel() ); + } - $errMsg = is_int($namespace) ? 'nolinkshere-ns' : 'nolinkshere'; + $errMsg = is_int( $namespace ) ? 'nolinkshere-ns' : 'nolinkshere'; $out->addWikiMsg( $errMsg, $this->target->getPrefixedText() ); } return; @@ -204,21 +208,21 @@ class SpecialWhatLinksHere extends SpecialPage { // Read the rows into an array and remove duplicates // templatelinks comes second so that the templatelinks row overwrites the // pagelinks row, so we get (inclusion) rather than nothing - if( $fetchlinks ) { + if ( $fetchlinks ) { foreach ( $plRes as $row ) { $row->is_template = 0; $row->is_image = 0; $rows[$row->page_id] = $row; } } - if( !$hidetrans ) { + if ( !$hidetrans ) { foreach ( $tlRes as $row ) { $row->is_template = 1; $row->is_image = 0; $rows[$row->page_id] = $row; } } - if( !$hideimages ) { + if ( !$hideimages ) { foreach ( $ilRes as $row ) { $row->is_template = 0; $row->is_image = 1; @@ -269,7 +273,7 @@ class SpecialWhatLinksHere extends SpecialPage { $out->addHTML( $this->listEnd() ); - if( $level == 0 ) { + if ( $level == 0 ) { $out->addHTML( $prevnext ); } } @@ -292,7 +296,7 @@ class SpecialWhatLinksHere extends SpecialPage { } } - if( $row->rd_from ) { + if ( $row->rd_from ) { $query = array( 'redirect' => 'no' ); } else { $query = array(); @@ -308,12 +312,15 @@ class SpecialWhatLinksHere extends SpecialPage { // Display properties (redirect or template) $propsText = ''; $props = array(); - if ( $row->rd_from ) + if ( $row->rd_from ) { $props[] = $msgcache['isredirect']; - if ( $row->is_template ) + } + if ( $row->is_template ) { $props[] = $msgcache['istemplate']; - if( $row->is_image ) + } + if ( $row->is_image ) { $props[] = $msgcache['isimage']; + } if ( count( $props ) ) { $propsText = $this->msg( 'parentheses' )->rawParams( implode( $msgcache['semicolon-separator'], $props ) )->escaped(); @@ -334,8 +341,9 @@ class SpecialWhatLinksHere extends SpecialPage { protected function wlhLink( Title $target, $text ) { static $title = null; - if ( $title === null ) + if ( $title === null ) { $title = $this->getTitle(); + } return Linker::linkKnown( $title, @@ -360,7 +368,7 @@ class SpecialWhatLinksHere extends SpecialPage { $next = $this->msg( 'whatlinkshere-next' )->numParams( $currentLimit )->escaped(); $changed = $this->opts->getChangedValues(); - unset($changed['target']); // Already in the request title + unset( $changed['target'] ); // Already in the request title if ( 0 != $prevId ) { $overrides = array( 'from' => $this->opts->getValue( 'back' ) ); @@ -419,8 +427,8 @@ class SpecialWhatLinksHere extends SpecialPage { 'all' => '', 'label' => $this->msg( 'namespace' )->text() ), array( - 'name' => 'namespace', - 'id' => 'namespace', + 'name' => 'namespace', + 'id' => 'namespace', 'class' => 'namespaceselector', ) ); @@ -446,22 +454,27 @@ class SpecialWhatLinksHere extends SpecialPage { $hide = $this->msg( 'hide' )->escaped(); $changed = $this->opts->getChangedValues(); - unset($changed['target']); // Already in the request title + unset( $changed['target'] ); // Already in the request title $links = array(); $types = array( 'hidetrans', 'hidelinks', 'hideredirs' ); - if( $this->target->getNamespace() == NS_FILE ) + if ( $this->target->getNamespace() == NS_FILE ) { $types[] = 'hideimages'; + } // Combined message keys: 'whatlinkshere-hideredirs', 'whatlinkshere-hidetrans', 'whatlinkshere-hidelinks', 'whatlinkshere-hideimages' // To be sure they will be found by grep - foreach( $types as $type ) { + foreach ( $types as $type ) { $chosen = $this->opts->getValue( $type ); $msg = $chosen ? $show : $hide; $overrides = array( $type => !$chosen ); - $links[] = $this->msg( "whatlinkshere-{$type}" )->rawParams( + $links[] = $this->msg( "whatlinkshere-{$type}" )->rawParams( $this->makeSelfLink( $msg, array_merge( $changed, $overrides ) ) )->escaped(); } return Xml::fieldset( $this->msg( 'whatlinkshere-filters' )->text(), $this->getLanguage()->pipeList( $links ) ); } + + protected function getGroupName() { + return 'pagetools'; + } } diff --git a/includes/specials/SpecialWithoutinterwiki.php b/includes/specials/SpecialWithoutinterwiki.php index 2988b04f..9d23499f 100644 --- a/includes/specials/SpecialWithoutinterwiki.php +++ b/includes/specials/SpecialWithoutinterwiki.php @@ -44,21 +44,21 @@ class WithoutInterwikiPage extends PageQueryPage { global $wgScript; # Do not show useless input form if special page is cached - if( $this->isCached() ) { + if ( $this->isCached() ) { return ''; } $prefix = $this->prefix; $t = $this->getTitle(); - return Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, $this->msg( 'withoutinterwiki-legend' )->text() ) . - Html::hidden( 'title', $t->getPrefixedText() ) . - Xml::inputLabel( $this->msg( 'allpagesprefix' )->text(), 'prefix', 'wiprefix', 20, $prefix ) . ' ' . - Xml::submitButton( $this->msg( 'withoutinterwiki-submit' )->text() ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ); + return Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . "\n" . + Html::openElement( 'fieldset' ) . "\n" . + Html::element( 'legend', null, $this->msg( 'withoutinterwiki-legend' )->text() ) . "\n" . + Html::hidden( 'title', $t->getPrefixedText() ) . "\n" . + Xml::inputLabel( $this->msg( 'allpagesprefix' )->text(), 'prefix', 'wiprefix', 20, $prefix ) . "\n" . + Xml::submitButton( $this->msg( 'withoutinterwiki-submit' )->text() ) . "\n" . + Html::closeElement( 'fieldset' ) . "\n" . + Html::closeElement( 'form' ); } function sortDescending() { @@ -78,15 +78,15 @@ class WithoutInterwikiPage extends PageQueryPage { } function getQueryInfo() { - $query = array ( - 'tables' => array ( 'page', 'langlinks' ), - 'fields' => array ( 'namespace' => 'page_namespace', + $query = array( + 'tables' => array( 'page', 'langlinks' ), + 'fields' => array( 'namespace' => 'page_namespace', 'title' => 'page_title', 'value' => 'page_title' ), - 'conds' => array ( 'll_title IS NULL', + 'conds' => array( 'll_title IS NULL', 'page_namespace' => MWNamespace::getContentNamespaces(), 'page_is_redirect' => 0 ), - 'join_conds' => array ( 'langlinks' => array ( + 'join_conds' => array( 'langlinks' => array( 'LEFT JOIN', 'll_from = page_id' ) ) ); if ( $this->prefix ) { @@ -95,4 +95,8 @@ class WithoutInterwikiPage extends PageQueryPage { } return $query; } + + protected function getGroupName() { + return 'maintenance'; + } } |