diff options
Diffstat (limited to 'includes/specials')
86 files changed, 7027 insertions, 5762 deletions
diff --git a/includes/specials/SpecialActiveusers.php b/includes/specials/SpecialActiveusers.php index f016ab92..e4bf42d3 100644 --- a/includes/specials/SpecialActiveusers.php +++ b/includes/specials/SpecialActiveusers.php @@ -32,6 +32,16 @@ */ class ActiveUsersPager extends UsersPager { + /** + * @var FormOptions + */ + protected $opts; + + /** + * @var Array + */ + protected $groups; + function __construct( $group = null ) { global $wgRequest, $wgActiveUserDays; $this->RCMaxAge = $wgActiveUserDays; @@ -49,6 +59,10 @@ class ActiveUsersPager extends UsersPager { parent::__construct(); } + function getTitle() { + return SpecialPage::getTitleFor( 'Activeusers' ); + } + public function setupOptions() { global $wgRequest; diff --git a/includes/specials/SpecialAllmessages.php b/includes/specials/SpecialAllmessages.php index 296c6f50..2214b4ab 100644 --- a/includes/specials/SpecialAllmessages.php +++ b/includes/specials/SpecialAllmessages.php @@ -30,6 +30,11 @@ class SpecialAllmessages extends SpecialPage { /** + * @var AllmessagesTablePager + */ + protected $table; + + /** * Constructor */ public function __construct() { @@ -42,37 +47,102 @@ class SpecialAllmessages extends SpecialPage { * @param $par Mixed: parameter passed to the page or null */ public function execute( $par ) { - global $wgOut, $wgRequest; + $request = $this->getRequest(); + $out = $this->getOutput(); $this->setHeaders(); global $wgUseDatabaseMessages; if( !$wgUseDatabaseMessages ) { - $wgOut->addWikiMsg( 'allmessagesnotsupportedDB' ); + $out->addWikiMsg( 'allmessagesnotsupportedDB' ); return; } else { $this->outputHeader( 'allmessagestext' ); } - $this->filter = $wgRequest->getVal( 'filter', 'all' ); - $this->prefix = $wgRequest->getVal( 'prefix', '' ); + $out->addModuleStyles( 'mediawiki.special' ); + + $this->filter = $request->getVal( 'filter', 'all' ); + $this->prefix = $request->getVal( 'prefix', '' ); $this->table = new AllmessagesTablePager( $this, - $conds = array(), - wfGetLangObj( $wgRequest->getVal( 'lang', $par ) ) + array(), + wfGetLangObj( $request->getVal( 'lang', $par ) ) ); - $this->langCode = $this->table->lang->getCode(); + $this->langcode = $this->table->lang->getCode(); - $wgOut->addHTML( $this->buildForm() . + $out->addHTML( $this->table->buildForm() . $this->table->getNavigationBar() . - $this->table->getLimitForm() . $this->table->getBody() . $this->table->getNavigationBar() ); } +} + +/** + * Use TablePager for prettified output. We have to pretend that we're + * getting data from a table when in fact not all of it comes from the database. + */ +class AllmessagesTablePager extends TablePager { + + protected $filter, $prefix, $langcode, $displayPrefix; + + public $mLimitsShown; + + /** + * @var Language + */ + public $lang; + + /** + * @var null|bool + */ + public $custom; + + function __construct( $page, $conds, $langObj = null ) { + parent::__construct(); + $this->mIndexField = 'am_title'; + $this->mPage = $page; + $this->mConds = $conds; + $this->mDefaultDirection = true; // always sort ascending + $this->mLimitsShown = array( 20, 50, 100, 250, 500, 5000 ); + + global $wgLang, $wgContLang, $wgRequest; + + $this->talk = htmlspecialchars( wfMsg( 'talkpagelinktext' ) ); + + $this->lang = ( $langObj ? $langObj : $wgContLang ); + $this->langcode = $this->lang->getCode(); + $this->foreign = $this->langcode != $wgContLang->getCode(); + + if( $wgRequest->getVal( 'filter', 'all' ) === 'all' ){ + $this->custom = null; // So won't match in either case + } else { + $this->custom = ($wgRequest->getVal( 'filter' ) == 'unmodified'); + } + + $prefix = $wgLang->ucfirst( $wgRequest->getVal( 'prefix', '' ) ); + $prefix = $prefix != '' ? Title::makeTitleSafe( NS_MEDIAWIKI, $wgRequest->getVal( 'prefix', null ) ) : null; + if( $prefix !== null ){ + $this->displayPrefix = $prefix->getDBkey(); + $this->prefix = '/^' . preg_quote( $this->displayPrefix ) . '/i'; + } else { + $this->displayPrefix = false; + $this->prefix = false; + } + + // 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 ) { + $this->suffix = '/' . $this->langcode; + } else { + $this->suffix = ''; + } + } + function buildForm() { global $wgScript; @@ -88,7 +158,7 @@ class SpecialAllmessages extends SpecialPage { Xml::label( wfMsg( 'allmessages-prefix' ), 'mw-allmessages-form-prefix' ) . "</td>\n <td class=\"mw-input\">" . - Xml::input( 'prefix', 20, str_replace( '_', ' ', $this->prefix ), array( 'id' => 'mw-allmessages-form-prefix' ) ) . + Xml::input( 'prefix', 20, str_replace( '_', ' ', $this->displayPrefix ), array( 'id' => 'mw-allmessages-form-prefix' ) ) . "</td>\n </tr> <tr>\n @@ -124,72 +194,33 @@ class SpecialAllmessages extends SpecialPage { Xml::openElement( 'select', array( 'id' => 'mw-allmessages-form-lang', 'name' => 'lang' ) ); foreach( $languages as $lang => $name ) { - $selected = $lang == $this->langCode; + $selected = $lang == $this->langcode; $out .= Xml::option( $lang . ' - ' . $name, $lang, $selected ) . "\n"; } $out .= Xml::closeElement( 'select' ) . "</td>\n - </tr> - <tr>\n + </tr>" . + + '<tr> + <td class="mw-label">' . + Xml::label( wfMsg( 'table_pager_limit_label'), 'mw-table_pager_limit_label' ) . + '</td> + <td class="mw-input">' . + $this->getLimitSelect() . + '</td> + <tr> <td></td> - <td>" . + <td>' . Xml::submitButton( wfMsg( 'allmessages-filter-submit' ) ) . "</td>\n </tr>" . + Xml::closeElement( 'table' ) . - $this->table->getHiddenFields( array( 'title', 'prefix', 'filter', 'lang' ) ) . + $this->getHiddenFields( array( 'title', 'prefix', 'filter', 'lang', 'limit' ) ) . Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ); return $out; } -} - -/* use TablePager for prettified output. We have to pretend that we're - * getting data from a table when in fact not all of it comes from the database. - */ -class AllmessagesTablePager extends TablePager { - - public $mLimitsShown; - - function __construct( $page, $conds, $langObj = null ) { - parent::__construct(); - $this->mIndexField = 'am_title'; - $this->mPage = $page; - $this->mConds = $conds; - $this->mDefaultDirection = true; // always sort ascending - $this->mLimitsShown = array( 20, 50, 100, 250, 500, 5000 ); - - global $wgLang, $wgContLang, $wgRequest; - - $this->talk = htmlspecialchars( wfMsg( 'talkpagelinktext' ) ); - - $this->lang = ( $langObj ? $langObj : $wgContLang ); - $this->langcode = $this->lang->getCode(); - $this->foreign = $this->langcode != $wgContLang->getCode(); - - if( $wgRequest->getVal( 'filter', 'all' ) === 'all' ){ - $this->custom = null; // So won't match in either case - } else { - $this->custom = ($wgRequest->getVal( 'filter' ) == 'unmodified'); - } - - $prefix = $wgLang->ucfirst( $wgRequest->getVal( 'prefix', '' ) ); - $prefix = $prefix != '' ? Title::makeTitleSafe( NS_MEDIAWIKI, $wgRequest->getVal( 'prefix', null ) ) : null; - if( $prefix !== null ){ - $this->prefix = '/^' . preg_quote( $prefix->getDBkey() ) . '/i'; - } else { - $this->prefix = false; - } - $this->getSkin(); - - // 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 ) { - $this->suffix = '/' . $this->langcode; - } else { - $this->suffix = ''; - } - } function getAllMessages( $descending ) { wfProfileIn( __METHOD__ ); @@ -208,12 +239,16 @@ class AllmessagesTablePager extends TablePager { } /** - * Determine which of the MediaWiki and MediaWiki_talk namespace pages exist. - * Returns array( 'pages' => ..., 'talks' => ... ), where the subarrays have - * an entry for each existing page, with the key being the message name and + * Determine which of the MediaWiki and MediaWiki_talk namespace pages exist. + * Returns array( 'pages' => ..., 'talks' => ... ), where the subarrays have + * an entry for each existing page, with the key being the message name and * value arbitrary. + * + * @param array $messageNames + * @param string $langcode What language code + * @param bool $foreign Whether the $langcode is not the content language */ - function getCustomisedStatuses( $messageNames ) { + public static function getCustomisedStatuses( $messageNames, $langcode = 'en', $foreign = false ) { wfProfileIn( __METHOD__ . '-db' ); $dbr = wfGetDB( DB_SLAVE ); @@ -226,20 +261,19 @@ class AllmessagesTablePager extends TablePager { $xNames = array_flip( $messageNames ); $pageFlags = $talkFlags = array(); - + foreach ( $res as $s ) { if( $s->page_namespace == NS_MEDIAWIKI ) { - if( $this->foreign ) { + if( $foreign ) { $title = explode( '/', $s->page_title ); - if( count( $title ) === 2 && $this->langcode == $title[1] - && isset( $xNames[$title[0]] ) ) - { + if( count( $title ) === 2 && $langcode == $title[1] + && isset( $xNames[$title[0]] ) ) { $pageFlags["{$title[0]}"] = true; } } elseif( isset( $xNames[$s->page_title] ) ) { $pageFlags[$s->page_title] = true; } - } else if( $s->page_namespace == NS_MEDIAWIKI_TALK ){ + } elseif( $s->page_namespace == NS_MEDIAWIKI_TALK ){ $talkFlags[$s->page_title] = true; } } @@ -249,14 +283,15 @@ class AllmessagesTablePager extends TablePager { return array( 'pages' => $pageFlags, 'talks' => $talkFlags ); } - /* This function normally does a database query to get the results; we need + /** + * This function normally does a database query to get the results; we need * to make a pretend result using a FakeResultWrapper. */ function reallyDoQuery( $offset, $limit, $descending ) { $result = new FakeResultWrapper( array() ); $messageNames = $this->getAllMessages( $descending ); - $statuses = $this->getCustomisedStatuses( $messageNames ); + $statuses = self::getCustomisedStatuses( $messageNames, $this->langcode, $this->foreign ); $count = 0; foreach( $messageNames as $key ) { @@ -264,17 +299,21 @@ class AllmessagesTablePager extends TablePager { 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' => wfMsgGetKey( $key, /*useDB*/true, $this->langcode, false ), - 'am_default' => wfMsgGetKey( $key, /*useDB*/false, $this->langcode, false ), + 'am_actual' => $actual, + 'am_default' => $default, 'am_customised' => $customised, 'am_talk_exists' => isset( $statuses['talks'][$key] ) ); $count++; } - if( $count == $limit ) break; + if( $count == $limit ) { + break; + } } return $result; } @@ -306,9 +345,9 @@ class AllmessagesTablePager extends TablePager { $talk = Title::makeTitle( NS_MEDIAWIKI_TALK, $value . $this->suffix ); if( $this->mCurrentRow->am_customised ){ - $title = $this->mSkin->linkKnown( $title, $wgLang->lcfirst( $value ) ); + $title = Linker::linkKnown( $title, $wgLang->lcfirst( $value ) ); } else { - $title = $this->mSkin->link( + $title = Linker::link( $title, $wgLang->lcfirst( $value ), array(), @@ -317,9 +356,9 @@ class AllmessagesTablePager extends TablePager { ); } if ( $this->mCurrentRow->am_talk_exists ) { - $talk = $this->mSkin->linkKnown( $talk , $this->talk ); + $talk = Linker::linkKnown( $talk , $this->talk ); } else { - $talk = $this->mSkin->link( + $talk = Linker::link( $talk, $this->talk, array(), @@ -330,7 +369,6 @@ class AllmessagesTablePager extends TablePager { return $title . ' (' . $talk . ')'; case 'am_default' : - return Sanitizer::escapeHtmlAllowEntities( $value, ENT_QUOTES ); case 'am_actual' : return Sanitizer::escapeHtmlAllowEntities( $value, ENT_QUOTES ); } @@ -369,8 +407,10 @@ class AllmessagesTablePager extends TablePager { function getCellAttrs( $field, $value ){ if( $this->mCurrentRow->am_customised && $field == 'am_title' ){ return array( 'rowspan' => '2', 'class' => $field ); - } else { + } else if( $field == 'am_title' ) { return array( 'class' => $field ); + } else { + return array( 'lang' => $this->langcode, 'dir' => $this->lang->getDir(), 'class' => $field ); } } @@ -381,15 +421,19 @@ class AllmessagesTablePager extends TablePager { 'am_default' => wfMsg( 'allmessagesdefault' ) ); } + function getTitle() { return SpecialPage::getTitleFor( 'Allmessages', false ); } + function isFieldSortable( $x ){ return false; } + function getDefaultSort(){ return ''; } + function getQueryInfo(){ return ''; } diff --git a/includes/specials/SpecialAllpages.php b/includes/specials/SpecialAllpages.php index 5fa1aa47..a9cbf3ab 100644 --- a/includes/specials/SpecialAllpages.php +++ b/includes/specials/SpecialAllpages.php @@ -20,7 +20,7 @@ * @file * @ingroup SpecialPage */ - + /** * Implements Special:Allpages * @@ -58,24 +58,27 @@ class SpecialAllpages extends IncludableSpecialPage { * @param $par String: becomes "FOO" when called like Special:Allpages/FOO (default NULL) */ function execute( $par ) { - global $wgRequest, $wgOut, $wgContLang; + global $wgContLang; + $request = $this->getRequest(); + $out = $this->getOutput(); $this->setHeaders(); $this->outputHeader(); - $wgOut->allowClickjacking(); + $out->allowClickjacking(); # GET values - $from = $wgRequest->getVal( 'from', null ); - $to = $wgRequest->getVal( 'to', null ); - $namespace = $wgRequest->getInt( 'namespace' ); + $from = $request->getVal( 'from', null ); + $to = $request->getVal( 'to', null ); + $namespace = $request->getInt( 'namespace' ); $namespaces = $wgContLang->getNamespaces(); - $wgOut->setPagetitle( + $out->setPagetitle( ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces) ) ) ? wfMsg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) : wfMsg( 'allarticles' ) ); + $out->addModuleStyles( 'mediawiki.special' ); if( isset($par) ) { $this->showChunk( $namespace, $par, $to ); @@ -96,7 +99,7 @@ class SpecialAllpages extends IncludableSpecialPage { function namespaceForm( $namespace = NS_MAIN, $from = '', $to = '' ) { global $wgScript; $t = $this->getTitle(); - + $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) ); $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); $out .= Html::hidden( 'title', $t->getPrefixedText() ); @@ -141,7 +144,7 @@ class SpecialAllpages extends IncludableSpecialPage { * @param $to String: list all pages to this name */ function showToplevel( $namespace = NS_MAIN, $from = '', $to = '' ) { - global $wgOut; + $output = $this->getOutput(); # TODO: Either make this *much* faster or cache the title index points # in the querycache table. @@ -220,7 +223,7 @@ class SpecialAllpages extends IncludableSpecialPage { if( !empty($lines) ) { $this->showChunk( $namespace, $from, $to ); } else { - $wgOut->addHTML( $this->namespaceForm( $namespace, $from, $to ) ); + $output->addHTML( $this->namespaceForm( $namespace, $from, $to ) ); } return; } @@ -240,14 +243,13 @@ class SpecialAllpages extends IncludableSpecialPage { $out2 = ''; } else { if( isset($from) || isset($to) ) { - global $wgUser; $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ). '<tr> <td>' . $nsForm . '</td> <td class="mw-allpages-nav">' . - $wgUser->getSkin()->link( $this->getTitle(), wfMsgHtml ( 'allpages' ), + $this->getSkin()->link( $this->getTitle(), wfMsgHtml ( 'allpages' ), array(), array(), 'known' ) . "</td> </tr>" . @@ -256,7 +258,7 @@ class SpecialAllpages extends IncludableSpecialPage { $out2 = $nsForm; } } - $wgOut->addHTML( $out2 . $out ); + $output->addHTML( $out2 . $out ); } /** @@ -291,9 +293,9 @@ class SpecialAllpages extends IncludableSpecialPage { * @param $to String: list all pages to this name (default FALSE) */ function showChunk( $namespace = NS_MAIN, $from = false, $to = false ) { - global $wgOut, $wgUser, $wgContLang, $wgLang; - - $sk = $wgUser->getSkin(); + global $wgContLang, $wgLang; + $output = $this->getOutput(); + $sk = $this->getSkin(); $fromList = $this->getNamespaceKeyAndText($namespace, $from); $toList = $this->getNamespaceKeyAndText( $namespace, $to ); @@ -301,7 +303,7 @@ class SpecialAllpages extends IncludableSpecialPage { $n = 0; if ( !$fromList || !$toList ) { - $out = wfMsgWikiHtml( 'allpagesbadtitle' ); + $out = wfMsgExt( 'allpagesbadtitle', 'parse' ); } elseif ( !in_array( $namespace, array_keys( $namespaces ) ) ) { // Show errormessage and reset to NS_MAIN $out = wfMsgExt( 'allpages-bad-ns', array( 'parseinline' ), $namespace ); @@ -320,7 +322,7 @@ class SpecialAllpages extends IncludableSpecialPage { } $res = $dbr->select( 'page', - array( 'page_namespace', 'page_title', 'page_is_redirect' ), + array( 'page_namespace', 'page_title', 'page_is_redirect', 'page_id' ), $conds, __METHOD__, array( @@ -333,10 +335,10 @@ class SpecialAllpages extends IncludableSpecialPage { if( $res->numRows() > 0 ) { $out = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-chunk' ) ); while( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) { - $t = Title::makeTitle( $s->page_namespace, $s->page_title ); + $t = Title::newFromRow( $s ); if( $t ) { $link = ( $s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) . - $sk->linkKnown( $t, htmlspecialchars( $t->getText() ) ) . + $sk->link( $t ) . ($s->page_is_redirect ? '</div>' : '' ); } else { $link = '[[' . htmlspecialchars( $s->page_title ) . ']]'; @@ -373,7 +375,7 @@ class SpecialAllpages extends IncludableSpecialPage { 'page_title', array( 'page_namespace' => $namespace, 'page_title < '.$dbr->addQuotes($from) ), __METHOD__, - array( 'ORDER BY' => 'page_title DESC', + array( 'ORDER BY' => 'page_title DESC', 'LIMIT' => $this->maxPerPage, 'OFFSET' => ($this->maxPerPage - 1 ) ) ); @@ -409,7 +411,7 @@ class SpecialAllpages extends IncludableSpecialPage { $nsForm . '</td> <td class="mw-allpages-nav">' . - $sk->link( $self, wfMsgHtml ( 'allpages' ), array(), array(), 'known' ); + $sk->link( $self, wfMsgHtml ( 'allpages' ) ); # Do we put a previous link ? if( isset( $prevTitle ) && $pt = $prevTitle->getText() ) { @@ -420,7 +422,7 @@ class SpecialAllpages extends IncludableSpecialPage { $prevLink = $sk->linkKnown( $self, - htmlspecialchars( wfMsg( 'prevpage', $pt ) ), + wfMessage( 'prevpage', $pt )->escaped(), array(), $query ); @@ -437,7 +439,7 @@ class SpecialAllpages extends IncludableSpecialPage { $nextLink = $sk->linkKnown( $self, - htmlspecialchars( wfMsg( 'nextpage', $t->getText() ) ), + wfMessage( 'nextpage', $t->getText() )->escaped(), array(), $query ); @@ -446,20 +448,18 @@ class SpecialAllpages extends IncludableSpecialPage { $out2 .= "</td></tr></table>"; } - $wgOut->addHTML( $out2 . $out ); - if( isset($prevLink) or isset($nextLink) ) { - $wgOut->addHTML( '<hr /><p class="mw-allpages-nav">' ); - if( isset( $prevLink ) ) { - $wgOut->addHTML( $prevLink ); - } - if( isset( $prevLink ) && isset( $nextLink ) ) { - $wgOut->addHTML( wfMsgExt( 'pipe-separator' , 'escapenoentities' ) ); - } - if( isset( $nextLink ) ) { - $wgOut->addHTML( $nextLink ); - } - $wgOut->addHTML( '</p>' ); + $output->addHTML( $out2 . $out ); + + $links = array(); + if ( isset( $prevLink ) ) $links[] = $prevLink; + if ( isset( $nextLink ) ) $links[] = $nextLink; + if ( count( $links ) ) { + $output->addHTML( + Html::element( 'hr' ) . + Html::rawElement( 'div', array( 'class' => 'mw-allpages-nav' ), + $wgLang->pipeList( $links ) + ) ); } } @@ -468,17 +468,15 @@ class SpecialAllpages extends IncludableSpecialPage { * @param $ns Integer: the namespace of the article * @param $text String: the name of the article * @return array( int namespace, string dbkey, string pagename ) or NULL on error - * @static (sort of) - * @access private */ - function getNamespaceKeyAndText($ns, $text) { + protected function getNamespaceKeyAndText($ns, $text) { if ( $text == '' ) return array( $ns, '', '' ); # shortcut for common case $t = Title::makeTitleSafe($ns, $text); if ( $t && $t->isLocal() ) { return array( $t->getNamespace(), $t->getDBkey(), $t->getText() ); - } else if ( $t ) { + } elseif ( $t ) { return null; } diff --git a/includes/specials/SpecialAncientpages.php b/includes/specials/SpecialAncientpages.php index 2d5047d2..cbb5df80 100644 --- a/includes/specials/SpecialAncientpages.php +++ b/includes/specials/SpecialAncientpages.php @@ -28,8 +28,8 @@ */ class AncientPagesPage extends QueryPage { - function getName() { - return "Ancientpages"; + function __construct( $name = 'Ancientpages' ) { + parent::__construct( $name ); } function isExpensive() { @@ -38,20 +38,20 @@ class AncientPagesPage extends QueryPage { function isSyndicated() { return false; } - function getSQL() { - $db = wfGetDB( DB_SLAVE ); - $page = $db->tableName( 'page' ); - $revision = $db->tableName( 'revision' ); - $epoch = $db->unixTimestamp( 'rev_timestamp' ); + function getQueryInfo() { + return array( + 'tables' => array( 'page', 'revision' ), + 'fields' => array( 'page_namespace AS namespace', + 'page_title AS title', + 'rev_timestamp AS value' ), + 'conds' => array( 'page_namespace' => MWNamespace::getContentNamespaces(), + 'page_is_redirect' => 0, + 'page_latest=rev_id' ) + ); + } - return - "SELECT 'Ancientpages' as type, - page_namespace as namespace, - page_title as title, - $epoch as value - FROM $page, $revision - WHERE page_namespace=".NS_MAIN." AND page_is_redirect=0 - AND page_latest=rev_id"; + function usesTimestamps() { + return true; } function sortDescending() { @@ -67,14 +67,6 @@ class AncientPagesPage extends QueryPage { $title, htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) ) ); - return wfSpecialList($link, htmlspecialchars($d) ); + return wfSpecialList( $link, htmlspecialchars($d) ); } } - -function wfSpecialAncientpages() { - list( $limit, $offset ) = wfCheckLimits(); - - $app = new AncientPagesPage(); - - $app->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialBlankpage.php b/includes/specials/SpecialBlankpage.php index aaa45a80..42d33779 100644 --- a/includes/specials/SpecialBlankpage.php +++ b/includes/specials/SpecialBlankpage.php @@ -32,8 +32,7 @@ class SpecialBlankpage extends UnlistedSpecialPage { parent::__construct( 'Blankpage' ); } public function execute( $par ) { - global $wgOut; $this->setHeaders(); - $wgOut->addWikiMsg('intentionallyblankpage'); + $this->getOutput()->addWikiMsg('intentionallyblankpage'); } } diff --git a/includes/specials/SpecialBlock.php b/includes/specials/SpecialBlock.php new file mode 100644 index 00000000..f1fe8386 --- /dev/null +++ b/includes/specials/SpecialBlock.php @@ -0,0 +1,855 @@ +<?php +/** + * Implements Special:Block + * + * 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 allows users with 'block' right to block users from + * editing pages and other actions + * + * @ingroup SpecialPage + */ +class SpecialBlock extends SpecialPage { + + /** The maximum number of edits a user can have and still be hidden + * TODO: config setting? */ + const HIDEUSER_CONTRIBLIMIT = 1000; + + /** @var User user to be blocked, as passed either by parameter (url?wpTarget=Foo) + * or as subpage (Special:Block/Foo) */ + protected $target; + + /// @var Block::TYPE_ constant + protected $type; + + /// @var User|String the previous block target + protected $previousTarget; + + /// @var Bool whether the previous submission of the form asked for HideUser + protected $requestedHideUser; + + /// @var Bool + protected $alreadyBlocked; + + /// @var Array + protected $preErrors = array(); + + public function __construct() { + parent::__construct( 'Block', 'block' ); + } + + public function execute( $par ) { + global $wgUser, $wgOut, $wgRequest; + + # Permission check + if( !$this->userCanExecute( $wgUser ) ) { + $this->displayRestrictionError(); + return; + } + + # Can't block when the database is locked + if( wfReadOnly() ) { + throw new ReadOnlyError; + } + + # Extract variables from the request. Try not to get into a situation where we + # need to extract *every* variable from the form just for processing here, but + # there are legitimate uses for some variables + list( $this->target, $this->type ) = self::getTargetAndType( $par, $wgRequest ); + if ( $this->target instanceof User ) { + # Set the 'relevant user' in the skin, so it displays links like Contributions, + # User logs, UserRights, etc. + $this->getSkin()->setRelevantUser( $this->target ); + } + + list( $this->previousTarget, /*...*/ ) = Block::parseTarget( $wgRequest->getVal( 'wpPreviousTarget' ) ); + $this->requestedHideUser = $wgRequest->getBool( 'wpHideUser' ); + + # bug 15810: blocked admins should have limited access here + $status = self::checkUnblockSelf( $this->target ); + if ( $status !== true ) { + throw new ErrorPageError( 'badaccess', $status ); + } + + $wgOut->setPageTitle( wfMsg( 'blockip-title' ) ); + $wgOut->addModules( 'mediawiki.special', 'mediawiki.special.block' ); + + $out = $this->getOutput(); + $out->setPageTitle( wfMsg( 'blockip-title' ) ); + $out->addModules( array( 'mediawiki.special', 'mediawiki.special.block' ) ); + + $fields = $this->getFormFields(); + $this->maybeAlterFormDefaults( $fields ); + + $form = new HTMLForm( $fields, $this->getContext() ); + $form->setWrapperLegend( wfMsg( 'blockip-legend' ) ); + $form->setSubmitCallback( array( __CLASS__, 'processForm' ) ); + + $t = $this->alreadyBlocked + ? wfMsg( 'ipb-change-block' ) + : wfMsg( 'ipbsubmit' ); + $form->setSubmitText( $t ); + + $this->doPreText( $form ); + $this->doHeadertext( $form ); + $this->doPostText( $form ); + + if( $form->show() ){ + $wgOut->setPageTitle( wfMsg( 'blockipsuccesssub' ) ); + $wgOut->addWikiMsg( 'blockipsuccesstext', $this->target ); + } + } + + /** + * Get the HTMLForm descriptor array for the block form + * @return Array + */ + protected static function getFormFields(){ + global $wgUser, $wgBlockAllowsUTEdit; + + $a = array( + 'Target' => array( + 'type' => 'text', + 'label-message' => 'ipadressorusername', + 'tabindex' => '1', + 'id' => 'mw-bi-target', + 'size' => '45', + 'required' => true, + 'validation-callback' => array( __CLASS__, 'validateTargetField' ), + ), + 'Expiry' => array( + 'type' => !count( self::getSuggestedDurations() ) ? 'text' : 'selectorother', + 'label-message' => 'ipbexpiry', + 'required' => true, + 'tabindex' => '2', + 'options' => self::getSuggestedDurations(), + 'other' => wfMsg( 'ipbother' ), + ), + 'Reason' => array( + 'type' => 'selectandother', + 'label-message' => 'ipbreason', + 'options-message' => 'ipbreason-dropdown', + ), + 'CreateAccount' => array( + 'type' => 'check', + 'label-message' => 'ipbcreateaccount', + 'default' => true, + ), + ); + + if( self::canBlockEmail( $wgUser ) ) { + $a['DisableEmail'] = array( + 'type' => 'check', + 'label-message' => 'ipbemailban', + ); + } + + if( $wgBlockAllowsUTEdit ){ + $a['DisableUTEdit'] = array( + 'type' => 'check', + 'label-message' => 'ipb-disableusertalk', + 'default' => false, + ); + } + + $a['AutoBlock'] = array( + 'type' => 'check', + 'label-message' => 'ipbenableautoblock', + 'default' => true, + ); + + # Allow some users to hide name from block log, blocklist and listusers + if( $wgUser->isAllowed( 'hideuser' ) ) { + $a['HideUser'] = array( + 'type' => 'check', + 'label-message' => 'ipbhidename', + 'cssclass' => 'mw-block-hideuser', + ); + } + + # Watchlist their user page? (Only if user is logged in) + if( $wgUser->isLoggedIn() ) { + $a['Watch'] = array( + 'type' => 'check', + 'label-message' => 'ipbwatchuser', + ); + } + + $a['HardBlock'] = array( + 'type' => 'check', + 'label-message' => 'ipb-hardblock', + 'default' => false, + ); + + # This is basically a copy of the Target field, but the user can't change it, so we + # can see if the warnings we maybe showed to the user before still apply + $a['PreviousTarget'] = array( + 'type' => 'hidden', + 'default' => false, + ); + + # We'll turn this into a checkbox if we need to + $a['Confirm'] = array( + 'type' => 'hidden', + 'default' => '', + 'label-message' => 'ipb-confirm', + ); + + return $a; + } + + /** + * 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 + * @return Bool whether fields were altered (that is, whether the target is + * already blocked) + */ + protected function maybeAlterFormDefaults( &$fields ){ + global $wgRequest, $wgUser; + + # This will be overwritten by request data + $fields['Target']['default'] = (string)$this->target; + + # This won't be + $fields['PreviousTarget']['default'] = (string)$this->target; + + $block = Block::newFromTarget( $this->target ); + + 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 + ) + { + $fields['HardBlock']['default'] = $block->isHardblock(); + $fields['CreateAccount']['default'] = $block->prevents( 'createaccount' ); + $fields['AutoBlock']['default'] = $block->isAutoblocking(); + if( isset( $fields['DisableEmail'] ) ){ + $fields['DisableEmail']['default'] = $block->prevents( 'sendemail' ); + } + if( isset( $fields['HideUser'] ) ){ + $fields['HideUser']['default'] = $block->mHideName; + } + if( isset( $fields['DisableUTEdit'] ) ){ + $fields['DisableUTEdit']['default'] = $block->prevents( 'editownusertalk' ); + } + $fields['Reason']['default'] = $block->mReason; + + if( $wgRequest->wasPosted() ){ + # Ok, so we got a POST submission asking us to reblock a user. So show the + # confirm checkbox; the user will only see it if they haven't previously + $fields['Confirm']['type'] = 'check'; + } else { + # We got a target, but it wasn't a POST request, so the user must have gone + # to a link like [[Special:Block/User]]. We don't need to show the checkbox + # as long as they go ahead and block *that* user + $fields['Confirm']['default'] = 1; + } + + if( $block->mExpiry == 'infinity' ) { + $fields['Expiry']['default'] = 'indefinite'; + } else { + $fields['Expiry']['default'] = wfTimestamp( TS_RFC2822, $block->mExpiry ); + } + + $this->alreadyBlocked = true; + $this->preErrors[] = array( 'ipb-needreblock', (string)$block->getTarget() ); + } + + # We always need confirmation to do HideUser + if( $this->requestedHideUser ){ + $fields['Confirm']['type'] = 'check'; + unset( $fields['Confirm']['default'] ); + $this->preErrors[] = 'ipb-confirmhideuser'; + } + + # Or if the user is trying to block themselves + if( (string)$this->target === $wgUser->getName() ){ + $fields['Confirm']['type'] = 'check'; + unset( $fields['Confirm']['default'] ); + $this->preErrors[] = 'ipb-blockingself'; + } + } + + /** + * Add header elements like block log entries, etc. + * @param $form HTMLForm + * @return void + */ + protected function doPreText( HTMLForm &$form ){ + $form->addPreText( wfMsgExt( 'blockiptext', 'parse' ) ); + + $otherBlockMessages = array(); + if( $this->target !== null ) { + # Get other blocks, i.e. from GlobalBlocking or TorBlock extension + wfRunHooks( 'OtherBlockLogLink', array( &$otherBlockMessages, $this->target ) ); + + if( count( $otherBlockMessages ) ) { + $s = Html::rawElement( + 'h2', + array(), + wfMsgExt( 'ipb-otherblocks-header', 'parseinline', count( $otherBlockMessages ) ) + ) . "\n"; + $list = ''; + foreach( $otherBlockMessages as $link ) { + $list .= Html::rawElement( 'li', array(), $link ) . "\n"; + } + $s .= Html::rawElement( + 'ul', + array( 'class' => 'mw-blockip-alreadyblocked' ), + $list + ) . "\n"; + $form->addPreText( $s ); + } + } + } + + /** + * Add header text inside the form, just underneath where the errors would go + * @param $form HTMLForm + * @return void + */ + protected function doHeaderText( HTMLForm &$form ){ + global $wgRequest; + # Don't need to do anything if the form has been posted + if( !$wgRequest->wasPosted() && $this->preErrors ){ + $s = HTMLForm::formatErrors( $this->preErrors ); + if( $s ){ + $form->addHeaderText( Html::rawElement( + 'div', + array( 'class' => 'error' ), + $s + ) ); + } + } + } + + /** + * Add footer elements to the form + * @param $form HTMLForm + * @return void + */ + protected function doPostText( HTMLForm &$form ){ + global $wgUser, $wgLang; + + # Link to the user's contributions, if applicable + if( $this->target instanceof User ){ + $contribsPage = SpecialPage::getTitleFor( 'Contributions', $this->target->getName() ); + $links[] = Linker::link( + $contribsPage, + wfMsgExt( 'ipb-blocklist-contribs', 'escape', $this->target->getName() ) + ); + } + + # Link to unblock the specified user, or to a blank unblock form + if( $this->target instanceof User ) { + $message = wfMsgExt( 'ipb-unblock-addr', array( 'parseinline' ), $this->target->getName() ); + $list = SpecialPage::getTitleFor( 'Unblock', $this->target->getName() ); + } else { + $message = wfMsgExt( 'ipb-unblock', array( 'parseinline' ) ); + $list = SpecialPage::getTitleFor( 'Unblock' ); + } + $links[] = Linker::linkKnown( $list, $message, array() ); + + # Link to the block list + $links[] = Linker::linkKnown( + SpecialPage::getTitleFor( 'BlockList' ), + wfMsg( 'ipb-blocklist' ) + ); + + # Link to edit the block dropdown reasons, if applicable + if ( $wgUser->isAllowed( 'editinterface' ) ) { + $links[] = Linker::link( + Title::makeTitle( NS_MEDIAWIKI, 'Ipbreason-dropdown' ), + wfMsgHtml( 'ipb-edit-dropdown' ), + array(), + array( 'action' => 'edit' ) + ); + } + + $form->addPostText( Html::rawElement( + 'p', + array( 'class' => 'mw-ipb-conveniencelinks' ), + $wgLang->pipeList( $links ) + ) ); + + if( $this->target instanceof User ){ + # Get relevant extracts from the block and suppression logs, if possible + $userpage = $this->target->getUserPage(); + $out = ''; + + LogEventsList::showLogExtract( + $out, + 'block', + $userpage->getPrefixedText(), + '', + array( + 'lim' => 10, + 'msgKey' => array( 'blocklog-showlog', $userpage->getText() ), + 'showIfEmpty' => false + ) + ); + $form->addPostText( $out ); + + # Add suppression block entries if allowed + if( $wgUser->isAllowed( 'suppressionlog' ) ) { + LogEventsList::showLogExtract( + $out, + 'suppress', + $userpage->getPrefixedText(), + '', + array( + 'lim' => 10, + 'conds' => array( 'log_action' => array( 'block', 'reblock', 'unblock' ) ), + 'msgKey' => array( 'blocklog-showsuppresslog', $userpage->getText() ), + 'showIfEmpty' => false + ) + ); + $form->addPostText( $out ); + } + } + } + + /** + * 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 + * the HTMLForm + * @param $request WebRequest optionally try and get data from a request too + * @return void + */ + public static function getTargetAndType( $par, WebRequest $request = null ){ + $i = 0; + $target = null; + 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 + # below; so this has to have a higher precedence here than $par, or + # we could end up with different values in $this->target and the HTMLForm! + if( $request instanceof WebRequest ){ + $target = $request->getText( 'wpTarget', null ); + } + break; + case 1: + $target = $par; + break; + case 2: + if( $request instanceof WebRequest ){ + $target = $request->getText( 'ip', null ); + } + break; + case 3: + # B/C @since 1.18 + if( $request instanceof WebRequest ){ + $target = $request->getText( 'wpBlockAddress', null ); + } + break; + case 4: + break 2; + } + list( $target, $type ) = Block::parseTarget( $target ); + if( $type !== null ){ + return array( $target, $type ); + } + } + return array( null, null ); + } + + /** + * HTMLForm field validation-callback for Target field. + * @since 1.18 + * @param $value String + * @param $alldata Array + * @return Message + */ + public static function validateTargetField( $value, $alldata = null ) { + global $wgBlockCIDRLimit; + + list( $target, $type ) = self::getTargetAndType( $value ); + + if( $type == Block::TYPE_USER ){ + # TODO: why do we not have a User->exists() method? + if( !$target->getId() ){ + return wfMessage( 'nosuchusershort', + wfEscapeWikiText( $target->getName() ) ); + } + + $status = self::checkUnblockSelf( $target ); + if ( $status !== true ) { + return wfMessage( 'badaccess', $status ); + } + + } 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 wfMessage( 'range_block_disabled' ); + } + + if( ( IP::isIPv4( $ip ) && $range > 32 ) + || ( IP::isIPv6( $ip ) && $range > 128 ) ) + { + # Dodgy range + return wfMessage( 'ip_range_invalid' ); + } + + if( IP::isIPv4( $ip ) && $range < $wgBlockCIDRLimit['IPv4'] ) { + return wfMessage( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv4'] ); + } + + if( IP::isIPv6( $ip ) && $range < $wgBlockCIDRLimit['IPv6'] ) { + return wfMessage( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv6'] ); + } + + } elseif( $type == Block::TYPE_IP ){ + # All is well + + } else { + return wfMessage( 'badipaddress' ); + } + + return true; + } + + /** + * Given the form data, actually implement a block + * @param $data Array + * @return Bool|String + */ + public static function processForm( array $data ){ + global $wgUser, $wgBlockAllowsUTEdit; + + // Handled by field validator callback + // self::validateTargetField( $data['Target'] ); + + # This might have been a hidden field or a checkbox, so interesting data + # can come from it + $data['Confirm'] = !in_array( $data['Confirm'], array( '', '0', null, false ), true ); + + list( $target, $type ) = self::getTargetAndType( $data['Target'] ); + if( $type == Block::TYPE_USER ){ + $user = $target; + $target = $user->getName(); + $userId = $user->getId(); + + # Give admins a heads-up before they go and block themselves. Much messier + # to do this for IPs, but it's pretty unlikely they'd ever get the 'block' + # permission anyway, although the code does allow for it + if( $target === $wgUser->getName() && + ( $data['PreviousTarget'] !== $data['Target'] || !$data['Confirm'] ) ) + { + return array( 'ipb-blockingself' ); + } + + } elseif( $type == Block::TYPE_RANGE ){ + $userId = 0; + + } elseif( $type == Block::TYPE_IP ){ + $target = $target->getName(); + $userId = 0; + + } else { + # This should have been caught in the form field validation + return array( 'badipaddress' ); + } + + if( ( strlen( $data['Expiry'] ) == 0) || ( strlen( $data['Expiry'] ) > 50 ) + || !self::parseExpiryInput( $data['Expiry'] ) ) + { + return array( 'ipb_expiry_invalid' ); + } + + if( !isset( $data['DisableEmail'] ) ){ + $data['DisableEmail'] = false; + } + + # If the user has done the form 'properly', they won't even have been given the + # option to suppress-block unless they have the 'hideuser' permission + if( !isset( $data['HideUser'] ) ){ + $data['HideUser'] = false; + } + if( $data['HideUser'] ) { + if( !$wgUser->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 + # rather than just silently disable hiding + return array( 'badaccess-group0' ); + } + + # Recheck params here... + if( $type != Block::TYPE_USER ) { + $data['HideUser'] = false; # IP users should not be hidden + + } elseif( !in_array( $data['Expiry'], array( 'infinite', 'infinity', 'indefinite' ) ) ) { + # Bad expiry. + return array( 'ipb_expiry_temp' ); + + } elseif( $user->getEditCount() > self::HIDEUSER_CONTRIBLIMIT ) { + # Typically, the user should have a handful of edits. + # Disallow hiding users with many edits for performance. + return array( 'ipb_hide_invalid' ); + + } elseif( !$data['Confirm'] ){ + return array( 'ipb-confirmhideuser' ); + } + } + + # Create block object. + $block = new Block(); + $block->setTarget( $target ); + $block->setBlocker( $wgUser ); + $block->mReason = $data['Reason'][0]; + $block->mExpiry = self::parseExpiryInput( $data['Expiry'] ); + $block->prevents( 'createaccount', $data['CreateAccount'] ); + $block->prevents( 'editownusertalk', ( !$wgBlockAllowsUTEdit || $data['DisableUTEdit'] ) ); + $block->prevents( 'sendemail', $data['DisableEmail'] ); + $block->isHardblock( $data['HardBlock'] ); + $block->isAutoblocking( $data['AutoBlock'] ); + $block->mHideName = $data['HideUser']; + + if( !wfRunHooks( 'BlockIp', array( &$block, &$wgUser ) ) ) { + return array( 'hookaborted' ); + } + + # Try to insert block. Is there a conflicting block? + $status = $block->insert(); + if( !$status ) { + # Show form unless the user is already aware of this... + if( !$data['Confirm'] || ( array_key_exists( 'PreviousTarget', $data ) + && $data['PreviousTarget'] !== $target ) ) + { + return array( array( 'ipb_already_blocked', $block->getTarget() ) ); + # 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 + $currentBlock = Block::newFromTarget( $target ); + + if( $block->equals( $currentBlock ) ) { + return array( array( 'ipb_already_blocked', $block->getTarget() ) ); + } + + # If the name was hidden and the blocking user cannot hide + # names, then don't allow any block changes... + if( $currentBlock->mHideName && !$wgUser->isAllowed( 'hideuser' ) ) { + return array( 'cant-see-hidden-user' ); + } + + $currentBlock->delete(); + $status = $block->insert(); + $logaction = 'reblock'; + + # Unset _deleted fields if requested + if( $currentBlock->mHideName && !$data['HideUser'] ) { + RevisionDeleteUser::unsuppressUserName( $target, $userId ); + } + + # If hiding/unhiding a name, this should go in the private logs + if( (bool)$currentBlock->mHideName ){ + $data['HideUser'] = true; + } + } + } else { + $logaction = 'block'; + } + + wfRunHooks( 'BlockIpComplete', array( $block, $wgUser ) ); + + # Set *_deleted fields if requested + if( $data['HideUser'] ) { + RevisionDeleteUser::suppressUserName( $target, $userId ); + } + + # Can't watch a rangeblock + if( $type != Block::TYPE_RANGE && $data['Watch'] ) { + $wgUser->addWatch( Title::makeTitle( NS_USER, $target ) ); + } + + # Block constructor sanitizes certain block options on insert + $data['BlockEmail'] = $block->prevents( 'sendemail' ); + $data['AutoBlock'] = $block->isAutoblocking(); + + # Prepare log parameters + $logParams = array(); + $logParams[] = $data['Expiry']; + $logParams[] = self::blockLogFlags( $data, $type ); + + # Make log entry, if the name is hidden, put it in the oversight log + $log_type = $data['HideUser'] ? 'suppress' : 'block'; + $log = new LogPage( $log_type ); + $log_id = $log->addEntry( + $logaction, + Title::makeTitle( NS_USER, $target ), + $data['Reason'][0], + $logParams + ); + # Relate log ID to block IDs (bug 25763) + $blockIds = array_merge( array( $status['id'] ), $status['autoIds'] ); + $log->addRelations( 'ipb_id', $blockIds, $log_id ); + + # Report to the user + return true; + } + + /** + * Get an array of suggested block durations from MediaWiki:Ipboptions + * @todo FIXME: This uses a rather odd syntax for the options, should it be converted + * to the standard "**<duration>|<displayname>" format? + * @param $lang Language|null the language to get the durations in, or null to use + * the wiki's content language + * @return Array + */ + public static function getSuggestedDurations( $lang = null ){ + $a = array(); + $msg = $lang === null + ? wfMessage( 'ipboptions' )->inContentLanguage()->text() + : wfMessage( 'ipboptions' )->inLanguage( $lang )->text(); + + if( $msg == '-' ){ + return array(); + } + + foreach( explode( ',', $msg ) as $option ) { + if( strpos( $option, ':' ) === false ){ + $option = "$option:$option"; + } + list( $show, $value ) = explode( ':', $option ); + $a[htmlspecialchars( $show )] = htmlspecialchars( $value ); + } + return $a; + } + + /** + * 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 + * @return String: timestamp or "infinity" string for the DB implementation + */ + public static function parseExpiryInput( $expiry ) { + static $infinity; + if( $infinity == null ){ + $infinity = wfGetDB( DB_SLAVE )->getInfinity(); + } + if ( $expiry == 'infinite' || $expiry == 'indefinite' ) { + $expiry = $infinity; + } else { + $expiry = strtotime( $expiry ); + if ( $expiry < 0 || $expiry === false ) { + return false; + } + $expiry = wfTimestamp( TS_MW, $expiry ); + } + return $expiry; + } + + /** + * Can we do an email block? + * @param $user User: the sysop wanting to make a block + * @return Boolean + */ + public static function canBlockEmail( $user ) { + global $wgEnableUserEmail, $wgSysopEmailBans; + return ( $wgEnableUserEmail && $wgSysopEmailBans && $user->isAllowed( 'blockemail' ) ); + } + + /** + * bug 15810: blocked admins should not be able to block/unblock + * others, and probably shouldn't be able to unblock themselves + * either. + * @param $user User|Int|String + * @return Bool|String true or error message key + */ + public static function checkUnblockSelf( $user ) { + global $wgUser; + if ( is_int( $user ) ) { + $user = User::newFromId( $user ); + } elseif ( is_string( $user ) ) { + $user = User::newFromName( $user ); + } + if( $wgUser->isBlocked() ){ + if( $user instanceof User && $user->getId() == $wgUser->getId() ) { + # User is trying to unblock themselves + if ( $wgUser->isAllowed( 'unblockself' ) ) { + return true; + # User blocked themselves and is now trying to reverse it + } elseif ( $wgUser->blockedBy() === $wgUser->getName() ) { + return true; + } else { + return 'ipbnounblockself'; + } + } else { + # User is trying to block/unblock someone else + return 'ipbblocked'; + } + } else { + return true; + } + } + + /** + * 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 $type Block::TYPE_ constant + * @return array + */ + protected static function blockLogFlags( array $data, $type ) { + global $wgBlockAllowsUTEdit; + $flags = array(); + + # when blocking a user the option 'anononly' is not available/has no effect -> do not write this into log + if( !$data['HardBlock'] && $type != Block::TYPE_USER ){ + $flags[] = 'anononly'; + } + + if( $data['CreateAccount'] ){ + $flags[] = 'nocreate'; + } + + # Same as anononly, this is not displayed when blocking an IP address + if( !$data['AutoBlock'] && $type != Block::TYPE_IP ){ + $flags[] = 'noautoblock'; + } + + if( $data['DisableEmail'] ){ + $flags[] = 'noemail'; + } + + if( $wgBlockAllowsUTEdit && $data['DisableUTEdit'] ){ + $flags[] = 'nousertalk'; + } + + if( $data['HideUser'] ){ + $flags[] = 'hiddenname'; + } + + return implode( ',', $flags ); + } +} + +# BC @since 1.18 +class IPBlockForm extends SpecialBlock {} diff --git a/includes/specials/SpecialBlockList.php b/includes/specials/SpecialBlockList.php new file mode 100644 index 00000000..ebeb5874 --- /dev/null +++ b/includes/specials/SpecialBlockList.php @@ -0,0 +1,437 @@ +<?php +/** + * Implements Special:BlockList + * + * 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 existing blocks + * + * @ingroup SpecialPage + */ +class SpecialBlockList extends SpecialPage { + + protected $target, $options; + + function __construct() { + parent::__construct( 'BlockList' ); + } + + /** + * Main execution point + * + * @param $par String title fragment + */ + public function execute( $par ) { + global $wgOut, $wgRequest, $wgLang; + + $this->setHeaders(); + $this->outputHeader(); + $wgOut->setPageTitle( wfMsg( 'ipblocklist' ) ); + $wgOut->addModuleStyles( 'mediawiki.special' ); + + $par = $wgRequest->getVal( 'ip', $par ); + $this->target = trim( $wgRequest->getVal( 'wpTarget', $par ) ); + + $this->options = $wgRequest->getArray( 'wpOptions', array() ); + + $action = $wgRequest->getText( 'action' ); + + if( $action == 'unblock' || $action == 'submit' && $wgRequest->wasPosted() ) { + # B/C @since 1.18: Unblock interface is now at Special:Unblock + $title = SpecialPage::getTitleFor( 'Unblock', $this->target ); + $wgOut->redirect( $title->getFullUrl() ); + return; + } + + # Just show the block list + $fields = array( + 'Target' => array( + 'type' => 'text', + 'label-message' => 'ipadressorusername', + 'tabindex' => '1', + 'size' => '45', + ), + 'Options' => array( + 'type' => 'multiselect', + 'options' => array( + wfMsg( 'blocklist-userblocks' ) => 'userblocks', + wfMsg( 'blocklist-tempblocks' ) => 'tempblocks', + wfMsg( 'blocklist-addressblocks' ) => 'addressblocks', + ), + 'flatlist' => true, + ), + 'Limit' => array( + 'class' => 'HTMLBlockedUsersItemSelect', + 'label-message' => 'table_pager_limit_label', + 'options' => array( + $wgLang->formatNum( 20 ) => 20, + $wgLang->formatNum( 50 ) => 50, + $wgLang->formatNum( 100 ) => 100, + $wgLang->formatNum( 250 ) => 250, + $wgLang->formatNum( 500 ) => 500, + ), + 'name' => 'limit', + 'default' => 50, + ), + ); + $form = new HTMLForm( $fields, $this->getContext() ); + $form->setMethod( 'get' ); + $form->setWrapperLegend( wfMsg( 'ipblocklist-legend' ) ); + $form->setSubmitText( wfMsg( 'ipblocklist-submit' ) ); + $form->prepareForm(); + + $form->displayForm( '' ); + $this->showList(); + } + + function showList() { + global $wgOut, $wgUser; + + # Purge expired entries on one in every 10 queries + if ( !mt_rand( 0, 10 ) ) { + Block::purgeExpired(); + } + + $conds = array(); + # Is the user allowed to see hidden blocks? + if ( !$wgUser->isAllowed( 'hideuser' ) ){ + $conds['ipb_deleted'] = 0; + } + + if ( $this->target !== '' ){ + list( $target, $type ) = Block::parseTarget( $this->target ); + + switch( $type ){ + case Block::TYPE_ID: + $conds['ipb_id'] = $target; + break; + + case Block::TYPE_IP: + case Block::TYPE_RANGE: + list( $start, $end ) = IP::parseRange( $target ); + $dbr = wfGetDB( DB_SLAVE ); + $conds[] = $dbr->makeList( + array( + 'ipb_address' => $target, + Block::getRangeCond( $start, $end ) + ), + LIST_OR + ); + $conds['ipb_auto'] = 0; + break; + + case Block::TYPE_USER: + $conds['ipb_address'] = (string)$this->target; + $conds['ipb_auto'] = 0; + break; + } + } + + # Apply filters + if( in_array( 'userblocks', $this->options ) ) { + $conds['ipb_user'] = 0; + } + if( in_array( 'tempblocks', $this->options ) ) { + $conds['ipb_expiry'] = 'infinity'; + } + if( in_array( 'addressblocks', $this->options ) ) { + $conds[] = "ipb_user != 0 OR ipb_range_end > ipb_range_start"; + } + + # Check for other blocks, i.e. global/tor blocks + $otherBlockLink = array(); + wfRunHooks( 'OtherBlockLogLink', array( &$otherBlockLink, $this->target ) ); + + # 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 ) ) { + $wgOut->addHTML( + Html::rawElement( 'h2', array(), wfMsg( 'ipblocklist-localblock' ) ) . "\n" + ); + } + + $pager = new BlockListPager( $this, $conds ); + if ( $pager->getNumRows() ) { + $wgOut->addHTML( + $pager->getNavigationBar() . + $pager->getBody(). + $pager->getNavigationBar() + ); + + } elseif ( $this->target ) { + $wgOut->addWikiMsg( 'ipblocklist-no-results' ); + + } else { + $wgOut->addWikiMsg( 'ipblocklist-empty' ); + } + + if( count( $otherBlockLink ) ) { + $wgOut->addHTML( + Html::rawElement( + 'h2', + array(), + wfMsgExt( + 'ipblocklist-otherblocks', + 'parseinline', + count( $otherBlockLink ) + ) + ) . "\n" + ); + $list = ''; + foreach( $otherBlockLink as $link ) { + $list .= Html::rawElement( 'li', array(), $link ) . "\n"; + } + $wgOut->addHTML( Html::rawElement( 'ul', array( 'class' => 'mw-ipblocklist-otherblocks' ), $list ) . "\n" ); + } + } +} + +class BlockListPager extends TablePager { + protected $conds; + protected $page; + + function __construct( $page, $conds ) { + $this->page = $page; + $this->conds = $conds; + $this->mDefaultDirection = true; + parent::__construct(); + } + + function getFieldNames() { + static $headers = null; + + if ( $headers == array() ) { + $headers = array( + 'ipb_timestamp' => 'blocklist-timestamp', + 'ipb_target' => 'blocklist-target', + 'ipb_expiry' => 'blocklist-expiry', + 'ipb_by' => 'blocklist-by', + 'ipb_params' => 'blocklist-params', + 'ipb_reason' => 'blocklist-reason', + ); + $headers = array_map( 'wfMsg', $headers ); + } + + return $headers; + } + + function formatValue( $name, $value ) { + global $wgLang, $wgUser; + + static $sk, $msg; + if ( empty( $sk ) ) { + $sk = $this->getSkin(); + $msg = array( + 'anononlyblock', + 'createaccountblock', + 'noautoblockblock', + 'emailblock', + 'blocklist-nousertalk', + 'unblocklink', + 'change-blocklink', + 'infiniteblock', + ); + $msg = array_combine( $msg, array_map( 'wfMessage', $msg ) ); + } + + $row = $this->mCurrentRow; + $formatted = ''; + + switch( $name ) { + case 'ipb_timestamp': + $formatted = $wgLang->timeanddate( $value, /* User preference timezone */ true ); + break; + + case 'ipb_target': + if( $row->ipb_auto ){ + $formatted = wfMessage( 'autoblockid', $row->ipb_id )->parse(); + } else { + list( $target, $type ) = Block::parseTarget( $row->ipb_address ); + switch( $type ){ + case Block::TYPE_USER: + case Block::TYPE_IP: + $formatted = $sk->userLink( $target->getId(), $target ); + $formatted .= $sk->userToolLinks( + $target->getId(), + $target, + false, + Linker::TOOL_LINKS_NOBLOCK + ); + break; + case Block::TYPE_RANGE: + $formatted = htmlspecialchars( $target ); + } + } + break; + + case 'ipb_expiry': + $formatted = $wgLang->formatExpiry( $value, /* User preference timezone */ true ); + if( $wgUser->isAllowed( 'block' ) ){ + if( $row->ipb_auto ){ + $links[] = $sk->linkKnown( + SpecialPage::getTitleFor( 'Unblock' ), + $msg['unblocklink'], + array(), + array( 'wpTarget' => "#{$row->ipb_id}" ) + ); + } else { + $links[] = $sk->linkKnown( + SpecialPage::getTitleFor( 'Unblock', $row->ipb_address ), + $msg['unblocklink'] + ); + $links[] = $sk->linkKnown( + SpecialPage::getTitleFor( 'Block', $row->ipb_address ), + $msg['change-blocklink'] + ); + } + $formatted .= ' ' . Html::rawElement( + 'span', + array( 'class' => 'mw-blocklist-actions' ), + wfMsg( 'parentheses', $wgLang->pipeList( $links ) ) + ); + } + break; + + case 'ipb_by': + $user = User::newFromId( $value ); + if( $user instanceof User ){ + $formatted = $sk->userLink( $user->getId(), $user->getName() ); + $formatted .= $sk->userToolLinks( $user->getId(), $user->getName() ); + } + break; + + case 'ipb_reason': + $formatted = $sk->commentBlock( $value ); + break; + + case 'ipb_params': + $properties = array(); + if ( $row->ipb_anon_only ) { + $properties[] = $msg['anononlyblock']; + } + if ( $row->ipb_create_account ) { + $properties[] = $msg['createaccountblock']; + } + if ( $row->ipb_user && !$row->ipb_enable_autoblock ) { + $properties[] = $msg['noautoblockblock']; + } + + if ( $row->ipb_block_email ) { + $properties[] = $msg['emailblock']; + } + + if ( !$row->ipb_allow_usertalk ) { + $properties[] = $msg['blocklist-nousertalk']; + } + + $formatted = $wgLang->commaList( $properties ); + break; + + default: + $formatted = "Unable to format $name"; + break; + } + + return $formatted; + } + + function getQueryInfo() { + $info = array( + 'tables' => array( 'ipblocks' ), + 'fields' => array( + 'ipb_id', + 'ipb_address', + 'ipb_user', + 'ipb_by', + 'ipb_reason', + 'ipb_timestamp', + 'ipb_auto', + 'ipb_anon_only', + 'ipb_create_account', + 'ipb_enable_autoblock', + 'ipb_expiry', + 'ipb_range_start', + 'ipb_range_end', + 'ipb_deleted', + 'ipb_block_email', + 'ipb_allow_usertalk', + ), + 'conds' => $this->conds, + ); + + global $wgUser; + # Is the user allowed to see hidden blocks? + if ( !$wgUser->isAllowed( 'hideuser' ) ){ + $conds['ipb_deleted'] = 0; + } + + return $info; + } + + public function getTableClass(){ + return 'TablePager mw-blocklist'; + } + + function getIndexField() { + return 'ipb_timestamp'; + } + + function getDefaultSort() { + return 'ipb_timestamp'; + } + + function isFieldSortable( $name ) { + return false; + } + + function getTitle() { + return $this->page->getTitle(); + } +} + +/** + * Items per page dropdown. Essentially a crap workaround for bug 32603. + * + * @todo Do not release 1.19 with this. + */ +class HTMLBlockedUsersItemSelect extends HTMLSelectField { + /** + * Basically don't do any validation. If it's a number that's fine. Also, + * add it to the list if it's not there already + * + * @param $value + * @param $alldata + * @return bool + */ + function validate( $value, $alldata ) { + if ( $value == '' ) { + return true; + } + + if ( !in_array( $value, $this->mParams['options'] ) ) { + $this->mParams['options'][ $this->mParent->getLanguage()->formatNum( $value ) ] = intval($value); + asort( $this->mParams['options'] ); + } + + return true; + } + +} diff --git a/includes/specials/SpecialBlockip.php b/includes/specials/SpecialBlockip.php deleted file mode 100644 index 28a0f3f1..00000000 --- a/includes/specials/SpecialBlockip.php +++ /dev/null @@ -1,892 +0,0 @@ -<?php -/** - * Implements Special:Blockip - * - * 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 allows users with 'block' right to block users from - * editing pages and other actions - * - * @ingroup SpecialPage - */ -class IPBlockForm extends SpecialPage { - var $BlockAddress, $BlockExpiry, $BlockReason, $BlockReasonList, $BlockOther, $BlockAnonOnly, $BlockCreateAccount, - $BlockEnableAutoblock, $BlockEmail, $BlockHideName, $BlockAllowUsertalk, $BlockReblock; - // The maximum number of edits a user can have and still be hidden - const HIDEUSER_CONTRIBLIMIT = 1000; - - public function __construct() { - parent::__construct( 'Blockip', 'block' ); - } - - public function execute( $par ) { - global $wgUser, $wgOut, $wgRequest; - - # Can't block when the database is locked - if( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } - # Permission check - if( !$this->userCanExecute( $wgUser ) ) { - $wgOut->permissionRequired( 'block' ); - return; - } - - $this->setup( $par ); - - # bug 15810: blocked admins should have limited access here - if ( $wgUser->isBlocked() ) { - $status = IPBlockForm::checkUnblockSelf( $this->BlockAddress ); - if ( $status !== true ) { - throw new ErrorPageError( 'badaccess', $status ); - } - } - - $action = $wgRequest->getVal( 'action' ); - if( 'success' == $action ) { - $this->showSuccess(); - } elseif( $wgRequest->wasPosted() && 'submit' == $action && - $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { - $this->doSubmit(); - } else { - $this->showForm( '' ); - } - } - - private function setup( $par ) { - global $wgRequest, $wgUser, $wgBlockAllowsUTEdit; - - $this->BlockAddress = $wgRequest->getVal( 'wpBlockAddress', $wgRequest->getVal( 'ip', $par ) ); - $this->BlockAddress = strtr( $this->BlockAddress, '_', ' ' ); - $this->BlockReason = $wgRequest->getText( 'wpBlockReason' ); - $this->BlockReasonList = $wgRequest->getText( 'wpBlockReasonList' ); - $this->BlockExpiry = $wgRequest->getVal( 'wpBlockExpiry', wfMsg( 'ipbotheroption' ) ); - $this->BlockOther = $wgRequest->getVal( 'wpBlockOther', '' ); - - # Unchecked checkboxes are not included in the form data at all, so having one - # that is true by default is a bit tricky - $byDefault = !$wgRequest->wasPosted(); - $this->BlockAnonOnly = $wgRequest->getBool( 'wpAnonOnly', $byDefault ); - $this->BlockCreateAccount = $wgRequest->getBool( 'wpCreateAccount', $byDefault ); - $this->BlockEnableAutoblock = $wgRequest->getBool( 'wpEnableAutoblock', $byDefault ); - $this->BlockEmail = false; - if( self::canBlockEmail( $wgUser ) ) { - $this->BlockEmail = $wgRequest->getBool( 'wpEmailBan', false ); - } - $this->BlockWatchUser = $wgRequest->getBool( 'wpWatchUser', false ) && $wgUser->isLoggedIn(); - # Re-check user's rights to hide names, very serious, defaults to null - if( $wgUser->isAllowed( 'hideuser' ) ) { - $this->BlockHideName = $wgRequest->getBool( 'wpHideName', null ); - } else { - $this->BlockHideName = false; - } - $this->BlockAllowUsertalk = ( $wgRequest->getBool( 'wpAllowUsertalk', $byDefault ) && $wgBlockAllowsUTEdit ); - $this->BlockReblock = $wgRequest->getBool( 'wpChangeBlock', false ); - - $this->wasPosted = $wgRequest->wasPosted(); - } - - public function showForm( $err ) { - global $wgOut, $wgUser, $wgSysopUserBans; - - $wgOut->setPageTitle( wfMsg( 'blockip-title' ) ); - $wgOut->addWikiMsg( 'blockiptext' ); - - if( $wgSysopUserBans ) { - $mIpaddress = Xml::label( wfMsg( 'ipadressorusername' ), 'mw-bi-target' ); - } else { - $mIpaddress = Xml::label( wfMsg( 'ipaddress' ), 'mw-bi-target' ); - } - $mIpbexpiry = Xml::label( wfMsg( 'ipbexpiry' ), 'wpBlockExpiry' ); - $mIpbother = Xml::label( wfMsg( 'ipbother' ), 'mw-bi-other' ); - $mIpbreasonother = Xml::label( wfMsg( 'ipbreason' ), 'wpBlockReasonList' ); - $mIpbreason = Xml::label( wfMsg( 'ipbotherreason' ), 'mw-bi-reason' ); - - $titleObj = SpecialPage::getTitleFor( 'Blockip' ); - $user = User::newFromName( $this->BlockAddress ); - - $alreadyBlocked = false; - $otherBlockedMsgs = array(); - if( $err && $err[0] != 'ipb_already_blocked' ) { - $key = array_shift( $err ); - $msg = wfMsgReal( $key, $err ); - $wgOut->setSubtitle( wfMsgHtml( 'formerror' ) ); - $wgOut->addHTML( Xml::tags( 'p', array( 'class' => 'error' ), $msg ) ); - } elseif( $this->BlockAddress !== null ) { - # Get other blocks, i.e. from GlobalBlocking or TorBlock extension - wfRunHooks( 'OtherBlockLogLink', array( &$otherBlockedMsgs, $this->BlockAddress ) ); - - $userId = is_object( $user ) ? $user->getId() : 0; - $currentBlock = Block::newFromDB( $this->BlockAddress, $userId ); - if( !is_null( $currentBlock ) && !$currentBlock->mAuto && # The block exists and isn't an autoblock - ( $currentBlock->mRangeStart == $currentBlock->mRangeEnd || # The block isn't a rangeblock - # or if it is, the range is what we're about to block - ( $currentBlock->mAddress == $this->BlockAddress ) ) - ) { - $alreadyBlocked = true; - # Set the block form settings to the existing block - if( !$this->wasPosted ) { - $this->BlockAnonOnly = $currentBlock->mAnonOnly; - $this->BlockCreateAccount = $currentBlock->mCreateAccount; - $this->BlockEnableAutoblock = $currentBlock->mEnableAutoblock; - $this->BlockEmail = $currentBlock->mBlockEmail; - $this->BlockHideName = $currentBlock->mHideName; - $this->BlockAllowUsertalk = $currentBlock->mAllowUsertalk; - if( $currentBlock->mExpiry == 'infinity' ) { - $this->BlockOther = 'indefinite'; - } else { - $this->BlockOther = wfTimestamp( TS_ISO_8601, $currentBlock->mExpiry ); - } - $this->BlockReason = $currentBlock->mReason; - } - } - } - - # Show other blocks from extensions, i.e. GlockBlocking and TorBlock - if( count( $otherBlockedMsgs ) ) { - $wgOut->addHTML( - Html::rawElement( 'h2', array(), wfMsgExt( 'ipb-otherblocks-header', 'parseinline', count( $otherBlockedMsgs ) ) ) . "\n" - ); - $list = ''; - foreach( $otherBlockedMsgs as $link ) { - $list .= Html::rawElement( 'li', array(), $link ) . "\n"; - } - $wgOut->addHTML( Html::rawElement( 'ul', array( 'class' => 'mw-blockip-alreadyblocked' ), $list ) . "\n" ); - } - - # Username/IP is blocked already locally - if( $alreadyBlocked ) { - $wgOut->wrapWikiMsg( "<div class='mw-ipb-needreblock'>\n$1\n</div>", array( 'ipb-needreblock', $this->BlockAddress ) ); - } - - $scBlockExpiryOptions = wfMsgForContent( 'ipboptions' ); - - $showblockoptions = $scBlockExpiryOptions != '-'; - if( !$showblockoptions ) $mIpbother = $mIpbexpiry; - - $blockExpiryFormOptions = Xml::option( wfMsg( 'ipbotheroption' ), 'other' ); - foreach( explode( ',', $scBlockExpiryOptions ) as $option ) { - if( strpos( $option, ':' ) === false ) $option = "$option:$option"; - list( $show, $value ) = explode( ':', $option ); - $show = htmlspecialchars( $show ); - $value = htmlspecialchars( $value ); - $blockExpiryFormOptions .= Xml::option( $show, $value, $this->BlockExpiry === $value ) . "\n"; - } - - $reasonDropDown = Xml::listDropDown( 'wpBlockReasonList', - wfMsgForContent( 'ipbreason-dropdown' ), - wfMsgForContent( 'ipbreasonotherlist' ), $this->BlockReasonList, 'wpBlockDropDown', 4 ); - - $wgOut->addModules( 'mediawiki.legacy.block' ); - $wgOut->addHTML( - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL( 'action=submit' ), 'id' => 'blockip' ) ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'blockip-legend' ) ) . - Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-blockip-table' ) ) . - "<tr> - <td class='mw-label'> - {$mIpaddress} - </td> - <td class='mw-input'>" . - Html::input( 'wpBlockAddress', $this->BlockAddress, 'text', array( - 'tabindex' => '1', - 'id' => 'mw-bi-target', - 'onchange' => 'updateBlockOptions()', - 'size' => '45', - 'required' => '' - ) + ( $this->BlockAddress ? array() : array( 'autofocus' ) ) ). " - </td> - </tr> - <tr>" - ); - if( $showblockoptions ) { - $wgOut->addHTML(" - <td class='mw-label'> - {$mIpbexpiry} - </td> - <td class='mw-input'>" . - Xml::tags( 'select', - array( - 'id' => 'wpBlockExpiry', - 'name' => 'wpBlockExpiry', - 'onchange' => 'considerChangingExpiryFocus()', - 'tabindex' => '2' ), - $blockExpiryFormOptions ) . - "</td>" - ); - } - $wgOut->addHTML(" - </tr> - <tr id='wpBlockOther'> - <td class='mw-label'> - {$mIpbother} - </td> - <td class='mw-input'>" . - Xml::input( 'wpBlockOther', 45, $this->BlockOther, - array( 'tabindex' => '3', 'id' => 'mw-bi-other' ) ) . " - </td> - </tr> - <tr> - <td class='mw-label'> - {$mIpbreasonother} - </td> - <td class='mw-input'> - {$reasonDropDown} - </td> - </tr> - <tr id=\"wpBlockReason\"> - <td class='mw-label'> - {$mIpbreason} - </td> - <td class='mw-input'>" . - Html::input( 'wpBlockReason', $this->BlockReason, 'text', array( - 'tabindex' => '5', - 'id' => 'mw-bi-reason', - 'maxlength' => '200', - 'size' => '45' - ) + ( $this->BlockAddress ? array( 'autofocus' ) : array() ) ) . " - </td> - </tr> - <tr id='wpAnonOnlyRow'> - <td> </td> - <td class='mw-input'>" . - Xml::checkLabel( wfMsg( 'ipbanononly' ), - 'wpAnonOnly', 'wpAnonOnly', $this->BlockAnonOnly, - array( 'tabindex' => '6' ) ) . " - </td> - </tr> - <tr id='wpCreateAccountRow'> - <td> </td> - <td class='mw-input'>" . - Xml::checkLabel( wfMsg( 'ipbcreateaccount' ), - 'wpCreateAccount', 'wpCreateAccount', $this->BlockCreateAccount, - array( 'tabindex' => '7' ) ) . " - </td> - </tr> - <tr id='wpEnableAutoblockRow'> - <td> </td> - <td class='mw-input'>" . - Xml::checkLabel( wfMsg( 'ipbenableautoblock' ), - 'wpEnableAutoblock', 'wpEnableAutoblock', $this->BlockEnableAutoblock, - array( 'tabindex' => '8' ) ) . " - </td> - </tr>" - ); - - if( self::canBlockEmail( $wgUser ) ) { - $wgOut->addHTML(" - <tr id='wpEnableEmailBan'> - <td> </td> - <td class='mw-input'>" . - Xml::checkLabel( wfMsg( 'ipbemailban' ), - 'wpEmailBan', 'wpEmailBan', $this->BlockEmail, - array( 'tabindex' => '9' ) ) . " - </td> - </tr>" - ); - } - - // Allow some users to hide name from block log, blocklist and listusers - if( $wgUser->isAllowed( 'hideuser' ) ) { - $wgOut->addHTML(" - <tr id='wpEnableHideUser'> - <td> </td> - <td class='mw-input'><strong>" . - Xml::checkLabel( wfMsg( 'ipbhidename' ), - 'wpHideName', 'wpHideName', $this->BlockHideName, - array( 'tabindex' => '10' ) - ) . " - </strong></td> - </tr>" - ); - } - - # Watchlist their user page? (Only if user is logged in) - if( $wgUser->isLoggedIn() ) { - $wgOut->addHTML(" - <tr id='wpEnableWatchUser'> - <td> </td> - <td class='mw-input'>" . - Xml::checkLabel( wfMsg( 'ipbwatchuser' ), - 'wpWatchUser', 'wpWatchUser', $this->BlockWatchUser, - array( 'tabindex' => '11' ) ) . " - </td> - </tr>" - ); - } - - # Can we explicitly disallow the use of user_talk? - global $wgBlockAllowsUTEdit; - if( $wgBlockAllowsUTEdit ){ - $wgOut->addHTML(" - <tr id='wpAllowUsertalkRow'> - <td> </td> - <td class='mw-input'>" . - Xml::checkLabel( wfMsg( 'ipballowusertalk' ), - 'wpAllowUsertalk', 'wpAllowUsertalk', $this->BlockAllowUsertalk, - array( 'tabindex' => '12' ) ) . " - </td> - </tr>" - ); - } - - $wgOut->addHTML(" - <tr> - <td style='padding-top: 1em'> </td> - <td class='mw-submit' style='padding-top: 1em'>" . - Xml::submitButton( wfMsg( $alreadyBlocked ? 'ipb-change-block' : 'ipbsubmit' ), - array( 'name' => 'wpBlock', 'tabindex' => '13' ) - + $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'blockip-block' ) ). " - </td> - </tr>" . - Xml::closeElement( 'table' ) . - Html::hidden( 'wpEditToken', $wgUser->editToken() ) . - ( $alreadyBlocked ? Html::hidden( 'wpChangeBlock', 1 ) : "" ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) - ); - - $wgOut->addHTML( $this->getConvenienceLinks() ); - - if( is_object( $user ) ) { - $this->showLogFragment( $wgOut, $user->getUserPage() ); - } elseif( preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', $this->BlockAddress ) ) { - $this->showLogFragment( $wgOut, Title::makeTitle( NS_USER, $this->BlockAddress ) ); - } elseif( preg_match( '/^\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}/', $this->BlockAddress ) ) { - $this->showLogFragment( $wgOut, Title::makeTitle( NS_USER, $this->BlockAddress ) ); - } - } - - /** - * Can we do an email block? - * @param $user User: the sysop wanting to make a block - * @return Boolean - */ - public static function canBlockEmail( $user ) { - global $wgEnableUserEmail, $wgSysopEmailBans; - return ( $wgEnableUserEmail && $wgSysopEmailBans && $user->isAllowed( 'blockemail' ) ); - } - - /** - * bug 15810: blocked admins should not be able to block/unblock - * others, and probably shouldn't be able to unblock themselves - * either. - * @param $user User, Int or String - */ - public static function checkUnblockSelf( $user ) { - global $wgUser; - if ( is_int( $user ) ) { - $user = User::newFromId( $user ); - } elseif ( is_string( $user ) ) { - $user = User::newFromName( $user ); - } - if( $user instanceof User && $user->getId() == $wgUser->getId() ) { - # User is trying to unblock themselves - if ( $wgUser->isAllowed( 'unblockself' ) ) { - return true; - } else { - return 'ipbnounblockself'; - } - } else { - # User is trying to block/unblock someone else - return 'ipbblocked'; - } - } - - /** - * Backend block code. - * $userID and $expiry will be filled accordingly - * @return array(message key, arguments) on failure, empty array on success - */ - function doBlock( &$userId = null, &$expiry = null ) { - global $wgUser, $wgSysopUserBans, $wgSysopRangeBans, $wgBlockAllowsUTEdit, $wgBlockCIDRLimit; - - $userId = 0; - # Expand valid IPv6 addresses, usernames are left as is - $this->BlockAddress = IP::sanitizeIP( $this->BlockAddress ); - # isIPv4() and IPv6() are used for final validation - $rxIP4 = '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'; - $rxIP6 = '\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}'; - $rxIP = "($rxIP4|$rxIP6)"; - - # Check for invalid specifications - if( !preg_match( "/^$rxIP$/", $this->BlockAddress ) ) { - $matches = array(); - if( preg_match( "/^($rxIP4)\\/(\\d{1,2})$/", $this->BlockAddress, $matches ) ) { - # IPv4 - if( $wgSysopRangeBans && $wgBlockCIDRLimit['IPv4'] != 32 ) { - if( !IP::isIPv4( $this->BlockAddress ) || $matches[2] > 32 ) { - return array( 'ip_range_invalid' ); - } elseif ( $matches[2] < $wgBlockCIDRLimit['IPv4'] ) { - return array( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv4'] ); - } - $this->BlockAddress = Block::normaliseRange( $this->BlockAddress ); - } else { - # Range block illegal - return array( 'range_block_disabled' ); - } - } elseif( preg_match( "/^($rxIP6)\\/(\\d{1,3})$/", $this->BlockAddress, $matches ) ) { - # IPv6 - if( $wgSysopRangeBans && $wgBlockCIDRLimit['IPv6'] != 128 ) { - if( !IP::isIPv6( $this->BlockAddress ) || $matches[2] > 128 ) { - return array( 'ip_range_invalid' ); - } elseif( $matches[2] < $wgBlockCIDRLimit['IPv6'] ) { - return array( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv6'] ); - } - $this->BlockAddress = Block::normaliseRange( $this->BlockAddress ); - } else { - # Range block illegal - return array('range_block_disabled'); - } - } else { - # Username block - if( $wgSysopUserBans ) { - $user = User::newFromName( $this->BlockAddress ); - if( $user instanceof User && $user->getId() ) { - # Use canonical name - $userId = $user->getId(); - $this->BlockAddress = $user->getName(); - } else { - return array( 'nosuchusershort', htmlspecialchars( $user ? $user->getName() : $this->BlockAddress ) ); - } - } else { - return array( 'badipaddress' ); - } - } - } - - if( $wgUser->isBlocked() && ( $wgUser->getId() !== $userId ) ) { - return array( 'cant-block-while-blocked' ); - } - - $reasonstr = $this->BlockReasonList; - if( $reasonstr != 'other' && $this->BlockReason != '' ) { - // Entry from drop down menu + additional comment - $reasonstr .= wfMsgForContent( 'colon-separator' ) . $this->BlockReason; - } elseif( $reasonstr == 'other' ) { - $reasonstr = $this->BlockReason; - } - - $expirestr = $this->BlockExpiry; - if( $expirestr == 'other' ) - $expirestr = $this->BlockOther; - - if( ( strlen( $expirestr ) == 0) || ( strlen( $expirestr ) > 50 ) ) { - return array( 'ipb_expiry_invalid' ); - } - - if( false === ( $expiry = Block::parseExpiryInput( $expirestr ) ) ) { - // Bad expiry. - return array( 'ipb_expiry_invalid' ); - } - - if( $this->BlockHideName ) { - // Recheck params here... - if( !$userId || !$wgUser->isAllowed('hideuser') ) { - $this->BlockHideName = false; // IP users should not be hidden - } elseif( $expiry !== 'infinity' ) { - // Bad expiry. - return array( 'ipb_expiry_temp' ); - } elseif( User::edits( $userId ) > self::HIDEUSER_CONTRIBLIMIT ) { - // Typically, the user should have a handful of edits. - // Disallow hiding users with many edits for performance. - return array( 'ipb_hide_invalid' ); - } - } - - # Create block object - # Note: for a user block, ipb_address is only for display purposes - $block = new Block( $this->BlockAddress, $userId, $wgUser->getId(), - $reasonstr, wfTimestampNow(), 0, $expiry, $this->BlockAnonOnly, - $this->BlockCreateAccount, $this->BlockEnableAutoblock, $this->BlockHideName, - $this->BlockEmail, - isset( $this->BlockAllowUsertalk ) ? $this->BlockAllowUsertalk : $wgBlockAllowsUTEdit - ); - - # Should this be privately logged? - $suppressLog = (bool)$this->BlockHideName; - if( wfRunHooks( 'BlockIp', array( &$block, &$wgUser ) ) ) { - # Try to insert block. Is there a conflicting block? - if( !$block->insert() ) { - # Show form unless the user is already aware of this... - if( !$this->BlockReblock ) { - return array( 'ipb_already_blocked' ); - # 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 - $currentBlock = Block::newFromDB( $this->BlockAddress, $userId ); - if( $block->equals( $currentBlock ) ) { - return array( 'ipb_already_blocked' ); - } - # If the name was hidden and the blocking user cannot hide - # names, then don't allow any block changes... - if( $currentBlock->mHideName && !$wgUser->isAllowed( 'hideuser' ) ) { - return array( 'cant-see-hidden-user' ); - } - $currentBlock->delete(); - $block->insert(); - # If hiding/unhiding a name, this should go in the private logs - $suppressLog = $suppressLog || (bool)$currentBlock->mHideName; - $log_action = 'reblock'; - # Unset _deleted fields if requested - if( $currentBlock->mHideName && !$this->BlockHideName ) { - self::unsuppressUserName( $this->BlockAddress, $userId ); - } - } - } else { - $log_action = 'block'; - } - wfRunHooks( 'BlockIpComplete', array( $block, $wgUser ) ); - - # Set *_deleted fields if requested - if( $this->BlockHideName ) { - self::suppressUserName( $this->BlockAddress, $userId ); - } - - # Only show watch link when this is no range block - if( $this->BlockWatchUser && $block->mRangeStart == $block->mRangeEnd ) { - $wgUser->addWatch( Title::makeTitle( NS_USER, $this->BlockAddress ) ); - } - - # Block constructor sanitizes certain block options on insert - $this->BlockEmail = $block->mBlockEmail; - $this->BlockEnableAutoblock = $block->mEnableAutoblock; - - # Prepare log parameters - $logParams = array(); - $logParams[] = $expirestr; - $logParams[] = $this->blockLogFlags(); - - # Make log entry, if the name is hidden, put it in the oversight log - $log_type = $suppressLog ? 'suppress' : 'block'; - $log = new LogPage( $log_type ); - $log->addEntry( $log_action, Title::makeTitle( NS_USER, $this->BlockAddress ), - $reasonstr, $logParams ); - - # Report to the user - return array(); - } else { - return array( 'hookaborted' ); - } - } - - public static function suppressUserName( $name, $userId, $dbw = null ) { - $op = '|'; // bitwise OR - return self::setUsernameBitfields( $name, $userId, $op, $dbw ); - } - - public static function unsuppressUserName( $name, $userId, $dbw = null ) { - $op = '&'; // bitwise AND - return self::setUsernameBitfields( $name, $userId, $op, $dbw ); - } - - private static function setUsernameBitfields( $name, $userId, $op, $dbw ) { - if( $op !== '|' && $op !== '&' ) return false; // sanity check - if( !$dbw ) - $dbw = wfGetDB( DB_MASTER ); - $delUser = Revision::DELETED_USER | Revision::DELETED_RESTRICTED; - $delAction = LogPage::DELETED_ACTION | Revision::DELETED_RESTRICTED; - # Normalize user name - $userTitle = Title::makeTitleSafe( NS_USER, $name ); - $userDbKey = $userTitle->getDBkey(); - # To suppress, we OR the current bitfields with Revision::DELETED_USER - # to put a 1 in the username *_deleted bit. To unsuppress we AND the - # current bitfields with the inverse of Revision::DELETED_USER. The - # username bit is made to 0 (x & 0 = 0), while others are unchanged (x & 1 = x). - # The same goes for the sysop-restricted *_deleted bit. - if( $op == '&' ) { - $delUser = "~{$delUser}"; - $delAction = "~{$delAction}"; - } - # Hide name from live edits - $dbw->update( 'revision', array( "rev_deleted = rev_deleted $op $delUser" ), - array( 'rev_user' => $userId ), __METHOD__ ); - # Hide name from deleted edits - $dbw->update( 'archive', array( "ar_deleted = ar_deleted $op $delUser" ), - array( 'ar_user_text' => $name ), __METHOD__ ); - # Hide name from logs - $dbw->update( 'logging', array( "log_deleted = log_deleted $op $delUser" ), - array( 'log_user' => $userId, "log_type != 'suppress'" ), __METHOD__ ); - $dbw->update( 'logging', array( "log_deleted = log_deleted $op $delAction" ), - array( 'log_namespace' => NS_USER, 'log_title' => $userDbKey, - "log_type != 'suppress'" ), __METHOD__ ); - # Hide name from RC - $dbw->update( 'recentchanges', array( "rc_deleted = rc_deleted $op $delUser" ), - array( 'rc_user_text' => $name ), __METHOD__ ); - $dbw->update( 'recentchanges', array( "rc_deleted = rc_deleted $op $delAction" ), - array( 'rc_namespace' => NS_USER, 'rc_title' => $userDbKey, 'rc_logid > 0' ), __METHOD__ ); - # Hide name from live images - $dbw->update( 'oldimage', array( "oi_deleted = oi_deleted $op $delUser" ), - array( 'oi_user_text' => $name ), __METHOD__ ); - # Hide name from deleted images - # WMF - schema change pending - # $dbw->update( 'filearchive', array( "fa_deleted = fa_deleted $op $delUser" ), - # array( 'fa_user_text' => $name ), __METHOD__ ); - # Done! - return true; - } - - /** - * UI entry point for blocking - * Wraps around doBlock() - */ - public function doSubmit() { - global $wgOut; - $retval = $this->doBlock(); - if( empty( $retval ) ) { - $titleObj = SpecialPage::getTitleFor( 'Blockip' ); - $wgOut->redirect( $titleObj->getFullURL( 'action=success&ip=' . - urlencode( $this->BlockAddress ) ) ); - return; - } - $this->showForm( $retval ); - } - - public function showSuccess() { - global $wgOut; - - $wgOut->setPageTitle( wfMsg( 'blockip-title' ) ); - $wgOut->setSubtitle( wfMsg( 'blockipsuccesssub' ) ); - $text = wfMsgExt( 'blockipsuccesstext', array( 'parse' ), $this->BlockAddress ); - $wgOut->addHTML( $text ); - } - - private function showLogFragment( $out, $title ) { - global $wgUser; - - // Used to support GENDER in 'blocklog-showlog' and 'blocklog-showsuppresslog' - $userBlocked = $title->getText(); - - LogEventsList::showLogExtract( - $out, - 'block', - $title->getPrefixedText(), - '', - array( - 'lim' => 10, - 'msgKey' => array( - 'blocklog-showlog', - $userBlocked - ), - 'showIfEmpty' => false - ) - ); - - // Add suppression block entries if allowed - if( $wgUser->isAllowed( 'suppressionlog' ) ) { - LogEventsList::showLogExtract( $out, 'suppress', $title->getPrefixedText(), '', - array( - 'lim' => 10, - 'conds' => array( - 'log_action' => array( - 'block', - 'reblock', - 'unblock' - ) - ), - 'msgKey' => array( - 'blocklog-showsuppresslog', - $userBlocked - ), - 'showIfEmpty' => false - ) - ); - } - } - - /** - * Return a comma-delimited list of "flags" to be passed to the log - * reader for this block, to provide more information in the logs - * - * @return array - */ - private function blockLogFlags() { - global $wgBlockAllowsUTEdit; - $flags = array(); - if( $this->BlockAnonOnly && IP::isIPAddress( $this->BlockAddress ) ) - // when blocking a user the option 'anononly' is not available/has no effect -> do not write this into log - $flags[] = 'anononly'; - if( $this->BlockCreateAccount ) - $flags[] = 'nocreate'; - if( !$this->BlockEnableAutoblock && !IP::isIPAddress( $this->BlockAddress ) ) - // Same as anononly, this is not displayed when blocking an IP address - $flags[] = 'noautoblock'; - if( $this->BlockEmail ) - $flags[] = 'noemail'; - if( !$this->BlockAllowUsertalk && $wgBlockAllowsUTEdit ) - $flags[] = 'nousertalk'; - if( $this->BlockHideName ) - $flags[] = 'hiddenname'; - return implode( ',', $flags ); - } - - /** - * Builds unblock and block list links - * - * @return string - */ - private function getConvenienceLinks() { - global $wgUser, $wgLang; - $skin = $wgUser->getSkin(); - if( $this->BlockAddress ) - $links[] = $this->getContribsLink( $skin ); - $links[] = $this->getUnblockLink( $skin ); - $links[] = $this->getBlockListLink( $skin ); - if ( $wgUser->isAllowed( 'editinterface' ) ) { - $title = Title::makeTitle( NS_MEDIAWIKI, 'Ipbreason-dropdown' ); - $links[] = $skin->link( - $title, - wfMsgHtml( 'ipb-edit-dropdown' ), - array(), - array( 'action' => 'edit' ) - ); - } - return '<p class="mw-ipb-conveniencelinks">' . $wgLang->pipeList( $links ) . '</p>'; - } - - /** - * Build a convenient link to a user or IP's contribs - * form - * - * @param $skin Skin to use - * @return string - */ - private function getContribsLink( $skin ) { - $contribsPage = SpecialPage::getTitleFor( 'Contributions', $this->BlockAddress ); - return $skin->link( $contribsPage, wfMsgExt( 'ipb-blocklist-contribs', 'escape', $this->BlockAddress ) ); - } - - /** - * Build a convenient link to unblock the given username or IP - * address, if available; otherwise link to a blank unblock - * form - * - * @param $skin Skin to use - * @return string - */ - private function getUnblockLink( $skin ) { - $list = SpecialPage::getTitleFor( 'Ipblocklist' ); - $query = array( 'action' => 'unblock' ); - - if( $this->BlockAddress ) { - $addr = strtr( $this->BlockAddress, '_', ' ' ); - $message = wfMsg( 'ipb-unblock-addr', $addr ); - $query['ip'] = $this->BlockAddress; - } else { - $message = wfMsg( 'ipb-unblock' ); - } - return $skin->linkKnown( - $list, - htmlspecialchars( $message ), - array(), - $query - ); - } - - /** - * Build a convenience link to the block list - * - * @param $skin Skin to use - * @return string - */ - private function getBlockListLink( $skin ) { - return $skin->linkKnown( - SpecialPage::getTitleFor( 'Ipblocklist' ), - wfMsg( 'ipb-blocklist' ) - ); - } - - /** - * Block a list of selected users - * - * @param $users Array - * @param $reason String - * @param $tag String: replaces user pages - * @param $talkTag String: replaces user talk pages - * @return Array: list of html-safe usernames - */ - public static function doMassUserBlock( $users, $reason = '', $tag = '', $talkTag = '' ) { - global $wgUser; - $counter = $blockSize = 0; - $safeUsers = array(); - $log = new LogPage( 'block' ); - foreach( $users as $name ) { - # Enforce limits - $counter++; - $blockSize++; - # Lets not go *too* fast - if( $blockSize >= 20 ) { - $blockSize = 0; - wfWaitForSlaves( 5 ); - } - $u = User::newFromName( $name, false ); - // If user doesn't exist, it ought to be an IP then - if( is_null( $u ) || ( !$u->getId() && !IP::isIPAddress( $u->getName() ) ) ) { - continue; - } - $userTitle = $u->getUserPage(); - $userTalkTitle = $u->getTalkPage(); - $userpage = new Article( $userTitle ); - $usertalk = new Article( $userTalkTitle ); - $safeUsers[] = '[[' . $userTitle->getPrefixedText() . '|' . $userTitle->getText() . ']]'; - $expirestr = $u->getId() ? 'indefinite' : '1 week'; - $expiry = Block::parseExpiryInput( $expirestr ); - $anonOnly = IP::isIPAddress( $u->getName() ) ? 1 : 0; - // Create the block - $block = new Block( $u->getName(), // victim - $u->getId(), // uid - $wgUser->getId(), // blocker - $reason, // comment - wfTimestampNow(), // block time - 0, // auto ? - $expiry, // duration - $anonOnly, // anononly? - 1, // block account creation? - 1, // autoblocking? - 0, // suppress name? - 0 // block from sending email? - ); - $oldblock = Block::newFromDB( $u->getName(), $u->getId() ); - if( !$oldblock ) { - $block->insert(); - # Prepare log parameters - $logParams = array(); - $logParams[] = $expirestr; - if( $anonOnly ) { - $logParams[] = 'anononly'; - } - $logParams[] = 'nocreate'; - # Add log entry - $log->addEntry( 'block', $userTitle, $reason, $logParams ); - } - # Tag userpage! (check length to avoid mistakes) - if( strlen( $tag ) > 2 ) { - $userpage->doEdit( $tag, $reason, EDIT_MINOR ); - } - if( strlen( $talkTag ) > 2 ) { - $usertalk->doEdit( $talkTag, $reason, EDIT_MINOR ); - } - } - return $safeUsers; - } -} diff --git a/includes/specials/SpecialBlockme.php b/includes/specials/SpecialBlockme.php index f5131f5f..40747667 100644 --- a/includes/specials/SpecialBlockme.php +++ b/includes/specials/SpecialBlockme.php @@ -48,10 +48,12 @@ class SpecialBlockme extends UnlistedSpecialPage { if ( !$user->isLoggedIn() ) { $user->addToDatabase(); } - $id = $user->getId(); - $reason = wfMsg( 'proxyblockreason' ); - $block = new Block( $ip, 0, $id, $reason, wfTimestampNow() ); + $block = new Block(); + $block->setTarget( $ip ); + $block->setBlocker( $user ); + $block->mReason = wfMsg( 'proxyblockreason' ); + $block->insert(); $wgOut->addWikiMsg( 'proxyblocksuccess' ); diff --git a/includes/specials/SpecialBooksources.php b/includes/specials/SpecialBooksources.php index 67fb5404..20819329 100644 --- a/includes/specials/SpecialBooksources.php +++ b/includes/specials/SpecialBooksources.php @@ -49,14 +49,13 @@ class SpecialBookSources extends SpecialPage { * @param $isbn ISBN passed as a subpage parameter */ public function execute( $isbn ) { - global $wgOut, $wgRequest; $this->setHeaders(); - $this->isbn = self::cleanIsbn( $isbn ? $isbn : $wgRequest->getText( 'isbn' ) ); - $wgOut->addWikiMsg( 'booksources-summary' ); - $wgOut->addHTML( $this->makeForm() ); + $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 ) ) { - $wgOut->wrapWikiMsg( "<div class=\"error\">\n$1\n</div>", 'booksources-invalid-isbn' ); + $this->getOutput()->wrapWikiMsg( "<div class=\"error\">\n$1\n</div>", 'booksources-invalid-isbn' ); } $this->showList(); } @@ -72,26 +71,26 @@ class SpecialBookSources extends SpecialPage { if( strlen( $isbn ) == 13 ) { for( $i = 0; $i < 12; $i++ ) { if($i % 2 == 0) { - $sum += $isbn{$i}; + $sum += $isbn[$i]; } else { - $sum += 3 * $isbn{$i}; + $sum += 3 * $isbn[$i]; } } - + $check = (10 - ($sum % 10)) % 10; - if ($check == $isbn{12}) { + if ($check == $isbn[12]) { return true; } } elseif( strlen( $isbn ) == 10 ) { for($i = 0; $i < 9; $i++) { - $sum += $isbn{$i} * ($i + 1); + $sum += $isbn[$i] * ($i + 1); } - + $check = $sum % 11; if($check == 10) { $check = "X"; } - if($check == $isbn{9}) { + if($check == $isbn[9]) { return true; } } @@ -115,10 +114,10 @@ class SpecialBookSources extends SpecialPage { */ private function makeForm() { global $wgScript; - $title = self::getTitleFor( 'Booksources' ); + $form = '<fieldset><legend>' . wfMsgHtml( 'booksources-search-legend' ) . '</legend>'; $form .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); - $form .= Html::hidden( 'title', $title->getPrefixedText() ); + $form .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ); $form .= '<p>' . Xml::inputLabel( wfMsg( 'booksources-isbn' ), 'isbn', 'isbn', 20, $this->isbn ); $form .= ' ' . Xml::submitButton( wfMsg( 'booksources-go' ) ) . '</p>'; $form .= Xml::closeElement( 'form' ); @@ -133,27 +132,27 @@ class SpecialBookSources extends SpecialPage { * @return string */ private function showList() { - global $wgOut, $wgContLang; + global $wgContLang; # Hook to allow extensions to insert additional HTML, # e.g. for API-interacting plugins and so on - wfRunHooks( 'BookInformation', array( $this->isbn, &$wgOut ) ); + wfRunHooks( 'BookInformation', array( $this->isbn, $this->getOutput() ) ); # Check for a local page such as Project:Book_sources and use that if available $title = Title::makeTitleSafe( NS_PROJECT, wfMsgForContent( 'booksources' ) ); # Show list in content language if( is_object( $title ) && $title->exists() ) { $rev = Revision::newFromTitle( $title ); - $wgOut->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $rev->getText() ) ); + $this->getOutput()->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $rev->getText() ) ); return true; } # Fall back to the defaults given in the language file - $wgOut->addWikiMsg( 'booksources-text' ); - $wgOut->addHTML( '<ul>' ); + $this->getOutput()->addWikiMsg( 'booksources-text' ); + $this->getOutput()->addHTML( '<ul>' ); $items = $wgContLang->getBookstoreList(); foreach( $items as $label => $url ) - $wgOut->addHTML( $this->makeListItem( $label, $url ) ); - $wgOut->addHTML( '</ul>' ); + $this->getOutput()->addHTML( $this->makeListItem( $label, $url ) ); + $this->getOutput()->addHTML( '</ul>' ); return true; } diff --git a/includes/specials/SpecialBrokenRedirects.php b/includes/specials/SpecialBrokenRedirects.php index 98b02126..2330c896 100644 --- a/includes/specials/SpecialBrokenRedirects.php +++ b/includes/specials/SpecialBrokenRedirects.php @@ -28,42 +28,56 @@ * @ingroup SpecialPage */ class BrokenRedirectsPage extends PageQueryPage { - var $targets = array(); - function getName() { - return 'BrokenRedirects'; + function __construct( $name = 'BrokenRedirects' ) { + parent::__construct( $name ); } - function isExpensive( ) { return true; } + function isExpensive() { return true; } function isSyndicated() { return false; } + function sortDescending() { return false; } - function getPageHeader( ) { + function getPageHeader() { return wfMsgExt( 'brokenredirectstext', array( 'parse' ) ); } - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $page, $redirect ) = $dbr->tableNamesN( 'page', 'redirect' ); - - $sql = "SELECT 'BrokenRedirects' AS type, - p1.page_namespace AS namespace, - p1.page_title AS title, - rd_namespace, - rd_title - FROM $redirect AS rd - JOIN $page p1 ON (rd.rd_from=p1.page_id) - LEFT JOIN $page AS p2 ON (rd_namespace=p2.page_namespace AND rd_title=p2.page_title ) - WHERE rd_namespace >= 0 - AND p2.page_namespace IS NULL"; - return $sql; + function getQueryInfo() { + return array( + 'tables' => array( 'redirect', 'p1' => 'page', + 'p2' => 'page' ), + 'fields' => array( 'p1.page_namespace AS namespace', + 'p1.page_title AS title', + 'rd_namespace', + 'rd_title' + ), + 'conds' => array( 'rd_namespace >= 0', + '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' + ) ) + ) + ); } - function getOrder() { - return ''; + /** + * @return array + */ + function getOrderFields() { + return array ( 'rd_namespace', 'rd_title', 'rd_from' ); } + /** + * @param $skin Skin + * @param $result + * @return String + */ function formatResult( $skin, $result ) { - global $wgUser, $wgContLang, $wgLang; + global $wgUser, $wgLang; $fromObj = Title::makeTitle( $result->namespace, $result->title ); if ( isset( $result->rd_title ) ) { @@ -95,14 +109,14 @@ class BrokenRedirectsPage extends PageQueryPage { array(), array( 'action' => 'edit' ) ); - $to = $skin->link( + $to = $skin->link( $toObj, null, array(), array(), array( 'broken' ) ); - $arr = $wgContLang->getArrow(); + $arr = $wgLang->getArrow(); $out = $from . wfMsg( 'word-separator' ); @@ -120,14 +134,3 @@ class BrokenRedirectsPage extends PageQueryPage { return $out; } } - -/** - * constructor - */ -function wfSpecialBrokenRedirects() { - list( $limit, $offset ) = wfCheckLimits(); - - $sbr = new BrokenRedirectsPage(); - - return $sbr->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialCategories.php b/includes/specials/SpecialCategories.php index c2dd40cd..91d98b86 100644 --- a/includes/specials/SpecialCategories.php +++ b/includes/specials/SpecialCategories.php @@ -69,16 +69,20 @@ class CategoryPager extends AlphabeticPager { $this->mOffset = $from; } } - + function getQueryInfo() { return array( 'tables' => array( 'category' ), 'fields' => array( 'cat_title','cat_pages' ), - 'conds' => array( 'cat_pages > 0' ), + 'conds' => array( 'cat_pages > 0' ), 'options' => array( 'USE INDEX' => 'cat_title' ), ); } + function getTitle() { + return SpecialPage::getTitleFor( 'Categories' ); + } + function getIndexField() { # return array( 'abc' => 'cat_title', 'count' => 'cat_pages' ); return 'cat_title'; @@ -116,19 +120,18 @@ class CategoryPager extends AlphabeticPager { function formatRow($result) { global $wgLang; $title = Title::makeTitle( NS_CATEGORY, $result->cat_title ); - $titleText = $this->getSkin()->link( $title, htmlspecialchars( $title->getText() ) ); + $titleText = Linker::link( $title, htmlspecialchars( $title->getText() ) ); $count = wfMsgExt( 'nmembers', array( 'parsemag', 'escape' ), $wgLang->formatNum( $result->cat_pages ) ); - return Xml::tags('li', null, "$titleText ($count)" ) . "\n"; + return Xml::tags('li', null, wfSpecialList( $titleText, $count ) ) . "\n"; } - + public function getStartForm( $from ) { global $wgScript; - $t = SpecialPage::getTitleFor( 'Categories' ); - + return Xml::tags( 'form', array( 'method' => 'get', 'action' => $wgScript ), - Html::hidden( 'title', $t->getPrefixedText() ) . + Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . Xml::fieldset( wfMsg( 'categories' ), Xml::inputLabel( wfMsg( 'categoriesfrom' ), 'from', 'from', 20, $from ) . diff --git a/includes/specials/SpecialResetpass.php b/includes/specials/SpecialChangePassword.php index 0af6fbf0..46562b36 100644 --- a/includes/specials/SpecialResetpass.php +++ b/includes/specials/SpecialChangePassword.php @@ -1,6 +1,6 @@ <?php /** - * Implements Special:Resetpass + * Implements Special:ChangePassword * * 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 @@ -26,9 +26,9 @@ * * @ingroup SpecialPage */ -class SpecialResetpass extends SpecialPage { +class SpecialChangePassword extends SpecialPage { public function __construct() { - parent::__construct( 'Resetpass' ); + parent::__construct( 'ChangePassword' ); } /** @@ -46,16 +46,12 @@ class SpecialResetpass extends SpecialPage { $this->mOldpass = $wgRequest->getVal( 'wpPassword' ); $this->mNewpass = $wgRequest->getVal( 'wpNewPassword' ); $this->mRetype = $wgRequest->getVal( 'wpRetype' ); - + $this->mDomain = $wgRequest->getVal( 'wpDomain' ); + $this->setHeaders(); $this->outputHeader(); $wgOut->disallowUserJs(); - if( !$wgAuth->allowPasswordChange() ) { - $this->error( wfMsg( 'resetpass_forbidden' ) ); - return; - } - if( !$wgRequest->wasPosted() && !$wgUser->isLoggedIn() ) { $this->error( wfMsg( 'resetpass-no-info' ) ); return; @@ -66,22 +62,35 @@ class SpecialResetpass extends SpecialPage { return; } - if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal('token') ) ) { + if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'token' ) ) ) { try { + if ( isset( $_SESSION['wsDomain'] ) ) { + $this->mDomain = $_SESSION['wsDomain']; + } + $wgAuth->setDomain( $this->mDomain ); + if( !$wgAuth->allowPasswordChange() ) { + $this->error( wfMsg( 'resetpass_forbidden' ) ); + return; + } + $this->attemptReset( $this->mNewpass, $this->mRetype ); $wgOut->addWikiMsg( 'resetpass_success' ); if( !$wgUser->isLoggedIn() ) { + LoginForm::setLoginToken(); + $token = LoginForm::getLoginToken(); $data = array( - 'action' => 'submitlogin', - 'wpName' => $this->mUserName, - 'wpPassword' => $this->mNewpass, - 'returnto' => $wgRequest->getVal( 'returnto' ), + 'action' => 'submitlogin', + 'wpName' => $this->mUserName, + 'wpDomain' => $this->mDomain, + 'wpLoginToken' => $token, + 'wpPassword' => $this->mNewpass, + 'returnto' => $wgRequest->getVal( 'returnto' ), ); if( $wgRequest->getCheck( 'wpRemember' ) ) { $data['wpRemember'] = 1; } $login = new LoginForm( new FauxRequest( $data, true ) ); - $login->execute(); + $login->execute( null ); } $this->doReturnTo(); } catch( PasswordError $e ) { @@ -90,7 +99,7 @@ class SpecialResetpass extends SpecialPage { } $this->showForm(); } - + function doReturnTo() { global $wgRequest, $wgOut; $titleObj = Title::newFromText( $wgRequest->getVal( 'returnto' ) ); @@ -118,7 +127,7 @@ class SpecialResetpass extends SpecialPage { $rememberMe = '<tr>' . '<td></td>' . '<td class="mw-input">' . - Xml::checkLabel( + Xml::checkLabel( wfMsgExt( 'remembermypassword', 'parsemag', $wgLang->formatNum( ceil( $wgCookieExpiration / ( 3600 * 24 ) ) ) ), 'wpRemember', 'wpRemember', $wgRequest->getCheck( 'wpRemember' ) ) . @@ -139,6 +148,7 @@ class SpecialResetpass extends SpecialPage { 'id' => 'mw-resetpass-form' ) ) . "\n" . Html::hidden( 'token', $wgUser->editToken() ) . "\n" . Html::hidden( 'wpName', $this->mUserName ) . "\n" . + Html::hidden( 'wpDomain', $this->mDomain ) . "\n" . Html::hidden( 'returnto', $wgRequest->getVal( 'returnto' ) ) . "\n" . wfMsgExt( 'resetpass_text', array( 'parse' ) ) . "\n" . Xml::openElement( 'table', array( 'id' => 'mw-resetpass-table' ) ) . "\n" . @@ -183,7 +193,7 @@ class SpecialResetpass extends SpecialPage { $out .= "\t<td class='mw-label'>"; if ( $type != 'text' ) $out .= Xml::label( wfMsg( $label ), $name ); - else + else $out .= wfMsgHtml( $label ); $out .= "</td>\n"; $out .= "\t<td class='mw-input'>"; @@ -200,19 +210,29 @@ class SpecialResetpass extends SpecialPage { protected function attemptReset( $newpass, $retype ) { $user = User::newFromName( $this->mUserName ); if( !$user || $user->isAnon() ) { - throw new PasswordError( 'no such user' ); + throw new PasswordError( wfMsg( 'nosuchusershort', $this->mUserName ) ); } - + if( $newpass !== $retype ) { wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'badretype' ) ); throw new PasswordError( wfMsg( 'badretype' ) ); } + $throttleCount = LoginForm::incLoginThrottle( $this->mUserName ); + if ( $throttleCount === true ) { + throw new PasswordError( wfMsg( 'login-throttled' ) ); + } + if( !$user->checkTemporaryPassword($this->mOldpass) && !$user->checkPassword($this->mOldpass) ) { wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'wrongpassword' ) ); throw new PasswordError( wfMsg( 'resetpass-wrong-oldpass' ) ); } - + + // Please reset throttle for successful logins, thanks! + if ( $throttleCount ) { + LoginForm::clearLoginThrottle( $this->mUserName ); + } + try { $user->setPassword( $this->mNewpass ); wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'success' ) ); @@ -221,7 +241,7 @@ class SpecialResetpass extends SpecialPage { wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'error' ) ); throw new PasswordError( $e->getMessage() ); } - + $user->setCookies(); $user->saveSettings(); } diff --git a/includes/specials/SpecialComparePages.php b/includes/specials/SpecialComparePages.php index 4650fc94..6b9ef0a9 100644 --- a/includes/specials/SpecialComparePages.php +++ b/includes/specials/SpecialComparePages.php @@ -40,47 +40,6 @@ class SpecialComparePages extends SpecialPage { parent::__construct( 'ComparePages' ); } - protected function setup( $par ) { - global $wgRequest, $wgUser; - - // Options - $opts = new FormOptions(); - $this->opts = $opts; // bind - $opts->add( 'page1', '' ); - $opts->add( 'page2', '' ); - $opts->add( 'rev1', '' ); - $opts->add( 'rev2', '' ); - $opts->add( 'action', '' ); - - // Set values - $opts->fetchValuesFromRequest( $wgRequest ); - - $title1 = Title::newFromText( $opts->getValue( 'page1' ) ); - $title2 = Title::newFromText( $opts->getValue( 'page2' ) ); - - if( $title1 && $title1->exists() && $opts->getValue( 'rev1' ) == '' ) { - $pda = new Article( $title1 ); - $pdi = $pda->getID(); - $pdLastRevision = Revision::loadFromPageId( wfGetDB( DB_SLAVE ), $pdi ); - $opts->setValue( 'rev1', $pdLastRevision->getId() ); - } elseif ( $opts->getValue( 'rev1' ) != '' ) { - $pdrev = Revision::newFromId( $opts->getValue( 'rev1' ) ); - if( $pdrev ) $opts->setValue( 'page1', $pdrev->getTitle()->getPrefixedText() ); - } - if( $title2 && $title2->exists() && $opts->getValue( 'rev2' ) == '' ) { - $pda = new Article( $title2 ); - $pdi = $pda->getID(); - $pdLastRevision = Revision::loadFromPageId( wfGetDB( DB_SLAVE ), $pdi ); - $opts->setValue('rev2', $pdLastRevision->getId() ); - } elseif ( $opts->getValue( 'rev2' ) != '' ) { - $pdrev = Revision::newFromId( $opts->getValue( 'rev2' ) ); - if( $pdrev ) $opts->setValue( 'page2', $pdrev->getTitle()->getPrefixedText() ); - } - - // Store some objects - $this->skin = $wgUser->getSkin(); - } - /** * Show a form for filtering namespace and username * @@ -91,80 +50,79 @@ class SpecialComparePages extends SpecialPage { $this->setHeaders(); $this->outputHeader(); - $this->setup( $par ); + $form = new HTMLForm( array( + 'Page1' => array( + 'type' => 'text', + 'name' => 'page1', + 'label-message' => 'compare-page1', + 'size' => '40', + 'section' => 'page1', + ), + 'Revision1' => array( + 'type' => 'int', + 'name' => 'rev1', + 'label-message' => 'compare-rev1', + 'size' => '8', + 'section' => 'page1', + ), + 'Page2' => array( + 'type' => 'text', + 'name' => 'page2', + 'label-message' => 'compare-page2', + 'size' => '40', + 'section' => 'page2', + ), + 'Revision2' => array( + 'type' => 'int', + 'name' => 'rev2', + 'label-message' => 'compare-rev2', + 'size' => '8', + 'section' => 'page2', + ), + 'Action' => array( + 'type' => 'hidden', + 'name' => 'action', + ), + 'Diffonly' => array( + 'type' => 'hidden', + 'name' => 'diffonly', + ), + ), 'compare' ); + $form->setSubmitText( wfMsg( 'compare-submit' ) ); + $form->suppressReset(); + $form->setMethod( 'get' ); + $form->setTitle( $this->getTitle() ); + + $form->loadData(); + $form->displayForm( '' ); + + self::showDiff( $form->mFieldData ); + } - // Settings - $this->form(); + public static function showDiff( $data ){ + $rev1 = self::revOrTitle( $data['Revision1'], $data['Page1'] ); + $rev2 = self::revOrTitle( $data['Revision2'], $data['Page2'] ); - if( $this->opts->getValue( 'rev1' ) && $this->opts->getValue( 'rev2' ) ) { + if( $rev1 && $rev2 ) { $de = new DifferenceEngine( null, - $this->opts->getValue( 'rev1' ), - $this->opts->getValue( 'rev2' ), + $rev1, + $rev2, null, // rcid - ( $this->opts->getValue( 'action' ) == 'purge' ), + ( $data["Action"] == 'purge' ), false ); $de->showDiffPage( true ); } } - protected function form() { - global $wgOut, $wgScript; - - // Consume values - $page1 = $this->opts->consumeValue( 'page1' ); - $page2 = $this->opts->consumeValue( 'page2' ); - $rev1 = $this->opts->consumeValue( 'rev1' ); - $rev2 = $this->opts->consumeValue( 'rev2' ); - - // Store query values in hidden fields so that form submission doesn't lose them - $hidden = array(); - foreach ( $this->opts->getUnconsumedValues() as $key => $value ) { - $hidden[] = Html::hidden( $key, $value ); + public static function revOrTitle( $revision, $title ) { + if( $revision ){ + return $revision; + } elseif( $title ) { + $title = Title::newFromText( $title ); + if( $title instanceof Title ){ + return $title->getLatestRevID(); + } } - $hidden = implode( "\n", $hidden ); - - $form = Html::openElement( 'form', array( 'action' => $wgScript ) ) . - Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . - Xml::fieldset( wfMsg( 'compare-selector' ) ) . - Html::openElement( 'table', array( 'id' => 'mw-diff-table', 'style' => 'width:100%' ) ) . - "<tr> - <td class='mw-label' style='width:10%'>" . - Html::element( 'label', array( 'for' => 'page1' ), wfMsg( 'compare-page1' ) ) . - "</td> - <td class='mw-input' style='width:40%'>" . - Html::input( 'page1', $page1, 'text', array( 'size' => 40, 'id' => 'page1' ) ) . - "</td> - <td class='mw-label' style='width:10%'>" . - Html::element( 'label', array( 'for' => 'page2' ), wfMsg( 'compare-page2' ) ) . - "</td> - <td class='mw-input' style='width:40%'>" . - Html::input( 'page2', $page2, 'text', array( 'size' => 40, 'id' => 'page2' ) ) . - "</td> - </tr>" . - "<tr> - <td class='mw-label'>" . - Html::element( 'label', array( 'for' => 'rev1' ), wfMsg( 'compare-rev1' ) ) . - "</td> - <td class='mw-input'>" . - Html::input( 'rev1', $rev1, 'text', array( 'size' => 8, 'id' => 'rev1' ) ) . - "</td> - <td class='mw-label'>" . - Html::element( 'label', array( 'for' => 'rev2' ), wfMsg( 'compare-rev2' ) ) . - "</td> - <td class='mw-input'>" . - Html::input( 'rev2', $rev2, 'text', array( 'size' => 8, 'id' => 'rev2' ) ) . - "</td> - </tr>" . - "<tr> <td></td> - <td class='mw-submit' colspan='3'>" . - Xml::submitButton( wfMsg( 'compare-submit' ) ) . - "</td> - </tr>" . - Html::closeElement( 'table' ) . - Html::closeElement( 'fieldset' ) . - $hidden . - Html::closeElement( 'form' ); - - $wgOut->addHTML( $form ); + return null; } } diff --git a/includes/specials/SpecialConfirmemail.php b/includes/specials/SpecialConfirmemail.php index e556a60b..70bbfe39 100644 --- a/includes/specials/SpecialConfirmemail.php +++ b/includes/specials/SpecialConfirmemail.php @@ -44,31 +44,27 @@ class EmailConfirmation extends UnlistedSpecialPage { * @param $code Confirmation code passed to the page */ function execute( $code ) { - global $wgUser, $wgOut; $this->setHeaders(); - + if ( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; + throw new ReadOnlyError; } - + if( empty( $code ) ) { - if( $wgUser->isLoggedIn() ) { - if( User::isValidEmailAddr( $wgUser->getEmail() ) ) { + if( $this->getUser()->isLoggedIn() ) { + if( Sanitizer::validateEmail( $this->getUser()->getEmail() ) ) { $this->showRequestForm(); } else { - $wgOut->addWikiMsg( 'confirmemail_noemail' ); + $this->getOutput()->addWikiMsg( 'confirmemail_noemail' ); } } else { - $title = SpecialPage::getTitleFor( 'Userlogin' ); - $skin = $wgUser->getSkin(); - $llink = $skin->linkKnown( - $title, + $llink = Linker::linkKnown( + SpecialPage::getTitleFor( 'Userlogin' ), wfMsgHtml( 'loginreqlink' ), array(), array( 'returnto' => $this->getTitle()->getPrefixedText() ) ); - $wgOut->addHTML( wfMsgWikiHtml( 'confirmemail_needlogin', $llink ) ); + $this->getOutput()->addHTML( wfMessage( 'confirmemail_needlogin' )->rawParams( $llink )->parse() ); } } else { $this->attemptConfirm( $code ); @@ -79,33 +75,34 @@ class EmailConfirmation extends UnlistedSpecialPage { * Show a nice form for the user to request a confirmation mail */ function showRequestForm() { - global $wgOut, $wgUser, $wgLang, $wgRequest; - if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getText( 'token' ) ) ) { - $status = $wgUser->sendConfirmationMail(); + $user = $this->getUser(); + $out = $this->getOutput(); + if( $this->getRequest()->wasPosted() && $user->matchEditToken( $this->getRequest()->getText( 'token' ) ) ) { + $status = $user->sendConfirmationMail(); if ( $status->isGood() ) { - $wgOut->addWikiMsg( 'confirmemail_sent' ); + $out->addWikiMsg( 'confirmemail_sent' ); } else { - $wgOut->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) ); + $out->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) ); } } else { - if( $wgUser->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 - $time = $wgLang->timeAndDate( $wgUser->mEmailAuthenticated, true ); - $d = $wgLang->date( $wgUser->mEmailAuthenticated, true ); - $t = $wgLang->time( $wgUser->mEmailAuthenticated, true ); - $wgOut->addWikiMsg( 'emailauthenticated', $time, $d, $t ); + $time = $this->getLang()->timeAndDate( $user->mEmailAuthenticated, true ); + $d = $this->getLang()->date( $user->mEmailAuthenticated, true ); + $t = $this->getLang()->time( $user->mEmailAuthenticated, true ); + $out->addWikiMsg( 'emailauthenticated', $time, $d, $t ); } - if( $wgUser->isEmailConfirmationPending() ) { - $wgOut->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' ); } - $wgOut->addWikiMsg( 'confirmemail_text' ); + $out->addWikiMsg( 'confirmemail_text' ); $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalUrl() ) ); - $form .= Html::hidden( 'token', $wgUser->editToken() ); + $form .= Html::hidden( 'token', $user->editToken() ); $form .= Xml::submitButton( wfMsg( 'confirmemail_send' ) ); $form .= Xml::closeElement( 'form' ); - $wgOut->addHTML( $form ); + $out->addHTML( $form ); } } @@ -116,19 +113,18 @@ class EmailConfirmation extends UnlistedSpecialPage { * @param $code Confirmation code */ function attemptConfirm( $code ) { - global $wgUser, $wgOut; $user = User::newFromConfirmationCode( $code ); if( is_object( $user ) ) { $user->confirmEmail(); $user->saveSettings(); - $message = $wgUser->isLoggedIn() ? 'confirmemail_loggedin' : 'confirmemail_success'; - $wgOut->addWikiMsg( $message ); - if( !$wgUser->isLoggedIn() ) { + $message = $this->getUser()->isLoggedIn() ? 'confirmemail_loggedin' : 'confirmemail_success'; + $this->getOutput()->addWikiMsg( $message ); + if( !$this->getUser()->isLoggedIn() ) { $title = SpecialPage::getTitleFor( 'Userlogin' ); - $wgOut->returnToMain( true, $title ); + $this->getOutput()->returnToMain( true, $title ); } } else { - $wgOut->addWikiMsg( 'confirmemail_invalid' ); + $this->getOutput()->addWikiMsg( 'confirmemail_invalid' ); } } @@ -150,11 +146,9 @@ class EmailInvalidation extends UnlistedSpecialPage { $this->setHeaders(); if ( wfReadOnly() ) { - global $wgOut; - $wgOut->readOnlyPage(); - return; + throw new ReadOnlyError; } - + $this->attemptInvalidate( $code ); } @@ -165,17 +159,16 @@ class EmailInvalidation extends UnlistedSpecialPage { * @param $code Confirmation code */ function attemptInvalidate( $code ) { - global $wgUser, $wgOut; $user = User::newFromConfirmationCode( $code ); if( is_object( $user ) ) { $user->invalidateEmail(); $user->saveSettings(); - $wgOut->addWikiMsg( 'confirmemail_invalidated' ); - if( !$wgUser->isLoggedIn() ) { - $wgOut->returnToMain(); + $this->getOutput()->addWikiMsg( 'confirmemail_invalidated' ); + if( !$this->getUser()->isLoggedIn() ) { + $this->getOutput()->returnToMain(); } } else { - $wgOut->addWikiMsg( 'confirmemail_invalid' ); + $this->getOutput()->addWikiMsg( 'confirmemail_invalid' ); } } } diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php index cee01a7f..fea27bfd 100644 --- a/includes/specials/SpecialContributions.php +++ b/includes/specials/SpecialContributions.php @@ -29,6 +29,8 @@ class SpecialContributions extends SpecialPage { + protected $opts; + public function __construct() { parent::__construct( 'Contributions' ); } @@ -38,6 +40,7 @@ class SpecialContributions extends SpecialPage { $this->setHeaders(); $this->outputHeader(); + $wgOut->addModuleStyles( 'mediawiki.special' ); $this->opts = array(); @@ -78,6 +81,10 @@ class SpecialContributions extends SpecialPage { $target = $nt->getText(); $wgOut->setSubtitle( $this->contributionsSub( $nt, $id ) ); $wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsgExt( 'contributions-title', array( 'parsemag' ),$target ) ) ); + $user = User::newFromName( $target, false ); + if ( is_object( $user ) ) { + $this->getSkin()->setRelevantUser( $user ); + } } else { $wgOut->setSubtitle( wfMsgHtml( 'sp-contributions-newbies-sub') ); $wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsg( 'sp-contributions-newbies-title' ) ) ); @@ -107,13 +114,43 @@ class SpecialContributions extends SpecialPage { $this->opts['month'] = $wgRequest->getIntOrNull( 'month' ); } - // Add RSS/atom links - $this->setSyndicated(); $feedType = $wgRequest->getVal( 'feed' ); if( $feedType ) { - return $this->feed( $feedType ); + // Maintain some level of backwards compatability + // If people request feeds using the old parameters, redirect to API + $apiParams = array( + 'action' => 'feedcontributions', + 'feedformat' => $feedType, + 'user' => $target, + ); + if ( $this->opts['topOnly'] ) { + $apiParams['toponly'] = true; + } + if ( $this->opts['deletedOnly'] ) { + $apiParams['deletedonly'] = true; + } + if ( $this->opts['tagFilter'] !== '' ) { + $apiParams['tagfilter'] = $this->opts['tagFilter']; + } + if ( $this->opts['namespace'] !== '' ) { + $apiParams['namespace'] = $this->opts['namespace']; + } + if ( $this->opts['year'] !== null ) { + $apiParams['year'] = $this->opts['year']; + } + if ( $this->opts['month'] !== null ) { + $apiParams['month'] = $this->opts['month']; + } + + $url = wfScript( 'api' ) . '?' . wfArrayToCGI( $apiParams ); + + $wgOut->redirect( $url, '301' ); + return; } + // Add RSS/atom links + $this->addFeedLinks( array( 'action' => 'feedcontributions', 'user' => $target ) ); + if ( wfRunHooks( 'SpecialContributionsBeforeMainOutput', array( $id ) ) ) { $wgOut->addHTML( $this->getForm() ); @@ -130,7 +167,8 @@ class SpecialContributions extends SpecialPage { $wgOut->addWikiMsg( 'nocontribs', $target ); } else { # Show a message about slave lag, if applicable - if( ( $lag = $pager->getDatabase()->getLag() ) > 0 ) + $lag = wfGetLB()->safeGetLag( $pager->getDatabase() ); + if( $lag > 0 ) $wgOut->showLagWarning( $lag ); $wgOut->addHTML( @@ -141,7 +179,6 @@ class SpecialContributions extends SpecialPage { } $wgOut->preventClickjacking( $pager->getPreventClickjacking() ); - # Show the appropriate "footer" message - WHOIS tools, etc. if( $target != 'newbies' ) { $message = 'sp-contributions-footer'; @@ -155,8 +192,7 @@ class SpecialContributions extends SpecialPage { } } - $text = wfMsgNoTrans( $message, $target ); - if( !wfEmptyMsg( $message, $text ) && $text != '-' ) { + if( !wfMessage( $message, $target )->isDisabled() ) { $wgOut->wrapWikiMsg( "<div class='mw-contributions-footer'>\n$1\n</div>", array( $message, $target ) ); @@ -165,23 +201,17 @@ class SpecialContributions extends SpecialPage { } } - protected function setSyndicated() { - global $wgOut; - $wgOut->setSyndicated( true ); - $wgOut->setFeedAppendQuery( wfArrayToCGI( $this->opts ) ); - } - /** * Generates the subheading with links * @param $nt Title object for the target * @param $id Integer: User ID 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( $nt, $id ) { - global $wgSysopUserBans, $wgLang, $wgUser, $wgOut; + global $wgLang, $wgUser, $wgOut; - $sk = $wgUser->getSkin(); + $sk = $this->getSkin(); if ( $id === null ) { $user = htmlspecialchars( $nt->getText() ); @@ -191,78 +221,7 @@ class SpecialContributions extends SpecialPage { $userObj = User::newFromName( $nt->getText(), /* check for username validity not needed */ false ); $talk = $nt->getTalkPage(); if( $talk ) { - # Talk page link - $tools[] = $sk->link( $talk, wfMsgHtml( 'sp-contributions-talk' ) ); - if( ( $id !== null && $wgSysopUserBans ) || ( $id === null && IP::isIPAddress( $nt->getText() ) ) ) { - if( $wgUser->isAllowed( 'block' ) ) { # Block / Change block / Unblock links - if ( $userObj->isBlocked() ) { - $tools[] = $sk->linkKnown( # Change block link - SpecialPage::getTitleFor( 'Blockip', $nt->getDBkey() ), - wfMsgHtml( 'change-blocklink' ) - ); - $tools[] = $sk->linkKnown( # Unblock link - SpecialPage::getTitleFor( 'Ipblocklist' ), - wfMsgHtml( 'unblocklink' ), - array(), - array( - 'action' => 'unblock', - 'ip' => $nt->getDBkey() - ) - ); - } - else { # User is not blocked - $tools[] = $sk->linkKnown( # Block link - SpecialPage::getTitleFor( 'Blockip', $nt->getDBkey() ), - wfMsgHtml( 'blocklink' ) - ); - } - } - # Block log link - $tools[] = $sk->linkKnown( - SpecialPage::getTitleFor( 'Log' ), - wfMsgHtml( 'sp-contributions-blocklog' ), - array(), - array( - 'type' => 'block', - 'page' => $nt->getPrefixedText() - ) - ); - } - # Uploads - $tools[] = $sk->linkKnown( - SpecialPage::getTitleFor( 'Listfiles' ), - wfMsgHtml( 'sp-contributions-uploads' ), - array(), - array( 'user' => $nt->getText() ) - ); - - # Other logs link - $tools[] = $sk->linkKnown( - SpecialPage::getTitleFor( 'Log' ), - wfMsgHtml( 'sp-contributions-logs' ), - array(), - array( 'user' => $nt->getText() ) - ); - - # Add link to deleted user contributions for priviledged users - if( $wgUser->isAllowed( 'deletedhistory' ) ) { - $tools[] = $sk->linkKnown( - SpecialPage::getTitleFor( 'DeletedContributions', $nt->getDBkey() ), - wfMsgHtml( 'sp-contributions-deleted' ) - ); - } - - # Add a link to change user rights for privileged users - $userrightsPage = new UserrightsPage(); - if( $id !== null && $userrightsPage->userCanChangeRights( User::newFromId( $id ) ) ) { - $tools[] = $sk->linkKnown( - SpecialPage::getTitleFor( 'Userrights', $nt->getDBkey() ), - wfMsgHtml( 'sp-contributions-userrights' ) - ); - } - - wfRunHooks( 'ContributionsToolLinks', array( $id, $nt, &$tools ) ); - + $tools = self::getUserLinks( $nt, $talk, $userObj, $wgUser ); $links = $wgLang->pipeList( $tools ); // Show a note if the user is blocked and display the last block log entry. @@ -291,7 +250,7 @@ class SpecialContributions extends SpecialPage { // 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'. - if( wfEmptyMsg( 'contribsub', wfMsg( 'contribsub' ) ) ) { + if( wfEmptyMsg( 'contribsub' ) ) { return wfMsgHtml( 'contribsub2', $user, $links ); } else { return wfMsgHtml( 'contribsub', "$user ($links)" ); @@ -299,6 +258,82 @@ class SpecialContributions extends SpecialPage { } /** + * Links to different places. + * @param $userpage Title: Target user page + * @param $talkpage Title: Talk page + * @param $target User: Target user object + * @param $subject User: The viewing user ($wgUser is still checked in some cases, like userrights page!!) + */ + public static function getUserLinks( Title $userpage, Title $talkpage, User $target, User $subject ) { + + $sk = $subject->getSkin(); + $id = $target->getId(); + $username = $target->getName(); + + $tools[] = $sk->link( $talkpage, wfMsgHtml( 'sp-contributions-talk' ) ); + + if( ( $id !== null ) || ( $id === null && IP::isIPAddress( $username ) ) ) { + if( $subject->isAllowed( 'block' ) ) { # Block / Change block / Unblock links + if ( $target->isBlocked() ) { + $tools[] = $sk->linkKnown( # Change block link + SpecialPage::getTitleFor( 'Block', $username ), + wfMsgHtml( 'change-blocklink' ) + ); + $tools[] = $sk->linkKnown( # Unblock link + SpecialPage::getTitleFor( 'Unblock', $username ), + wfMsgHtml( 'unblocklink' ) + ); + } else { # User is not blocked + $tools[] = $sk->linkKnown( # Block link + SpecialPage::getTitleFor( 'Block', $username ), + wfMsgHtml( 'blocklink' ) + ); + } + } + # Block log link + $tools[] = $sk->linkKnown( + SpecialPage::getTitleFor( 'Log', 'block' ), + wfMsgHtml( 'sp-contributions-blocklog' ), + array(), + array( + 'page' => $userpage->getPrefixedText() + ) + ); + } + # Uploads + $tools[] = $sk->linkKnown( + SpecialPage::getTitleFor( 'Listfiles', $username ), + wfMsgHtml( 'sp-contributions-uploads' ) + ); + + # Other logs link + $tools[] = $sk->linkKnown( + SpecialPage::getTitleFor( 'Log', $username ), + wfMsgHtml( 'sp-contributions-logs' ) + ); + + # Add link to deleted user contributions for priviledged users + if( $subject->isAllowed( 'deletedhistory' ) ) { + $tools[] = $sk->linkKnown( + SpecialPage::getTitleFor( 'DeletedContributions', $username ), + wfMsgHtml( 'sp-contributions-deleted' ) + ); + } + + # Add a link to change user rights for privileged users + $userrightsPage = new UserrightsPage(); + if( $id !== null && $userrightsPage->userCanChangeRights( $target ) ) { + $tools[] = $sk->linkKnown( + SpecialPage::getTitleFor( 'Userrights', $username ), + wfMsgHtml( 'sp-contributions-userrights' ) + ); + } + + wfRunHooks( 'ContributionsToolLinks', array( $id, $userpage, &$tools ) ); + return $tools; + } + + /** * Generates the namespace selector form with hidden attributes. * @return String: HTML fragment */ @@ -374,104 +409,15 @@ class SpecialContributions extends SpecialPage { Html::rawElement( 'p', array( 'style' => 'white-space: nowrap' ), Xml::dateMenu( $this->opts['year'], $this->opts['month'] ) . ' ' . Xml::submitButton( wfMsg( 'sp-contributions-submit' ) ) - ) . ' '; - $explain = wfMsgExt( 'sp-contributions-explain', 'parseinline' ); - if( !wfEmptyMsg( 'sp-contributions-explain', $explain ) ) { + ) . ' '; + $explain = wfMessage( 'sp-contributions-explain' ); + if ( $explain->exists() ) { $f .= "<p id='mw-sp-contributions-explain'>{$explain}</p>"; } $f .= Xml::closeElement('fieldset' ) . Xml::closeElement( 'form' ); return $f; } - - /** - * Output a subscription feed listing recent edits to this page. - * @param $type String - */ - protected function feed( $type ) { - global $wgFeed, $wgFeedClasses, $wgFeedLimit, $wgOut; - - if( !$wgFeed ) { - $wgOut->addWikiMsg( 'feed-unavailable' ); - return; - } - - if( !isset( $wgFeedClasses[$type] ) ) { - $wgOut->addWikiMsg( 'feed-invalid' ); - return; - } - - $feed = new $wgFeedClasses[$type]( - $this->feedTitle(), - wfMsgExt( 'tagline', 'parsemag' ), - $this->getTitle()->getFullUrl() . "/" . urlencode($this->opts['target']) - ); - - // Already valid title - $nt = Title::makeTitleSafe( NS_USER, $this->opts['target'] ); - $target = $this->opts['target'] == 'newbies' ? 'newbies' : $nt->getText(); - - $pager = new ContribsPager( array( - 'target' => $target, - 'namespace' => $this->opts['namespace'], - 'year' => $this->opts['year'], - 'month' => $this->opts['month'], - 'tagFilter' => $this->opts['tagFilter'], - 'deletedOnly' => $this->opts['deletedOnly'], - 'topOnly' => $this->opts['topOnly'], - ) ); - - $pager->mLimit = min( $this->opts['limit'], $wgFeedLimit ); - - $feed->outHeader(); - if( $pager->getNumRows() > 0 ) { - foreach ( $pager->mResult as $row ) { - $feed->outItem( $this->feedItem( $row ) ); - } - } - $feed->outFooter(); - } - - protected function feedTitle() { - global $wgLanguageCode, $wgSitename; - $page = SpecialPage::getPage( 'Contributions' ); - $desc = $page->getDescription(); - return "$wgSitename - $desc [$wgLanguageCode]"; - } - - protected function feedItem( $row ) { - $title = Title::MakeTitle( intval( $row->page_namespace ), $row->page_title ); - if( $title ) { - $date = $row->rev_timestamp; - $comments = $title->getTalkPage()->getFullURL(); - $revision = Revision::newFromTitle( $title, $row->rev_id ); - - return new FeedItem( - $title->getPrefixedText(), - $this->feedItemDesc( $revision ), - $title->getFullURL(), - $date, - $this->feedItemAuthor( $revision ), - $comments - ); - } else { - return null; - } - } - - protected function feedItemAuthor( $revision ) { - return $revision->getUserText(); - } - - protected function feedItemDesc( $revision ) { - if( $revision ) { - return '<p>' . htmlspecialchars( $revision->getUserText() ) . wfMsgForContent( 'colon-separator' ) . - htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) . - "</p>\n<hr />\n<div>" . - nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>"; - } - return ''; - } } /** @@ -513,6 +459,10 @@ class ContribsPager extends ReverseChronologicalPager { return $query; } + function getTitle() { + return SpecialPage::getTitleFor( 'Contributions' ); + } + function getQueryInfo() { global $wgUser; list( $tables, $index, $userCond, $join_cond ) = $this->getUserCond(); @@ -521,7 +471,7 @@ class ContribsPager extends ReverseChronologicalPager { // Paranoia: avoid brute force searches (bug 17342) if( !$wgUser->isAllowed( 'deletedhistory' ) ) { $conds[] = $this->mDb->bitAnd('rev_deleted',Revision::DELETED_USER) . ' = 0'; - } else if( !$wgUser->isAllowed( 'suppressrevision' ) ) { + } elseif( !$wgUser->isAllowed( 'suppressrevision' ) ) { $conds[] = $this->mDb->bitAnd('rev_deleted',Revision::SUPPRESSED_USER) . ' != ' . Revision::SUPPRESSED_USER; } @@ -561,7 +511,7 @@ class ContribsPager extends ReverseChronologicalPager { $condition[] = 'rev_user >' . (int)($max - $max / 100); $condition[] = 'ug_group IS NULL'; $index = 'user_timestamp'; - # FIXME: other groups may have 'bot' rights + # @todo FIXME: Other groups may have 'bot' rights $join_conds['user_groups'] = array( 'LEFT JOIN', "ug_user = rev_user AND ug_group = 'bot'" ); } else { $tables = array( 'page', 'revision' ); @@ -608,7 +558,7 @@ class ContribsPager extends ReverseChronologicalPager { * @todo This would probably look a lot nicer in a table. */ function formatRow( $row ) { - global $wgUser, $wgLang, $wgContLang; + global $wgUser, $wgLang; wfProfileIn( __METHOD__ ); $sk = $this->getSkin(); @@ -655,7 +605,7 @@ class ContribsPager extends ReverseChronologicalPager { array( 'action' => 'history' ) ); - $comment = $wgContLang->getDirMark() . $sk->revComment( $rev, false, true ); + $comment = $wgLang->getDirMark() . $sk->revComment( $rev, false, true ); $date = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->rev_timestamp ), true ); if( $rev->userCan( Revision::DELETED_TEXT ) ) { $d = $sk->linkKnown( @@ -734,7 +684,7 @@ class ContribsPager extends ReverseChronologicalPager { /** * Get the Database object in use * - * @return Database + * @return DatabaseBase */ public function getDatabase() { return $this->mDb; diff --git a/includes/specials/SpecialDeadendpages.php b/includes/specials/SpecialDeadendpages.php index dfa053aa..f8ef4d44 100644 --- a/includes/specials/SpecialDeadendpages.php +++ b/includes/specials/SpecialDeadendpages.php @@ -28,8 +28,8 @@ */ class DeadendPagesPage extends PageQueryPage { - function getName( ) { - return "Deadendpages"; + function __construct( $name = 'Deadendpages' ) { + parent::__construct( $name ); } function getPageHeader() { @@ -41,11 +41,13 @@ class DeadendPagesPage extends PageQueryPage { * * @return true */ - function isExpensive( ) { - return 1; + function isExpensive() { + return true; } - function isSyndicated() { return false; } + function isSyndicated() { + return false; + } /** * @return false @@ -54,28 +56,30 @@ class DeadendPagesPage extends PageQueryPage { return false; } - /** - * @return string an sqlquery - */ - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $page, $pagelinks ) = $dbr->tableNamesN( 'page', 'pagelinks' ); - return "SELECT 'Deadendpages' as type, page_namespace AS namespace, page_title as title, page_title AS value " . - "FROM $page LEFT JOIN $pagelinks ON page_id = pl_from " . - "WHERE pl_from IS NULL " . - "AND page_namespace = 0 " . - "AND page_is_redirect = 0"; + function getQueryInfo() { + return array( + 'tables' => array( 'page', 'pagelinks' ), + 'fields' => array( 'page_namespace AS namespace', + 'page_title AS title', + 'page_title AS value' + ), + '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' + ) ) ) + ); } -} -/** - * Constructor - */ -function wfSpecialDeadendpages() { - - list( $limit, $offset ) = wfCheckLimits(); - - $depp = new DeadendPagesPage(); - - return $depp->doQuery( $offset, $limit ); + function getOrderFields() { + // For some crazy reason ordering by a constant + // causes a filesort + if( count( MWNamespace::getContentNamespaces() ) > 1 ) { + return array( 'page_namespace', 'page_title' ); + } else { + return array( 'page_title' ); + } + } } diff --git a/includes/specials/SpecialDeletedContributions.php b/includes/specials/SpecialDeletedContributions.php index 92e22586..65858482 100644 --- a/includes/specials/SpecialDeletedContributions.php +++ b/includes/specials/SpecialDeletedContributions.php @@ -55,7 +55,7 @@ class DeletedContribsPager extends IndexPager { // Paranoia: avoid brute force searches (bug 17792) if( !$wgUser->isAllowed( 'deletedhistory' ) ) { $conds[] = $this->mDb->bitAnd('ar_deleted',Revision::DELETED_USER) . ' = 0'; - } else if( !$wgUser->isAllowed( 'suppressrevision' ) ) { + } elseif( !$wgUser->isAllowed( 'suppressrevision' ) ) { $conds[] = $this->mDb->bitAnd('ar_deleted',Revision::SUPPRESSED_USER) . ' != ' . Revision::SUPPRESSED_USER; } @@ -211,7 +211,7 @@ class DeletedContribsPager extends IndexPager { } else { $mflag = ''; } - + // Revision delete link $canHide = $wgUser->isAllowed( 'deleterevision' ); if( $canHide || ($rev->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) { @@ -234,9 +234,9 @@ class DeletedContribsPager extends IndexPager { array( 'class' => 'mw-deletedcontribs-tools' ), wfMsg( 'parentheses', $wgLang->pipeList( array( $last, $dellog, $reviewlink ) ) ) ); - + $ret = "{$del}{$link} {$tools} . . {$mflag} {$pagelink} {$comment}"; - + # Denote if username is redacted for this edit if( $rev->isDeleted( Revision::DELETED_USER ) ) { $ret .= " <strong>" . wfMsgHtml('rev-deleted-user-contribs') . "</strong>"; @@ -325,7 +325,8 @@ class DeletedContributionsPage extends SpecialPage { } # Show a message about slave lag, if applicable - if( ( $lag = $pager->getDatabase()->getLag() ) > 0 ) + $lag = wfGetLB()->safeGetLag( $pager->getDatabase() ); + if( $lag > 0 ) $wgOut->showLagWarning( $lag ); $wgOut->addHTML( @@ -340,9 +341,7 @@ class DeletedContributionsPage extends SpecialPage { ? 'sp-contributions-footer-anon' : 'sp-contributions-footer'; - - $text = wfMsgNoTrans( $message, $target ); - if( !wfEmptyMsg( $message, $text ) && $text != '-' ) { + if( !wfMessage( $message )->isDisabled() ) { $wgOut->wrapWikiMsg( "<div class='mw-contributions-footer'>\n$1\n</div>", array( $message, $target ) ); } } @@ -353,12 +352,12 @@ class DeletedContributionsPage extends SpecialPage { * @param $nt Title object for the target * @param $id Integer: User ID for the target * @return String: appropriately-escaped HTML to be output literally - * @todo Fixme: almost the same as contributionsSub in SpecialContributions.php. Could be combined. + * @todo FIXME: Almost the same as contributionsSub in SpecialContributions.php. Could be combined. */ function getSubTitle( $nt, $id ) { - global $wgSysopUserBans, $wgLang, $wgUser, $wgOut; + global $wgLang, $wgUser, $wgOut; - $sk = $wgUser->getSkin(); + $sk = $this->getSkin(); if ( $id === null ) { $user = htmlspecialchars( $nt->getText() ); @@ -370,11 +369,11 @@ class DeletedContributionsPage extends SpecialPage { if( $talk ) { # Talk page link $tools[] = $sk->link( $talk, wfMsgHtml( 'sp-contributions-talk' ) ); - if( ( $id !== null && $wgSysopUserBans ) || ( $id === null && IP::isIPAddress( $nt->getText() ) ) ) { + if( ( $id !== null ) || ( $id === null && IP::isIPAddress( $nt->getText() ) ) ) { if( $wgUser->isAllowed( 'block' ) ) { # Block / Change block / Unblock links if ( $userObj->isBlocked() ) { $tools[] = $sk->linkKnown( # Change block link - SpecialPage::getTitleFor( 'Blockip', $nt->getDBkey() ), + SpecialPage::getTitleFor( 'Block', $nt->getDBkey() ), wfMsgHtml( 'change-blocklink' ) ); $tools[] = $sk->linkKnown( # Unblock link @@ -383,13 +382,13 @@ class DeletedContributionsPage extends SpecialPage { array(), array( 'action' => 'unblock', - 'ip' => $nt->getDBkey() + 'ip' => $nt->getDBkey() ) ); } else { # User is not blocked $tools[] = $sk->linkKnown( # Block link - SpecialPage::getTitleFor( 'Blockip', $nt->getDBkey() ), + SpecialPage::getTitleFor( 'Block', $nt->getDBkey() ), wfMsgHtml( 'blocklink' ) ); } @@ -455,7 +454,7 @@ class DeletedContributionsPage extends SpecialPage { // 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'. - if( wfEmptyMsg( 'contribsub', wfMsg( 'contribsub' ) ) ) { + if( wfEmptyMsg( 'contribsub' ) ) { return wfMsgHtml( 'contribsub2', $user, $links ); } else { return wfMsgHtml( 'contribsub', "$user ($links)" ); diff --git a/includes/specials/SpecialDisambiguations.php b/includes/specials/SpecialDisambiguations.php index 3e706189..431dfe76 100644 --- a/includes/specials/SpecialDisambiguations.php +++ b/includes/specials/SpecialDisambiguations.php @@ -28,33 +28,28 @@ */ class DisambiguationsPage extends PageQueryPage { - function getName() { - return 'Disambiguations'; + function __construct( $name = 'Disambiguations' ) { + parent::__construct( $name ); } - function isExpensive( ) { return true; } + function isExpensive() { return true; } function isSyndicated() { return false; } - - function getPageHeader( ) { + function getPageHeader() { return wfMsgExt( 'disambiguations-text', array( 'parse' ) ); } - function getSQL() { - global $wgContentNamespaces; - + function getQueryInfo() { $dbr = wfGetDB( DB_SLAVE ); - - $dMsgText = wfMsgForContent('disambiguationspage'); - + $dMsgText = wfMsgForContent( 'disambiguationspage' ); $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) { - # FIXME we assume the disambiguation message is a template but + 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"); } @@ -65,69 +60,79 @@ class DisambiguationsPage extends PageQueryPage { $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()), + 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( 'lb.tl', $dbr ); + $set = $linkBatch->constructSet( 'tl', $dbr ); if( $set === false ) { - # We must always return a valid sql query, but this way DB will always quicly return an empty result + # 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"); } - list( $page, $pagelinks, $templatelinks) = $dbr->tableNamesN( 'page', 'pagelinks', 'templatelinks' ); + // @todo FIXME: What are pagelinks and p2 doing here? + return array ( + 'tables' => array( 'templatelinks', 'p1' => 'page', 'pagelinks', 'p2' => 'page' ), + 'fields' => array( 'p1.page_namespace AS namespace', + 'p1.page_title AS title', + 'pl_from AS value' ), + 'conds' => array( $set, + '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() ) + ); + } - if ( $wgContentNamespaces ) { - $nsclause = 'IN (' . $dbr->makeList( $wgContentNamespaces ) . ')'; - } else { - $nsclause = '= ' . NS_MAIN; - } + function getOrderFields() { + return array( 'tl_namespace', 'tl_title', 'value' ); + } - $sql = "SELECT 'Disambiguations' AS \"type\", pb.page_namespace AS namespace," - ." pb.page_title AS title, la.pl_from AS value" - ." FROM {$templatelinks} AS lb, {$page} AS pb, {$pagelinks} AS la, {$page} AS pa" - ." WHERE $set" # disambiguation template(s) - .' AND pa.page_id = la.pl_from' - .' AND pa.page_namespace ' . $nsclause - .' AND pb.page_id = lb.tl_from' - .' AND pb.page_namespace = la.pl_namespace' - .' AND pb.page_title = la.pl_title' - .' ORDER BY lb.tl_namespace, lb.tl_title'; - - return $sql; + function sortDescending() { + return false; } - function getOrder() { - return ''; + /** + * Fetch links and cache their existence + * + * @param $db DatabaseBase + * @param $res + */ + function preprocessResults( $db, $res ) { + $batch = new LinkBatch; + foreach ( $res as $row ) { + $batch->add( $row->namespace, $row->title ); + } + $batch->execute(); + + // Back to start for display + if ( $db->numRows( $res ) > 0 ) { + // If there are no rows we get an error seeking. + $db->dataSeek( $res, 0 ); + } } function formatResult( $skin, $result ) { - global $wgContLang; + global $wgLang; + $title = Title::newFromID( $result->value ); $dp = Title::makeTitle( $result->namespace, $result->title ); $from = $skin->link( $title ); - $edit = $skin->link( $title, wfMsgExt( 'parentheses', array( 'escape' ), wfMsg( 'editlink' ) ) , array(), array( 'redirect' => 'no', 'action' => 'edit' ) ); - $arr = $wgContLang->getArrow(); + $edit = $skin->link( $title, wfMsgExt( 'parentheses', array( 'escape' ), wfMsg( 'editlink' ) ) , + array(), array( 'redirect' => 'no', 'action' => 'edit' ) ); + $arr = $wgLang->getArrow(); $to = $skin->link( $dp ); return "$from $edit $arr $to"; } } - -/** - * Constructor - */ -function wfSpecialDisambiguations() { - list( $limit, $offset ) = wfCheckLimits(); - - $sd = new DisambiguationsPage(); - - return $sd->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialDoubleRedirects.php b/includes/specials/SpecialDoubleRedirects.php index c7f63210..ec899d8a 100644 --- a/includes/specials/SpecialDoubleRedirects.php +++ b/includes/specials/SpecialDoubleRedirects.php @@ -29,63 +29,63 @@ */ class DoubleRedirectsPage extends PageQueryPage { - function getName() { - return 'DoubleRedirects'; + function __construct( $name = 'DoubleRedirects' ) { + parent::__construct( $name ); } - function isExpensive( ) { return true; } + function isExpensive() { return true; } function isSyndicated() { return false; } + function sortDescending() { return false; } - function getPageHeader( ) { + function getPageHeader() { return wfMsgExt( 'doubleredirectstext', array( 'parse' ) ); } - function getSQLText( &$dbr, $namespace = null, $title = null ) { - - list( $page, $redirect ) = $dbr->tableNamesN( 'page', 'redirect' ); - + function reallyGetQueryInfo( $namespace = null, $title = null ) { $limitToTitle = !( $namespace === null && $title === null ); - $sql = $limitToTitle ? "SELECT" : "SELECT 'DoubleRedirects' as type," ; - $sql .= - " pa.page_namespace as namespace, pa.page_title as title," . - " pb.page_namespace as nsb, pb.page_title as tb," . - " pc.page_namespace as nsc, pc.page_title as tc" . - " FROM $redirect AS ra, $redirect AS rb, $page AS pa, $page AS pb, $page AS pc" . - " WHERE ra.rd_from=pa.page_id" . - " AND ra.rd_namespace=pb.page_namespace" . - " AND ra.rd_title=pb.page_title" . - " AND rb.rd_from=pb.page_id" . - " AND rb.rd_namespace=pc.page_namespace" . - " AND rb.rd_title=pc.page_title"; - - if( $limitToTitle ) { - $encTitle = $dbr->addQuotes( $title ); - $sql .= " AND pa.page_namespace=$namespace" . - " AND pa.page_title=$encTitle"; + $retval = array ( + 'tables' => array ( 'ra' => 'redirect', + 'rb' => 'redirect', 'pa' => 'page', + 'pb' => 'page', 'pc' => 'page' ), + 'fields' => array ( 'pa.page_namespace AS namespace', + 'pa.page_title AS title', + 'pb.page_namespace AS nsb', + 'pb.page_title AS tb', + 'pc.page_namespace AS nsc', + 'pc.page_title AS tc' ), + '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' ) + ); + if ( $limitToTitle ) { + $retval['conds']['pa.page_namespace'] = $namespace; + $retval['conds']['pa.page_title'] = $title; } - - return $sql; + return $retval; } - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - return $this->getSQLText( $dbr ); + function getQueryInfo() { + return $this->reallyGetQueryInfo(); } - function getOrder() { - return ''; + function getOrderFields() { + return array ( 'ra.rd_namespace', 'ra.rd_title' ); } function formatResult( $skin, $result ) { - global $wgContLang; + global $wgLang; - $fname = 'DoubleRedirectsPage::formatResult'; $titleA = Title::makeTitle( $result->namespace, $result->title ); if ( $result && !isset( $result->nsb ) ) { $dbr = wfGetDB( DB_SLAVE ); - $sql = $this->getSQLText( $dbr, $result->namespace, $result->title ); - $res = $dbr->query( $sql, $fname ); + $qi = $this->reallyGetQueryInfo( $result->namespace, + $result->title ); + $res = $dbr->select($qi['tables'], $qi['fields'], + $qi['conds'], __METHOD__ ); if ( $res ) { $result = $dbr->fetchObject( $res ); } @@ -119,20 +119,8 @@ class DoubleRedirectsPage extends PageQueryPage { array( 'redirect' => 'no' ) ); $linkC = $skin->linkKnown( $titleC ); - $arr = $wgContLang->getArrow() . $wgContLang->getDirMark(); + $arr = $wgLang->getArrow() . $wgLang->getDirMark(); return( "{$linkA} {$edit} {$arr} {$linkB} {$arr} {$linkC}" ); } } - -/** - * constructor - */ -function wfSpecialDoubleRedirects() { - list( $limit, $offset ) = wfCheckLimits(); - - $sdr = new DoubleRedirectsPage(); - - return $sdr->doQuery( $offset, $limit ); - -} diff --git a/includes/specials/SpecialEditWatchlist.php b/includes/specials/SpecialEditWatchlist.php new file mode 100644 index 00000000..bb2ecd80 --- /dev/null +++ b/includes/specials/SpecialEditWatchlist.php @@ -0,0 +1,596 @@ +<?php + +/** + * Provides the UI through which users can perform editing + * operations on their watchlist + * + * @ingroup Watchlist + * @author Rob Church <robchur@gmail.com> + */ +class SpecialEditWatchlist extends UnlistedSpecialPage { + + /** + * Editing modes + */ + const EDIT_CLEAR = 1; + const EDIT_RAW = 2; + const EDIT_NORMAL = 3; + + protected $successMessage; + + protected $toc; + + public function __construct(){ + parent::__construct( 'EditWatchlist' ); + } + + /** + * Main execution point + * + * @param $mode int + */ + public function execute( $mode ) { + if( wfReadOnly() ) { + throw new ReadOnlyError; + } + + $out = $this->getOutput(); + + # Anons don't get a watchlist + if( $this->getUser()->isAnon() ) { + $out->setPageTitle( wfMsg( 'watchnologin' ) ); + $llink = Linker::linkKnown( + SpecialPage::getTitleFor( 'Userlogin' ), + wfMsgHtml( 'loginreqlink' ), + array(), + array( 'returnto' => $this->getTitle()->getPrefixedText() ) + ); + $out->addHTML( wfMessage( 'watchlistanontext' )->rawParams( $llink )->parse() ); + return; + } + + $sub = wfMsgExt( + 'watchlistfor2', + array( 'parseinline', 'replaceafter' ), + $this->getUser()->getName(), + SpecialEditWatchlist::buildTools( null ) + ); + $out->setSubtitle( $sub ); + + # B/C: $mode used to be waaay down the parameter list, and the first parameter + # was $wgUser + if( $mode instanceof User ){ + $args = func_get_args(); + if( count( $args >= 4 ) ){ + $mode = $args[3]; + } + } + $mode = self::getMode( $this->getRequest(), $mode ); + + switch( $mode ) { + case self::EDIT_CLEAR: + // The "Clear" link scared people too much. + // Pass on to the raw editor, from which it's very easy to clear. + + case self::EDIT_RAW: + $out->setPageTitle( wfMsg( 'watchlistedit-raw-title' ) ); + $form = $this->getRawForm(); + if( $form->show() ){ + $out->addHTML( $this->successMessage ); + $out->returnToMain(); + } + break; + + case self::EDIT_NORMAL: + default: + $out->setPageTitle( wfMsg( 'watchlistedit-normal-title' ) ); + $form = $this->getNormalForm(); + if( $form->show() ){ + $out->addHTML( $this->successMessage ); + $out->returnToMain(); + } elseif ( $this->toc !== false ) { + $out->prependHTML( $this->toc ); + } + break; + } + } + + /** + * Extract a list of titles from a blob of text, returning + * (prefixed) strings; unwatchable titles are ignored + * + * @param $list String + * @return array + */ + private function extractTitles( $list ) { + $titles = array(); + $list = explode( "\n", trim( $list ) ); + if( !is_array( $list ) ) { + return array(); + } + foreach( $list as $text ) { + $text = trim( $text ); + if( strlen( $text ) > 0 ) { + $title = Title::newFromText( $text ); + if( $title instanceof Title && $title->isWatchable() ) { + $titles[] = $title->getPrefixedText(); + } + } + } + return array_unique( $titles ); + } + + public function submitRaw( $data ){ + $wanted = $this->extractTitles( $data['Titles'] ); + $current = $this->getWatchlist(); + + 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 ){ + $this->successMessage = wfMessage( 'watchlistedit-raw-done' )->parse(); + } else { + return false; + } + + if( count( $toWatch ) > 0 ) { + $this->successMessage .= wfMessage( + 'watchlistedit-raw-added', + $this->getLang()->formatNum( count( $toWatch ) ) + ); + $this->showTitles( $toWatch, $this->successMessage ); + } + + if( count( $toUnwatch ) > 0 ) { + $this->successMessage .= wfMessage( + 'watchlistedit-raw-removed', + $this->getLang()->formatNum( count( $toUnwatch ) ) + ); + $this->showTitles( $toUnwatch, $this->successMessage ); + } + } else { + $this->clearWatchlist(); + $this->getUser()->invalidateCache(); + $this->successMessage .= wfMessage( + 'watchlistedit-raw-removed', + $this->getLang()->formatNum( count( $current ) ) + ); + $this->showTitles( $current, $this->successMessage ); + } + return true; + } + + /** + * Print out a list of linked titles + * + * $titles can be an array of strings or Title objects; the former + * is preferred, since Titles are very memory-heavy + * + * @param $titles array of strings, or Title objects + * @param $output String + */ + private function showTitles( $titles, &$output ) { + $talk = wfMsgHtml( 'talkpagelinktext' ); + // Do a batch existence check + $batch = new LinkBatch(); + foreach( $titles as $title ) { + if( !$title instanceof Title ) { + $title = Title::newFromText( $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 ) { + $title = Title::newFromText( $title ); + } + if( $title instanceof Title ) { + $output .= "<li>" + . Linker::link( $title ) + . ' (' . Linker::link( $title->getTalkPage(), $talk ) + . ")</li>\n"; + } + } + $output .= "</ul>\n"; + } + + /** + * Prepare a list of titles on a user's watchlist (excluding talk pages) + * and return an array of (prefixed) strings + * + * @return array + */ + private function getWatchlist() { + $list = array(); + $dbr = wfGetDB( DB_MASTER ); + $res = $dbr->select( + 'watchlist', + '*', + array( + 'wl_user' => $this->getUser()->getId(), + ), + __METHOD__ + ); + if( $res->numRows() > 0 ) { + foreach ( $res as $row ) { + $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title ); + if( $title instanceof Title && !$title->isTalkPage() ) + $list[] = $title->getPrefixedText(); + } + $res->free(); + } + return $list; + } + + /** + * Get a list of titles on a user's watchlist, excluding talk pages, + * and return as a two-dimensional array with namespace, title and + * redirect status + * + * @return array + */ + private function getWatchlistInfo() { + $titles = array(); + $dbr = wfGetDB( DB_MASTER ); + + $res = $dbr->select( + array( 'watchlist', 'page' ), + array( + 'wl_namespace', + 'wl_title', + 'page_id', + 'page_len', + 'page_is_redirect', + 'page_latest' + ), + array( 'wl_user' => $this->getUser()->getId() ), + __METHOD__, + array( 'ORDER BY' => 'wl_namespace, wl_title' ), + array( 'page' => array( + 'LEFT JOIN', + 'wl_namespace = page_namespace AND wl_title = page_title' + ) ) + ); + + if( $res && $dbr->numRows( $res ) > 0 ) { + $cache = LinkCache::singleton(); + foreach ( $res as $row ) { + $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title ); + if( $title instanceof Title ) { + // Update the link cache while we're at it + if( $row->page_id ) { + $cache->addGoodLinkObj( $row->page_id, $title, $row->page_len, $row->page_is_redirect, $row->page_latest ); + } else { + $cache->addBadLinkObj( $title ); + } + // Ignore non-talk + if( !$title->isTalkPage() ) { + $titles[$row->wl_namespace][$row->wl_title] = $row->page_is_redirect; + } + } + } + } + return $titles; + } + + /** + * Remove all titles from a user's watchlist + */ + private function clearWatchlist() { + $dbw = wfGetDB( DB_MASTER ); + $dbw->delete( + 'watchlist', + array( 'wl_user' => $this->getUser()->getId() ), + __METHOD__ + ); + } + + /** + * Add a list of titles to a user's watchlist + * + * $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 + */ + private function watchTitles( $titles ) { + $dbw = wfGetDB( DB_MASTER ); + $rows = array(); + foreach( $titles as $title ) { + if( !$title instanceof Title ) { + $title = Title::newFromText( $title ); + } + if( $title instanceof Title ) { + $rows[] = array( + 'wl_user' => $this->getUser()->getId(), + 'wl_namespace' => ( $title->getNamespace() & ~1 ), + 'wl_title' => $title->getDBkey(), + 'wl_notificationtimestamp' => null, + ); + $rows[] = array( + 'wl_user' => $this->getUser()->getId(), + 'wl_namespace' => ( $title->getNamespace() | 1 ), + 'wl_title' => $title->getDBkey(), + 'wl_notificationtimestamp' => null, + ); + } + } + $dbw->insert( 'watchlist', $rows, __METHOD__, 'IGNORE' ); + } + + /** + * Remove a list of titles from a user's watchlist + * + * $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 + */ + private function unwatchTitles( $titles ) { + $dbw = wfGetDB( DB_MASTER ); + foreach( $titles as $title ) { + if( !$title instanceof Title ) { + $title = Title::newFromText( $title ); + } + if( $title instanceof Title ) { + $dbw->delete( + 'watchlist', + array( + 'wl_user' => $this->getUser()->getId(), + 'wl_namespace' => ( $title->getNamespace() & ~1 ), + 'wl_title' => $title->getDBkey(), + ), + __METHOD__ + ); + $dbw->delete( + 'watchlist', + array( + 'wl_user' => $this->getUser()->getId(), + 'wl_namespace' => ( $title->getNamespace() | 1 ), + 'wl_title' => $title->getDBkey(), + ), + __METHOD__ + ); + $article = new Article( $title, 0 ); + wfRunHooks( 'UnwatchArticleComplete', array( $this->getUser(), &$article ) ); + } + } + } + + public function submitNormal( $data ) { + $removed = array(); + + foreach( $data as $titles ) { + $this->unwatchTitles( $titles ); + $removed += $titles; + } + + if( count( $removed ) > 0 ) { + $this->successMessage = wfMessage( + 'watchlistedit-normal-done', + $this->getLang()->formatNum( count( $removed ) ) + ); + $this->showTitles( $removed, $this->successMessage ); + return true; + } else { + return false; + } + } + + /** + * Get the standard watchlist editing form + * + * @return HTMLForm + */ + protected function getNormalForm(){ + global $wgContLang; + + $fields = array(); + $count = 0; + + $haveInvalidNamespaces = false; + foreach( $this->getWatchlistInfo() as $namespace => $pages ){ + if ( $namespace < 0 ) { + $haveInvalidNamespaces = true; + continue; + } + + $fields['TitlesNs'.$namespace] = array( + 'class' => 'EditWatchlistCheckboxSeriesField', + 'options' => array(), + 'section' => "ns$namespace", + ); + + foreach( $pages as $dbkey => $redirect ){ + $title = Title::makeTitleSafe( $namespace, $dbkey ); + $text = $this->buildRemoveLine( $title, $redirect ); + $fields['TitlesNs'.$namespace]['options'][$text] = $title->getEscapedText(); + $count++; + } + } + if ( $haveInvalidNamespaces ) { + wfDebug( "User {$this->getContext()->getUser()->getId()} has invalid watchlist entries, clening up...\n" ); + $this->getContext()->getUser()->cleanupWatchlist(); + } + + if ( count( $fields ) > 1 && $count > 30 ) { + $this->toc = Linker::tocIndent(); + $tocLength = 0; + foreach( $fields as $key => $data ) { + $ns = substr( $data['section'], 2 ); + $nsText = $ns == NS_MAIN + ? wfMsgHtml( 'blanknamespace' ) + : htmlspecialchars( $wgContLang->getFormattedNsText( $ns ) ); + $this->toc .= Linker::tocLine( "editwatchlist-{$data['section']}", $nsText, ++$tocLength, 1 ) . Linker::tocLineEnd(); + } + $this->toc = Linker::tocList( $this->toc ); + } else { + $this->toc = false; + } + + $form = new EditWatchlistNormalHTMLForm( $fields, $this->getContext() ); + $form->setTitle( $this->getTitle() ); + $form->setSubmitText( wfMessage( 'watchlistedit-normal-submit' )->text() ); + $form->setWrapperLegend( wfMessage( 'watchlistedit-normal-legend' )->text() ); + $form->addHeaderText( wfMessage( 'watchlistedit-normal-explain' )->parse() ); + $form->setSubmitCallback( array( $this, 'submitNormal' ) ); + return $form; + } + + /** + * Build the label for a checkbox, with a link to the title, and various additional bits + * + * @param $title Title + * @param $redirect bool + * @return string + */ + private function buildRemoveLine( $title, $redirect ) { + $link = Linker::link( $title ); + if( $redirect ) { + $link = '<span class="watchlistredir">' . $link . '</span>'; + } + $tools[] = Linker::link( $title->getTalkPage(), wfMsgHtml( 'talkpagelinktext' ) ); + if( $title->exists() ) { + $tools[] = Linker::linkKnown( + $title, + wfMsgHtml( 'history_short' ), + array(), + array( 'action' => 'history' ) + ); + } + if( $title->getNamespace() == NS_USER && !$title->isSubpage() ) { + $tools[] = Linker::linkKnown( + SpecialPage::getTitleFor( 'Contributions', $title->getText() ), + wfMsgHtml( 'contributions' ) + ); + } + + wfRunHooks( 'WatchlistEditorBuildRemoveLine', array( &$tools, $title, $redirect, $this->getSkin() ) ); + + return $link . " (" . $this->getLang()->pipeList( $tools ) . ")"; + } + + /** + * Get a form for editing the watchlist in "raw" mode + * + * @return HTMLForm + */ + protected function getRawForm(){ + $titles = implode( $this->getWatchlist(), "\n" ); + $fields = array( + 'Titles' => array( + 'type' => 'textarea', + 'label-message' => 'watchlistedit-raw-titles', + 'default' => $titles, + ), + ); + $form = new HTMLForm( $fields ); + $form->setTitle( $this->getTitle( 'raw' ) ); + $form->setSubmitText( wfMessage( 'watchlistedit-raw-submit' )->text() ); + $form->setWrapperLegend( wfMessage( 'watchlistedit-raw-legend' )->text() ); + $form->addHeaderText( wfMessage( 'watchlistedit-raw-explain' )->parse() ); + $form->setSubmitCallback( array( $this, 'submitRaw' ) ); + return $form; + } + + /** + * Determine whether we are editing the watchlist, and if so, what + * kind of editing operation + * + * @param $request WebRequest + * @param $par mixed + * @return int + */ + public static function getMode( $request, $par ) { + $mode = strtolower( $request->getVal( 'action', $par ) ); + 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; + } + } + + /** + * Build a set of links for convenient navigation + * between watchlist viewing and editing modes + * + * @param $unused Unused + * @return string + */ + public static function buildTools( $unused ) { + global $wgLang; + + $tools = array(); + $modes = array( + 'view' => array( 'Watchlist', false ), + 'edit' => array( 'EditWatchlist', false ), + 'raw' => array( 'EditWatchlist', 'raw' ), + ); + foreach( $modes as $mode => $arr ) { + // can use messages 'watchlisttools-view', 'watchlisttools-edit', 'watchlisttools-raw' + $tools[] = Linker::linkKnown( + SpecialPage::getTitleFor( $arr[0], $arr[1] ), + wfMsgHtml( "watchlisttools-{$mode}" ) + ); + } + return Html::rawElement( 'span', + array( 'class' => 'mw-watchlist-toollinks' ), + wfMsg( 'parentheses', $wgLang->pipeList( $tools ) ) ); + } +} + +# B/C since 1.18 +class WatchlistEditor extends SpecialEditWatchlist {} + +/** + * Extend HTMLForm purely so we can have a more sane way of getting the section headers + */ +class EditWatchlistNormalHTMLForm extends HTMLForm { + public function getLegend( $namespace ){ + $namespace = substr( $namespace, 2 ); + return $namespace == NS_MAIN + ? wfMsgHtml( 'blanknamespace' ) + : htmlspecialchars( $this->getContext()->getLang()->getFormattedNsText( $namespace ) ); + } + public function getBody() { + return $this->displaySection( $this->mFieldTree, '', 'editwatchlist-' ); + } +} + +class EditWatchlistCheckboxSeriesField extends HTMLMultiSelectField { + /** + * HTMLMultiSelectField throws validation errors if we get input data + * that doesn't match the data set in the form setup. This causes + * problems if something gets removed from the watchlist while the + * 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 + * @return Mixed Bool true on success, or String error to display. + */ + function validate( $value, $alldata ) { + // Need to call into grandparent to be a good citizen. :) + return HTMLFormField::validate( $value, $alldata ); + } +} diff --git a/includes/specials/SpecialEmailuser.php b/includes/specials/SpecialEmailuser.php index 61271227..7c2ba570 100644 --- a/includes/specials/SpecialEmailuser.php +++ b/includes/specials/SpecialEmailuser.php @@ -28,20 +28,20 @@ */ class SpecialEmailUser extends UnlistedSpecialPage { protected $mTarget; - - public function __construct(){ + + public function __construct() { parent::__construct( 'Emailuser' ); } - - protected function getFormFields(){ + + protected function getFormFields() { global $wgUser; return array( 'From' => array( 'type' => 'info', 'raw' => 1, - 'default' => $wgUser->getSkin()->link( - $wgUser->getUserPage(), - htmlspecialchars( $wgUser->getName() ) + 'default' => $this->getSkin()->link( + $wgUser->getUserPage(), + htmlspecialchars( $wgUser->getName() ) ), 'label-message' => 'emailfrom', 'id' => 'mw-emailuser-sender', @@ -49,8 +49,8 @@ class SpecialEmailUser extends UnlistedSpecialPage { 'To' => array( 'type' => 'info', 'raw' => 1, - 'default' => $wgUser->getSkin()->link( - $this->mTargetObj->getUserPage(), + 'default' => $this->getSkin()->link( + $this->mTargetObj->getUserPage(), htmlspecialchars( $this->mTargetObj->getName() ) ), 'label-message' => 'emailto', @@ -82,25 +82,17 @@ class SpecialEmailUser extends UnlistedSpecialPage { ), ); } - + public function execute( $par ) { global $wgRequest, $wgOut, $wgUser; $this->setHeaders(); $this->outputHeader(); - + $wgOut->addModuleStyles( 'mediawiki.special' ); $this->mTarget = is_null( $par ) ? $wgRequest->getVal( 'wpTarget', $wgRequest->getVal( 'target', '' ) ) : $par; - - $ret = self::getTarget( $this->mTarget ); - if( $ret instanceof User ){ - $this->mTargetObj = $ret; - } else { - $wgOut->showErrorPage( "{$ret}title", "{$ret}text" ); - return false; - } - + // error out if sending user cannot do this $error = self::getPermissionsError( $wgUser, $wgRequest->getVal( 'wpEditToken' ) ); switch ( $error ) { case null: @@ -125,7 +117,19 @@ class SpecialEmailUser extends UnlistedSpecialPage { $wgOut->showErrorPage( $title, $msg, $params ); return; } - + // Got a valid target user name? Else ask for one. + $ret = self::getTarget( $this->mTarget ); + if( !$ret instanceof User ) { + if( $this->mTarget != '' ) { + $ret = ( $ret == 'notarget' ) ? 'emailnotarget' : ( $ret . 'text' ); + $wgOut->wrapWikiMsg( "<p class='error'>$1</p>", $ret ); + } + $wgOut->addHTML( self::userForm( $this->mTarget ) ); + return false; + } + + $this->mTargetObj = $ret; + $form = new HTMLForm( $this->getFormFields() ); $form->addPreText( wfMsgExt( 'emailpagetext', 'parseinline' ) ); $form->setSubmitText( wfMsg( 'emailsend' ) ); @@ -133,16 +137,16 @@ class SpecialEmailUser extends UnlistedSpecialPage { $form->setSubmitCallback( array( __CLASS__, 'submit' ) ); $form->setWrapperLegend( wfMsgExt( 'email-legend', 'parsemag' ) ); $form->loadData(); - - if( !wfRunHooks( 'EmailUserForm', array( &$form ) ) ){ + + if( !wfRunHooks( 'EmailUserForm', array( &$form ) ) ) { return false; } - - $wgOut->setPagetitle( wfMsg( 'emailpage' ) ); + + $wgOut->setPageTitle( wfMsg( 'emailpage' ) ); $result = $form->show(); - - if( $result === true || ( $result instanceof Status && $result->isGood() ) ){ - $wgOut->setPagetitle( wfMsg( 'emailsent' ) ); + + if( $result === true || ( $result instanceof Status && $result->isGood() ) ) { + $wgOut->setPageTitle( wfMsg( 'emailsent' ) ); $wgOut->addWikiMsg( 'emailsenttext' ); $wgOut->returnToMain( false, $this->mTargetObj->getUserPage() ); } @@ -159,15 +163,15 @@ class SpecialEmailUser extends UnlistedSpecialPage { wfDebug( "Target is empty.\n" ); return 'notarget'; } - + $nu = User::newFromName( $target ); if( !$nu instanceof User || !$nu->getId() ) { wfDebug( "Target is invalid user.\n" ); return 'notarget'; - } else if ( !$nu->isEmailConfirmed() ) { + } elseif ( !$nu->isEmailConfirmed() ) { wfDebug( "User has no valid email.\n" ); return 'noemail'; - } else if ( !$nu->canReceiveEmail() ) { + } elseif ( !$nu->canReceiveEmail() ) { wfDebug( "User does not allow user emails.\n" ); return 'nowikiemail'; } @@ -184,15 +188,15 @@ class SpecialEmailUser extends UnlistedSpecialPage { */ public static function getPermissionsError( $user, $editToken ) { global $wgEnableEmail, $wgEnableUserEmail; - if( !$wgEnableEmail || !$wgEnableUserEmail ){ + if( !$wgEnableEmail || !$wgEnableUserEmail ) { return 'usermaildisabled'; } - + if( !$user->isAllowed( 'sendemail' ) ) { return 'badaccess'; } - - if( !$user->isEmailConfirmed() ){ + + if( !$user->isEmailConfirmed() ) { return 'mailnologin'; } @@ -217,8 +221,28 @@ class SpecialEmailUser extends UnlistedSpecialPage { } /** + * Form to ask for target user name. + * + * @param $name String: user name submitted. + * @return String: form asking for user name. + */ + + function userForm( $name ) { + global $wgScript ; + $string = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'askusername' ) ) . + Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . + Xml::openElement( 'fieldset' ) . + Html::rawElement( 'legend', null, wfMessage( 'emailtarget' )->parse() ) . + Xml::inputLabel( wfMessage( 'emailusername' )->text(), 'target', 'emailusertarget', 30, $name ) . ' ' . + Xml::submitButton( wfMessage( 'emailusernamesubmit' )->text() ) . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) . "\n"; + return $string; + } + + /** * Really send a mail. Permissions should have been checked using - * getPermissionsError(). It is probably also a good + * getPermissionsError(). It is probably also a good * idea to check the edit token and ping limiter in advance. * * @return Mixed: Status object, or potentially a String on error @@ -228,7 +252,7 @@ class SpecialEmailUser extends UnlistedSpecialPage { global $wgUser, $wgUserEmailUseReplyTo; $target = self::getTarget( $data['Target'] ); - if( !$target instanceof User ){ + if( !$target instanceof User ) { return wfMsgExt( $target . 'text', 'parse' ); } $to = new MailAddress( $target ); @@ -238,17 +262,17 @@ class SpecialEmailUser extends UnlistedSpecialPage { // Add a standard footer and trim up trailing newlines $text = rtrim( $text ) . "\n\n-- \n"; - $text .= wfMsgExt( + $text .= wfMsgExt( 'emailuserfooter', - array( 'content', 'parsemag' ), - array( $from->name, $to->name ) + array( 'content', 'parsemag' ), + array( $from->name, $to->name ) ); $error = ''; if( !wfRunHooks( 'EmailUser', array( &$to, &$from, &$subject, &$text, &$error ) ) ) { return $error; } - + if( $wgUserEmailUseReplyTo ) { // Put the generic wiki autogenerated address in the From: // header and reserve the user for Reply-To. @@ -283,12 +307,12 @@ class SpecialEmailUser extends UnlistedSpecialPage { return $status; } else { // if the user requested a copy of this mail, do this now, - // unless they are emailing themselves, in which case one + // unless they are emailing themselves, in which case one // copy of the message is sufficient. if ( $data['CCMe'] && $to != $from ) { $cc_subject = wfMsg( - 'emailccsubject', - $target->getName(), + 'emailccsubject', + $target->getName(), $subject ); wfRunHooks( 'EmailUserCC', array( &$from, &$from, &$cc_subject, &$text ) ); diff --git a/includes/specials/SpecialExport.php b/includes/specials/SpecialExport.php index eaed2393..50754b6a 100644 --- a/includes/specials/SpecialExport.php +++ b/includes/specials/SpecialExport.php @@ -40,7 +40,7 @@ class SpecialExport extends SpecialPage { public function execute( $par ) { global $wgOut, $wgRequest, $wgSitename, $wgExportAllowListContributors; global $wgExportAllowHistory, $wgExportMaxHistory, $wgExportMaxLinkDepth; - global $wgExportFromNamespaces, $wgUser; + global $wgExportFromNamespaces; $this->setHeaders(); $this->outputHeader(); @@ -63,16 +63,18 @@ class SpecialExport extends SpecialPage { $t = Title::makeTitleSafe( NS_MAIN, $catname ); if ( $t ) { /** - * @todo Fixme: this can lead to hitting memory limit for very large + * @todo FIXME: This can lead to hitting memory limit for very large * categories. Ideally we would do the lookup synchronously * during the export in a single query. */ $catpages = $this->getPagesFromCategory( $t ); - if ( $catpages ) $page .= "\n" . implode( "\n", $catpages ); + if ( $catpages ) { + $page .= "\n" . implode( "\n", $catpages ); + } } } } - else if( $wgRequest->getCheck( 'addns' ) && $wgExportFromNamespaces ) { + elseif( $wgRequest->getCheck( 'addns' ) && $wgExportFromNamespaces ) { $page = $wgRequest->getText( 'pages' ); $nsindex = $wgRequest->getText( 'nsindex', '' ); @@ -81,20 +83,22 @@ class SpecialExport extends SpecialPage { * Same implementation as above, so same @todo */ $nspages = $this->getPagesFromNamespace( $nsindex ); - if ( $nspages ) $page .= "\n" . implode( "\n", $nspages ); + if ( $nspages ) { + $page .= "\n" . implode( "\n", $nspages ); + } } } - else if( $wgRequest->wasPosted() && $par == '' ) { + elseif( $wgRequest->wasPosted() && $par == '' ) { $page = $wgRequest->getText( 'pages' ); $this->curonly = $wgRequest->getCheck( 'curonly' ); $rawOffset = $wgRequest->getVal( 'offset' ); - + if( $rawOffset ) { $offset = wfTimestamp( TS_MW, $rawOffset ); } else { $offset = null; } - + $limit = $wgRequest->getInt( 'limit' ); $dir = $wgRequest->getVal( 'dir' ); $history = array( @@ -103,7 +107,7 @@ class SpecialExport extends SpecialPage { 'limit' => $wgExportMaxHistory, ); $historyCheck = $wgRequest->getCheck( 'history' ); - + if ( $this->curonly ) { $history = WikiExporter::CURRENT; } elseif ( !$historyCheck ) { @@ -118,19 +122,23 @@ class SpecialExport extends SpecialPage { } } - if( $page != '' ) $this->doExport = true; + if( $page != '' ) { + $this->doExport = true; + } } else { // Default to current-only for GET requests. $page = $wgRequest->getText( 'pages', $par ); $historyCheck = $wgRequest->getCheck( 'history' ); - + if( $historyCheck ) { $history = WikiExporter::FULL; } else { $history = WikiExporter::CURRENT; } - if( $page != '' ) $this->doExport = true; + if( $page != '' ) { + $this->doExport = true; + } } if( !$wgExportAllowHistory ) { @@ -139,24 +147,26 @@ class SpecialExport extends SpecialPage { } $list_authors = $wgRequest->getCheck( 'listauthors' ); - if ( !$this->curonly || !$wgExportAllowListContributors ) $list_authors = false ; + if ( !$this->curonly || !$wgExportAllowListContributors ) { + $list_authors = false ; + } if ( $this->doExport ) { $wgOut->disable(); - + // Cancel output buffering and gzipping if set // This should provide safer streaming for pages with history wfResetOutputBuffers(); $wgRequest->response()->header( "Content-type: application/xml; charset=utf-8" ); - + if( $wgRequest->getCheck( 'wpDownload' ) ) { // Provide a sane filename suggestion $filename = urlencode( $wgSitename . '-' . wfTimestampNow() . '.xml' ); $wgRequest->response()->header( "Content-disposition: attachment;filename={$filename}" ); } - + $this->doExport( $page, $history, $list_authors ); - + return; } @@ -176,23 +186,38 @@ class SpecialExport extends SpecialPage { $form .= '<br />'; if( $wgExportAllowHistory ) { - $form .= Xml::checkLabel( wfMsg( 'exportcuronly' ), 'curonly', 'curonly', true ) . '<br />'; + $form .= Xml::checkLabel( + wfMsg( 'exportcuronly' ), + 'curonly', + 'curonly', + $wgRequest->wasPosted() ? $wgRequest->getCheck( 'curonly' ) : true + ) . '<br />'; } else { $wgOut->addHTML( wfMsgExt( 'exportnohistory', 'parse' ) ); } - - $form .= Xml::checkLabel( wfMsg( 'export-templates' ), 'templates', 'wpExportTemplates', false ) . '<br />'; - + + $form .= Xml::checkLabel( + wfMsg( 'export-templates' ), + 'templates', + 'wpExportTemplates', + $wgRequest->wasPosted() ? $wgRequest->getCheck( 'templates' ) : false + ) . '<br />'; + if( $wgExportMaxLinkDepth || $this->userCanOverrideExportDepth() ) { $form .= Xml::inputLabel( wfMsg( 'export-pagelinks' ), 'pagelink-depth', 'pagelink-depth', 20, 0 ) . '<br />'; } // Enable this when we can do something useful exporting/importing image information. :) //$form .= Xml::checkLabel( wfMsg( 'export-images' ), 'images', 'wpExportImages', false ) . '<br />'; - $form .= Xml::checkLabel( wfMsg( 'export-download' ), 'wpDownload', 'wpDownload', true ) . '<br />'; - - $form .= Xml::submitButton( wfMsg( 'export-submit' ), $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'export' ) ); + $form .= Xml::checkLabel( + wfMsg( 'export-download' ), + 'wpDownload', + 'wpDownload', + $wgRequest->wasPosted() ? $wgRequest->getCheck( 'wpDownload' ) : true + ) . '<br />'; + + $form .= Xml::submitButton( wfMsg( 'export-submit' ), Linker::tooltipAndAccesskeyAttribs( 'export' ) ); $form .= Xml::closeElement( 'form' ); - + $wgOut->addHTML( $form ); } @@ -247,7 +272,7 @@ class SpecialExport extends SpecialPage { foreach( $pages as $k => $v ) { $pages[$k] = str_replace( " ", "_", $v ); } - + $pages = array_unique( $pages ); /* Ok, let's get to it... */ @@ -266,11 +291,11 @@ class SpecialExport extends SpecialPage { set_time_limit(0); wfRestoreWarnings(); } - + $exporter = new WikiExporter( $db, $history, $buffer ); $exporter->list_authors = $list_authors; $exporter->openStream(); - + foreach( $pages as $page ) { /* if( $wgExportMaxHistory && !$this->curonly ) { @@ -286,14 +311,18 @@ class SpecialExport extends SpecialPage { }*/ #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( !$title->userCanRead() ) continue; #TODO: perhaps output an <error> tag or something. + if( is_null( $title ) ) { + continue; #TODO: perhaps output an <error> tag or something. + } + if( !$title->userCanRead() ) { + continue; #TODO: perhaps output an <error> tag or something. + } $exporter->pageByTitle( $title ); } $exporter->closeStream(); - + if( $lb ) { $lb->closeAll(); } @@ -314,7 +343,7 @@ class SpecialExport extends SpecialPage { ); $pages = array(); - + foreach ( $res as $row ) { $n = $row->page_title; if ($row->page_namespace) { @@ -340,10 +369,10 @@ class SpecialExport extends SpecialPage { ); $pages = array(); - + foreach ( $res as $row ) { $n = $row->page_title; - + if ( $row->page_namespace ) { $ns = $wgContLang->getNsText( $row->page_namespace ); $n = $ns . ':' . $n; @@ -373,17 +402,17 @@ class SpecialExport extends SpecialPage { */ private function validateLinkDepth( $depth ) { global $wgExportMaxLinkDepth; - + if( $depth < 0 ) { return 0; } - + if ( !$this->userCanOverrideExportDepth() ) { if( $depth > $wgExportMaxLinkDepth ) { return $wgExportMaxLinkDepth; } } - + /* * There's a HARD CODED limit of 5 levels of recursion here to prevent a * crazy-big export from being done by someone setting the depth @@ -394,24 +423,24 @@ class SpecialExport extends SpecialPage { /** Expand a list of pages to include pages linked to from that page. */ private function getPageLinks( $inputPages, $pageSet, $depth ) { - for(; $depth > 0; --$depth ) { + for( ; $depth > 0; --$depth ) { $pageSet = $this->getLinks( $inputPages, $pageSet, 'pagelinks', - array( 'pl_namespace AS namespace', 'pl_title AS title' ), + array( 'pl_namespace AS namespace', 'pl_title AS title' ), array( 'page_id=pl_from' ) ); $inputPages = array_keys( $pageSet ); } - + return $pageSet; } /** * Expand a list of pages to include images used in those pages. - * + * * @param $inputPages array, list of titles to look up * @param $pageSet array, associative array indexed by titles for output - * + * * @return array associative array index by titles */ private function getImages( $inputPages, $pageSet ) { @@ -429,13 +458,13 @@ class SpecialExport extends SpecialPage { */ private function getLinks( $inputPages, $pageSet, $table, $fields, $join ) { $dbr = wfGetDB( DB_SLAVE ); - + foreach( $inputPages as $page ) { $title = Title::newFromText( $page ); - + if( $title ) { $pageSet[$title->getPrefixedText()] = true; - /// @todo Fixme: May or may not be more efficient to batch these + /// @todo FIXME: May or may not be more efficient to batch these /// by namespace when given multiple input pages. $result = $dbr->select( array( 'page', $table ), @@ -449,15 +478,15 @@ class SpecialExport extends SpecialPage { ), __METHOD__ ); - + foreach( $result as $row ) { $template = Title::makeTitle( $row->namespace, $row->title ); $pageSet[$template->getPrefixedText()] = true; } } } - + return $pageSet; } - -}
\ No newline at end of file + +} diff --git a/includes/specials/SpecialFewestrevisions.php b/includes/specials/SpecialFewestrevisions.php index c265ed38..6d621a2e 100644 --- a/includes/specials/SpecialFewestrevisions.php +++ b/includes/specials/SpecialFewestrevisions.php @@ -29,8 +29,8 @@ */ class FewestrevisionsPage extends QueryPage { - function getName() { - return 'Fewestrevisions'; + function __construct( $name = 'Fewestrevisions' ) { + parent::__construct( $name ); } function isExpensive() { @@ -41,31 +41,34 @@ class FewestrevisionsPage extends QueryPage { return false; } - function getSql() { - $dbr = wfGetDB( DB_SLAVE ); - list( $revision, $page ) = $dbr->tableNamesN( 'revision', 'page' ); - - return "SELECT 'Fewestrevisions' as type, - page_namespace as namespace, - page_title as title, - page_is_redirect as redirect, - COUNT(*) as value - FROM $revision - JOIN $page ON page_id = rev_page - WHERE page_namespace = " . NS_MAIN . " - GROUP BY page_namespace, page_title, page_is_redirect - HAVING COUNT(*) > 1"; + function getQueryInfo() { + return array ( + 'tables' => array ( 'revision', 'page' ), + 'fields' => array ( 'page_namespace AS namespace', + 'page_title AS title', + 'COUNT(*) AS value', + 'page_is_redirect AS 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' => 'page_namespace, page_title, page_is_redirect' ) + ); } + function sortDescending() { return false; } + /** + * @param $skin Skin object + * @param $result Object: database row + */ function formatResult( $skin, $result ) { global $wgLang, $wgContLang; @@ -94,9 +97,3 @@ class FewestrevisionsPage extends QueryPage { return wfSpecialList( $plink, $nlink ); } } - -function wfSpecialFewestrevisions() { - list( $limit, $offset ) = wfCheckLimits(); - $frp = new FewestrevisionsPage(); - $frp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialFileDuplicateSearch.php b/includes/specials/SpecialFileDuplicateSearch.php index 172e92ad..a296fd95 100644 --- a/includes/specials/SpecialFileDuplicateSearch.php +++ b/includes/specials/SpecialFileDuplicateSearch.php @@ -29,123 +29,161 @@ * @ingroup SpecialPage */ class FileDuplicateSearchPage extends QueryPage { - var $hash, $filename; + protected $hash = '', $filename = ''; - function __construct( $hash, $filename ) { - $this->hash = $hash; - $this->filename = $filename; + /** + * @var File $file selected reference file, if present + */ + protected $file = null; + + function __construct( $name = 'FileDuplicateSearch' ) { + parent::__construct( $name ); } - function getName() { return 'FileDuplicateSearch'; } - function isExpensive() { return false; } function isSyndicated() { return false; } + function isCacheable() { return false; } + function isCached() { return false; } function linkParameters() { return array( 'filename' => $this->filename ); } - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - $image = $dbr->tableName( 'image' ); - $hash = $dbr->addQuotes( $this->hash ); - - return "SELECT 'FileDuplicateSearch' AS type, - img_name AS title, - img_sha1 AS value, - img_user_text, - img_timestamp - FROM $image - WHERE img_sha1 = $hash - "; + /** + * Fetch dupes from all connected file repositories. + * + * @return Array of File objects + */ + function getDupes() { + return RepoGroup::singleton()->findBySha1( $this->hash ); } - function formatResult( $skin, $result ) { - global $wgContLang, $wgLang; + /** + * + * @param $dupes Array of File objects + */ + function showList( $dupes ) { + global $wgOut; + $skin = $this->getSkin(); - $nt = Title::makeTitle( NS_FILE, $result->title ); - $text = $wgContLang->convert( $nt->getText() ); - $plink = $skin->link( - Title::newFromText( $nt->getPrefixedText() ), - $text - ); + $html = array(); + $html[] = $this->openList( 0 ); - $user = $skin->link( Title::makeTitle( NS_USER, $result->img_user_text ), $result->img_user_text ); - $time = $wgLang->timeanddate( $result->img_timestamp ); + foreach ( $dupes as $dupe ) { + $line = $this->formatResult( $skin, $dupe ); + $html[] = "<li>" . $line . "</li>"; + } + $html[] = $this->closeList(); - return "$plink . . $user . . $time"; + $wgOut->addHtml( implode( "\n", $html ) ); } -} -/** - * Output the HTML search form, and constructs the FileDuplicateSearch object. - */ -function wfSpecialFileDuplicateSearch( $par = null ) { - global $wgRequest, $wgOut, $wgLang, $wgContLang, $wgScript; - - $hash = ''; - $filename = isset( $par ) ? $par : $wgRequest->getText( 'filename' ); - - $title = Title::makeTitleSafe( NS_FILE, $filename ); - if( $title && $title->getText() != '' ) { - $dbr = wfGetDB( DB_SLAVE ); - $image = $dbr->tableName( 'image' ); - $encFilename = $dbr->addQuotes( htmlspecialchars( $title->getDBkey() ) ); - $sql = "SELECT img_sha1 from $image where img_name = $encFilename"; - $res = $dbr->query( $sql ); - $row = $dbr->fetchRow( $res ); - if( $row !== false ) { - $hash = $row[0]; - } + function getQueryInfo() { + return array( + 'tables' => array( 'image' ), + 'fields' => array( + 'img_name AS title', + 'img_sha1 AS value', + 'img_user_text', + 'img_timestamp' + ), + 'conds' => array( 'img_sha1' => $this->hash ) + ); } - # Create the input form - $wgOut->addHTML( - Xml::openElement( 'form', array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => $wgScript ) ) . - Html::hidden( 'title', SpecialPage::getTitleFor( 'FileDuplicateSearch' )->getPrefixedDbKey() ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'fileduplicatesearch-legend' ) ) . - Xml::inputLabel( wfMsg( 'fileduplicatesearch-filename' ), 'filename', 'filename', 50, $filename ) . ' ' . - Xml::submitButton( wfMsg( 'fileduplicatesearch-submit' ) ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) - ); - - if( $hash != '' ) { - $align = $wgContLang->alignEnd(); - - # Show a thumbnail of the file - $img = wfFindFile( $title ); - if ( $img ) { - $thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) ); - if( $thumb ) { - $wgOut->addHTML( '<div style="float:' . $align . '" id="mw-fileduplicatesearch-icon">' . - $thumb->toHtml( array( 'desc-link' => false ) ) . '<br />' . - wfMsgExt( 'fileduplicatesearch-info', array( 'parse' ), - $wgLang->formatNum( $img->getWidth() ), - $wgLang->formatNum( $img->getHeight() ), - $wgLang->formatSize( $img->getSize() ), - $img->getMimeType() - ) . - '</div>' ); - } + function execute( $par ) { + global $wgRequest, $wgOut, $wgLang, $wgScript; + + $this->setHeaders(); + $this->outputHeader(); + + $this->filename = isset( $par ) ? $par : $wgRequest->getText( 'filename' ); + $this->file = null; + $this->hash = ''; + $title = Title::newFromText( $this->filename, NS_FILE ); + if( $title && $title->getText() != '' ) { + $this->file = wfFindFile( $title ); } - # Do the query - $wpp = new FileDuplicateSearchPage( $hash, $filename ); - list( $limit, $offset ) = wfCheckLimits(); - $count = $wpp->doQuery( $offset, $limit ); + # Create the input form + $wgOut->addHTML( + Xml::openElement( 'form', array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => $wgScript ) ) . + Html::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) . + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', null, wfMsg( 'fileduplicatesearch-legend' ) ) . + Xml::inputLabel( wfMsg( 'fileduplicatesearch-filename' ), 'filename', 'filename', 50, $this->filename ) . ' ' . + Xml::submitButton( wfMsg( 'fileduplicatesearch-submit' ) ) . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) + ); - # Show a short summary - if( $count == 1 ) { - $wgOut->wrapWikiMsg( - "<p class='mw-fileduplicatesearch-result-1'>\n$1\n</p>", - array( 'fileduplicatesearch-result-1', $filename ) - ); - } elseif ( $count > 1 ) { + if( $this->file ) { + $this->hash = $this->file->getSha1(); + } elseif( $this->filename !== '' ) { $wgOut->wrapWikiMsg( - "<p class='mw-fileduplicatesearch-result-n'>\n$1\n</p>", - array( 'fileduplicatesearch-result-n', $filename, $wgLang->formatNum( $count - 1 ) ) + "<p class='mw-fileduplicatesearch-noresults'>\n$1\n</p>", + array( 'fileduplicatesearch-noresults', wfEscapeWikiText( $this->filename ) ) ); } + + if( $this->hash != '' ) { + # Show a thumbnail of the file + $img = $this->file; + if ( $img ) { + $thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) ); + if( $thumb ) { + $wgOut->addHTML( '<div id="mw-fileduplicatesearch-icon">' . + $thumb->toHtml( array( 'desc-link' => false ) ) . '<br />' . + wfMsgExt( 'fileduplicatesearch-info', array( 'parse' ), + $wgLang->formatNum( $img->getWidth() ), + $wgLang->formatNum( $img->getHeight() ), + $wgLang->formatSize( $img->getSize() ), + $img->getMimeType() + ) . + '</div>' ); + } + } + + $dupes = $this->getDupes(); + $numRows = count( $dupes ); + + # Show a short summary + if( $numRows == 1 ) { + $wgOut->wrapWikiMsg( + "<p class='mw-fileduplicatesearch-result-1'>\n$1\n</p>", + array( 'fileduplicatesearch-result-1', wfEscapeWikiText( $this->filename ) ) + ); + } elseif ( $numRows ) { + $wgOut->wrapWikiMsg( + "<p class='mw-fileduplicatesearch-result-n'>\n$1\n</p>", + array( 'fileduplicatesearch-result-n', wfEscapeWikiText( $this->filename ), + $wgLang->formatNum( $numRows - 1 ) ) + ); + } + + $this->showList( $dupes ); + } + } + + /** + * + * @param Skin $skin + * @param File $result + * @return string + */ + function formatResult( $skin, $result ) { + global $wgContLang, $wgLang; + + $nt = $result->getTitle(); + $text = $wgContLang->convert( $nt->getText() ); + $plink = $skin->link( + Title::newFromText( $nt->getPrefixedText() ), + $text + ); + + $userText = $result->getUser( 'text' ); + $user = $skin->link( Title::makeTitle( NS_USER, $userText ), $userText ); + $time = $wgLang->timeanddate( $result->getTimestamp() ); + + return "$plink . . $user . . $time"; } } diff --git a/includes/specials/SpecialFilepath.php b/includes/specials/SpecialFilepath.php index 8bb0890c..08f90fd2 100644 --- a/includes/specials/SpecialFilepath.php +++ b/includes/specials/SpecialFilepath.php @@ -40,19 +40,25 @@ class SpecialFilepath extends SpecialPage { $file = !is_null( $par ) ? $par : $wgRequest->getText( 'file' ); - $title = Title::makeTitleSafe( NS_FILE, $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 = $wgRequest->getInt( 'width', -1 ); $height = $wgRequest->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(); } } @@ -64,6 +70,9 @@ class SpecialFilepath extends SpecialPage { } } + /** + * @param $title Title + */ function showForm( $title ) { global $wgOut, $wgScript; diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php index 7d1cf0dd..fc904a23 100644 --- a/includes/specials/SpecialImport.php +++ b/includes/specials/SpecialImport.php @@ -30,14 +30,15 @@ * @ingroup SpecialPage */ class SpecialImport extends SpecialPage { - + private $interwiki = false; private $namespace; private $frompage = ''; private $logcomment= false; private $history = true; private $includeTemplates = false; - + private $pageLinkDepth; + /** * Constructor */ @@ -46,26 +47,27 @@ class SpecialImport extends SpecialPage { global $wgImportTargetNamespace; $this->namespace = $wgImportTargetNamespace; } - + /** * Execute */ function execute( $par ) { global $wgRequest, $wgUser, $wgOut; - + $this->setHeaders(); $this->outputHeader(); - + if ( wfReadOnly() ) { $wgOut->readOnlyPage(); return; } - - if( !$wgUser->isAllowed( 'import' ) && !$wgUser->isAllowed( 'importupload' ) ) + + if( !$wgUser->isAllowedAny( 'import', 'importupload' ) ) { return $wgOut->permissionRequired( 'import' ); + } - # TODO: allow Title::getUserPermissionsErrors() to take an array - # FIXME: Title::checkSpecialsAndNSPermissions() has a very wierd expectation of what + # @todo Allow Title::getUserPermissionsErrors() to take an array + # @todo FIXME: Title::checkSpecialsAndNSPermissions() has a very wierd expectation of what # getUserPermissionsErrors() might actually be used for, hence the 'ns-specialprotected' $errors = wfMergeErrorArrays( $this->getTitle()->getUserPermissionsErrors( @@ -88,7 +90,7 @@ class SpecialImport extends SpecialPage { } $this->showForm(); } - + /** * Do the actual import */ @@ -162,7 +164,7 @@ class SpecialImport extends SpecialPage { # Success! $wgOut->addWikiMsg( 'importsuccess' ); } - $wgOut->addWikiText( '<hr />' ); + $wgOut->addHTML( '<hr />' ); } } @@ -290,7 +292,7 @@ class SpecialImport extends SpecialPage { <td> </td> <td class='mw-submit'>" . - Xml::submitButton( wfMsg( 'import-interwiki-submit' ), $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'import' ) ) . + Xml::submitButton( wfMsg( 'import-interwiki-submit' ), Linker::tooltipAndAccesskeyAttribs( 'import' ) ) . "</td> </tr>" . Xml::closeElement( 'table' ). @@ -312,8 +314,8 @@ class ImportReporter { private $mLogItemCount = 0; function __construct( $importer, $upload, $interwiki , $reason=false ) { - $this->mOriginalPageOutCallback = - $importer->setPageOutCallback( array( $this, 'reportPage' ) ); + $this->mOriginalPageOutCallback = + $importer->setPageOutCallback( array( $this, 'reportPage' ) ); $this->mOriginalLogCallback = $importer->setLogItemCallback( array( $this, 'reportLogItem' ) ); $this->mPageCount = 0; @@ -326,7 +328,7 @@ class ImportReporter { global $wgOut; $wgOut->addHTML( "<ul>\n" ); } - + function reportLogItem( /* ... */ ) { $this->mLogItemCount++; if ( is_callable( $this->mOriginalLogCallback ) ) { @@ -334,21 +336,27 @@ class ImportReporter { } } + /** + * @param Title $title + * @param Title $origTitle + * @param int $revisionCount + * @param $successCount + * @param $pageInfo + * @return void + */ function reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo ) { global $wgOut, $wgUser, $wgLang, $wgContLang; - + $args = func_get_args(); call_user_func_array( $this->mOriginalPageOutCallback, $args ); - $skin = $wgUser->getSkin(); - $this->mPageCount++; $localCount = $wgLang->formatNum( $successCount ); $contentCount = $wgContLang->formatNum( $successCount ); if( $successCount > 0 ) { - $wgOut->addHTML( "<li>" . $skin->linkKnown( $title ) . " " . + $wgOut->addHTML( "<li>" . Linker::linkKnown( $title ) . " " . wfMsgExt( 'import-revision-count', array( 'parsemag', 'escape' ), $localCount ) . "</li>\n" ); @@ -382,14 +390,14 @@ class ImportReporter { $article->updateRevisionOn( $dbw, $nullRevision ); wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $wgUser) ); } else { - $wgOut->addHTML( "<li>" . $skin->linkKnown( $title ) . " " . + $wgOut->addHTML( "<li>" . Linker::linkKnown( $title ) . " " . wfMsgHtml( 'import-nonewrevisions' ) . "</li>\n" ); } } function close() { global $wgOut, $wgLang; - + if ( $this->mLogItemCount > 0 ) { $msg = wfMsgExt( 'imported-log-entries', 'parseinline', $wgLang->formatNum( $this->mLogItemCount ) ); diff --git a/includes/specials/SpecialIpblocklist.php b/includes/specials/SpecialIpblocklist.php deleted file mode 100644 index 24d7f008..00000000 --- a/includes/specials/SpecialIpblocklist.php +++ /dev/null @@ -1,581 +0,0 @@ -<?php -/** - * Implements Special:ipblocklist - * - * 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 existing blocks and allows users with the 'block' - * permission to remove blocks - * - * @ingroup SpecialPage - */ -class IPUnblockForm extends SpecialPage { - var $ip, $reason, $id; - var $hideuserblocks, $hidetempblocks, $hideaddressblocks; - - function __construct() { - parent::__construct( 'Ipblocklist' ); - } - - /** - * Main execution point - * - * @param $ip part of title: Special:Ipblocklist/<ip>. - */ - function execute( $ip ) { - global $wgUser, $wgOut, $wgRequest; - - $this->setHeaders(); - $this->outputHeader(); - - $ip = $wgRequest->getVal( 'ip', $ip ); - $this->ip = trim( $wgRequest->getVal( 'wpUnblockAddress', $ip ) ); - $this->id = $wgRequest->getVal( 'id' ); - $this->reason = $wgRequest->getText( 'wpUnblockReason' ); - $this->hideuserblocks = $wgRequest->getBool( 'hideuserblocks' ); - $this->hidetempblocks = $wgRequest->getBool( 'hidetempblocks' ); - $this->hideaddressblocks = $wgRequest->getBool( 'hideaddressblocks' ); - - $action = $wgRequest->getText( 'action' ); - $successip = $wgRequest->getVal( 'successip' ); - - if( $action == 'unblock' || $action == 'submit' && $wgRequest->wasPosted() ) { - # Check permissions - if( !$wgUser->isAllowed( 'block' ) ) { - $wgOut->permissionRequired( 'block' ); - return; - } - # Check for database lock - if( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } - - # bug 15810: blocked admins should have limited access here - if ( $wgUser->isBlocked() ) { - if ( $this->id ) { - # This doesn't pick up on autoblocks, but admins - # should have the ipblock-exempt permission anyway - $block = Block::newFromID( $this->id ); - $user = User::newFromName( $block->mAddress ); - } else { - $user = User::newFromName( $this->ip ); - } - $status = IPBlockForm::checkUnblockSelf( $user ); - if ( $status !== true ) { - throw new ErrorPageError( 'badaccess', $status ); - } - } - - if( $action == 'unblock' ){ - # Show unblock form - $this->showForm( '' ); - } elseif( $action == 'submit' - && $wgRequest->wasPosted() - && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) - { - # Remove blocks and redirect user to success page - $this->doSubmit(); - } - - } elseif( $action == 'success' ) { - # Inform the user of a successful unblock - # (No need to check permissions or locks here, - # if something was done, then it's too late!) - if ( substr( $successip, 0, 1) == '#' ) { - // A block ID was unblocked - $this->showList( $wgOut->parse( wfMsg( 'unblocked-id', $successip ) ) ); - } else { - // A username/IP was unblocked - $this->showList( $wgOut->parse( wfMsg( 'unblocked', $successip ) ) ); - } - } else { - # Just show the block list - $this->showList( '' ); - } - } - - /** - * Generates the unblock form - * - * @param $err string: error message - * @return $out string: HTML form - */ - function showForm( $err ) { - global $wgOut, $wgUser, $wgSysopUserBans; - - $wgOut->addWikiMsg( 'unblockiptext' ); - - $action = $this->getTitle()->getLocalURL( 'action=submit' ); - - if ( $err != '' ) { - $wgOut->setSubtitle( wfMsg( 'formerror' ) ); - $wgOut->addWikiText( Html::rawElement( 'span', array( 'class' => 'error' ), $err ) . "\n" ); - } - - $addressPart = false; - if ( $this->id ) { - $block = Block::newFromID( $this->id ); - if ( $block ) { - $encName = htmlspecialchars( $block->getRedactedName() ); - $encId = $this->id; - $addressPart = $encName . Html::hidden( 'id', $encId ); - $ipa = wfMsgHtml( $wgSysopUserBans ? 'ipadressorusername' : 'ipaddress' ); - } - } - if ( !$addressPart ) { - $addressPart = Xml::input( 'wpUnblockAddress', 40, $this->ip, array( 'type' => 'text', 'tabindex' => '1' ) ); - $ipa = Xml::label( wfMsg( $wgSysopUserBans ? 'ipadressorusername' : 'ipaddress' ), 'wpUnblockAddress' ); - } - - $wgOut->addHTML( - Html::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'unblockip' ) ) . - Html::openElement( 'fieldset' ) . - Html::element( 'legend', null, wfMsg( 'ipb-unblock' ) ) . - Html::openElement( 'table', array( 'id' => 'mw-unblock-table' ) ). - "<tr> - <td class='mw-label'> - {$ipa} - </td> - <td class='mw-input'> - {$addressPart} - </td> - </tr> - <tr> - <td class='mw-label'>" . - Xml::label( wfMsg( 'ipbreason' ), 'wpUnblockReason' ) . - "</td> - <td class='mw-input'>" . - Xml::input( 'wpUnblockReason', 40, $this->reason, array( 'type' => 'text', 'tabindex' => '2' ) ) . - "</td> - </tr> - <tr> - <td> </td> - <td class='mw-submit'>" . - Xml::submitButton( wfMsg( 'ipusubmit' ), array( 'name' => 'wpBlock', 'tabindex' => '3' ) ) . - "</td> - </tr>" . - Html::closeElement( 'table' ) . - Html::closeElement( 'fieldset' ) . - Html::hidden( 'wpEditToken', $wgUser->editToken() ) . - Html::closeElement( 'form' ) . "\n" - ); - - } - - const UNBLOCK_SUCCESS = 0; // Success - const UNBLOCK_NO_SUCH_ID = 1; // No such block ID - const UNBLOCK_USER_NOT_BLOCKED = 2; // IP wasn't blocked - const UNBLOCK_BLOCKED_AS_RANGE = 3; // IP is part of a range block - const UNBLOCK_UNKNOWNERR = 4; // Unknown error - - /** - * Backend code for unblocking. doSubmit() wraps around this. - * $range is only used when UNBLOCK_BLOCKED_AS_RANGE is returned, in which - * case it contains the range $ip is part of. - * @return array array(message key, parameters) on failure, empty array on success - */ - public static function doUnblock( &$id, &$ip, &$reason, &$range = null, $blocker = null ) { - if ( $id ) { - $block = Block::newFromID( $id ); - if ( !$block ) { - return array( 'ipb_cant_unblock', htmlspecialchars( $id ) ); - } - $ip = $block->getRedactedName(); - } else { - $ip = trim( $ip ); - if ( substr( $ip, 0, 1 ) == "#" ) { - $id = substr( $ip, 1 ); - $block = Block::newFromID( $id ); - if( !$block ) { - return array( 'ipb_cant_unblock', htmlspecialchars( $id ) ); - } - $ip = $block->getRedactedName(); - } else { - # FIXME: do proper sanitisation/cleanup here - $ip = str_replace( '_', ' ', $ip ); - - $block = Block::newFromDB( $ip ); - if ( !$block ) { - return array( 'ipb_cant_unblock', htmlspecialchars( $id ) ); - } - if( $block->mRangeStart != $block->mRangeEnd && !strstr( $ip, "/" ) ) { - /* If the specified IP is a single address, and the block is - * a range block, don't unblock the range. */ - $range = $block->mAddress; - return array( 'ipb_blocked_as_range', $ip, $range ); - } - } - } - // Yes, this is really necessary - $id = $block->mId; - - # If the name was hidden and the blocking user cannot hide - # names, then don't allow any block removals... - if( $blocker && $block->mHideName && !$blocker->isAllowed( 'hideuser' ) ) { - return array( 'ipb_cant_unblock', htmlspecialchars( $id ) ); - } - - # Delete block - if ( !$block->delete() ) { - return array( 'ipb_cant_unblock', htmlspecialchars( $id ) ); - } - - # Unset _deleted fields as needed - if( $block->mHideName ) { - IPBlockForm::unsuppressUserName( $block->mAddress, $block->mUser ); - } - - # Make log entry - $log = new LogPage( 'block' ); - $log->addEntry( 'unblock', Title::makeTitle( NS_USER, $ip ), $reason ); - return array(); - } - - function doSubmit() { - global $wgOut, $wgUser; - - $retval = self::doUnblock( $this->id, $this->ip, $this->reason, $range, $wgUser ); - if( !empty( $retval ) ) { - $key = array_shift( $retval ); - $this->showForm( wfMsgReal( $key, $retval ) ); - return; - } - - # Report to the user - $success = $this->getTitle()->getFullURL( 'action=success&successip=' . urlencode( $this->ip ) ); - $wgOut->redirect( $success ); - } - - function showList( $msg ) { - global $wgOut, $wgUser; - - if ( $msg != '' ) { - $wgOut->setSubtitle( $msg ); - } - - // Purge expired entries on one in every 10 queries - if ( !mt_rand( 0, 10 ) ) { - Block::purgeExpired(); - } - - $conds = array(); - // Is user allowed to see all the blocks? - if ( !$wgUser->isAllowed( 'hideuser' ) ) - $conds['ipb_deleted'] = 0; - if ( $this->ip == '' ) { - // No extra conditions - } elseif ( substr( $this->ip, 0, 1 ) == '#' ) { - $conds['ipb_id'] = substr( $this->ip, 1 ); - // Single IPs - } elseif ( IP::isIPAddress( $this->ip ) && strpos( $this->ip, '/' ) === false ) { - $iaddr = IP::toHex( $this->ip ); - if( $iaddr ) { - # Only scan ranges which start in this /16, this improves search speed - # Blocks should not cross a /16 boundary. - $range = substr( $iaddr, 0, 4 ); - // Fixme -- encapsulate this sort of query-building. - $dbr = wfGetDB( DB_SLAVE ); - $encIp = $dbr->addQuotes( IP::sanitizeIP( $this->ip ) ); - $encAddr = $dbr->addQuotes( $iaddr ); - $conds[] = "(ipb_address = $encIp) OR - (ipb_range_start" . $dbr->buildLike( $range, $dbr->anyString() ) . " AND - ipb_range_start <= $encAddr - AND ipb_range_end >= $encAddr)"; - } else { - $conds['ipb_address'] = IP::sanitizeIP( $this->ip ); - } - $conds['ipb_auto'] = 0; - // IP range - } elseif ( IP::isIPAddress( $this->ip ) ) { - $conds['ipb_address'] = Block::normaliseRange( $this->ip ); - $conds['ipb_auto'] = 0; - } else { - $user = User::newFromName( $this->ip ); - if ( $user && ( $id = $user->getId() ) != 0 ) { - $conds['ipb_user'] = $id; - } else { - // Uh...? - $conds['ipb_address'] = $this->ip; - $conds['ipb_auto'] = 0; - } - } - // Apply filters - if( $this->hideuserblocks ) { - $conds['ipb_user'] = 0; - } - if( $this->hidetempblocks ) { - $conds['ipb_expiry'] = 'infinity'; - } - if( $this->hideaddressblocks ) { - $conds[] = "ipb_user != 0 OR ipb_range_end > ipb_range_start"; - } - - // Search form - $wgOut->addHTML( $this->searchForm() ); - - // Check for other blocks, i.e. global/tor blocks - $otherBlockLink = array(); - wfRunHooks( 'OtherBlockLogLink', array( &$otherBlockLink, $this->ip ) ); - - // 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 ) ) { - $wgOut->addHTML( - Html::rawElement( 'h2', array(), wfMsg( 'ipblocklist-localblock' ) ) . "\n" - ); - } - $pager = new IPBlocklistPager( $this, $conds ); - if ( $pager->getNumRows() ) { - $wgOut->addHTML( - $pager->getNavigationBar() . - Html::rawElement( 'ul', null, $pager->getBody() ) . - $pager->getNavigationBar() - ); - } elseif ( $this->ip != '') { - $wgOut->addWikiMsg( 'ipblocklist-no-results' ); - } else { - $wgOut->addWikiMsg( 'ipblocklist-empty' ); - } - - if( count( $otherBlockLink ) ) { - $wgOut->addHTML( - Html::rawElement( 'h2', array(), wfMsgExt( 'ipblocklist-otherblocks', 'parseinline', count( $otherBlockLink ) ) ) . "\n" - ); - $list = ''; - foreach( $otherBlockLink as $link ) { - $list .= Html::rawElement( 'li', array(), $link ) . "\n"; - } - $wgOut->addHTML( Html::rawElement( 'ul', array( 'class' => 'mw-ipblocklist-otherblocks' ), $list ) . "\n" ); - } - - } - - function searchForm() { - global $wgScript, $wgLang; - - $showhide = array( wfMsg( 'show' ), wfMsg( 'hide' ) ); - $nondefaults = array(); - if( $this->hideuserblocks ) { - $nondefaults['hideuserblocks'] = $this->hideuserblocks; - } - if( $this->hidetempblocks ) { - $nondefaults['hidetempblocks'] = $this->hidetempblocks; - } - if( $this->hideaddressblocks ) { - $nondefaults['hideaddressblocks'] = $this->hideaddressblocks; - } - $ubLink = $this->makeOptionsLink( $showhide[1-$this->hideuserblocks], - array( 'hideuserblocks' => 1-$this->hideuserblocks ), $nondefaults); - $tbLink = $this->makeOptionsLink( $showhide[1-$this->hidetempblocks], - array( 'hidetempblocks' => 1-$this->hidetempblocks ), $nondefaults); - $sipbLink = $this->makeOptionsLink( $showhide[1-$this->hideaddressblocks], - array( 'hideaddressblocks' => 1-$this->hideaddressblocks ), $nondefaults); - - $links = array(); - $links[] = wfMsgHtml( 'ipblocklist-sh-userblocks', $ubLink ); - $links[] = wfMsgHtml( 'ipblocklist-sh-tempblocks', $tbLink ); - $links[] = wfMsgHtml( 'ipblocklist-sh-addressblocks', $sipbLink ); - $hl = $wgLang->pipeList( $links ); - - return - Html::rawElement( 'form', array( 'action' => $wgScript ), - Html::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) . - Html::openElement( 'fieldset' ) . - Html::element( 'legend', null, wfMsg( 'ipblocklist-legend' ) ) . - Xml::inputLabel( wfMsg( 'ipblocklist-username' ), 'ip', 'ip', /* size */ false, $this->ip ) . - ' ' . - Xml::submitButton( wfMsg( 'ipblocklist-submit' ) ) . '<br />' . - $hl . - Html::closeElement( 'fieldset' ) - ); - } - - /** - * Makes change an option link which carries all the other options - * - * @param $title see Title - * @param $override Array: special query string options, will override the - * ones in $options - * @param $options Array: query string options - * @param $active Boolean: whether to display the link in bold - */ - function makeOptionsLink( $title, $override, $options, $active = false ) { - global $wgUser; - $sk = $wgUser->getSkin(); - $params = $override + $options; - return $sk->link( $this->getTitle(), htmlspecialchars( $title ), - ( $active ? array( 'style'=>'font-weight: bold;' ) : array() ), $params, array( 'known' ) ); - } - - /** - * Callback function to output a block - */ - function formatRow( $block ) { - global $wgUser, $wgLang, $wgBlockAllowsUTEdit; - - wfProfileIn( __METHOD__ ); - - static $sk=null, $msg=null; - - if( is_null( $sk ) ) - $sk = $wgUser->getSkin(); - if( is_null( $msg ) ) { - $msg = array(); - $keys = array( 'infiniteblock', 'expiringblock', 'unblocklink', 'change-blocklink', - 'anononlyblock', 'createaccountblock', 'noautoblockblock', 'emailblock', 'blocklist-nousertalk', 'blocklistline' ); - foreach( $keys as $key ) { - $msg[$key] = wfMsgHtml( $key ); - } - } - - # Prepare links to the blocker's user and talk pages - $blocker_id = $block->getBy(); - $blocker_name = $block->getByName(); - $blocker = $sk->userLink( $blocker_id, $blocker_name ); - $blocker .= $sk->userToolLinks( $blocker_id, $blocker_name ); - - # Prepare links to the block target's user and contribs. pages (as applicable, don't do it for autoblocks) - if( $block->mAuto ) { - $target = $block->getRedactedName(); # Hide the IP addresses of auto-blocks; privacy - } else { - $target = $sk->userLink( $block->mUser, $block->mAddress ) - . $sk->userToolLinks( $block->mUser, $block->mAddress, false, Linker::TOOL_LINKS_NOBLOCK ); - } - - $formattedTime = htmlspecialchars( $wgLang->timeanddate( $block->mTimestamp, true ) ); - - $properties = array(); - $properties[] = Block::formatExpiry( $block->mExpiry ); - if ( $block->mAnonOnly ) { - $properties[] = $msg['anononlyblock']; - } - if ( $block->mCreateAccount ) { - $properties[] = $msg['createaccountblock']; - } - if (!$block->mEnableAutoblock && $block->mUser ) { - $properties[] = $msg['noautoblockblock']; - } - - if ( $block->mBlockEmail && $block->mUser ) { - $properties[] = $msg['emailblock']; - } - - if ( !$block->mAllowUsertalk && $wgBlockAllowsUTEdit ) { - $properties[] = $msg['blocklist-nousertalk']; - } - - $properties = $wgLang->commaList( $properties ); - - $line = wfMsgReplaceArgs( $msg['blocklistline'], array( $formattedTime, $blocker, $target, $properties ) ); - - $changeblocklink = ''; - $toolLinks = ''; - if ( $wgUser->isAllowed( 'block' ) ) { - $unblocklink = $sk->link( $this->getTitle(), - $msg['unblocklink'], - array(), - array( 'action' => 'unblock', 'id' => $block->mId ), - 'known' ); - - # Create changeblocklink for all blocks with exception of autoblocks - if( !$block->mAuto ) { - $changeblocklink = wfMsgExt( 'pipe-separator', 'escapenoentities' ) . - $sk->link( SpecialPage::getTitleFor( 'Blockip', $block->mAddress ), - $msg['change-blocklink'], - array(), array(), 'known' ); - } - $toolLinks = "($unblocklink$changeblocklink)"; - } - - $comment = $sk->commentBlock( htmlspecialchars($block->mReason) ); - - $s = "{$line} $comment"; - if ( $block->mHideName ) - $s = '<span class="history-deleted">' . $s . '</span>'; - - wfProfileOut( __METHOD__ ); - return "<li>$s $toolLinks</li>\n"; - } -} - -/** - * @todo document - * @ingroup Pager - */ -class IPBlocklistPager extends ReverseChronologicalPager { - public $mForm, $mConds; - - function __construct( $form, $conds = array() ) { - $this->mForm = $form; - $this->mConds = $conds; - parent::__construct(); - } - - function getStartBody() { - wfProfileIn( __METHOD__ ); - # Do a link batch query - $this->mResult->seek( 0 ); - $lb = new LinkBatch; - - /* - while ( $row = $this->mResult->fetchObject() ) { - $lb->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) ); - $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) ); - $lb->addObj( Title::makeTitleSafe( NS_USER, $row->ipb_address ) ); - $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ipb_address ) ); - }*/ - # Faster way - # Usernames and titles are in fact related by a simple substitution of space -> underscore - # The last few lines of Title::secureAndSplit() tell the story. - foreach ( $this->mResult as $row ) { - $name = str_replace( ' ', '_', $row->ipb_by_text ); - $lb->add( NS_USER, $name ); - $lb->add( NS_USER_TALK, $name ); - $name = str_replace( ' ', '_', $row->ipb_address ); - $lb->add( NS_USER, $name ); - $lb->add( NS_USER_TALK, $name ); - } - $lb->execute(); - wfProfileOut( __METHOD__ ); - return ''; - } - - function formatRow( $row ) { - $block = new Block; - $block->initFromRow( $row ); - return $this->mForm->formatRow( $block ); - } - - function getQueryInfo() { - $conds = $this->mConds; - $conds[] = 'ipb_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() ); - return array( - 'tables' => 'ipblocks', - 'fields' => '*', - 'conds' => $conds, - ); - } - - function getIndexField() { - return 'ipb_timestamp'; - } -} diff --git a/includes/specials/SpecialLinkSearch.php b/includes/specials/SpecialLinkSearch.php index 9dee9d5a..bef859a2 100644 --- a/includes/specials/SpecialLinkSearch.php +++ b/includes/specials/SpecialLinkSearch.php @@ -21,74 +21,10 @@ * @ingroup SpecialPage * @author Brion Vibber */ - -/** - * Special:LinkSearch to search the external-links table. - */ -function wfSpecialLinkSearch( $par ) { - - list( $limit, $offset ) = wfCheckLimits(); - global $wgOut, $wgUrlProtocols, $wgMiserMode, $wgLang; - $target = $GLOBALS['wgRequest']->getVal( 'target', $par ); - $namespace = $GLOBALS['wgRequest']->getIntorNull( 'namespace', null ); - - $protocols_list[] = ''; - foreach( $wgUrlProtocols as $prot ) { - $protocols_list[] = $prot; - } - - $target2 = $target; - $protocol = ''; - $pr_sl = strpos($target2, '//' ); - $pr_cl = strpos($target2, ':' ); - if ( $pr_sl ) { - // For protocols with '//' - $protocol = substr( $target2, 0 , $pr_sl+2 ); - $target2 = substr( $target2, $pr_sl+2 ); - } elseif ( !$pr_sl && $pr_cl ) { - // For protocols without '//' like 'mailto:' - $protocol = substr( $target2, 0 , $pr_cl+1 ); - $target2 = substr( $target2, $pr_cl+1 ); - } elseif ( $protocol == '' && $target2 != '' ) { - // default - $protocol = 'http://'; - } - if ( !in_array( $protocol, $protocols_list ) ) { - // unsupported protocol, show original search request - $target2 = $target; - $protocol = ''; - } - - $self = Title::makeTitle( NS_SPECIAL, 'Linksearch' ); - - $wgOut->allowClickjacking(); - $wgOut->addWikiMsg( 'linksearch-text', '<nowiki>' . $wgLang->commaList( $wgUrlProtocols ) . '</nowiki>' ); - $s = Xml::openElement( 'form', array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => $GLOBALS['wgScript'] ) ) . - Html::hidden( 'title', $self->getPrefixedDbKey() ) . - '<fieldset>' . - Xml::element( 'legend', array(), wfMsg( 'linksearch' ) ) . - Xml::inputLabel( wfMsg( 'linksearch-pat' ), 'target', 'target', 50, $target ) . ' '; - if ( !$wgMiserMode ) { - $s .= Xml::label( wfMsg( 'linksearch-ns' ), 'namespace' ) . ' ' . - Xml::namespaceSelector( $namespace, '' ); - } - $s .= Xml::submitButton( wfMsg( 'linksearch-ok' ) ) . - '</fieldset>' . - Xml::closeElement( 'form' ); - $wgOut->addHTML( $s ); - - if( $target != '' ) { - $searcher = new LinkSearchPage; - $searcher->setParams( array( - 'query' => $target2, - 'namespace' => $namespace, - 'protocol' => $protocol ) ); - $searcher->doQuery( $offset, $limit ); - } -} /** + * Special:LinkSearch to search the external-links table. * @ingroup SpecialPage */ class LinkSearchPage extends QueryPage { @@ -98,8 +34,75 @@ class LinkSearchPage extends QueryPage { $this->mProt = $params['protocol']; } - function getName() { - return 'LinkSearch'; + function __construct( $name = 'LinkSearch' ) { + parent::__construct( $name ); + } + + function isCacheable() { + return false; + } + + function execute( $par ) { + global $wgOut, $wgRequest, $wgUrlProtocols, $wgMiserMode, $wgLang; + $this->setHeaders(); + $wgOut->allowClickjacking(); + + $target = $wgRequest->getVal( 'target', $par ); + $namespace = $wgRequest->getIntorNull( 'namespace', null ); + + $protocols_list = array(); + foreach( $wgUrlProtocols as $prot ) { + if ( $prot !== '//' ) { + $protocols_list[] = $prot; + } + } + + $target2 = $target; + $protocol = ''; + $pr_sl = strpos($target2, '//' ); + $pr_cl = strpos($target2, ':' ); + if ( $pr_sl ) { + // For protocols with '//' + $protocol = substr( $target2, 0 , $pr_sl+2 ); + $target2 = substr( $target2, $pr_sl+2 ); + } elseif ( !$pr_sl && $pr_cl ) { + // For protocols without '//' like 'mailto:' + $protocol = substr( $target2, 0 , $pr_cl+1 ); + $target2 = substr( $target2, $pr_cl+1 ); + } elseif ( $protocol == '' && $target2 != '' ) { + // default + $protocol = 'http://'; + } + if ( $protocol != '' && !in_array( $protocol, $protocols_list ) ) { + // unsupported protocol, show original search request + $target2 = $target; + $protocol = ''; + } + + $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', $self->getPrefixedDbKey() ) . + '<fieldset>' . + Xml::element( 'legend', array(), wfMsg( 'linksearch' ) ) . + Xml::inputLabel( wfMsg( 'linksearch-pat' ), 'target', 'target', 50, $target ) . ' '; + if ( !$wgMiserMode ) { + $s .= Xml::label( wfMsg( 'linksearch-ns' ), 'namespace' ) . ' ' . + Xml::namespaceSelector( $namespace, '' ); + } + $s .= Xml::submitButton( wfMsg( 'linksearch-ok' ) ) . + '</fieldset>' . + Xml::closeElement( 'form' ); + $wgOut->addHTML( $s ); + + if( $target != '' ) { + $this->setParams( array( + 'query' => $target2, + 'namespace' => $namespace, + 'protocol' => $protocol ) ); + parent::execute( $par ); + if( $this->mMungedQuery === false ) + $wgOut->addWikiMsg( 'linksearch-error' ); + } } /** @@ -111,15 +114,17 @@ class LinkSearchPage extends QueryPage { /** * Return an appropriately formatted LIKE query and the clause + * + * @return array */ - static function mungeQuery( $query , $prot ) { + static function mungeQuery( $query, $prot ) { $field = 'el_index'; $rv = LinkFilter::makeLikeArray( $query , $prot ); - if ($rv === false) { + 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)) { $dbr = wfGetDB( DB_SLAVE ); - $rv = array( $prot . rtrim($query, " \t*"), $dbr->anyString() ); + $rv = array( $prot . rtrim( $query, " \t*" ), $dbr->anyString() ); $field = 'el_to'; } } @@ -136,35 +141,32 @@ class LinkSearchPage extends QueryPage { return $params; } - function getSQL() { + function getQueryInfo() { global $wgMiserMode; $dbr = wfGetDB( DB_SLAVE ); - $page = $dbr->tableName( 'page' ); - $externallinks = $dbr->tableName( 'externallinks' ); - - /* strip everything past first wildcard, so that index-based-only lookup would be done */ - list( $munged, $clause ) = self::mungeQuery( $this->mQuery, $this->mProt ); - $stripped = LinkFilter::keepOneWildcard( $munged ); + // 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 ) + // Invalid query; return no results + return array( 'tables' => 'page', 'fields' => 'page_id', 'conds' => '0=1' ); + + $stripped = LinkFilter::keepOneWildcard( $this->mMungedQuery ); $like = $dbr->buildLike( $stripped ); - - $encSQL = ''; - if ( isset ($this->mNs) && !$wgMiserMode ) - $encSQL = 'AND page_namespace=' . $dbr->addQuotes( $this->mNs ); - - $use_index = $dbr->useIndexClause( $clause ); - return - "SELECT - page_namespace AS namespace, - page_title AS title, - el_index AS value, - el_to AS url - FROM - $page, - $externallinks $use_index - WHERE - page_id=el_from - AND $clause $like - $encSQL"; + $retval = array ( + 'tables' => array ( 'page', 'externallinks' ), + 'fields' => array ( 'page_namespace AS namespace', + 'page_title AS title', + 'el_index AS value', 'el_to AS url' ), + '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; } function formatResult( $skin, $result ) { @@ -179,7 +181,7 @@ class LinkSearchPage extends QueryPage { /** * Override to check query validity. */ - function doQuery( $offset, $limit, $shownavigation=true ) { + function doQuery( $offset = false, $limit = false ) { global $wgOut; list( $this->mMungedQuery, ) = LinkSearchPage::mungeQuery( $this->mQuery, $this->mProt ); if( $this->mMungedQuery === false ) { @@ -188,7 +190,7 @@ class LinkSearchPage extends QueryPage { // For debugging // Generates invalid xhtml with patterns that contain -- //$wgOut->addHTML( "\n<!-- " . htmlspecialchars( $this->mMungedQuery ) . " -->\n" ); - parent::doQuery( $offset, $limit, $shownavigation ); + parent::doQuery( $offset, $limit ); } } @@ -198,7 +200,7 @@ class LinkSearchPage extends QueryPage { * it as good enough for optimizing sort. The implicit ordering * from the scan will usually do well enough for our needs. */ - function getOrder() { - return ''; + function getOrderFields() { + return array(); } } diff --git a/includes/specials/SpecialListfiles.php b/includes/specials/SpecialListfiles.php index 350e833b..427de167 100644 --- a/includes/specials/SpecialListfiles.php +++ b/includes/specials/SpecialListfiles.php @@ -20,16 +20,38 @@ * @file * @ingroup SpecialPage */ - -function wfSpecialListfiles( $par = null ) { - global $wgOut; - $pager = new ImageListPager( $par ); +class SpecialListFiles extends IncludableSpecialPage { - $limit = $pager->getForm(); - $body = $pager->getBody(); - $nav = $pager->getNavigationBar(); - $wgOut->addHTML( "$limit<br />\n$body<br />\n$nav" ); + public function __construct(){ + parent::__construct( 'Listfiles' ); + } + + public function execute( $par ){ + global $wgOut, $wgRequest; + $this->setHeaders(); + $this->outputHeader(); + + if ( $this->including() ) { + $userName = $par; + $search = ''; + } else { + $userName = $wgRequest->getText( 'user', $par ); + $search = $wgRequest->getText( 'ilsearch', '' ); + } + + $pager = new ImageListPager( $userName, $search, $this->including() ); + + if ( $this->including() ) { + $html = $pager->getBody(); + } else { + $form = $pager->getForm(); + $body = $pager->getBody(); + $nav = $pager->getNavigationBar(); + $html = "$form<br />\n$body<br />\n$nav"; + } + $wgOut->addHTML( $html ); + } } /** @@ -39,46 +61,62 @@ class ImageListPager extends TablePager { var $mFieldNames = null; var $mQueryConds = array(); var $mUserName = null; - - function __construct( $par = null ) { + var $mSearch = ''; + var $mIncluding = false; + + function __construct( $userName = null, $search = '', $including = false ) { global $wgRequest, $wgMiserMode; - if ( $wgRequest->getText( 'sort', 'img_date' ) == 'img_date' ) { - $this->mDefaultDirection = true; - } else { - $this->mDefaultDirection = false; - } - - $userName = $wgRequest->getText( 'user', $par ); + + $this->mIncluding = $including; + if ( $userName ) { $nt = Title::newFromText( $userName, NS_USER ); if ( !is_null( $nt ) ) { $this->mUserName = $nt->getText(); $this->mQueryConds['img_user_text'] = $this->mUserName; } - } - - $search = $wgRequest->getText( 'ilsearch' ); + } + if ( $search != '' && !$wgMiserMode ) { - $nt = Title::newFromURL( $search ); + $this->mSearch = $search; + $nt = Title::newFromURL( $this->mSearch ); if ( $nt ) { $dbr = wfGetDB( DB_SLAVE ); - $this->mQueryConds[] = 'LOWER(img_name)' . $dbr->buildLike( $dbr->anyString(), - strtolower( $nt->getDBkey() ), $dbr->anyString() ); + $this->mQueryConds[] = 'LOWER(img_name)' . + $dbr->buildLike( $dbr->anyString(), + strtolower( $nt->getDBkey() ), $dbr->anyString() ); } } + if ( !$including ) { + if ( $wgRequest->getText( 'sort', 'img_date' ) == 'img_date' ) { + $this->mDefaultDirection = true; + } else { + $this->mDefaultDirection = false; + } + } else { + $this->mDefaultDirection = true; + } + parent::__construct(); } + function getTitle() { + return SpecialPage::getTitleFor( 'Listfiles' ); + } + + /** + * @return Array + */ function getFieldNames() { if ( !$this->mFieldNames ) { global $wgMiserMode; $this->mFieldNames = array( - 'thumb' => wfMsg( 'listfiles_thumb' ), 'img_timestamp' => wfMsg( 'listfiles_date' ), 'img_name' => wfMsg( 'listfiles_name' ), - 'img_user_text' => wfMsg( 'listfiles_user' ), + 'thumb' => wfMsg( 'listfiles_thumb' ), 'img_size' => wfMsg( 'listfiles_size' ), + 'img_user_text' => wfMsg( 'listfiles_user' ), 'img_description' => wfMsg( 'listfiles_description' ), ); if( !$wgMiserMode ) { @@ -89,6 +127,9 @@ class ImageListPager extends TablePager { } function isFieldSortable( $field ) { + 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 @@ -101,7 +142,7 @@ class ImageListPager extends TablePager { $tables = array( 'image' ); $fields = array_keys( $this->getFieldNames() ); $fields[] = 'img_user'; - $fields[array_search('thumb', $fields)] = 'img_name as thumb'; + $fields[array_search('thumb', $fields)] = 'img_name AS thumb'; $options = $join_conds = array(); # Depends on $wgMiserMode @@ -111,7 +152,7 @@ class ImageListPager extends TablePager { # Need to rewrite this one foreach ( $fields as &$field ) { if ( $field == 'count' ) { - $field = 'COUNT(oi_archive_name) as count'; + $field = 'COUNT(oi_archive_name) AS count'; } } unset( $field ); @@ -120,7 +161,8 @@ class ImageListPager extends TablePager { if( $dbr->implicitGroupby() ) { $options = array( 'GROUP BY' => 'img_name' ); } else { - $columnlist = implode( ',', preg_grep( '/^img/', array_keys( $this->getFieldNames() ) ) ); + $columnlist = implode( ',', + preg_grep( '/^img/', array_keys( $this->getFieldNames() ) ) ); $options = array( 'GROUP BY' => "img_user, $columnlist" ); } $join_conds = array( 'oldimage' => array( 'LEFT JOIN', 'oi_name = img_name' ) ); @@ -159,7 +201,7 @@ class ImageListPager extends TablePager { switch ( $field ) { case 'thumb': $file = wfLocalFile( $value ); - $thumb = $file->transform( array( 'width' => 180 ) ); + $thumb = $file->transform( array( 'width' => 180, 'height' => 360 ) ); return $thumb->toHtml( array( 'desc-link' => true ) ); case 'img_timestamp': return htmlspecialchars( $wgLang->timeanddate( $value, true ) ); @@ -167,12 +209,18 @@ class ImageListPager extends TablePager { static $imgfile = null; if ( $imgfile === null ) $imgfile = wfMsg( 'imgfile' ); - $filePage = Title::makeTitle( NS_FILE, $value ); - $link = $this->getSkin()->linkKnown( $filePage, htmlspecialchars( $filePage->getText() ) ); - $image = wfLocalFile( $value ); - $url = $image->getURL(); - $download = Xml::element('a', array( 'href' => $url ), $imgfile ); - return "$link ($download)"; + // Weird files can maybe exist? Bug 22227 + $filePage = Title::makeTitleSafe( NS_FILE, $value ); + if( $filePage ) { + $link = $this->getSkin()->linkKnown( $filePage, htmlspecialchars( $filePage->getText() ) ); + $download = Xml::element( 'a', + array( 'href' => wfLocalFile( $filePage )->getURL() ), + $imgfile + ); + return "$link ($download)"; + } else { + return htmlspecialchars( $value ); + } case 'img_user_text': if ( $this->mCurrentRow->img_user ) { $link = $this->getSkin()->link( @@ -188,34 +236,34 @@ class ImageListPager extends TablePager { case 'img_description': return $this->getSkin()->commentBlock( $value ); case 'count': - return intval($value)+1; + return intval( $value ) + 1; } } function getForm() { - global $wgRequest, $wgScript, $wgMiserMode; - $search = $wgRequest->getText( 'ilsearch' ); + global $wgScript, $wgMiserMode; $inputForm = array(); $inputForm['table_pager_limit_label'] = $this->getLimitSelect(); if ( !$wgMiserMode ) { - $inputForm['listfiles_search_for'] = Html::input( 'ilsearch', $search, 'text', array( - 'size' => '40', - 'maxlength' => '255', - 'id' => 'mw-ilsearch', + $inputForm['listfiles_search_for'] = Html::input( 'ilsearch', $this->mSearch, 'text', + array( + 'size' => '40', + 'maxlength' => '255', + 'id' => 'mw-ilsearch', ) ); } $inputForm['username'] = Html::input( 'user', $this->mUserName, 'text', array( - 'size' => '40', - 'maxlength' => '255', - 'id' => 'mw-listfiles-user', + 'size' => '40', + 'maxlength' => '255', + 'id' => 'mw-listfiles-user', ) ); - $s = Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listfiles-form' ) ) . + return Html::openElement( 'form', + array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listfiles-form' ) ) . Xml::fieldset( wfMsg( 'listfiles' ) ) . Xml::buildForm( $inputForm, 'table_pager_limit_submit' ) . $this->getHiddenFields( array( 'limit', 'ilsearch', 'user' ) ) . Html::closeElement( 'fieldset' ) . Html::closeElement( 'form' ) . "\n"; - return $s; } function getTableClass() { @@ -229,7 +277,7 @@ class ImageListPager extends TablePager { function getSortHeaderClass() { return 'listfiles_sort ' . parent::getSortHeaderClass(); } - + function getPagingQueries() { $queries = parent::getPagingQueries(); if ( !is_null( $this->mUserName ) ) { @@ -243,9 +291,7 @@ class ImageListPager extends TablePager { function getDefaultQuery() { $queries = parent::getDefaultQuery(); - if ( !isset( $queries['user'] ) - && !is_null( $this->mUserName ) ) - { + 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 910ffd08..07e08e77 100644 --- a/includes/specials/SpecialListgrouprights.php +++ b/includes/specials/SpecialListgrouprights.php @@ -30,29 +30,27 @@ */ class SpecialListGroupRights extends SpecialPage { - var $skin; - /** * Constructor */ function __construct() { - global $wgUser; parent::__construct( 'Listgrouprights' ); - $this->skin = $wgUser->getSkin(); } /** * Show the special page */ public function execute( $par ) { - global $wgOut, $wgImplicitGroups; + global $wgImplicitGroups; global $wgGroupPermissions, $wgRevokePermissions, $wgAddGroups, $wgRemoveGroups; global $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; + $out = $this->getOutput(); $this->setHeaders(); $this->outputHeader(); + $out->addModuleStyles( 'mediawiki.special' ); - $wgOut->addHTML( + $out->addHTML( Xml::openElement( 'table', array( 'class' => 'wikitable mw-listgrouprights-table' ) ) . '<tr>' . Xml::element( 'th', null, wfMsg( 'listgrouprights-group' ) ) . @@ -60,7 +58,7 @@ class SpecialListGroupRights extends SpecialPage { '</tr>' ); - $allGroups = array_unique( array_merge( + $allGroups = array_unique( array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ), array_keys( $wgAddGroups ), @@ -69,34 +67,28 @@ class SpecialListGroupRights extends SpecialPage { array_keys( $wgGroupsRemoveFromSelf ) ) ); asort( $allGroups ); - + foreach ( $allGroups as $group ) { - $permissions = isset( $wgGroupPermissions[$group] ) - ? $wgGroupPermissions[$group] + $permissions = isset( $wgGroupPermissions[$group] ) + ? $wgGroupPermissions[$group] : array(); $groupname = ( $group == '*' ) // Replace * with a more descriptive groupname - ? 'all' - : $group; + ? 'all' + : $group; - $msg = wfMsg( 'group-' . $groupname ); - if ( wfEmptyMsg( 'group-' . $groupname, $msg ) || $msg == '' ) { - $groupnameLocalized = $groupname; - } else { - $groupnameLocalized = $msg; - } + $msg = wfMessage( 'group-' . $groupname ); + $groupnameLocalized = !$msg->isBlank() ? $msg->text() : $groupname; - $msg = wfMsgForContent( 'grouppage-' . $groupname ); - if ( wfEmptyMsg( 'grouppage-' . $groupname, $msg ) || $msg == '' ) { - $grouppageLocalized = MWNamespace::getCanonicalName( NS_PROJECT ) . ':' . $groupname; - } else { - $grouppageLocalized = $msg; - } + $msg = wfMessage( 'grouppage-' . $groupname )->inContentLanguage(); + $grouppageLocalized = !$msg->isBlank() ? + $msg->text() : + MWNamespace::getCanonicalName( NS_PROJECT ) . ':' . $groupname; if( $group == '*' ) { // Do not make a link for the generic * group $grouppage = htmlspecialchars( $groupnameLocalized ); } else { - $grouppage = $this->skin->link( + $grouppage = Linker::link( Title::newFromText( $grouppageLocalized ), htmlspecialchars( $groupnameLocalized ) ); @@ -104,7 +96,7 @@ class SpecialListGroupRights extends SpecialPage { if ( $group === 'user' ) { // Link to Special:listusers for implicit group 'user' - $grouplink = '<br />' . $this->skin->link( + $grouplink = '<br />' . Linker::link( SpecialPage::getTitleFor( 'Listusers' ), wfMsgHtml( 'listgrouprights-members' ), array(), @@ -112,7 +104,7 @@ class SpecialListGroupRights extends SpecialPage { array( 'known', 'noclasses' ) ); } elseif ( !in_array( $group, $wgImplicitGroups ) ) { - $grouplink = '<br />' . $this->skin->link( + $grouplink = '<br />' . Linker::link( SpecialPage::getTitleFor( 'Listusers' ), wfMsgHtml( 'listgrouprights-members' ), array(), @@ -131,7 +123,7 @@ class SpecialListGroupRights extends SpecialPage { $removegroupsSelf = isset( $wgGroupsRemoveFromSelf[$group] ) ? $wgGroupsRemoveFromSelf[$group] : array(); $id = $group == '*' ? false : Sanitizer::escapeId( $group ); - $wgOut->addHTML( Html::rawElement( 'tr', array( 'id' => $id ), + $out->addHTML( Html::rawElement( 'tr', array( 'id' => $id ), " <td>$grouppage$grouplink</td> <td>" . @@ -140,10 +132,10 @@ class SpecialListGroupRights extends SpecialPage { ' ) ); } - $wgOut->addHTML( + $out->addHTML( Xml::closeElement( 'table' ) . "\n<br /><hr />\n" ); - $wgOut->wrapWikiMsg( "<div class=\"mw-listgrouprights-key\">\n$1\n</div>", 'listgrouprights-key' ); + $out->wrapWikiMsg( "<div class=\"mw-listgrouprights-key\">\n$1\n</div>", 'listgrouprights-key' ); } /** @@ -158,7 +150,7 @@ class SpecialListGroupRights extends SpecialPage { * @return string List of all granted permissions, separated by comma separator */ private static function formatPermissions( $permissions, $revoke, $add, $remove, $addSelf, $removeSelf ) { - global $wgLang; + global $wgLang; $r = array(); foreach( $permissions as $permission => $granted ) { @@ -183,25 +175,25 @@ class SpecialListGroupRights extends SpecialPage { sort( $r ); if( $add === true ){ $r[] = wfMsgExt( 'listgrouprights-addgroup-all', array( 'escape' ) ); - } else if( is_array( $add ) && count( $add ) ) { + } elseif( is_array( $add ) && count( $add ) ) { $add = array_values( array_unique( $add ) ); $r[] = wfMsgExt( 'listgrouprights-addgroup', array( 'parseinline' ), $wgLang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $add ) ), count( $add ) ); } if( $remove === true ){ $r[] = wfMsgExt( 'listgrouprights-removegroup-all', array( 'escape' ) ); - } else if( is_array( $remove ) && count( $remove ) ) { + } elseif( is_array( $remove ) && count( $remove ) ) { $remove = array_values( array_unique( $remove ) ); $r[] = wfMsgExt( 'listgrouprights-removegroup', array( 'parseinline' ), $wgLang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $remove ) ), count( $remove ) ); } if( $addSelf === true ){ $r[] = wfMsgExt( 'listgrouprights-addgroup-self-all', array( 'escape' ) ); - } else if( is_array( $addSelf ) && count( $addSelf ) ) { + } elseif( is_array( $addSelf ) && count( $addSelf ) ) { $addSelf = array_values( array_unique( $addSelf ) ); $r[] = wfMsgExt( 'listgrouprights-addgroup-self', array( 'parseinline' ), $wgLang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $addSelf ) ), count( $addSelf ) ); } if( $removeSelf === true ){ $r[] = wfMsgExt( 'listgrouprights-removegroup-self-all', array( 'escape' ) ); - } else if( is_array( $removeSelf ) && count( $removeSelf ) ) { + } elseif( is_array( $removeSelf ) && count( $removeSelf ) ) { $removeSelf = array_values( array_unique( $removeSelf ) ); $r[] = wfMsgExt( 'listgrouprights-removegroup-self', array( 'parseinline' ), $wgLang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $removeSelf ) ), count( $removeSelf ) ); } diff --git a/includes/specials/SpecialListredirects.php b/includes/specials/SpecialListredirects.php index 315047da..acf5fbd9 100644 --- a/includes/specials/SpecialListredirects.php +++ b/includes/specials/SpecialListredirects.php @@ -30,22 +30,72 @@ */ class ListredirectsPage extends QueryPage { - function getName() { return( 'Listredirects' ); } - function isExpensive() { return( true ); } - function isSyndicated() { return( false ); } - function sortDescending() { return( false ); } + function __construct( $name = 'Listredirects' ) { + parent::__construct( $name ); + } + + function isExpensive() { return true; } + function isSyndicated() { return false; } + function sortDescending() { return false; } - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - $page = $dbr->tableName( 'page' ); - $sql = "SELECT 'Listredirects' AS type, page_title AS title, page_namespace AS namespace, - 0 AS value FROM $page WHERE page_is_redirect = 1"; - return( $sql ); + function getQueryInfo() { + return array( + 'tables' => array( 'p1' => 'page', 'redirect', 'p2' => 'page' ), + 'fields' => array( 'p1.page_namespace AS namespace', + 'p1.page_title AS title', + 'rd_namespace', + 'rd_title', + 'rd_fragment', + 'rd_interwiki', + 'p2.page_id AS redirid' ), + 'conds' => array( 'p1.page_is_redirect' => 1 ), + 'join_conds' => array( 'redirect' => array( + 'LEFT JOIN', 'rd_from=p1.page_id' ), + 'p2' => array( 'LEFT JOIN', array( + 'p2.page_namespace=rd_namespace', + 'p2.page_title=rd_title' ) ) ) + ); } - function formatResult( $skin, $result ) { - global $wgContLang; + function getOrderFields() { + return array ( 'p1.page_namespace', 'p1.page_title' ); + } + /** + * Cache page existence for performance + * + * @param $db DatabaseBase + * @param $res ResultWrapper + */ + 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 there are no rows we get an error seeking. + $db->dataSeek( $res, 0 ); + } + } + + protected function getRedirectTarget( $row ) { + if ( isset( $row->rd_title ) ) { + return Title::makeTitle( $row->rd_namespace, + $row->rd_title, $row->rd_fragment, + $row->rd_interwiki + ); + } else { + $title = Title::makeTitle( $row->namespace, $row->title ); + $article = new Article( $title ); + return $article->getRedirectTarget(); + } + } + + function formatResult( $skin, $result ) { # Make a link to the redirect itself $rd_title = Title::makeTitle( $result->namespace, $result->title ); $rd_link = $skin->link( @@ -56,25 +106,15 @@ class ListredirectsPage extends QueryPage { ); # Find out where the redirect leads - $revision = Revision::newFromTitle( $rd_title ); - if( $revision ) { + $target = $this->getRedirectTarget( $result ); + if( $target ) { + global $wgLang; # Make a link to the destination page - $target = Title::newFromRedirect( $revision->getText() ); - if( $target ) { - $arr = $wgContLang->getArrow() . $wgContLang->getDirMark(); - $targetLink = $skin->link( $target ); - return "$rd_link $arr $targetLink"; - } else { - return "<del>$rd_link</del>"; - } + $arr = $wgLang->getArrow() . $wgLang->getDirMark(); + $targetLink = $skin->link( $target ); + return "$rd_link $arr $targetLink"; } else { return "<del>$rd_link</del>"; } } } - -function wfSpecialListredirects() { - list( $limit, $offset ) = wfCheckLimits(); - $lrp = new ListredirectsPage(); - $lrp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialListusers.php b/includes/specials/SpecialListusers.php index abc0363a..0531444a 100644 --- a/includes/specials/SpecialListusers.php +++ b/includes/specials/SpecialListusers.php @@ -41,7 +41,7 @@ class UsersPager extends AlphabeticPager { if ( $parms[0] != '' && ( in_array( $par, User::getAllGroups() ) || in_array( $par, $symsForAll ) ) ) { $this->requestedGroup = $par; $un = $wgRequest->getText( 'username' ); - } else if ( count( $parms ) == 2 ) { + } elseif ( count( $parms ) == 2 ) { $this->requestedGroup = $parms[0]; $un = $parms[1]; } else { @@ -64,6 +64,9 @@ class UsersPager extends AlphabeticPager { parent::__construct(); } + function getTitle() { + return SpecialPage::getTitleFor( 'Listusers' ); + } function getIndexField() { return $this->creationSort ? 'user_id' : 'user_name'; @@ -74,13 +77,16 @@ class UsersPager extends AlphabeticPager { $dbr = wfGetDB( DB_SLAVE ); $conds = array(); // Don't show hidden names - if( !$wgUser->isAllowed('hideuser') ) + if( !$wgUser->isAllowed('hideuser') ) { $conds[] = 'ipb_deleted IS NULL'; + } + + $options = array(); + if( $this->requestedGroup != '' ) { $conds['ug_group'] = $this->requestedGroup; - $useIndex = ''; } else { - $useIndex = $dbr->useIndexClause( $this->creationSort ? 'PRIMARY' : 'user_name'); + //$options['USE INDEX'] = $this->creationSort ? 'PRIMARY' : 'user_name'; } if( $this->requestedUser != '' ) { # Sorted either by account creation or name @@ -94,11 +100,10 @@ class UsersPager extends AlphabeticPager { $conds[] = 'user_editcount > 0'; } - list ($user,$user_groups,$ipblocks) = $dbr->tableNamesN('user','user_groups','ipblocks'); + $options['GROUP BY'] = $this->creationSort ? 'user_id' : 'user_name'; $query = array( - 'tables' => " $user $useIndex LEFT JOIN $user_groups ON user_id=ug_user - LEFT JOIN $ipblocks ON user_id=ipb_user AND ipb_deleted=1 AND ipb_auto=0 ", + 'tables' => array( 'user', 'user_groups', 'ipblocks'), 'fields' => array( $this->creationSort ? 'MAX(user_name) AS user_name' : 'user_name', $this->creationSort ? 'user_id' : 'MAX(user_id) AS user_id', @@ -108,7 +113,11 @@ class UsersPager extends AlphabeticPager { 'MIN(user_registration) AS creation', 'MAX(ipb_deleted) AS ipb_deleted' // block/hide status ), - 'options' => array('GROUP BY' => $this->creationSort ? 'user_id' : 'user_name'), + 'options' => $options, + 'join_conds' => array( + 'user_groups' => array( 'LEFT JOIN', 'user_id=ug_user' ), + 'ipblocks' => array( 'LEFT JOIN', 'user_id=ipb_user AND ipb_deleted=1 AND ipb_auto=0' ), + ), 'conds' => $conds ); @@ -123,7 +132,7 @@ class UsersPager extends AlphabeticPager { return ''; $userPage = Title::makeTitle( NS_USER, $row->user_name ); - $name = $this->getSkin()->link( $userPage, htmlspecialchars( $userPage->getText() ) ); + $name = Linker::link( $userPage, htmlspecialchars( $userPage->getText() ) ); $groups_list = self::getGroups( $row->user_id ); if( count( $groups_list ) > 0 ) { @@ -247,7 +256,7 @@ class UsersPager extends AlphabeticPager { */ protected static function getGroups( $uid ) { $user = User::newFromId( $uid ); - $groups = array_diff( $user->getEffectiveGroups(), $user->getImplicitGroups() ); + $groups = array_diff( $user->getEffectiveGroups(), User::getImplicitGroups() ); return $groups; } @@ -266,25 +275,42 @@ class UsersPager extends AlphabeticPager { } /** - * constructor - * $par string (optional) A group to list users from + * @ingroup SpecialPage */ -function wfSpecialListusers( $par = null ) { - global $wgOut; - - $up = new UsersPager($par); - - # getBody() first to check, if empty - $usersbody = $up->getBody(); - $s = Xml::openElement( 'div', array('class' => 'mw-spcontent') ); - $s .= $up->getPageHeader(); - if( $usersbody ) { - $s .= $up->getNavigationBar(); - $s .= '<ul>' . $usersbody . '</ul>'; - $s .= $up->getNavigationBar() ; - } else { - $s .= '<p>' . wfMsgHTML('listusers-noresult') . '</p>'; - }; - $s .= Xml::closeElement( 'div' ); - $wgOut->addHTML( $s ); +class SpecialListUsers extends SpecialPage { + + /** + * Constructor + */ + public function __construct() { + parent::__construct( 'Listusers' ); + } + + /** + * Show the special page + * + * @param $par string (optional) A group to list users from + */ + public function execute( $par ) { + global $wgOut; + + $this->setHeaders(); + $this->outputHeader(); + + $up = new UsersPager( $par ); + + # getBody() first to check, if empty + $usersbody = $up->getBody(); + + $s = $up->getPageHeader(); + if( $usersbody ) { + $s .= $up->getNavigationBar(); + $s .= Html::rawElement( 'ul', array(), $usersbody ); + $s .= $up->getNavigationBar(); + } else { + $s .= wfMessage( 'listusers-noresult' )->parseAsBlock(); + } + + $wgOut->addHTML( $s ); + } } diff --git a/includes/specials/SpecialLockdb.php b/includes/specials/SpecialLockdb.php index aad3cea4..5c861b31 100644 --- a/includes/specials/SpecialLockdb.php +++ b/includes/specials/SpecialLockdb.php @@ -34,12 +34,13 @@ class SpecialLockdb extends SpecialPage { } public function execute( $par ) { - global $wgUser, $wgOut, $wgRequest; + global $wgUser, $wgRequest; $this->setHeaders(); - if( !$wgUser->isAllowed( 'siteadmin' ) ) { - $wgOut->permissionRequired( 'siteadmin' ); + # Permission check + if( !$this->userCanExecute( $wgUser ) ) { + $this->displayRestrictionError(); return; } @@ -57,7 +58,7 @@ class SpecialLockdb extends SpecialPage { if ( $action == 'success' ) { $this->showSuccess(); - } else if ( $action == 'submit' && $wgRequest->wasPosted() && + } elseif ( $action == 'submit' && $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { $this->doSubmit(); } else { @@ -109,7 +110,10 @@ class SpecialLockdb extends SpecialPage { $this->showForm( wfMsg( 'locknoconfirm' ) ); return; } - $fp = @fopen( $wgReadOnlyFile, 'w' ); + + wfSuppressWarnings(); + $fp = fopen( $wgReadOnlyFile, 'w' ); + wfRestoreWarnings(); if ( false === $fp ) { # This used to show a file not found error, but the likeliest reason for fopen() @@ -119,8 +123,14 @@ class SpecialLockdb extends SpecialPage { return; } fwrite( $fp, $this->reason ); - fwrite( $fp, "\n<p>(by " . $wgUser->getName() . " at " . - $wgContLang->timeanddate( wfTimestampNow() ) . ")</p>\n" ); + $timestamp = wfTimestampNow(); + fwrite( $fp, "\n<p>" . wfMsgExt( + 'lockedbyandtime', + array( 'content', 'parsemag' ), + $wgUser->getName(), + $wgContLang->date( $timestamp ), + $wgContLang->time( $timestamp ) + ) . "</p>\n" ); fclose( $fp ); $wgOut->redirect( $this->getTitle()->getFullURL( 'action=success' ) ); diff --git a/includes/specials/SpecialLog.php b/includes/specials/SpecialLog.php index a2af8de5..d8f6d8cf 100644 --- a/includes/specials/SpecialLog.php +++ b/includes/specials/SpecialLog.php @@ -95,10 +95,10 @@ class SpecialLog extends SpecialPage { } private function show( FormOptions $opts, array $extraConds ) { - global $wgOut, $wgUser; + global $wgOut; # Create a LogPager item to get the results and a LogEventsList item to format them... - $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, 0 ); + $loglist = new LogEventsList( $this->getSkin(), $wgOut, 0 ); $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' ) ); @@ -106,6 +106,11 @@ class SpecialLog extends SpecialPage { # Set title and add header $loglist->showHeader( $pager->getType() ); + # Set relevant user + if ( $pager->getUser() ) { + $this->getSkin()->setRelevantUser( User::newFromName( $pager->getUser() ) ); + } + # Show form options $loglist->showOptions( $pager->getType(), $pager->getUser(), $pager->getPage(), $pager->getPattern(), $pager->getYear(), $pager->getMonth(), $pager->getFilterParams(), $opts->getValue( 'tagfilter' ) ); diff --git a/includes/specials/SpecialLonelypages.php b/includes/specials/SpecialLonelypages.php index 0788037f..0800e43c 100644 --- a/includes/specials/SpecialLonelypages.php +++ b/includes/specials/SpecialLonelypages.php @@ -29,9 +29,10 @@ */ class LonelyPagesPage extends PageQueryPage { - function getName() { - return "Lonelypages"; + function __construct( $name = 'Lonelypages' ) { + parent::__construct( $name ); } + function getPageHeader() { return wfMsgExt( 'lonelypagestext', array( 'parse' ) ); } @@ -45,35 +46,36 @@ class LonelyPagesPage extends PageQueryPage { } function isSyndicated() { return false; } - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $page, $pagelinks, $templatelinks ) = $dbr->tableNamesN( 'page', 'pagelinks', 'templatelinks' ); - - return - "SELECT 'Lonelypages' AS type, - page_namespace AS namespace, - page_title AS title, - page_title AS value - FROM $page - LEFT JOIN $pagelinks - ON page_namespace=pl_namespace AND page_title=pl_title - LEFT JOIN $templatelinks - ON page_namespace=tl_namespace AND page_title=tl_title - WHERE pl_namespace IS NULL - AND page_namespace=".NS_MAIN." - AND page_is_redirect=0 - AND tl_namespace IS NULL"; - + function getQueryInfo() { + return array ( + 'tables' => array ( 'page', 'pagelinks', + 'templatelinks' ), + 'fields' => array ( 'page_namespace AS namespace', + 'page_title AS title', + 'page_title AS value' ), + 'conds' => array ( 'pl_namespace IS NULL', + 'page_namespace' => MWNamespace::getContentNamespaces(), + 'page_is_redirect' => 0, + 'tl_namespace IS NULL' ), + 'join_conds' => array ( + 'pagelinks' => array ( + 'LEFT JOIN', array ( + 'pl_namespace = page_namespace', + 'pl_title = page_title' ) ), + 'templatelinks' => array ( + 'LEFT JOIN', array ( + 'tl_namespace = page_namespace', + 'tl_title = page_title' ) ) ) + ); } -} -/** - * Constructor - */ -function wfSpecialLonelypages() { - list( $limit, $offset ) = wfCheckLimits(); - - $lpp = new LonelyPagesPage(); - - return $lpp->doQuery( $offset, $limit ); + function getOrderFields() { + // For some crazy reason ordering by a constant + // causes a filesort in MySQL 5 + if( count( MWNamespace::getContentNamespaces() ) > 1 ) { + return array( 'page_namespace', 'page_title' ); + } else { + return array( 'page_title' ); + } + } } diff --git a/includes/specials/SpecialLongpages.php b/includes/specials/SpecialLongpages.php index cd0f3090..dd60e37d 100644 --- a/includes/specials/SpecialLongpages.php +++ b/includes/specials/SpecialLongpages.php @@ -27,22 +27,11 @@ */ class LongPagesPage extends ShortPagesPage { - function getName() { - return "Longpages"; + function __construct( $name = 'Longpages' ) { + parent::__construct( $name ); } function sortDescending() { return true; } } - -/** - * constructor - */ -function wfSpecialLongpages() { - list( $limit, $offset ) = wfCheckLimits(); - - $lpp = new LongPagesPage(); - - $lpp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialMIMEsearch.php b/includes/specials/SpecialMIMEsearch.php index 79683a35..aefe7bf5 100644 --- a/includes/specials/SpecialMIMEsearch.php +++ b/includes/specials/SpecialMIMEsearch.php @@ -28,50 +28,62 @@ * @ingroup SpecialPage */ class MIMEsearchPage extends QueryPage { - var $major, $minor; + protected $major, $minor; - function __construct( $major, $minor ) { - $this->major = $major; - $this->minor = $minor; + function __construct( $name = 'MIMEsearch' ) { + parent::__construct( $name ); } - function getName() { return 'MIMEsearch'; } - - /** - * Due to this page relying upon extra fields being passed in the SELECT it - * will fail if it's set as expensive and misermode is on - */ function isExpensive() { return true; } function isSyndicated() { return false; } + function isCacheable() { return false; } function linkParameters() { - $arr = array( $this->major, $this->minor ); - $mime = implode( '/', $arr ); - return array( 'mime' => $mime ); + return array( 'mime' => "{$this->major}/{$this->minor}" ); } - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - $image = $dbr->tableName( 'image' ); - $major = $dbr->addQuotes( $this->major ); - $minor = $dbr->addQuotes( $this->minor ); + public function getQueryInfo() { + return array( + 'tables' => array( 'image' ), + 'fields' => array( "'" . NS_FILE . "' AS namespace", + 'img_name AS title', + 'img_major_mime AS value', + 'img_size', + 'img_width', + 'img_height', + 'img_user_text', + 'img_timestamp' ), + 'conds' => array( 'img_major_mime' => $this->major, + 'img_minor_mime' => $this->minor ) + ); + } - return - "SELECT 'MIMEsearch' AS type, - " . NS_FILE . " AS namespace, - img_name AS title, - img_major_mime AS value, + function execute( $par ) { + global $wgRequest, $wgOut; + $mime = $par ? $par : $wgRequest->getText( 'mime' ); + + $this->setHeaders(); + $this->outputHeader(); + $wgOut->addHTML( + Xml::openElement( 'form', array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => SpecialPage::getTitleFor( 'MIMEsearch' )->getLocalUrl() ) ) . + Xml::openElement( 'fieldset' ) . + Html::hidden( 'title', SpecialPage::getTitleFor( 'MIMEsearch' )->getPrefixedText() ) . + Xml::element( 'legend', null, wfMsg( 'mimesearch' ) ) . + Xml::inputLabel( wfMsg( 'mimetype' ), 'mime', 'mime', 20, $mime ) . ' ' . + Xml::submitButton( wfMsg( 'ilsubmit' ) ) . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) + ); - img_size, - img_width, - img_height, - img_user_text, - img_timestamp - FROM $image - WHERE img_major_mime = $major AND img_minor_mime = $minor - "; + list( $this->major, $this->minor ) = File::splitMime( $mime ); + if ( $this->major == '' || $this->minor == '' || $this->minor == 'unknown' || + !self::isValidType( $this->major ) ) { + return; + } + parent::execute( $par ); } + function formatResult( $skin, $result ) { global $wgContLang, $wgLang; @@ -83,7 +95,7 @@ class MIMEsearchPage extends QueryPage { ); $download = $skin->makeMediaLinkObj( $nt, wfMsgHtml( 'download' ) ); - $bytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape'), + $bytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ), $wgLang->formatNum( $result->img_size ) ); $dimensions = htmlspecialchars( wfMsg( 'widthheight', $wgLang->formatNum( $result->img_width ), @@ -94,63 +106,24 @@ class MIMEsearchPage extends QueryPage { return "($download) $plink . . $dimensions . . $bytes . . $user . . $time"; } -} - -/** - * Output the HTML search form, and constructs the MIMEsearchPage object. - */ -function wfSpecialMIMEsearch( $par = null ) { - global $wgRequest, $wgOut; - - $mime = isset( $par ) ? $par : $wgRequest->getText( 'mime' ); - $wgOut->addHTML( - Xml::openElement( 'form', array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => SpecialPage::getTitleFor( 'MIMEsearch' )->getLocalUrl() ) ) . - Xml::openElement( 'fieldset' ) . - Html::hidden( 'title', SpecialPage::getTitleFor( 'MIMEsearch' )->getPrefixedText() ) . - Xml::element( 'legend', null, wfMsg( 'mimesearch' ) ) . - Xml::inputLabel( wfMsg( 'mimetype' ), 'mime', 'mime', 20, $mime ) . ' ' . - Xml::submitButton( wfMsg( 'ilsubmit' ) ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) - ); - - list( $major, $minor ) = wfSpecialMIMEsearchParse( $mime ); - if ( $major == '' or $minor == '' or !wfSpecialMIMEsearchValidType( $major ) ) - return; - $wpp = new MIMEsearchPage( $major, $minor ); - - list( $limit, $offset ) = wfCheckLimits(); - $wpp->doQuery( $offset, $limit ); -} - -function wfSpecialMIMEsearchParse( $str ) { - // searched for an invalid MIME type. - if( strpos( $str, '/' ) === false) { - return array ('', ''); + /** + * @param $type string + * @return bool + */ + protected static function isValidType( $type ) { + // From maintenance/tables.sql => img_major_mime + $types = array( + 'unknown', + 'application', + 'audio', + 'image', + 'text', + 'video', + 'message', + 'model', + 'multipart' + ); + return in_array( $type, $types ); } - - list( $major, $minor ) = explode( '/', $str, 2 ); - - return array( - ltrim( $major, ' ' ), - rtrim( $minor, ' ' ) - ); -} - -function wfSpecialMIMEsearchValidType( $type ) { - // From maintenance/tables.sql => img_major_mime - $types = array( - 'unknown', - 'application', - 'audio', - 'image', - 'text', - 'video', - 'message', - 'model', - 'multipart' - ); - - return in_array( $type, $types ); } diff --git a/includes/specials/SpecialMergeHistory.php b/includes/specials/SpecialMergeHistory.php index 43b4ef6a..88e90ee5 100644 --- a/includes/specials/SpecialMergeHistory.php +++ b/includes/specials/SpecialMergeHistory.php @@ -29,14 +29,23 @@ */ class SpecialMergeHistory extends SpecialPage { var $mAction, $mTarget, $mDest, $mTimestamp, $mTargetID, $mDestID, $mComment; + + /** + * @var Title + */ var $mTargetObj, $mDestObj; public function __construct() { parent::__construct( 'MergeHistory', 'mergehistory' ); } + /** + * @param $request WebRequest + * @return void + */ private function loadRequestParams( $request ) { global $wgUser; + $this->mAction = $request->getVal( 'action' ); $this->mTarget = $request->getVal( 'target' ); $this->mDest = $request->getVal( 'dest' ); @@ -45,7 +54,7 @@ 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' ); @@ -73,7 +82,7 @@ class SpecialMergeHistory extends SpecialPage { } } - function execute( $par ) { + public function execute( $par ) { global $wgOut, $wgRequest, $wgUser; if ( wfReadOnly() ) { @@ -91,7 +100,7 @@ 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 ) { return $this->merge(); } @@ -109,14 +118,14 @@ class SpecialMergeHistory extends SpecialPage { ); } - if ( !$this->mDestObj instanceof Title) { + if ( !$this->mDestObj instanceof Title ) { $errors[] = wfMsgExt( 'mergehistory-invalid-destination', array( 'parse' ) ); } elseif( !$this->mDestObj->exists() ) { $errors[] = wfMsgExt( 'mergehistory-no-destination', array( 'parse' ), wfEscapeWikiText( $this->mDestObj->getPrefixedText() ) ); } - + if ( $this->mTargetObj && $this->mDestObj && $this->mTargetObj->equals( $this->mDestObj ) ) { $errors[] = wfMsgExt( 'mergehistory-same-destination', array( 'parse' ) ); } @@ -146,37 +155,47 @@ class SpecialMergeHistory extends SpecialPage { Html::hidden( 'submitted', '1' ) . Html::hidden( 'mergepoint', $this->mTimestamp ) . Xml::openElement( 'table' ) . - "<tr> - <td>".Xml::label( wfMsg( 'mergehistory-from' ), 'target' )."</td> - <td>".Xml::input( 'target', 30, $this->mTarget, array('id'=>'target') )."</td> + '<tr> + <td>' . Xml::label( wfMsg( 'mergehistory-from' ), 'target' ) . '</td> + <td>' . Xml::input( 'target', 30, $this->mTarget, array( 'id' => 'target' ) ) . '</td> </tr><tr> - <td>".Xml::label( wfMsg( 'mergehistory-into' ), 'dest' )."</td> - <td>".Xml::input( 'dest', 30, $this->mDest, array('id'=>'dest') )."</td> - </tr><tr><td>" . + <td>' . Xml::label( wfMsg( 'mergehistory-into' ), 'dest' ) . '</td> + <td>' . Xml::input( 'dest', 30, $this->mDest, array( 'id' => 'dest' ) ) . '</td> + </tr><tr><td>' . Xml::submitButton( wfMsg( 'mergehistory-go' ) ) . - "</td></tr>" . + '</td></tr>' . Xml::closeElement( 'table' ) . '</fieldset>' . - '</form>' ); + '</form>' + ); } private function showHistory() { global $wgUser, $wgOut; - $this->sk = $wgUser->getSkin(); + $this->sk = $this->getSkin(); - $wgOut->setPagetitle( wfMsg( "mergehistory" ) ); + $wgOut->setPageTitle( wfMsg( 'mergehistory' ) ); $this->showMergeForm(); # List all stored revisions - $revisions = new MergeHistoryPager( $this, array(), $this->mTargetObj, $this->mDestObj ); + $revisions = new MergeHistoryPager( + $this, array(), $this->mTargetObj, $this->mDestObj + ); $haveRevisions = $revisions && $revisions->getNumRows() > 0; $titleObj = $this->getTitle(); $action = $titleObj->getLocalURL( array( 'action' => 'submit' ) ); # Start the form here - $top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'merge' ) ); + $top = Xml::openElement( + 'form', + array( + 'method' => 'post', + 'action' => $action, + 'id' => 'merge' + ) + ); $wgOut->addHTML( $top ); if( $haveRevisions ) { @@ -184,43 +203,46 @@ class SpecialMergeHistory extends SpecialPage { # in a nice little table $table = Xml::openElement( 'fieldset' ) . - wfMsgExt( 'mergehistory-merge', array('parseinline'), + wfMsgExt( 'mergehistory-merge', array( 'parseinline' ), $this->mTargetObj->getPrefixedText(), $this->mDestObj->getPrefixedText() ) . Xml::openElement( 'table', array( 'id' => 'mw-mergehistory-table' ) ) . - "<tr> - <td class='mw-label'>" . + '<tr> + <td class="mw-label">' . Xml::label( wfMsg( 'mergehistory-reason' ), 'wpComment' ) . - "</td> - <td class='mw-input'>" . - Xml::input( 'wpComment', 50, $this->mComment, array('id' => 'wpComment') ) . - "</td> + '</td> + <td class="mw-input">' . + Xml::input( 'wpComment', 50, $this->mComment, array( 'id' => 'wpComment' ) ) . + '</td> </tr> <tr> <td> </td> - <td class='mw-submit'>" . + <td class="mw-submit">' . Xml::submitButton( wfMsg( 'mergehistory-submit' ), array( 'name' => 'merge', 'id' => 'mw-merge-submit' ) ) . - "</td> - </tr>" . + '</td> + </tr>' . Xml::closeElement( 'table' ) . Xml::closeElement( 'fieldset' ); $wgOut->addHTML( $table ); } - $wgOut->addHTML( "<h2 id=\"mw-mergehistory\">" . wfMsgHtml( "mergehistory-list" ) . "</h2>\n" ); + $wgOut->addHTML( + '<h2 id="mw-mergehistory">' . + wfMsgHtml( 'mergehistory-list' ) . "</h2>\n" + ); if( $haveRevisions ) { $wgOut->addHTML( $revisions->getNavigationBar() ); - $wgOut->addHTML( "<ul>" ); + $wgOut->addHTML( '<ul>' ); $wgOut->addHTML( $revisions->getBody() ); - $wgOut->addHTML( "</ul>" ); + $wgOut->addHTML( '</ul>' ); $wgOut->addHTML( $revisions->getNavigationBar() ); } else { - $wgOut->addWikiMsg( "mergehistory-empty" ); + $wgOut->addWikiMsg( 'mergehistory-empty' ); } # Show relevant lines from the deletion log: - $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'merge' ) ) . "</h2>\n" ); + $wgOut->addHTML( '<h2>' . htmlspecialchars( LogPage::logName( 'merge' ) ) . "</h2>\n" ); LogEventsList::showLogExtract( $wgOut, 'merge', $this->mTargetObj->getPrefixedText() ); # When we submit, go by page ID to avoid some nasty but unlikely collisions. @@ -245,7 +267,7 @@ class SpecialMergeHistory extends SpecialPage { $last = $this->message['last']; $ts = wfTimestamp( TS_MW, $row->rev_timestamp ); - $checkBox = Xml::radio( "mergepoint", $ts, false ); + $checkBox = Xml::radio( 'mergepoint', $ts, false ); $pageLink = $this->sk->linkKnown( $rev->getTitle(), @@ -258,9 +280,9 @@ class SpecialMergeHistory extends SpecialPage { } # Last link - if( !$rev->userCan( Revision::DELETED_TEXT ) ) + if( !$rev->userCan( Revision::DELETED_TEXT ) ) { $last = $this->message['last']; - else if( isset($this->prevId[$row->rev_id]) ) + } elseif( isset( $this->prevId[$row->rev_id] ) ) { $last = $this->sk->linkKnown( $rev->getTitle(), $this->message['last'], @@ -270,10 +292,12 @@ class SpecialMergeHistory extends SpecialPage { 'oldid' => $this->prevId[$row->rev_id] ) ); + } $userLink = $this->sk->revUserTools( $rev ); - if(!is_null($size = $row->rev_len)) { + $size = $row->rev_len; + if( !is_null( $size ) ) { $stxt = $this->sk->formatRevisionSize( $size ); } $comment = $this->sk->revComment( $rev ); @@ -288,8 +312,9 @@ class SpecialMergeHistory extends SpecialPage { function getPageLink( $row, $titleObj, $ts, $target ) { global $wgLang; - if( !$this->userCan($row, Revision::DELETED_TEXT) ) { - return '<span class="history-deleted">' . $wgLang->timeanddate( $ts, true ) . '</span>'; + if( !$this->userCan( $row, Revision::DELETED_TEXT ) ) { + return '<span class="history-deleted">' . + $wgLang->timeanddate( $ts, true ) . '</span>'; } else { $link = $this->sk->linkKnown( $titleObj, @@ -300,8 +325,9 @@ class SpecialMergeHistory extends SpecialPage { 'timestamp' => $ts ) ); - if( $this->isDeleted($row, Revision::DELETED_TEXT) ) + if( $this->isDeleted( $row, Revision::DELETED_TEXT ) ) { $link = '<span class="history-deleted">' . $link . '</span>'; + } return $link; } } @@ -313,63 +339,80 @@ 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 # Must be older than the destination page $dbw = wfGetDB( DB_MASTER ); # Get timestamp into DB format - $this->mTimestamp = $this->mTimestamp ? $dbw->timestamp($this->mTimestamp) : ''; + $this->mTimestamp = $this->mTimestamp ? $dbw->timestamp( $this->mTimestamp ) : ''; # Max timestamp should be min of destination page - $maxtimestamp = $dbw->selectField( 'revision', 'MIN(rev_timestamp)', - array('rev_page' => $this->mDestID ), - __METHOD__ ); + $maxtimestamp = $dbw->selectField( + 'revision', + 'MIN(rev_timestamp)', + array( 'rev_page' => $this->mDestID ), + __METHOD__ + ); # Destination page must exist with revisions if( !$maxtimestamp ) { - $wgOut->addWikiMsg('mergehistory-fail'); + $wgOut->addWikiMsg( 'mergehistory-fail' ); return false; } # Get the latest timestamp of the source - $lasttimestamp = $dbw->selectField( array('page','revision'), + $lasttimestamp = $dbw->selectField( + array( 'page', 'revision' ), 'rev_timestamp', - array('page_id' => $this->mTargetID, 'page_latest = rev_id' ), - __METHOD__ ); + array( 'page_id' => $this->mTargetID, 'page_latest = rev_id' ), + __METHOD__ + ); # $this->mTimestamp must be older than $maxtimestamp if( $this->mTimestamp >= $maxtimestamp ) { - $wgOut->addWikiMsg('mergehistory-fail'); + $wgOut->addWikiMsg( 'mergehistory-fail' ); return false; } # Update the revisions if( $this->mTimestamp ) { $timewhere = "rev_timestamp <= {$this->mTimestamp}"; - $TimestampLimit = wfTimestamp(TS_MW,$this->mTimestamp); + $timestampLimit = wfTimestamp( TS_MW, $this->mTimestamp ); } else { $timewhere = "rev_timestamp <= {$maxtimestamp}"; - $TimestampLimit = wfTimestamp(TS_MW,$lasttimestamp); + $timestampLimit = wfTimestamp( TS_MW, $lasttimestamp ); } # Do the moving... - $dbw->update( 'revision', + $dbw->update( + 'revision', array( 'rev_page' => $this->mDestID ), - array( 'rev_page' => $this->mTargetID, - $timewhere ), - __METHOD__ ); + array( 'rev_page' => $this->mTargetID, $timewhere ), + __METHOD__ + ); $count = $dbw->affectedRows(); # Make the source page a redirect if no revisions are left - $haveRevisions = $dbw->selectField( 'revision', + $haveRevisions = $dbw->selectField( + 'revision', 'rev_timestamp', array( 'rev_page' => $this->mTargetID ), __METHOD__, - array( 'FOR UPDATE' ) ); + array( 'FOR UPDATE' ) + ); if( !$haveRevisions ) { if( $this->mComment ) { - $comment = wfMsgForContent( 'mergehistory-comment', $targetTitle->getPrefixedText(), - $destTitle->getPrefixedText(), $this->mComment ); + $comment = wfMsgForContent( + 'mergehistory-comment', + $targetTitle->getPrefixedText(), + $destTitle->getPrefixedText(), + $this->mComment + ); } else { - $comment = wfMsgForContent( 'mergehistory-autocomment', $targetTitle->getPrefixedText(), - $destTitle->getPrefixedText() ); + $comment = wfMsgForContent( + 'mergehistory-autocomment', + $targetTitle->getPrefixedText(), + $destTitle->getPrefixedText() + ); } $mwRedir = MagicWord::get( 'redirect' ); $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $destTitle->getPrefixedText() . "]]\n"; @@ -389,22 +432,26 @@ class SpecialMergeHistory extends SpecialPage { 'pl_from' => $this->mDestID, 'pl_namespace' => $destTitle->getNamespace(), 'pl_title' => $destTitle->getDBkey() ), - __METHOD__ ); + __METHOD__ + ); } else { $targetTitle->invalidateCache(); // update histories } $destTitle->invalidateCache(); // update histories # Check if this did anything if( !$count ) { - $wgOut->addWikiMsg('mergehistory-fail'); + $wgOut->addWikiMsg( 'mergehistory-fail' ); return false; } # Update our logs $log = new LogPage( 'merge' ); - $log->addEntry( 'merge', $targetTitle, $this->mComment, - array($destTitle->getPrefixedText(),$TimestampLimit) ); + $log->addEntry( + 'merge', $targetTitle, $this->mComment, + array( $destTitle->getPrefixedText(), $timestampLimit ) + ); - $wgOut->addHTML( wfMsgExt( 'mergehistory-success', array('parseinline'), + $wgOut->addHTML( + wfMsgExt( 'mergehistory-success', array('parseinline'), $targetTitle->getPrefixedText(), $destTitle->getPrefixedText(), $count ) ); wfRunHooks( 'ArticleMergeComplete', array( $targetTitle, $destTitle ) ); @@ -423,14 +470,21 @@ class MergeHistoryPager extends ReverseChronologicalPager { $this->articleID = $source->getArticleID(); $dbr = wfGetDB( DB_SLAVE ); - $maxtimestamp = $dbr->selectField( 'revision', 'MIN(rev_timestamp)', - array('rev_page' => $dest->getArticleID() ), - __METHOD__ ); + $maxtimestamp = $dbr->selectField( + 'revision', + 'MIN(rev_timestamp)', + array( 'rev_page' => $dest->getArticleID() ), + __METHOD__ + ); $this->maxTimestamp = $maxtimestamp; parent::__construct(); } + function getTitle() { + return SpecialPage::getTitleFor( 'Contributions' ); + } + function getStartBody() { wfProfileIn( __METHOD__ ); # Do a link batch query @@ -442,11 +496,12 @@ class MergeHistoryPager extends ReverseChronologicalPager { $batch->addObj( Title::makeTitleSafe( NS_USER, $row->rev_user_text ) ); $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->rev_user_text ) ); - $rev_id = isset($rev_id) ? $rev_id : $row->rev_id; - if( $rev_id > $row->rev_id ) + $rev_id = isset( $rev_id ) ? $rev_id : $row->rev_id; + if( $rev_id > $row->rev_id ) { $this->mForm->prevId[$rev_id] = $row->rev_id; - else if( $rev_id < $row->rev_id ) + } elseif( $rev_id < $row->rev_id ) { $this->mForm->prevId[$row->rev_id] = $rev_id; + } $rev_id = $row->rev_id; } @@ -468,9 +523,12 @@ class MergeHistoryPager extends ReverseChronologicalPager { $conds[] = 'page_id = rev_page'; $conds[] = "rev_timestamp < {$this->maxTimestamp}"; return array( - 'tables' => array('revision','page'), - 'fields' => array( 'rev_minor_edit', 'rev_timestamp', 'rev_user', 'rev_user_text', 'rev_comment', - 'rev_id', 'rev_page', 'rev_parent_id', 'rev_text_id', 'rev_len', 'rev_deleted' ), + 'tables' => array( 'revision', 'page' ), + 'fields' => array( + 'rev_minor_edit', 'rev_timestamp', 'rev_user', 'rev_user_text', + 'rev_comment', 'rev_id', 'rev_page', 'rev_parent_id', + 'rev_text_id', 'rev_len', 'rev_deleted' + ), 'conds' => $conds ); } diff --git a/includes/specials/SpecialMostcategories.php b/includes/specials/SpecialMostcategories.php index 124f0bd5..2e437196 100644 --- a/includes/specials/SpecialMostcategories.php +++ b/includes/specials/SpecialMostcategories.php @@ -31,28 +31,32 @@ */ class MostcategoriesPage extends QueryPage { - function getName() { return 'Mostcategories'; } + function __construct( $name = 'Mostcategories' ) { + parent::__construct( $name ); + } + function isExpensive() { return true; } function isSyndicated() { return false; } - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $categorylinks, $page) = $dbr->tableNamesN( 'categorylinks', 'page' ); - return - " - SELECT - 'Mostcategories' as type, - page_namespace as namespace, - page_title as title, - COUNT(*) as value - FROM $categorylinks - LEFT JOIN $page ON cl_from = page_id - WHERE page_namespace = " . NS_MAIN . " - GROUP BY page_namespace, page_title - HAVING COUNT(*) > 1 - "; + function getQueryInfo() { + return array ( + 'tables' => array ( 'categorylinks', 'page' ), + 'fields' => array ( 'page_namespace AS namespace', + 'page_title AS title', + 'COUNT(*) AS value' ), + 'conds' => array ( 'page_namespace' => MWNamespace::getContentNamespaces() ), + 'options' => array ( 'HAVING' => 'COUNT(*) > 1', + 'GROUP BY' => 'page_namespace, page_title' ), + 'join_conds' => array ( 'page' => array ( 'LEFT JOIN', + 'page_id = cl_from' ) ) + ); } + /** + * @param $skin Skin + * @param $result + * @return string + */ function formatResult( $skin, $result ) { global $wgLang; $title = Title::makeTitleSafe( $result->namespace, $result->title ); @@ -62,14 +66,3 @@ class MostcategoriesPage extends QueryPage { return wfSpecialList( $link, $count ); } } - -/** - * constructor - */ -function wfSpecialMostcategories() { - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new MostcategoriesPage(); - - $wpp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialMostimages.php b/includes/specials/SpecialMostimages.php index 411a281b..ac2b5206 100644 --- a/includes/specials/SpecialMostimages.php +++ b/includes/specials/SpecialMostimages.php @@ -31,24 +31,22 @@ */ class MostimagesPage extends ImageQueryPage { - function getName() { return 'Mostimages'; } + function __construct( $name = 'Mostimages' ) { + parent::__construct( $name ); + } + function isExpensive() { return true; } function isSyndicated() { return false; } - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - $imagelinks = $dbr->tableName( 'imagelinks' ); - return - " - SELECT - 'Mostimages' as type, - " . NS_FILE . " as namespace, - il_to as title, - COUNT(*) as value - FROM $imagelinks - GROUP BY il_to - HAVING COUNT(*) > 1 - "; + function getQueryInfo() { + return array ( + 'tables' => array ( 'imagelinks' ), + 'fields' => array ( "'" . NS_FILE . "' AS namespace", + 'il_to AS title', + 'COUNT(*) AS value' ), + 'options' => array ( 'GROUP BY' => 'il_to', + 'HAVING' => 'COUNT(*) > 1' ) + ); } function getCellHtml( $row ) { @@ -58,14 +56,3 @@ class MostimagesPage extends ImageQueryPage { } } - -/** - * Constructor - */ -function wfSpecialMostimages() { - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new MostimagesPage(); - - $wpp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialMostlinked.php b/includes/specials/SpecialMostlinked.php index c731588a..58f686e8 100644 --- a/includes/specials/SpecialMostlinked.php +++ b/includes/specials/SpecialMostlinked.php @@ -32,43 +32,34 @@ */ class MostlinkedPage extends QueryPage { - function getName() { return 'Mostlinked'; } + function __construct( $name = 'Mostlinked' ) { + parent::__construct( $name ); + } + function isExpensive() { return true; } function isSyndicated() { return false; } - function getSQL() { - global $wgMiserMode; - - $dbr = wfGetDB( DB_SLAVE ); - - # In miser mode, reduce the query cost by adding a threshold for large wikis - if ( $wgMiserMode ) { - $numPages = SiteStats::pages(); - if ( $numPages > 10000 ) { - $cutoff = 100; - } elseif ( $numPages > 100 ) { - $cutoff = intval( sqrt( $numPages ) ); - } else { - $cutoff = 1; - } - } else { - $cutoff = 1; - } - - list( $pagelinks, $page ) = $dbr->tableNamesN( 'pagelinks', 'page' ); - return - "SELECT 'Mostlinked' AS type, - pl_namespace AS namespace, - pl_title AS title, - COUNT(*) AS value - FROM $pagelinks - LEFT JOIN $page ON pl_namespace=page_namespace AND pl_title=page_title - GROUP BY pl_namespace, pl_title - HAVING COUNT(*) > $cutoff"; + function getQueryInfo() { + return array ( + 'tables' => array ( 'pagelinks', 'page' ), + 'fields' => array ( 'pl_namespace AS namespace', + 'pl_title AS title', + 'COUNT(*) AS value', + 'page_namespace' ), + 'options' => array ( 'HAVING' => 'COUNT(*) > 1', + 'GROUP BY' => '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 */ function preprocessResults( $db, $res ) { if( $db->numRows( $res ) > 0 ) { @@ -114,14 +105,3 @@ class MostlinkedPage extends QueryPage { return wfSpecialList( $link, $wlh ); } } - -/** - * constructor - */ -function wfSpecialMostlinked() { - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new MostlinkedPage(); - - $wpp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialMostlinkedcategories.php b/includes/specials/SpecialMostlinkedcategories.php index e1fc1d95..195282f7 100644 --- a/includes/specials/SpecialMostlinkedcategories.php +++ b/includes/specials/SpecialMostlinkedcategories.php @@ -31,65 +31,60 @@ */ class MostlinkedCategoriesPage extends QueryPage { - function getName() { return 'Mostlinkedcategories'; } + function __construct( $name = 'Mostlinkedcategories' ) { + parent::__construct( $name ); + } + function isExpensive() { return true; } function isSyndicated() { return false; } - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - $categorylinks = $dbr->tableName( 'categorylinks' ); - $name = $dbr->addQuotes( $this->getName() ); - return - " - SELECT - $name as type, - " . NS_CATEGORY . " as namespace, - cl_to as title, - COUNT(*) as value - FROM $categorylinks - GROUP BY cl_to - "; + function getQueryInfo() { + return array ( + 'tables' => array ( 'categorylinks' ), + 'fields' => array ( 'cl_to AS title', + NS_CATEGORY . ' AS namespace', + 'COUNT(*) AS value' ), + 'options' => array ( 'GROUP BY' => 'cl_to' ) + ); } function sortDescending() { return true; } /** * Fetch user page links and cache their existence + * + * @param $db DatabaseBase + * @param $res DatabaseResult */ function preprocessResults( $db, $res ) { $batch = new LinkBatch; foreach ( $res as $row ) { - $batch->add( $row->namespace, $row->title ); + $batch->add( NS_CATEGORY, $row->title ); } $batch->execute(); // Back to start for display - if ( $db->numRows( $res ) > 0 ) + if ( $db->numRows( $res ) > 0 ) { // If there are no rows we get an error seeking. $db->dataSeek( $res, 0 ); + } } + /** + * @param $skin Skin + * @param $result + * @return string + */ function formatResult( $skin, $result ) { global $wgLang, $wgContLang; - $nt = Title::makeTitle( $result->namespace, $result->title ); + $nt = Title::makeTitle( NS_CATEGORY, $result->title ); $text = $wgContLang->convert( $nt->getText() ); $plink = $skin->link( $nt, htmlspecialchars( $text ) ); - $nlinks = wfMsgExt( 'nmembers', array( 'parsemag', 'escape'), + $nlinks = wfMsgExt( 'nmembers', array( 'parsemag', 'escape' ), $wgLang->formatNum( $result->value ) ); - return wfSpecialList($plink, $nlinks); + return wfSpecialList( $plink, $nlinks ); } } - -/** - * constructor - */ -function wfSpecialMostlinkedCategories() { - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new MostlinkedCategoriesPage(); - - $wpp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialMostlinkedtemplates.php b/includes/specials/SpecialMostlinkedtemplates.php index 822d6bc9..69771925 100644 --- a/includes/specials/SpecialMostlinkedtemplates.php +++ b/includes/specials/SpecialMostlinkedtemplates.php @@ -21,22 +21,17 @@ * @ingroup SpecialPage * @author Rob Church <robchur@gmail.com> */ - + /** * Special page lists templates with a large number of * transclusion links, i.e. "most used" templates * * @ingroup SpecialPage */ -class SpecialMostlinkedtemplates extends QueryPage { +class MostlinkedTemplatesPage extends QueryPage { - /** - * Name of the report - * - * @return String - */ - public function getName() { - return 'Mostlinkedtemplates'; + function __construct( $name = 'Mostlinkedtemplates' ) { + parent::__construct( $name ); } /** @@ -66,22 +61,15 @@ class SpecialMostlinkedtemplates extends QueryPage { return true; } - /** - * Generate SQL for the report - * - * @return String - */ - public function getSql() { - $dbr = wfGetDB( DB_SLAVE ); - $templatelinks = $dbr->tableName( 'templatelinks' ); - $name = $dbr->addQuotes( $this->getName() ); - return "SELECT {$name} AS type, - " . NS_TEMPLATE . " AS namespace, - tl_title AS title, - COUNT(*) AS value - FROM {$templatelinks} - WHERE tl_namespace = " . NS_TEMPLATE . " - GROUP BY tl_title"; + public function getQueryInfo() { + return array ( + 'tables' => array ( 'templatelinks' ), + 'fields' => array ( 'tl_namespace AS namespace', + 'tl_title AS title', + 'COUNT(*) AS value' ), + 'conds' => array ( 'tl_namespace' => NS_TEMPLATE ), + 'options' => array( 'GROUP BY' => 'tl_namespace, tl_title' ) + ); } /** @@ -108,7 +96,7 @@ class SpecialMostlinkedtemplates extends QueryPage { * @return String */ public function formatResult( $skin, $result ) { - $title = Title::makeTitleSafe( $result->namespace, $result->title ); + $title = Title::makeTitle( $result->namespace, $result->title ); return wfSpecialList( $skin->link( $title ), @@ -133,13 +121,3 @@ class SpecialMostlinkedtemplates extends QueryPage { } } -/** - * Execution function - * - * @param $par Mixed: parameters passed to the page - */ -function wfSpecialMostlinkedtemplates( $par = false ) { - list( $limit, $offset ) = wfCheckLimits(); - $mlt = new SpecialMostlinkedtemplates(); - $mlt->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialMostrevisions.php b/includes/specials/SpecialMostrevisions.php index f9bafabc..b0253316 100644 --- a/includes/specials/SpecialMostrevisions.php +++ b/includes/specials/SpecialMostrevisions.php @@ -23,64 +23,12 @@ * @ingroup SpecialPage * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> */ - -/** - * A special page to show pages with highest revision count - * - * @ingroup SpecialPage - */ -class MostrevisionsPage extends QueryPage { - - function getName() { return 'Mostrevisions'; } - function isExpensive() { return true; } - function isSyndicated() { return false; } - - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $revision, $page ) = $dbr->tableNamesN( 'revision', 'page' ); - return - " - SELECT - 'Mostrevisions' as type, - page_namespace as namespace, - page_title as title, - COUNT(*) as value - FROM $revision - JOIN $page ON page_id = rev_page - WHERE page_namespace = " . NS_MAIN . " - GROUP BY page_namespace, page_title - HAVING COUNT(*) > 1 - "; +class MostrevisionsPage extends FewestrevisionsPage { + function __construct( $name = 'Mostrevisions' ) { + parent::__construct( $name ); } - function formatResult( $skin, $result ) { - global $wgLang, $wgContLang; - - $nt = Title::makeTitle( $result->namespace, $result->title ); - $text = $wgContLang->convert( $nt->getPrefixedText() ); - - $plink = $skin->linkKnown( $nt, $text ); - - $nl = wfMsgExt( 'nrevisions', array( 'parsemag', 'escape'), - $wgLang->formatNum( $result->value ) ); - $nlink = $skin->linkKnown( - $nt, - $nl, - array(), - array( 'action' => 'history' ) - ); - - return wfSpecialList($plink, $nlink); + function sortDescending() { + return true; } } - -/** - * constructor - */ -function wfSpecialMostrevisions() { - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new MostrevisionsPage(); - - $wpp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialMovepage.php b/includes/specials/SpecialMovepage.php index 2f156c65..7ac7eba4 100644 --- a/includes/specials/SpecialMovepage.php +++ b/includes/specials/SpecialMovepage.php @@ -27,6 +27,10 @@ * @ingroup SpecialPage */ class MovePageForm extends UnlistedSpecialPage { + + /** + * @var Title + */ var $oldTitle, $newTitle; # Objects var $reason; # Text input var $moveTalk, $deleteAndMove, $moveSubpages, $fixRedirects, $leaveRedirect, $moveOverShared; # Checks @@ -70,7 +74,9 @@ class MovePageForm extends UnlistedSpecialPage { # Check rights $permErrors = $this->oldTitle->getUserPermissionsErrors( 'move', $wgUser ); if( !empty( $permErrors ) ) { - $wgOut->showPermissionsErrorPage( $permErrors ); + // Auto-block user's IP if the account was "hard" blocked + $user->spreadAnyEditBlock(); + $this->getOutput()->showPermissionsErrorPage( $permErrors ); return; } @@ -103,12 +109,14 @@ class MovePageForm extends UnlistedSpecialPage { function showForm( $err ) { global $wgOut, $wgUser, $wgContLang, $wgFixDoubleRedirects; - $skin = $wgUser->getSkin(); + $skin = $this->getSkin(); $oldTitleLink = $skin->link( $this->oldTitle ); $wgOut->setPagetitle( wfMsg( 'move-page', $this->oldTitle->getPrefixedText() ) ); - $wgOut->setSubtitle( wfMsg( 'move-page-backlink', $oldTitleLink ) ); + $skin->setRelevantTitle( $this->oldTitle ); + + $wgOut->addModules( 'mediawiki.special.movePage' ); $newTitle = $this->newTitle; @@ -211,7 +219,7 @@ class MovePageForm extends UnlistedSpecialPage { Xml::element( 'legend', null, wfMsg( 'move-page-legend' ) ) . Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-movepage-table' ) ) . "<tr> - <td class='mw-label'>" . + <td class='mw-label'>" . wfMsgHtml( 'movearticle' ) . "</td> <td class='mw-input'> @@ -233,7 +241,7 @@ class MovePageForm extends UnlistedSpecialPage { "</td> <td class='mw-input'>" . Html::element( 'textarea', array( 'name' => 'wpReason', 'id' => 'wpReason', 'cols' => 60, 'rows' => 2, - 'maxlength' => 200 ), $this->reason ) . + 'maxlength' => 200 ), $this->reason ) . // maxlength byte limit is enforce in mediawiki.special.movePage.js "</td> </tr>" ); @@ -398,7 +406,7 @@ class MovePageForm extends UnlistedSpecialPage { # Do the actual move. $error = $ot->moveTo( $nt, true, $this->reason, $createRedirect ); if ( $error !== true ) { - # FIXME: show all the errors in a list, not just the first one + # @todo FIXME: Show all the errors in a list, not just the first one $this->showForm( reset( $error ) ); return; } @@ -407,7 +415,7 @@ class MovePageForm extends UnlistedSpecialPage { DoubleRedirectJob::fixRedirects( 'move', $ot, $nt ); } - wfRunHooks( 'SpecialMovepageAfterMove', array( &$this , &$ot , &$nt ) ) ; + wfRunHooks( 'SpecialMovepageAfterMove', array( &$this, &$ot, &$nt ) ); $wgOut->setPagetitle( wfMsg( 'pagemovedsub' ) ); @@ -442,10 +450,10 @@ class MovePageForm extends UnlistedSpecialPage { # # If the target namespace doesn't allow subpages, moving with subpages # would mean that you couldn't move them back in one operation, which - # is bad. FIXME: A specific error message should be given in this - # case. + # is bad. + # @todo FIXME: A specific error message should be given in this case. - // FIXME: Use Title::moveSubpages() here + // @todo FIXME: Use Title::moveSubpages() here $dbr = wfGetDB( DB_MASTER ); if( $this->moveSubpages && ( MWNamespace::hasSubpages( $nt->getNamespace() ) || ( @@ -486,7 +494,7 @@ class MovePageForm extends UnlistedSpecialPage { } $extraOutput = array(); - $skin = $wgUser->getSkin(); + $skin = $this->getSkin(); $count = 1; foreach( $extraPages as $oldSubpage ) { if( $ot->equals( $oldSubpage ) ) { @@ -561,7 +569,7 @@ class MovePageForm extends UnlistedSpecialPage { # Re-clear the file redirect cache, which may have been polluted by # parsing in messages above. See CR r56745. - # FIXME: needs a more robust solution inside FileRepo. + # @todo FIXME: Needs a more robust solution inside FileRepo. if( $ot->getNamespace() == NS_FILE ) { RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $ot ); } @@ -573,7 +581,7 @@ class MovePageForm extends UnlistedSpecialPage { } function showSubpages( $title, $out ) { - global $wgUser, $wgLang; + global $wgLang; if( !MWNamespace::hasSubpages( $title->getNamespace() ) ) return; @@ -590,7 +598,7 @@ class MovePageForm extends UnlistedSpecialPage { } $out->addWikiMsg( 'movesubpagetext', $wgLang->formatNum( $count ) ); - $skin = $wgUser->getSkin(); + $skin = $this->getSkin(); $out->addHTML( "<ul>\n" ); foreach( $subpages as $subpage ) { diff --git a/includes/specials/SpecialNewimages.php b/includes/specials/SpecialNewimages.php index cecd7dfd..ea20c2f7 100644 --- a/includes/specials/SpecialNewimages.php +++ b/includes/specials/SpecialNewimages.php @@ -20,244 +20,152 @@ * @file * @ingroup SpecialPage */ +class SpecialNewFiles extends IncludableSpecialPage { -/** - * @todo FIXME: this code is crap, should use Pager and Database::select(). - */ -function wfSpecialNewimages( $par, $specialPage ) { - global $wgUser, $wgOut, $wgLang, $wgRequest, $wgMiserMode; - - $wpIlMatch = $wgRequest->getText( 'wpIlMatch' ); - $dbr = wfGetDB( DB_SLAVE ); - $sk = $wgUser->getSkin(); - $shownav = !$specialPage->including(); - $hidebots = $wgRequest->getBool( 'hidebots' , 1 ); + public function __construct(){ + parent::__construct( 'Newimages' ); + } - $hidebotsql = ''; - if ( $hidebots ) { - # Make a list of group names which have the 'bot' flag set. - $botconds = array(); - foreach ( User::getGroupsWithPermission('bot') as $groupname ) { - $botconds[] = 'ug_group = ' . $dbr->addQuotes( $groupname ); - } + public function execute( $par ){ + $this->setHeaders(); + $this->outputHeader(); - # If not bot groups, do not set $hidebotsql - if ( $botconds ) { - $isbotmember = $dbr->makeList( $botconds, LIST_OR ); + $pager = new NewFilesPager( $par ); - # This join, in conjunction with WHERE ug_group IS NULL, returns - # only those rows from IMAGE where the uploading user is not a mem- - # ber of a group which has the 'bot' permission set. - $ug = $dbr->tableName( 'user_groups' ); - $hidebotsql = " LEFT JOIN $ug ON img_user=ug_user AND ($isbotmember)"; + if ( !$this->including() ) { + $form = $pager->getForm(); + $form->prepareForm(); + $form->displayForm( '' ); + } + $this->getOutput()->addHTML( $pager->getBody() ); + if ( !$this->including() ) { + $this->getOutput()->addHTML( $pager->getNavigationBar() ); } } +} - $image = $dbr->tableName( 'image' ); - $sql = "SELECT img_timestamp from $image"; - if ($hidebotsql) { - $sql .= "$hidebotsql WHERE ug_group IS NULL"; - } - $sql .= ' ORDER BY img_timestamp DESC'; - $sql = $dbr->limitResult($sql, 1, false); - $res = $dbr->query( $sql, __FUNCTION__ ); - $row = $dbr->fetchRow( $res ); - if( $row !== false ) { - $ts = $row[0]; - } else { - $ts = false; - } +/** + * @ingroup SpecialPage Pager + */ +class NewFilesPager extends ReverseChronologicalPager { - # If we were clever, we'd use this to cache. - $latestTimestamp = wfTimestamp( TS_MW, $ts ); + function __construct( $par = null ) { + global $wgRequest; - # Hardcode this for now. - $limit = 48; - $parval = intval( $par ); - if ( $parval ) { - if ( $parval <= $limit && $parval > 0 ) { - $limit = $parval; - } - } + $this->like = $wgRequest->getText( 'like' ); + $this->showbots = $wgRequest->getBool( 'showbots' , 0 ); + $this->skin = $this->getSkin(); - $where = array(); - $searchpar = array(); - if ( $wpIlMatch != '' && !$wgMiserMode) { - $nt = Title::newFromURL( $wpIlMatch ); - if( $nt ) { - $where[] = 'LOWER(img_name) ' . $dbr->buildLike( $dbr->anyString(), strtolower( $nt->getDBkey() ), $dbr->anyString() ); - $searchpar['wpIlMatch'] = $wpIlMatch; - } + parent::__construct(); } - $invertSort = false; - $until = $wgRequest->getVal( 'until' ); - if( $until ) { - $where[] = "img_timestamp < '" . $dbr->timestamp( $until ) . "'"; + function getTitle() { + return SpecialPage::getTitleFor( 'Newimages' ); } - $from = $wgRequest->getVal( 'from' ); - if( $from ) { - $where[] = "img_timestamp >= '" . $dbr->timestamp( $from ) . "'"; - $invertSort = true; - } - $sql = 'SELECT img_size, img_name, img_user, img_user_text,'. - "img_description,img_timestamp FROM $image"; - if( $hidebotsql ) { - $sql .= $hidebotsql; - $where[] = 'ug_group IS NULL'; - } - if( count( $where ) ) { - $sql .= ' WHERE ' . $dbr->makeList( $where, LIST_AND ); - } - $sql.=' ORDER BY img_timestamp '. ( $invertSort ? '' : ' DESC' ); - $sql = $dbr->limitResult($sql, ( $limit + 1 ), false); - $res = $dbr->query( $sql, __FUNCTION__ ); + function getQueryInfo() { + global $wgMiserMode; + $conds = $jconds = array(); + $tables = array( 'image' ); - /** - * We have to flip things around to get the last N after a certain date - */ - $images = array(); - foreach ( $res as $s ) { - if( $invertSort ) { - array_unshift( $images, $s ); - } else { - array_push( $images, $s ); + if( !$this->showbots ) { + $tables[] = 'user_groups'; + $conds[] = 'ug_group IS NULL'; + $jconds['user_groups'] = array( + 'LEFT JOIN', + array( + 'ug_group' => User::getGroupsWithPermission( 'bot' ), + 'ug_user = img_user' + ) + ); } - } - $gallery = new ImageGallery(); - $firstTimestamp = null; - $lastTimestamp = null; - $shownImages = 0; - foreach( $images as $s ) { - $shownImages++; - if( $shownImages > $limit ) { - # One extra just to test for whether to show a page link; - # don't actually show it. - break; + 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() ); + $conds[] = "LOWER(img_name) $like"; + } } - $name = $s->img_name; - $ut = $s->img_user_text; - - $nt = Title::newFromText( $name, NS_FILE ); - $ul = $sk->link( Title::makeTitle( NS_USER, $ut ), $ut ); - - $gallery->add( $nt, "$ul<br />\n<i>".htmlspecialchars($wgLang->timeanddate( $s->img_timestamp, true ))."</i><br />\n" ); + $query = array( + 'tables' => $tables, + 'fields' => '*', + 'join_conds' => $jconds, + 'conds' => $conds + ); - $timestamp = wfTimestamp( TS_MW, $s->img_timestamp ); - if( empty( $firstTimestamp ) ) { - $firstTimestamp = $timestamp; - } - $lastTimestamp = $timestamp; + return $query; } - $titleObj = SpecialPage::getTitleFor( 'Newimages' ); - $action = $titleObj->getLocalURL( $hidebots ? '' : 'hidebots=0' ); - if ( $shownav && !$wgMiserMode ) { - $wgOut->addHTML( - Xml::openElement( 'form', array( 'action' => $action, 'method' => 'post', 'id' => 'imagesearch' ) ) . - Xml::fieldset( wfMsg( 'newimages-legend' ) ) . - Xml::inputLabel( wfMsg( 'newimages-label' ), 'wpIlMatch', 'wpIlMatch', 20, $wpIlMatch ) . ' ' . - Xml::submitButton( wfMsg( 'ilsubmit' ), array( 'name' => 'wpIlSubmit' ) ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) - ); + function getIndexField(){ + return 'img_timestamp'; } - $bydate = wfMsg( 'bydate' ); - $lt = $wgLang->formatNum( min( $shownImages, $limit ) ); - if ( $shownav ) { - $text = wfMsgExt( 'imagelisttext', array('parse'), $lt, $bydate ); - $wgOut->addHTML( $text . "\n" ); + function getStartBody(){ + $this->gallery = new ImageGallery(); } - /** - * Paging controls... - */ - - # If we change bot visibility, this needs to be carried along. - if( !$hidebots ) { - $botpar = array( 'hidebots' => 0 ); - } else { - $botpar = array(); + function getEndBody(){ + return $this->gallery->toHTML(); } - $now = wfTimestampNow(); - $d = $wgLang->date( $now, true ); - $t = $wgLang->time( $now, true ); - $query = array_merge( - array( 'from' => $now ), - $botpar, - $searchpar - ); - - $dateLink = $sk->linkKnown( - $titleObj, - htmlspecialchars( wfMsg( 'sp-newimages-showfrom', $d, $t ) ), - array(), - $query - ); - $query = array_merge( - array( 'hidebots' => ( $hidebots ? 0 : 1 ) ), - $searchpar - ); + function formatRow( $row ) { + global $wgLang; - $showhide = $hidebots ? wfMsg( 'show' ) : wfMsg( 'hide' ); + $name = $row->img_name; + $user = User::newFromId( $row->img_user ); - $botLink = $sk->linkKnown( - $titleObj, - htmlspecialchars( wfMsg( 'showhidebots', $showhide ) ), - array(), - $query - ); + $title = Title::makeTitle( NS_FILE, $name ); + $ul = $this->skin->link( $user->getUserpage(), $user->getName() ); - $opts = array( 'parsemag', 'escapenoentities' ); - $prevLink = wfMsgExt( 'pager-newer-n', $opts, $wgLang->formatNum( $limit ) ); - if( $firstTimestamp && $firstTimestamp != $latestTimestamp ) { - $query = array_merge( - array( 'from' => $firstTimestamp ), - $botpar, - $searchpar - ); - - $prevLink = $sk->linkKnown( - $titleObj, - $prevLink, - array(), - $query + $this->gallery->add( + $title, + "$ul<br />\n<i>" + . htmlspecialchars( $wgLang->timeanddate( $row->img_timestamp, true ) ) + . "</i><br />\n" ); } - $nextLink = wfMsgExt( 'pager-older-n', $opts, $wgLang->formatNum( $limit ) ); - if( $invertSort || ( $shownImages > $limit && $lastTimestamp ) ) { - $query = array_merge( - array( 'until' => ( $lastTimestamp ? $lastTimestamp : "" ) ), - $botpar, - $searchpar - ); - - $nextLink = $sk->linkKnown( - $titleObj, - $nextLink, - array(), - $query + function getForm() { + global $wgRequest, $wgMiserMode; + + $fields = array( + 'like' => array( + 'type' => 'text', + 'label-message' => 'newimages-label', + 'name' => 'like', + ), + 'showbots' => array( + 'type' => 'check', + 'label' => wfMessage( 'showhidebots', wfMsg( 'show' ) ), + 'name' => 'showbots', + # 'default' => $wgRequest->getBool( 'showbots', 0 ), + ), + 'limit' => array( + 'type' => 'hidden', + 'default' => $wgRequest->getText( 'limit' ), + 'name' => 'limit', + ), + 'offset' => array( + 'type' => 'hidden', + 'default' => $wgRequest->getText( 'offset' ), + 'name' => 'offset', + ), ); - } - - $prevnext = '<p>' . $botLink . ' '. wfMsgHtml( 'viewprevnext', $prevLink, $nextLink, $dateLink ) .'</p>'; + if( $wgMiserMode ){ + unset( $fields['like'] ); + } - if ($shownav) - $wgOut->addHTML( $prevnext ); + $form = new HTMLForm( $fields ); + $form->setTitle( $this->getTitle() ); + $form->setSubmitText( wfMsg( 'ilsubmit' ) ); + $form->setMethod( 'get' ); + $form->setWrapperLegend( wfMsg( 'newimages-legend' ) ); - if( count( $images ) ) { - $wgOut->addHTML( $gallery->toHTML() ); - if ($shownav) - $wgOut->addHTML( $prevnext ); - } else { - $wgOut->addWikiMsg( 'noimages' ); + return $form; } } diff --git a/includes/specials/SpecialNewpages.php b/includes/specials/SpecialNewpages.php index 3235436a..bf9fb9f7 100644 --- a/includes/specials/SpecialNewpages.php +++ b/includes/specials/SpecialNewpages.php @@ -29,7 +29,12 @@ class SpecialNewpages extends IncludableSpecialPage { // Stored objects - protected $opts, $skin; + + /** + * @var FormOptions + */ + protected $opts; + protected $customFilters; // Some internal settings protected $showNavigation = false; @@ -39,24 +44,30 @@ class SpecialNewpages extends IncludableSpecialPage { } protected function setup( $par ) { - global $wgRequest, $wgUser, $wgEnableNewpagesUserFilter; + global $wgEnableNewpagesUserFilter; // Options $opts = new FormOptions(); $this->opts = $opts; // bind $opts->add( 'hideliu', false ); - $opts->add( 'hidepatrolled', $wgUser->getBoolOption( 'newpageshidepatrolled' ) ); + $opts->add( 'hidepatrolled', $this->getUser()->getBoolOption( 'newpageshidepatrolled' ) ); $opts->add( 'hidebots', false ); $opts->add( 'hideredirs', true ); - $opts->add( 'limit', (int)$wgUser->getOption( 'rclimit' ) ); + $opts->add( 'limit', (int)$this->getUser()->getOption( 'rclimit' ) ); $opts->add( 'offset', '' ); $opts->add( 'namespace', '0' ); $opts->add( 'username', '' ); $opts->add( 'feed', '' ); $opts->add( 'tagfilter', '' ); + $this->customFilters = array(); + wfRunHooks( 'SpecialNewPagesFilters', array( $this, &$this->customFilters ) ); + foreach( $this->customFilters as $key => $params ) { + $opts->add( $key, $params['default'] ); + } + // Set values - $opts->fetchValuesFromRequest( $wgRequest ); + $opts->fetchValuesFromRequest( $this->getRequest() ); if ( $par ) $this->parseParams( $par ); // Validate @@ -64,36 +75,42 @@ class SpecialNewpages extends IncludableSpecialPage { if( !$wgEnableNewpagesUserFilter ) { $opts->setValue( 'username', '' ); } - - // Store some objects - $this->skin = $wgUser->getSkin(); } protected function parseParams( $par ) { global $wgLang; $bits = preg_split( '/\s*,\s*/', trim( $par ) ); foreach ( $bits as $bit ) { - if ( 'shownav' == $bit ) + if ( 'shownav' == $bit ) { $this->showNavigation = true; - if ( 'hideliu' === $bit ) + } + if ( 'hideliu' === $bit ) { $this->opts->setValue( 'hideliu', true ); - if ( 'hidepatrolled' == $bit ) + } + if ( 'hidepatrolled' == $bit ) { $this->opts->setValue( 'hidepatrolled', true ); - if ( 'hidebots' == $bit ) + } + if ( 'hidebots' == $bit ) { $this->opts->setValue( 'hidebots', true ); - if ( 'showredirs' == $bit ) + } + if ( 'showredirs' == $bit ) { $this->opts->setValue( 'hideredirs', false ); - if ( is_numeric( $bit ) ) + } + if ( is_numeric( $bit ) ) { $this->opts->setValue( 'limit', intval( $bit ) ); + } $m = array(); - if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) - $this->opts->setValue( 'limit', intval($m[1]) ); + if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) { + $this->opts->setValue( 'limit', intval( $m[1] ) ); + } // PG offsets not just digits! - if ( preg_match( '/^offset=([^=]+)$/', $bit, $m ) ) - $this->opts->setValue( 'offset', intval($m[1]) ); - if ( preg_match( '/^username=(.*)$/', $bit, $m ) ) + if ( preg_match( '/^offset=([^=]+)$/', $bit, $m ) ) { + $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 = $wgLang->getNsIndex( $m[1] ); if( $ns !== false ) { @@ -110,7 +127,7 @@ class SpecialNewpages extends IncludableSpecialPage { * @return String */ public function execute( $par ) { - global $wgOut; + $out = $this->getOutput(); $this->setHeaders(); $this->outputHeader(); @@ -135,15 +152,17 @@ class SpecialNewpages extends IncludableSpecialPage { if( $pager->getNumRows() ) { $navigation = ''; - if ( $this->showNavigation ) $navigation = $pager->getNavigationBar(); - $wgOut->addHTML( $navigation . $pager->getBody() . $navigation ); + if ( $this->showNavigation ) { + $navigation = $pager->getNavigationBar(); + } + $out->addHTML( $navigation . $pager->getBody() . $navigation ); } else { - $wgOut->addWikiMsg( 'specialpage-empty' ); + $out->addWikiMsg( 'specialpage-empty' ); } } protected function filterLinks() { - global $wgGroupPermissions, $wgUser, $wgLang; + global $wgGroupPermissions, $wgLang; // show/hide links $showhide = array( wfMsgHtml( 'show' ), wfMsgHtml( 'hide' ) ); @@ -155,24 +174,28 @@ class SpecialNewpages extends IncludableSpecialPage { 'hidebots' => 'rcshowhidebots', 'hideredirs' => 'whatlinkshere-hideredirs' ); + foreach ( $this->customFilters as $key => $params ) { + $filters[$key] = $params['msg']; + } // Disable some if needed - # FIXME: throws E_NOTICEs if not set; and doesn't obey hooks etc - if ( $wgGroupPermissions['*']['createpage'] !== true ) - unset($filters['hideliu']); - - if ( !$wgUser->useNPPatrol() ) - unset($filters['hidepatrolled']); + # @todo FIXME: Throws E_NOTICEs if not set; and doesn't obey hooks etc. + if ( $wgGroupPermissions['*']['createpage'] !== true ) { + unset( $filters['hideliu'] ); + } + if ( !$this->getUser()->useNPPatrol() ) { + unset( $filters['hidepatrolled'] ); + } $links = array(); $changed = $this->opts->getChangedValues(); - unset($changed['offset']); // Reset offset if query type changes + unset( $changed['offset'] ); // Reset offset if query type changes $self = $this->getTitle(); foreach ( $filters as $key => $msg ) { - $onoff = 1 - $this->opts->getValue($key); - $link = $this->skin->link( $self, $showhide[$onoff], array(), - array( $key => $onoff ) + $changed + $onoff = 1 - $this->opts->getValue( $key ); + $link = $this->getSkin()->link( $self, $showhide[$onoff], array(), + array( $key => $onoff ) + $changed ); $links[$key] = wfMsgHtml( $msg, $link ); } @@ -181,7 +204,7 @@ class SpecialNewpages extends IncludableSpecialPage { } protected function form() { - global $wgOut, $wgEnableNewpagesUserFilter, $wgScript; + global $wgEnableNewpagesUserFilter, $wgScript; // Consume values $this->opts->consumeValue( 'offset' ); // don't carry offset, DWIW @@ -201,61 +224,62 @@ class SpecialNewpages extends IncludableSpecialPage { $hidden = implode( "\n", $hidden ); $tagFilter = ChangeTags::buildTagFilterSelector( $tagFilterVal ); - if ($tagFilter) + if ( $tagFilter ) { list( $tagFilterLabel, $tagFilterSelector ) = $tagFilter; + } $form = Xml::openElement( 'form', array( 'action' => $wgScript ) ) . Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . Xml::fieldset( wfMsg( 'newpages' ) ) . Xml::openElement( 'table', array( 'id' => 'mw-newpages-table' ) ) . - "<tr> - <td class='mw-label'>" . + '<tr> + <td class="mw-label">' . Xml::label( wfMsg( 'namespace' ), 'namespace' ) . - "</td> - <td class='mw-input'>" . + '</td> + <td class="mw-input">' . Xml::namespaceSelector( $namespace, 'all' ) . - "</td> - </tr>" . ( $tagFilter ? ( - "<tr> - <td class='mw-label'>" . + '</td> + </tr>' . ( $tagFilter ? ( + '<tr> + <td class="mw-label">' . $tagFilterLabel . - "</td> - <td class='mw-input'>" . + '</td> + <td class="mw-input">' . $tagFilterSelector . - "</td> - </tr>" ) : '' ) . - ($wgEnableNewpagesUserFilter ? - "<tr> - <td class='mw-label'>" . + '</td> + </tr>' ) : '' ) . + ( $wgEnableNewpagesUserFilter ? + '<tr> + <td class="mw-label">' . Xml::label( wfMsg( 'newpages-username' ), 'mw-np-username' ) . - "</td> - <td class='mw-input'>" . + '</td> + <td class="mw-input">' . Xml::input( 'username', 30, $userText, array( 'id' => 'mw-np-username' ) ) . - "</td> - </tr>" : "" ) . - "<tr> <td></td> - <td class='mw-submit'>" . + '</td> + </tr>' : '' ) . + '<tr> <td></td> + <td class="mw-submit">' . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . - "</td> - </tr>" . - "<tr> + '</td> + </tr>' . + '<tr> <td></td> - <td class='mw-input'>" . + <td class="mw-input">' . $this->filterLinks() . - "</td> - </tr>" . + '</td> + </tr>' . Xml::closeElement( 'table' ) . Xml::closeElement( 'fieldset' ) . $hidden . Xml::closeElement( 'form' ); - $wgOut->addHTML( $form ); + $this->getOutput()->addHTML( $form ); } protected function setSyndicated() { - global $wgOut; - $wgOut->setSyndicated( true ); - $wgOut->setFeedAppendQuery( wfArrayToCGI( $this->opts->getAllValues() ) ); + $out = $this->getOutput(); + $out->setSyndicated( true ); + $out->setFeedAppendQuery( wfArrayToCGI( $this->opts->getAllValues() ) ); } /** @@ -265,11 +289,20 @@ class SpecialNewpages extends IncludableSpecialPage { * @return String */ public function formatRow( $result ) { - global $wgLang, $wgContLang; + global $wgLang; + + # 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, + ); + $rev = new Revision( $row ); $classes = array(); - - $dm = $wgContLang->getDirMark(); + + $dm = $wgLang->getDirMark(); $title = Title::makeTitleSafe( $result->rc_namespace, $result->rc_title ); $time = Html::element( 'span', array( 'class' => 'mw-newpages-time' ), @@ -278,17 +311,18 @@ class SpecialNewpages extends IncludableSpecialPage { $query = array( 'redirect' => 'no' ); - if( $this->patrollable( $result ) ) + if( $this->patrollable( $result ) ) { $query['rcid'] = $result->rc_id; + } - $plink = $this->skin->linkKnown( + $plink = $this->getSkin()->linkKnown( $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 ); - $histLink = $this->skin->linkKnown( + $histLink = $this->getSkin()->linkKnown( $title, wfMsgHtml( 'hist' ), array(), @@ -300,10 +334,10 @@ class SpecialNewpages extends IncludableSpecialPage { '[' . wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ), $wgLang->formatNum( $result->length ) ) . ']' ); - $ulink = $this->skin->userLink( $result->rc_user, $result->rc_user_text ) . ' ' . - $this->skin->userToolLinks( $result->rc_user, $result->rc_user_text ); - $comment = $this->skin->commentBlock( $result->rc_comment ); - + + $ulink = $this->getSkin()->revUserTools( $rev ); + $comment = $this->getSkin()->revComment( $rev ); + if ( $this->patrollable( $result ) ) { $classes[] = 'not-patrolled'; } @@ -321,7 +355,7 @@ class SpecialNewpages extends IncludableSpecialPage { $tagDisplay = ''; } - $css = count($classes) ? ' class="'.implode( " ", $classes).'"' : ''; + $css = count( $classes ) ? ' class="' . implode( ' ', $classes ) . '"' : ''; return "<li{$css}>{$time} {$dm}{$plink} {$hist} {$dm}{$length} {$dm}{$ulink} {$comment} {$tagDisplay}</li>\n"; } @@ -333,8 +367,7 @@ class SpecialNewpages extends IncludableSpecialPage { * @return Boolean */ protected function patrollable( $result ) { - global $wgUser; - return ( $wgUser->useNPPatrol() && !$result->rc_patrolled ); + return ( $this->getUser()->useNPPatrol() && !$result->rc_patrolled ); } /** @@ -343,22 +376,23 @@ class SpecialNewpages extends IncludableSpecialPage { * @param $type String */ protected function feed( $type ) { - global $wgFeed, $wgFeedClasses, $wgFeedLimit, $wgOut; + global $wgFeed, $wgFeedClasses, $wgFeedLimit; if ( !$wgFeed ) { - $wgOut->addWikiMsg( 'feed-unavailable' ); + $this->getOutput()->addWikiMsg( 'feed-unavailable' ); return; } if( !isset( $wgFeedClasses[$type] ) ) { - $wgOut->addWikiMsg( 'feed-invalid' ); + $this->getOutput()->addWikiMsg( 'feed-invalid' ); return; } $feed = new $wgFeedClasses[$type]( $this->feedTitle(), wfMsgExt( 'tagline', 'parsemag' ), - $this->getTitle()->getFullUrl() ); + $this->getTitle()->getFullUrl() + ); $pager = new NewPagesPager( $this, $this->opts ); $limit = $this->opts->getValue( 'limit' ); @@ -375,8 +409,7 @@ class SpecialNewpages extends IncludableSpecialPage { protected function feedTitle() { global $wgLanguageCode, $wgSitename; - $page = SpecialPage::getPage( 'Newpages' ); - $desc = $page->getDescription(); + $desc = $this->getDescription(); return "$wgSitename - $desc [$wgLanguageCode]"; } @@ -392,7 +425,8 @@ class SpecialNewpages extends IncludableSpecialPage { $title->getFullURL(), $date, $this->feedItemAuthor( $row ), - $comments); + $comments + ); } else { return null; } @@ -406,7 +440,7 @@ class SpecialNewpages extends IncludableSpecialPage { $revision = Revision::newFromId( $row->rev_id ); if( $revision ) { return '<p>' . htmlspecialchars( $revision->getUserText() ) . wfMsgForContent( 'colon-separator' ) . - htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) . + htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) . "</p>\n<hr />\n<div>" . nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>"; } @@ -419,7 +453,12 @@ class SpecialNewpages extends IncludableSpecialPage { */ class NewPagesPager extends ReverseChronologicalPager { // Stored opts - protected $opts, $mForm; + protected $opts; + + /** + * @var HtmlForm + */ + protected $mForm; function __construct( $form, FormOptions $opts ) { parent::__construct(); @@ -427,15 +466,30 @@ class NewPagesPager extends ReverseChronologicalPager { $this->opts = $opts; } + /** + * @return Title + */ function getTitle() { static $title = null; - if ( $title === null ) + if ( $title === null ) { $title = $this->mForm->getTitle(); + } return $title; } + /** + * @return User + */ + function getUser() { + static $user = null; + if ( $user === null ) { + $user = $this->mForm->getUser(); + } + return $user; + } + function getQueryInfo() { - global $wgEnableNewpagesUserFilter, $wgGroupPermissions, $wgUser; + global $wgEnableNewpagesUserFilter, $wgGroupPermissions; $conds = array(); $conds['rc_new'] = 1; @@ -461,7 +515,7 @@ class NewPagesPager extends ReverseChronologicalPager { $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' ) && $wgUser->useNPPatrol() ) { + if( $this->opts->getValue( 'hidepatrolled' ) && $this->getUser()->useNPPatrol() ) { $conds['rc_patrolled'] = 0; } if( $this->opts->getValue( 'hidebots' ) ) { @@ -471,31 +525,39 @@ class NewPagesPager extends ReverseChronologicalPager { if ( $this->opts->getValue( 'hideredirs' ) ) { $conds['page_is_redirect'] = 0; } - + // Allow changes to the New Pages query - wfRunHooks('SpecialNewpagesConditions', array(&$this, $this->opts, &$conds)); + $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', + 'page_len AS length', 'page_latest AS rev_id', 'ts_tags' + ); + $join_conds = array( 'page' => array( 'INNER JOIN', 'page_id=rc_cur_id' ) ); + + wfRunHooks( 'SpecialNewpagesConditions', + array( &$this, $this->opts, &$conds, &$tables, &$fields, &$join_conds ) ); $info = array( - 'tables' => array( 'recentchanges', 'page' ), - 'fields' => 'rc_namespace,rc_title, rc_cur_id, rc_user,rc_user_text,rc_comment, - rc_timestamp,rc_patrolled,rc_id,page_len as length, page_latest as rev_id, ts_tags', - 'conds' => $conds, - 'options' => array( 'USE INDEX' => array('recentchanges' => $rcIndexes) ), - 'join_conds' => array( - 'page' => array('INNER JOIN', 'page_id=rc_cur_id'), - ), + 'tables' => $tables, + 'fields' => $fields, + 'conds' => $conds, + 'options' => array( 'USE INDEX' => array( 'recentchanges' => $rcIndexes ) ), + 'join_conds' => $join_conds ); - ## Empty array for fields, it'll be set by us anyway. + // Empty array for fields, it'll be set by us anyway. $fields = array(); - ## Modify query for tags - ChangeTags::modifyDisplayQuery( $info['tables'], - $fields, - $info['conds'], - $info['join_conds'], - $info['options'], - $this->opts['tagfilter'] ); + // Modify query for tags + ChangeTags::modifyDisplayQuery( + $info['tables'], + $fields, + $info['conds'], + $info['join_conds'], + $info['options'], + $this->opts['tagfilter'] + ); return $info; } @@ -517,10 +579,10 @@ class NewPagesPager extends ReverseChronologicalPager { $linkBatch->add( $row->rc_namespace, $row->rc_title ); } $linkBatch->execute(); - return "<ul>"; + return '<ul>'; } function getEndBody() { - return "</ul>"; + return '</ul>'; } } diff --git a/includes/specials/SpecialPasswordReset.php b/includes/specials/SpecialPasswordReset.php new file mode 100644 index 00000000..1caa2c51 --- /dev/null +++ b/includes/specials/SpecialPasswordReset.php @@ -0,0 +1,273 @@ +<?php +/** + * Implements Special:Blankpage + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage + */ + +/** + * Special page for requesting a password reset email + * + * @ingroup SpecialPage + */ +class SpecialPasswordReset extends FormSpecialPage { + + public function __construct() { + parent::__construct( 'PasswordReset' ); + } + + public function userCanExecute( User $user ) { + $error = $this->canChangePassword( $user ); + if ( is_string( $error ) ) { + throw new ErrorPageError( 'internalerror', $error ); + } else if ( !$error ) { + throw new ErrorPageError( 'internalerror', 'resetpass_forbidden' ); + } + + return parent::userCanExecute( $user ); + } + + protected function getFormFields() { + global $wgPasswordResetRoutes, $wgAuth; + $a = array(); + if ( isset( $wgPasswordResetRoutes['username'] ) && $wgPasswordResetRoutes['username'] ) { + $a['Username'] = array( + 'type' => 'text', + 'label-message' => 'passwordreset-username', + ); + } + + if ( isset( $wgPasswordResetRoutes['email'] ) && $wgPasswordResetRoutes['email'] ) { + $a['Email'] = array( + 'type' => 'email', + 'label-message' => 'passwordreset-email', + ); + } + + if ( isset( $wgPasswordResetRoutes['domain'] ) && $wgPasswordResetRoutes['domain'] ) { + $domains = $wgAuth->domainList(); + $a['Domain'] = array( + 'type' => 'select', + 'options' => $domains, + 'label-message' => 'passwordreset-domain', + ); + } + + return $a; + } + + public function alterForm( HTMLForm $form ) { + $form->setSubmitText( wfMessage( "mailmypassword" ) ); + } + + protected function preText() { + global $wgPasswordResetRoutes; + $i = 0; + if ( isset( $wgPasswordResetRoutes['username'] ) && $wgPasswordResetRoutes['username'] ) { + $i++; + } + if ( isset( $wgPasswordResetRoutes['email'] ) && $wgPasswordResetRoutes['email'] ) { + $i++; + } + if ( isset( $wgPasswordResetRoutes['domain'] ) && $wgPasswordResetRoutes['domain'] ) { + $i++; + } + return wfMessage( 'passwordreset-pretext', $i )->parseAsBlock(); + } + + /** + * Process the form. At this point we know that the user passes all the criteria in + * userCanExecute(), and if the data array contains 'Username', etc, then Username + * resets are allowed. + * @param $data array + * @return Bool|Array + */ + public function onSubmit( array $data ) { + global $wgAuth; + + if ( isset( $data['Domain'] ) ) { + if ( $wgAuth->validDomain( $data['Domain'] ) ) { + $wgAuth->setDomain( $data['Domain'] ); + } else { + $wgAuth->setDomain( 'invaliddomain' ); + } + } + + if ( isset( $data['Username'] ) && $data['Username'] !== '' ) { + $method = 'username'; + $users = array( User::newFromName( $data['Username'] ) ); + } elseif ( isset( $data['Email'] ) + && $data['Email'] !== '' + && Sanitizer::validateEmail( $data['Email'] ) ) + { + $method = 'email'; + $res = wfGetDB( DB_SLAVE )->select( + 'user', + '*', + array( 'user_email' => $data['Email'] ), + __METHOD__ + ); + if ( $res ) { + $users = array(); + foreach( $res as $row ){ + $users[] = User::newFromRow( $row ); + } + } else { + // Some sort of database error, probably unreachable + throw new MWException( 'Unknown database error in ' . __METHOD__ ); + } + } else { + // The user didn't supply any data + return false; + } + + // Check for hooks (captcha etc), and allow them to modify the users list + $error = array(); + if ( !wfRunHooks( 'SpecialPasswordResetOnSubmit', array( &$users, $data, &$error ) ) ) { + return array( $error ); + } + + if( count( $users ) == 0 ){ + if( $method == 'email' ){ + // Don't reveal whether or not an email address is in use + return true; + } else { + return array( 'noname' ); + } + } + + $firstUser = $users[0]; + + if ( !$firstUser instanceof User || !$firstUser->getID() ) { + return array( array( 'nosuchuser', $data['Username'] ) ); + } + + // Check against the rate limiter + if ( $this->getUser()->pingLimiter( 'mailpassword' ) ) { + throw new ThrottledError; + } + + // Check against password throttle + foreach ( $users as $user ) { + if ( $user->isPasswordReminderThrottled() ) { + global $wgPasswordReminderResendTime; + # Round the time in hours to 3 d.p., in case someone is specifying + # minutes or seconds. + return array( array( 'throttled-mailpassword', round( $wgPasswordReminderResendTime, 3 ) ) ); + } + } + + global $wgNewPasswordExpiry; + + // All the users will have the same email address + if ( $firstUser->getEmail() == '' ) { + // This won't be reachable from the email route, so safe to expose the username + return array( array( 'noemail', $firstUser->getName() ) ); + } + + // We need to have a valid IP address for the hook, but per bug 18347, we should + // send the user's name if they're logged in. + $ip = wfGetIP(); + if ( !$ip ) { + return array( 'badipaddress' ); + } + $caller = $this->getUser(); + wfRunHooks( 'User::mailPasswordInternal', array( &$caller, &$ip, &$firstUser ) ); + $username = $caller->getName(); + $msg = IP::isValid( $username ) + ? 'passwordreset-emailtext-ip' + : 'passwordreset-emailtext-user'; + + $passwords = array(); + foreach ( $users as $user ) { + $password = $user->randomPassword(); + $user->setNewpassword( $password ); + $user->saveSettings(); + $passwords[] = wfMessage( 'passwordreset-emailelement', $user->getName(), $password ); + } + $passwordBlock = implode( "\n\n", $passwords ); + + // Send in the user's language; which should hopefully be the same + $userLanguage = $firstUser->getOption( 'language' ); + + $body = wfMessage( $msg )->inLanguage( $userLanguage ); + $body->params( + $username, + $passwordBlock, + count( $passwords ), + Title::newMainPage()->getCanonicalUrl(), + round( $wgNewPasswordExpiry / 86400 ) + ); + + $title = wfMessage( 'passwordreset-emailtitle' ); + + $result = $firstUser->sendMail( $title->text(), $body->text() ); + + if ( $result->isGood() ) { + 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... :( + return array( array( 'mailerror', $result->getMessage() ) ); + } + } + + public function onSuccess() { + $this->getOutput()->addWikiMsg( 'passwordreset-emailsent' ); + $this->getOutput()->returnToMain(); + } + + function canChangePassword(User $user) { + global $wgPasswordResetRoutes, $wgAuth; + + // Maybe password resets are disabled, or there are no allowable routes + if ( !is_array( $wgPasswordResetRoutes ) || + !in_array( true, array_values( $wgPasswordResetRoutes ) ) ) { + return 'passwordreset-disabled'; + } + + // Maybe the external auth plugin won't allow local password changes + if ( !$wgAuth->allowPasswordChange() ) { + return 'resetpass_forbidden'; + } + + // 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() ) { + return 'blocked-mailpassword'; + } + + return true; + } + + + /** + * Hide the password reset page if resets are disabled. + * @return Bool + */ + function isListed() { + global $wgUser; + + if ( $this->canChangePassword( $wgUser ) === true ) { + return parent::isListed(); + } + + return false; + } +}
\ No newline at end of file diff --git a/includes/specials/SpecialPopularpages.php b/includes/specials/SpecialPopularpages.php index 375cefdf..7c7190ad 100644 --- a/includes/specials/SpecialPopularpages.php +++ b/includes/specials/SpecialPopularpages.php @@ -28,8 +28,8 @@ */ class PopularPagesPage extends QueryPage { - function getName() { - return "Popularpages"; + function __construct( $name = 'Popularpages' ) { + parent::__construct( $name ); } function isExpensive() { @@ -38,31 +38,21 @@ class PopularPagesPage extends QueryPage { } function isSyndicated() { return false; } - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - $page = $dbr->tableName( 'page' ); - - $query = - "SELECT 'Popularpages' as type, - page_namespace as namespace, - page_title as title, - page_counter as value - FROM $page "; - $where = - "WHERE page_is_redirect=0 AND page_namespace"; - - global $wgContentNamespaces; - if( empty( $wgContentNamespaces ) ) { - $where .= '='.NS_MAIN; - } else if( count( $wgContentNamespaces ) > 1 ) { - $where .= ' in (' . implode( ', ', $wgContentNamespaces ) . ')'; - } else { - $where .= '='.$wgContentNamespaces[0]; - } - - return $query . $where; + function getQueryInfo() { + return array ( + 'tables' => array( 'page' ), + 'fields' => array( 'page_namespace AS namespace', + 'page_title AS title', + 'page_counter AS value'), + 'conds' => array( 'page_is_redirect' => 0, + 'page_namespace' => MWNamespace::getContentNamespaces() ) ); } + /** + * @param $skin Skin + * @param $result + * @return string + */ function formatResult( $skin, $result ) { global $wgLang, $wgContLang; $title = Title::makeTitle( $result->namespace, $result->title ); @@ -78,14 +68,3 @@ class PopularPagesPage extends QueryPage { return wfSpecialList($link, $nv); } } - -/** - * Constructor - */ -function wfSpecialPopularpages() { - list( $limit, $offset ) = wfCheckLimits(); - - $ppp = new PopularPagesPage(); - - return $ppp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialPreferences.php b/includes/specials/SpecialPreferences.php index e63aeee6..edc26bc1 100644 --- a/includes/specials/SpecialPreferences.php +++ b/includes/specials/SpecialPreferences.php @@ -52,7 +52,6 @@ class SpecialPreferences extends SpecialPage { return; } - $wgOut->addModules( 'mediawiki.legacy.prefs' ); $wgOut->addModules( 'mediawiki.special.preferences' ); if ( $wgRequest->getCheck( 'success' ) ) { @@ -61,7 +60,7 @@ class SpecialPreferences extends SpecialPage { 'savedprefs' ); } - + if ( $wgRequest->getCheck( 'eauth' ) ) { $wgOut->wrapWikiMsg( "<div class='error' style='clear: both;'>\n$1\n</div>", 'eauthentsent', $wgUser->getName() ); @@ -78,7 +77,7 @@ class SpecialPreferences extends SpecialPage { $wgOut->addWikiMsg( 'prefs-reset-intro' ); - $htmlForm = new HTMLForm( array(), 'prefs-restore' ); + $htmlForm = new HTMLForm( array(), $this->getContext(), 'prefs-restore' ); $htmlForm->setSubmitText( wfMsg( 'restoreprefs' ) ); $htmlForm->setTitle( $this->getTitle( 'reset' ) ); diff --git a/includes/specials/SpecialPrefixindex.php b/includes/specials/SpecialPrefixindex.php index 09e7734c..28be4daf 100644 --- a/includes/specials/SpecialPrefixindex.php +++ b/includes/specials/SpecialPrefixindex.php @@ -28,11 +28,11 @@ */ class SpecialPrefixindex extends SpecialAllpages { // Inherit $maxPerPage - + 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) @@ -42,34 +42,39 @@ class SpecialPrefixindex extends SpecialAllpages { $this->setHeaders(); $this->outputHeader(); + $wgOut->addModuleStyles( 'mediawiki.special' ); # GET values $from = $wgRequest->getVal( 'from', '' ); $prefix = $wgRequest->getVal( 'prefix', '' ); - $namespace = $wgRequest->getInt( 'namespace' ); - $namespaces = $wgContLang->getNamespaces(); + $ns = $wgRequest->getIntOrNull( 'namespace' ); + $namespace = (int)$ns; // if no namespace given, use 0 (NS_MAIN). - $wgOut->setPagetitle( ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces ) ) ) - ? wfMsg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) - : wfMsg( 'prefixindex' ) + $namespaces = $wgContLang->getNamespaces(); + $wgOut->setPagetitle( + ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces ) ) ) + ? wfMsg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) + : wfMsg( 'prefixindex' ) ); $showme = ''; - if( isset( $par ) ){ + if( isset( $par ) ) { $showme = $par; - } elseif( $prefix != '' ){ + } elseif( $prefix != '' ) { $showme = $prefix; - } elseif( $from != '' ){ + } elseif( $from != '' ) { // For back-compat with Special:Allpages $showme = $from; } - if ($showme != '' || $namespace) { + + // Bug 27864: if transcluded, show all pages instead of the form. + if ( $this->including() || $showme != '' || $ns !== null ) { $this->showPrefixChunk( $namespace, $showme, $from ); } else { $wgOut->addHTML( $this->namespacePrefixForm( $namespace, null ) ); } } - + /** * HTML for the top form * @param $namespace Integer: a namespace constant (default NS_MAIN). @@ -115,9 +120,9 @@ class SpecialPrefixindex extends SpecialAllpages { * @param $from String: list all pages from this name (default FALSE) */ function showPrefixChunk( $namespace = NS_MAIN, $prefix, $from = null ) { - global $wgOut, $wgUser, $wgContLang, $wgLang; + global $wgOut, $wgContLang, $wgLang; - $sk = $wgUser->getSkin(); + $sk = $this->getSkin(); if (!isset($from)) $from = $prefix; @@ -126,7 +131,7 @@ class SpecialPrefixindex extends SpecialAllpages { $namespaces = $wgContLang->getNamespaces(); if ( !$prefixList || !$fromList ) { - $out = wfMsgWikiHtml( 'allpagesbadtitle' ); + $out = wfMsgExt( 'allpagesbadtitle', 'parse' ); } elseif ( !in_array( $namespace, array_keys( $namespaces ) ) ) { // Show errormessage and reset to NS_MAIN $out = wfMsgExt( 'allpages-bad-ns', array( 'parseinline' ), $namespace ); @@ -135,7 +140,7 @@ class SpecialPrefixindex extends SpecialAllpages { list( $namespace, $prefixKey, $prefix ) = $prefixList; list( /* $fromNS */, $fromKey, ) = $fromList; - ### FIXME: should complain if $fromNs != $namespace + ### @todo FIXME: Should complain if $fromNs != $namespace $dbr = wfGetDB( DB_SLAVE ); @@ -154,12 +159,12 @@ class SpecialPrefixindex extends SpecialAllpages { ) ); - ### FIXME: side link to previous + ### @todo FIXME: Side link to previous $n = 0; if( $res->numRows() > 0 ) { $out = Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-prefixindex-list-table' ) ); - + while( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) { $t = Title::makeTitle( $s->page_namespace, $s->page_title ); if( $t ) { @@ -190,6 +195,7 @@ class SpecialPrefixindex extends SpecialAllpages { } } + $footer = ''; if ( $this->including() ) { $out2 = ''; } else { @@ -200,8 +206,7 @@ class SpecialPrefixindex extends SpecialAllpages { <td>' . $nsForm . '</td> - <td id="mw-prefixindex-nav-form">' . - $sk->linkKnown( $self, wfMsgHtml( 'allpages' ) ); + <td id="mw-prefixindex-nav-form" class="mw-prefixindex-nav">'; if( isset( $res ) && $res && ( $n == $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) { $query = array( @@ -213,20 +218,21 @@ class SpecialPrefixindex extends SpecialAllpages { $query['namespace'] = $namespace; } - $out2 = $wgLang->pipeList( array( - $out2, - $sk->linkKnown( + $nextLink = Linker::linkKnown( $self, wfMsgHtml( 'nextpage', str_replace( '_',' ', htmlspecialchars( $s->page_title ) ) ), array(), $query - ) - ) ); + ); + $out2 .= $nextLink; + + $footer = "\n" . Html::element( "hr" ) + . Html::rawElement( "div", array( "class" => "mw-prefixindex-nav" ), $nextLink ); } $out2 .= "</td></tr>" . Xml::closeElement( 'table' ); } - $wgOut->addHTML( $out2 . $out ); + $this->getOutput()->addHTML( $out2 . $out . $footer ); } } diff --git a/includes/specials/SpecialProtectedpages.php b/includes/specials/SpecialProtectedpages.php index c676aa00..b1f61f09 100644 --- a/includes/specials/SpecialProtectedpages.php +++ b/includes/specials/SpecialProtectedpages.php @@ -76,14 +76,16 @@ class SpecialProtectedpages extends SpecialPage { * @return string Formatted <li> element */ public function formatRow( $row ) { - global $wgUser, $wgLang, $wgContLang; + global $wgUser, $wgLang; wfProfileIn( __METHOD__ ); - static $skin=null; + static $skin = null, $infinity = null; - if( is_null( $skin ) ) + if( is_null( $skin ) ){ $skin = $wgUser->getSkin(); + $infinity = wfGetDB( DB_SLAVE )->getInfinity(); + } $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title ); $link = $skin->link( $title ); @@ -100,17 +102,21 @@ class SpecialProtectedpages extends SpecialPage { $stxt = ''; - if( $row->pr_expiry != 'infinity' && strlen($row->pr_expiry) ) { - $expiry = Block::decodeExpiry( $row->pr_expiry ); + $expiry = $wgLang->formatExpiry( $row->pr_expiry, TS_MW ); + if( $expiry != $infinity ) { - $expiry_description = wfMsg( 'protect-expiring' , $wgLang->timeanddate( $expiry ) , - $wgLang->date( $expiry ) , $wgLang->time( $expiry ) ); + $expiry_description = wfMsg( + 'protect-expiring', + $wgLang->timeanddate( $expiry ), + $wgLang->date( $expiry ), + $wgLang->time( $expiry ) + ); $description_items[] = htmlspecialchars($expiry_description); } if(!is_null($size = $row->page_len)) { - $stxt = $wgContLang->getDirMark() . ' ' . $skin->formatRevisionSize( $size ); + $stxt = $wgLang->getDirMark() . ' ' . $skin->formatRevisionSize( $size ); } # Show a link to the change protection form for allowed users otherwise a link to the protection log @@ -139,7 +145,7 @@ class SpecialProtectedpages extends SpecialPage { return Html::rawElement( 'li', array(), - wfSpecialList( $link . $stxt, $wgLang->commaList( $description_items ) ) . $changeProtection ) . "\n"; + wfSpecialList( $link . $stxt, $wgLang->commaList( $description_items ), false ) . $changeProtection ) . "\n"; } /** @@ -193,7 +199,7 @@ class SpecialProtectedpages extends SpecialPage { return Xml::checkLabel( wfMsg('protectedpages-indef'), 'indefonly', 'indefonly', $indefOnly ) . "\n"; } - + /** * @return string Formatted HTML */ @@ -288,7 +294,7 @@ 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, + function __construct( $form, $conds = array(), $type, $level, $namespace, $sizetype='', $size=0, $indefonly = false, $cascadeonly = false ) { $this->mForm = $form; @@ -313,6 +319,10 @@ class ProtectedPagesPager extends AlphabeticPager { return ''; } + function getTitle() { + return SpecialPage::getTitleFor( 'Protectedpages' ); + } + function formatRow( $row ) { return $this->mForm->formatRow( $row ); } @@ -323,15 +333,16 @@ class ProtectedPagesPager extends AlphabeticPager { 'OR pr_expiry IS NULL)'; $conds[] = 'page_id=pr_page'; $conds[] = 'pr_type=' . $this->mDb->addQuotes( $this->type ); - + if( $this->sizetype=='min' ) { $conds[] = 'page_len>=' . $this->size; - } else if( $this->sizetype=='max' ) { + } elseif( $this->sizetype=='max' ) { $conds[] = 'page_len<=' . $this->size; } if( $this->indefonly ) { - $conds[] = "pr_expiry = 'infinity' OR pr_expiry IS NULL"; + $db = wfGetDB( DB_SLAVE ); + $conds[] = "pr_expiry = {$db->addQuotes( $db->getInfinity() )} OR pr_expiry IS NULL"; } if( $this->cascadeonly ) { $conds[] = "pr_cascade = '1'"; diff --git a/includes/specials/SpecialProtectedtitles.php b/includes/specials/SpecialProtectedtitles.php index 5b18d87f..5fb91af7 100644 --- a/includes/specials/SpecialProtectedtitles.php +++ b/includes/specials/SpecialProtectedtitles.php @@ -70,16 +70,20 @@ class SpecialProtectedtitles extends SpecialPage { /** * Callback function to output a restriction + * + * @return string */ function formatRow( $row ) { - global $wgUser, $wgLang; + global $wgLang; wfProfileIn( __METHOD__ ); - static $skin=null; + static $skin = null, $infinity = null; - if( is_null( $skin ) ) - $skin = $wgUser->getSkin(); + if( is_null( $skin ) ){ + $skin = $this->getSkin(); + $infinity = wfGetDB( DB_SLAVE )->getInfinity(); + } $title = Title::makeTitleSafe( $row->pt_namespace, $row->pt_title ); $link = $skin->link( $title ); @@ -90,19 +94,17 @@ class SpecialProtectedtitles extends SpecialPage { $description_items[] = $protType; - $stxt = ''; - - if ( $row->pt_expiry != 'infinity' && strlen($row->pt_expiry) ) { - $expiry = Block::decodeExpiry( $row->pt_expiry ); + $expiry = strlen( $row->pt_expiry ) ? $wgLang->formatExpiry( $row->pt_expiry, TS_MW ) : $infinity; + if( $expiry != $infinity ) { $expiry_description = wfMsg( 'protect-expiring', $wgLang->timeanddate( $expiry ) , $wgLang->date( $expiry ) , $wgLang->time( $expiry ) ); - $description_items[] = $expiry_description; + $description_items[] = htmlspecialchars($expiry_description); } wfProfileOut( __METHOD__ ); - return '<li>' . wfSpecialList( $link . $stxt, implode( $description_items, ', ' ) ) . "</li>\n"; + return '<li>' . wfSpecialList( $link, implode( $description_items, ', ' ) ) . "</li>\n"; } /** @@ -205,6 +207,10 @@ class ProtectedTitlesPager extends AlphabeticPager { return ''; } + function getTitle() { + return SpecialPage::getTitleFor( 'Protectedtitles' ); + } + function formatRow( $row ) { return $this->mForm->formatRow( $row ); } diff --git a/includes/specials/SpecialRandompage.php b/includes/specials/SpecialRandompage.php index 6299f384..e299dc77 100644 --- a/includes/specials/SpecialRandompage.php +++ b/includes/specials/SpecialRandompage.php @@ -43,7 +43,9 @@ class RandomPage extends SpecialPage { } public function setNamespace ( $ns ) { - if( !$ns || $ns < NS_MAIN ) $ns = NS_MAIN; + if( !$ns || $ns < NS_MAIN ) { + $ns = NS_MAIN; + } $this->namespaces = array( $ns ); } @@ -63,7 +65,7 @@ class RandomPage extends SpecialPage { if( is_null( $title ) ) { $this->setHeaders(); - $wgOut->addWikiMsg( strtolower( $this->mName ) . '-nopages', + $wgOut->addWikiMsg( strtolower( $this->mName ) . '-nopages', $this->getNsList(), count( $this->namespaces ) ); return; } @@ -83,15 +85,15 @@ class RandomPage extends SpecialPage { global $wgContLang; $nsNames = array(); foreach( $this->namespaces as $n ) { - if( $n === NS_MAIN ) - $nsNames[] = wfMsgForContent( 'blanknamespace' ); - else + if( $n === NS_MAIN ) { + $nsNames[] = wfMsgNoTrans( 'blanknamespace' ); + } else { $nsNames[] = $wgContLang->getNsText( $n ); + } } return $wgContLang->commaList( $nsNames ); } - /** * Choose a random title. * @return Title object (or null if nothing to choose from) @@ -99,7 +101,8 @@ 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 ); @@ -111,53 +114,50 @@ 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 + } else { return null; + } + } + + protected function getQueryInfo( $randstr ) { + $redirect = $this->isRedirect() ? 1 : 0; + + return array( + 'tables' => array( 'page' ), + 'fields' => array( 'page_title', 'page_namespace' ), + 'conds' => array_merge( array( + 'page_namespace' => $this->namespaces, + 'page_is_redirect' => $redirect, + 'page_random >= ' . $randstr + ), $this->extra ), + 'options' => array( + 'ORDER BY' => 'page_random', + 'USE INDEX' => 'page_random', + 'LIMIT' => 1, + ), + 'join_conds' => array() + ); } - private function selectRandomPageFromDB( $randstr ) { - global $wgExtraRandompageSQL; + private function selectRandomPageFromDB( $randstr, $fname = __METHOD__ ) { $dbr = wfGetDB( DB_SLAVE ); - $use_index = $dbr->useIndexClause( 'page_random' ); - $page = $dbr->tableName( 'page' ); + $query = $this->getQueryInfo( $randstr ); + $res = $dbr->select( + $query['tables'], + $query['fields'], + $query['conds'], + $fname, + $query['options'], + $query['join_conds'] + ); - $ns = implode( ",", $this->namespaces ); - $redirect = $this->isRedirect() ? 1 : 0; - - if ( $wgExtraRandompageSQL ) { - $this->extra[] = $wgExtraRandompageSQL; - } - if ( $this->addExtraSQL() ) { - $this->extra[] = $this->addExtraSQL(); - } - $extra = ''; - if ( $this->extra ) { - $extra = 'AND (' . implode( ') AND (', $this->extra ) . ')'; - } - $sql = "SELECT page_title, page_namespace - FROM $page $use_index - WHERE page_namespace IN ( $ns ) - AND page_is_redirect = $redirect - AND page_random >= $randstr - $extra - ORDER BY page_random"; - - $sql = $dbr->limitResult( $sql, 1, 0 ); - $res = $dbr->query( $sql, __METHOD__ ); return $dbr->fetchObject( $res ); } - - /* an alternative to $wgExtraRandompageSQL so subclasses - * can add their own SQL by overriding this function - * @deprecated, append to $this->extra instead - */ - public function addExtraSQL() { - return ''; - } } diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php index c012beca..6c78ced0 100644 --- a/includes/specials/SpecialRecentchanges.php +++ b/includes/specials/SpecialRecentchanges.php @@ -28,6 +28,7 @@ */ class SpecialRecentChanges extends IncludableSpecialPage { var $rcOptions, $rcSubpage; + protected $customFilters; public function __construct( $name = 'Recentchanges' ) { parent::__construct( $name ); @@ -39,22 +40,22 @@ class SpecialRecentChanges extends IncludableSpecialPage { * @return FormOptions */ public function getDefaultOptions() { - global $wgUser; $opts = new FormOptions(); - $opts->add( 'days', (int)$wgUser->getOption( 'rcdays' ) ); - $opts->add( 'limit', (int)$wgUser->getOption( 'rclimit' ) ); + $opts->add( 'days', (int)$this->getUser()->getOption( 'rcdays' ) ); + $opts->add( 'limit', (int)$this->getUser()->getOption( 'rclimit' ) ); $opts->add( 'from', '' ); - $opts->add( 'hideminor', $wgUser->getBoolOption( 'hideminor' ) ); + $opts->add( 'hideminor', $this->getUser()->getBoolOption( 'hideminor' ) ); $opts->add( 'hidebots', true ); $opts->add( 'hideanons', false ); $opts->add( 'hideliu', false ); - $opts->add( 'hidepatrolled', $wgUser->getBoolOption( 'hidepatrolled' ) ); + $opts->add( 'hidepatrolled', $this->getUser()->getBoolOption( 'hidepatrolled' ) ); $opts->add( 'hidemyself', false ); $opts->add( 'namespace', '', FormOptions::INTNULL ); $opts->add( 'invert', false ); + $opts->add( 'associated', false ); $opts->add( 'categories', '' ); $opts->add( 'categories_any', false ); @@ -65,13 +66,20 @@ class SpecialRecentChanges extends IncludableSpecialPage { /** * Create a FormOptions object with options as specified by the user * + * @param $parameters array + * * @return FormOptions */ public function setup( $parameters ) { - global $wgRequest; - $opts = $this->getDefaultOptions(); - $opts->fetchValuesFromRequest( $wgRequest ); + + $this->customFilters = array(); + wfRunHooks( 'SpecialRecentChangesFilters', array( $this, &$this->customFilters ) ); + foreach( $this->customFilters as $key => $params ) { + $opts->add( $key, $params['default'] ); + } + + $opts->fetchValuesFromRequest( $this->getRequest() ); // Give precedence to subpage syntax if( $parameters !== null ) { @@ -88,10 +96,10 @@ class SpecialRecentChanges extends IncludableSpecialPage { * @return FormOptions */ public function feedSetup() { - global $wgFeedLimit, $wgRequest; + global $wgFeedLimit; $opts = $this->getDefaultOptions(); # Feed is cached on limit,hideminor,namespace; other params would randomly not work - $opts->fetchValuesFromRequest( $wgRequest, array( 'limit', 'hideminor', 'namespace' ) ); + $opts->fetchValuesFromRequest( $this->getRequest(), array( 'limit', 'hideminor', 'namespace' ) ); $opts->validateIntBounds( 'limit', 0, $wgFeedLimit ); return $opts; } @@ -101,9 +109,12 @@ class SpecialRecentChanges extends IncludableSpecialPage { */ public function getOptions() { if ( $this->rcOptions === null ) { - global $wgRequest; - $feedFormat = $wgRequest->getVal( 'feed' ); - $this->rcOptions = $feedFormat ? $this->feedSetup() : $this->setup( $this->rcSubpage ); + if ( $this->including() ) { + $isFeed = false; + } else { + $isFeed = (bool)$this->getRequest()->getVal( 'feed' ); + } + $this->rcOptions = $isFeed ? $this->feedSetup() : $this->setup( $this->rcSubpage ); } return $this->rcOptions; } @@ -115,12 +126,11 @@ class SpecialRecentChanges extends IncludableSpecialPage { * @param $subpage String */ public function execute( $subpage ) { - global $wgRequest, $wgOut; $this->rcSubpage = $subpage; - $feedFormat = $wgRequest->getVal( 'feed' ); + $feedFormat = $this->including() ? null : $this->getRequest()->getVal( 'feed' ); # 10 seconds server-side caching max - $wgOut->setSquidMaxage( 10 ); + $this->getOutput()->setSquidMaxage( 10 ); # Check if the client has a cached version $lastmod = $this->checkLastModified( $feedFormat ); if( $lastmod === false ) { @@ -130,6 +140,7 @@ class SpecialRecentChanges extends IncludableSpecialPage { $opts = $this->getOptions(); $this->setHeaders(); $this->outputHeader(); + $this->addRecentChangesJS(); // Fetch results, prepare a batch link existence check query $conds = $this->buildMainQueryConds( $opts ); @@ -144,8 +155,8 @@ class SpecialRecentChanges extends IncludableSpecialPage { if( !$feedFormat ) { $batch = new LinkBatch; foreach( $rows as $row ) { - $batch->add( NS_USER, $row->rc_user_text ); - $batch->add( NS_USER_TALK, $row->rc_user_text ); + $batch->add( 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(); @@ -169,7 +180,8 @@ class SpecialRecentChanges extends IncludableSpecialPage { $changesFeed = new ChangesFeed( $feedFormat, 'rcfeed' ); $formatter = $changesFeed->getFeedObject( wfMsgForContent( 'recentchanges' ), - wfMsgForContent( 'recentchanges-feed-description' ) + wfMsgForContent( 'recentchanges-feed-description' ), + $this->getTitle()->getFullURL() ); return array( $changesFeed, $formatter ); } @@ -184,20 +196,42 @@ class SpecialRecentChanges extends IncludableSpecialPage { public function parseParameters( $par, FormOptions $opts ) { $bits = preg_split( '/\s*,\s*/', trim( $par ) ); foreach( $bits as $bit ) { - if( 'hidebots' === $bit ) $opts['hidebots'] = true; - if( 'bots' === $bit ) $opts['hidebots'] = false; - if( 'hideminor' === $bit ) $opts['hideminor'] = true; - if( 'minor' === $bit ) $opts['hideminor'] = false; - if( 'hideliu' === $bit ) $opts['hideliu'] = true; - if( 'hidepatrolled' === $bit ) $opts['hidepatrolled'] = true; - if( 'hideanons' === $bit ) $opts['hideanons'] = true; - if( 'hidemyself' === $bit ) $opts['hidemyself'] = true; + if( 'hidebots' === $bit ) { + $opts['hidebots'] = true; + } + if( 'bots' === $bit ) { + $opts['hidebots'] = false; + } + if( 'hideminor' === $bit ) { + $opts['hideminor'] = true; + } + if( 'minor' === $bit ) { + $opts['hideminor'] = false; + } + if( 'hideliu' === $bit ) { + $opts['hideliu'] = true; + } + if( 'hidepatrolled' === $bit ) { + $opts['hidepatrolled'] = true; + } + if( 'hideanons' === $bit ) { + $opts['hideanons'] = true; + } + 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 ) ) $opts['limit'] = $m[1]; - if( preg_match( '/^days=(\d+)$/', $bit, $m ) ) $opts['days'] = $m[1]; + if( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) { + $opts['limit'] = $m[1]; + } + if( preg_match( '/^days=(\d+)$/', $bit, $m ) ) { + $opts['days'] = $m[1]; + } } } @@ -210,11 +244,10 @@ class SpecialRecentChanges extends IncludableSpecialPage { * @return String or false */ public function checkLastModified( $feedFormat ) { - global $wgUseRCPatrol, $wgOut; $dbr = wfGetDB( DB_SLAVE ); $lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', false, __METHOD__ ); - if( $feedFormat || !$wgUseRCPatrol ) { - if( $lastmod && $wgOut->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; } @@ -229,8 +262,6 @@ class SpecialRecentChanges extends IncludableSpecialPage { * @return array */ public function buildMainQueryConds( FormOptions $opts ) { - global $wgUser; - $dbr = wfGetDB( DB_SLAVE ); $conds = array(); @@ -261,35 +292,58 @@ class SpecialRecentChanges extends IncludableSpecialPage { $conds[] = 'rc_timestamp >= ' . $dbr->addQuotes( $cutoff ); - - $hidePatrol = $wgUser->useRCPatrol() && $opts['hidepatrolled']; + $hidePatrol = $this->getUser()->useRCPatrol() && $opts['hidepatrolled']; $hideLoggedInUsers = $opts['hideliu'] && !$forcebot; $hideAnonymousUsers = $opts['hideanons'] && !$forcebot; - if( $opts['hideminor'] ) $conds['rc_minor'] = 0; - if( $opts['hidebots'] ) $conds['rc_bot'] = 0; - if( $hidePatrol ) $conds['rc_patrolled'] = 0; - if( $forcebot ) $conds['rc_bot'] = 1; - if( $hideLoggedInUsers ) $conds[] = 'rc_user = 0'; - if( $hideAnonymousUsers ) $conds[] = 'rc_user != 0'; + if( $opts['hideminor'] ) { + $conds['rc_minor'] = 0; + } + if( $opts['hidebots'] ) { + $conds['rc_bot'] = 0; + } + if( $hidePatrol ) { + $conds['rc_patrolled'] = 0; + } + if( $forcebot ) { + $conds['rc_bot'] = 1; + } + if( $hideLoggedInUsers ) { + $conds[] = 'rc_user = 0'; + } + if( $hideAnonymousUsers ) { + $conds[] = 'rc_user != 0'; + } if( $opts['hidemyself'] ) { - if( $wgUser->getId() ) { - $conds[] = 'rc_user != ' . $dbr->addQuotes( $wgUser->getId() ); + if( $this->getUser()->getId() ) { + $conds[] = 'rc_user != ' . $dbr->addQuotes( $this->getUser()->getId() ); } else { - $conds[] = 'rc_user_text != ' . $dbr->addQuotes( $wgUser->getName() ); + $conds[] = 'rc_user_text != ' . $dbr->addQuotes( $this->getUser()->getName() ); } } # Namespace filtering if( $opts['namespace'] !== '' ) { - if( !$opts['invert'] ) { - $conds[] = 'rc_namespace = ' . $dbr->addQuotes( $opts['namespace'] ); + $selectedNS = $dbr->addQuotes( $opts['namespace'] ); + $operator = $opts['invert'] ? '!=' : '='; + $boolean = $opts['invert'] ? 'AND' : 'OR'; + + # namespace association (bug 2429) + if( !$opts['associated'] ) { + $condition = "rc_namespace $operator $selectedNS"; } else { - $conds[] = 'rc_namespace != ' . $dbr->addQuotes( $opts['namespace'] ); + # Also add the associated namespace + $associatedNS = $dbr->addQuotes( + MWNamespace::getAssociated( $opts['namespace'] ) + ); + $condition = "(rc_namespace $operator $selectedNS " + . $boolean + . " rc_namespace $operator $associatedNS)"; } - } + $conds[] = $condition; + } return $conds; } @@ -301,75 +355,94 @@ class SpecialRecentChanges extends IncludableSpecialPage { * @return database result or false (for Recentchangeslinked only) */ public function doMainQuery( $conds, $opts ) { - global $wgUser; - $tables = array( 'recentchanges' ); $join_conds = array(); - $query_options = array( 'USE INDEX' => array('recentchanges' => 'rc_timestamp') ); + $query_options = array( + 'USE INDEX' => array( 'recentchanges' => 'rc_timestamp' ) + ); - $uid = $wgUser->getId(); + $uid = $this->getUser()->getId(); $dbr = wfGetDB( DB_SLAVE ); $limit = $opts['limit']; $namespace = $opts['namespace']; - $select = '*'; $invert = $opts['invert']; + $associated = $opts['associated']; + $fields = array( $dbr->tableName( 'recentchanges' ) . '.*' ); // all rc columns // JOIN on watchlist for users - if( $uid ) { + if ( $uid ) { $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"); } - if ($wgUser->isAllowed("rollback")) { + if ( $this->getUser()->isAllowed( 'rollback' ) ) { $tables[] = 'page'; + $fields[] = 'page_latest'; $join_conds['page'] = array('LEFT JOIN', 'rc_cur_id=page_id'); } if ( !$this->including() ) { // Tag stuff. // Doesn't work when transcluding. See bug 23293 - $fields = array(); - // Fields are * in this case, so let the function modify an empty array to keep it happy. ChangeTags::modifyDisplayQuery( - $tables, $fields, $conds, $join_conds, $query_options, $opts['tagfilter'] + $tables, $fields, $conds, $join_conds, $query_options, + $opts['tagfilter'] ); } - if ( !wfRunHooks( 'SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$select ) ) ) + if ( !wfRunHooks( 'SpecialRecentChangesQuery', + 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 all pages NOT in a certain namespaces (inverted) + // (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( is_null( $namespace ) - || ( $invert && !is_null( $namespace ) ) + if( $namespace === '' + || ( $invert || $associated ) || $opts['tagfilter'] != '' || !$dbr->unionSupportsOrderAndLimit() ) { - $res = $dbr->select( $tables, '*', $conds, __METHOD__, + $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, $select, + $sqlNew = $dbr->selectSQLText( + $tables, + $fields, array( 'rc_new' => 1 ) + $conds, __METHOD__, - array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit, - 'USE INDEX' => array('recentchanges' => 'rc_timestamp') ), - $join_conds ); + array( + 'ORDER BY' => 'rc_timestamp DESC', + 'LIMIT' => $limit, + 'USE INDEX' => array( 'recentchanges' => 'new_name_timestamp' ) + ), + $join_conds + ); // Old pages - $sqlOld = $dbr->selectSQLText( $tables, '*', + $sqlOld = $dbr->selectSQLText( + $tables, + $fields, array( 'rc_new' => 0 ) + $conds, __METHOD__, - array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit, - 'USE INDEX' => array('recentchanges' => 'rc_timestamp') ), - $join_conds ); + 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); + $sql = $dbr->unionQueries( array( $sqlNew, $sqlOld ), false ) . + ' ORDER BY rc_timestamp DESC'; + $sql = $dbr->limitResult( $sql, $limit, false ); $res = $dbr->query( $sql, __METHOD__ ); } @@ -377,14 +450,13 @@ class SpecialRecentChanges extends IncludableSpecialPage { } /** - * Send output to $wgOut, only called if not used feeds + * Send output to the OutputPage object, only called if not used feeds * * @param $rows Array of database rows * @param $opts FormOptions */ public function webOutput( $rows, $opts ) { - global $wgOut, $wgUser, $wgRCShowWatchingUsers, $wgShowUpdatedMarker; - global $wgAllowCategorizedRecentChanges; + global $wgRCShowWatchingUsers, $wgShowUpdatedMarker, $wgAllowCategorizedRecentChanges; $limit = $opts['limit']; @@ -394,43 +466,47 @@ class SpecialRecentChanges extends IncludableSpecialPage { } // And now for the content - $wgOut->setFeedAppendQuery( $this->getFeedQuery() ); + $this->getOutput()->setFeedAppendQuery( $this->getFeedQuery() ); if( $wgAllowCategorizedRecentChanges ) { $this->filterByCategories( $rows, $opts ); } - $showWatcherCount = $wgRCShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' ); + $showWatcherCount = $wgRCShowWatchingUsers && $this->getUser()->getOption( 'shownumberswatching' ); $watcherCache = array(); $dbr = wfGetDB( DB_SLAVE ); $counter = 1; - $list = ChangesList::newFromUser( $wgUser ); + $list = ChangesList::newFromContext( $this->getContext() ); $s = $list->beginRecentChangesList(); foreach( $rows as $obj ) { - if( $limit == 0 ) break; + 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) ) { - $rc->notificationtimestamp = ($obj->rc_timestamp >= $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( !isset( $watcherCache[$obj->rc_namespace][$obj->rc_title] ) ) { $watcherCache[$obj->rc_namespace][$obj->rc_title] = - $dbr->selectField( 'watchlist', + $dbr->selectField( + 'watchlist', 'COUNT(*)', array( 'wl_namespace' => $obj->rc_namespace, 'wl_title' => $obj->rc_title, ), - __METHOD__ . '-watchers' ); + __METHOD__ . '-watchers' + ); } $rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title]; } @@ -438,7 +514,7 @@ class SpecialRecentChanges extends IncludableSpecialPage { --$limit; } $s .= $list->endRecentChangesList(); - $wgOut->addHTML( $s ); + $this->getOutput()->addHTML( $s ); } /** @@ -456,14 +532,16 @@ class SpecialRecentChanges extends IncludableSpecialPage { * @return String: XHTML */ public function doHeader( $opts ) { - global $wgScript, $wgOut; + global $wgScript; - $this->setTopText( $wgOut, $opts ); + $this->setTopText( $opts ); $defaults = $opts->getAllValues(); $nondefaults = $opts->getChangedValues(); - $opts->consumeValues( array( 'namespace', 'invert', 'tagfilter', - 'categories', 'categories_any' ) ); + $opts->consumeValues( array( + 'namespace', 'invert', 'associated', 'tagfilter', + 'categories', 'categories_any' + ) ); $panel = array(); $panel[] = $this->optionsPanel( $defaults, $nondefaults ); @@ -502,11 +580,11 @@ class SpecialRecentChanges extends IncludableSpecialPage { $panel[] = $form; $panelString = implode( "\n", $panel ); - $wgOut->addHTML( + $this->getOutput()->addHTML( Xml::fieldset( wfMsg( 'recentchanges-legend' ), $panelString, array( 'class' => 'rcoptions' ) ) ); - $this->setBottomText( $wgOut, $opts ); + $this->setBottomText( $opts ); } /** @@ -515,7 +593,7 @@ class SpecialRecentChanges extends IncludableSpecialPage { * @param $opts FormOptions * @return Array */ - function getExtraOptions( $opts ){ + function getExtraOptions( $opts ) { $extraOpts = array(); $extraOpts['namespace'] = $this->namespaceFilterForm( $opts ); @@ -525,8 +603,9 @@ class SpecialRecentChanges extends IncludableSpecialPage { } $tagFilter = ChangeTags::buildTagFilterSelector( $opts['tagfilter'] ); - if ( count($tagFilter) ) + if ( count( $tagFilter ) ) { $extraOpts['tagfilter'] = $tagFilter; + } wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) ); return $extraOpts; @@ -535,33 +614,41 @@ class SpecialRecentChanges extends IncludableSpecialPage { /** * Send the text to be displayed above the options * - * @param $out OutputPage * @param $opts FormOptions */ - function setTopText( OutputPage $out, FormOptions $opts ){ - $out->addWikiText( wfMsgForContentNoTrans( 'recentchangestext' ) ); + function setTopText( FormOptions $opts ) { + $this->getOutput()->addWikiText( wfMsgForContentNoTrans( 'recentchangestext' ) ); } /** * Send the text to be displayed after the options, for use in * Recentchangeslinked * - * @param $out OutputPage * @param $opts FormOptions */ - function setBottomText( OutputPage $out, FormOptions $opts ){} + function setBottomText( FormOptions $opts ) {} /** * Creates the choose namespace selection * + * @todo Uses radio buttons (HASHAR) * @param $opts FormOptions * @return String */ protected function namespaceFilterForm( FormOptions $opts ) { $nsSelect = Xml::namespaceSelector( $opts['namespace'], '' ); - $nsLabel = Xml::label( wfMsg('namespace'), 'namespace' ); - $invert = Xml::checkLabel( wfMsg('invert'), 'invert', 'nsinvert', $opts['invert'] ); - return array( $nsLabel, "$nsSelect $invert" ); + $nsLabel = Xml::label( wfMsg( 'namespace' ), 'namespace' ); + $invert = Xml::checkLabel( + wfMsg( 'invert' ), 'invert', 'nsinvert', + $opts['invert'], + array( 'title' => wfMsg( 'tooltip-invert' ) ) + ); + $associated = Xml::checkLabel( + wfMsg( 'namespace_association' ), 'associated', 'nsassociated', + $opts['associated'], + array( 'title' => wfMsg( 'tooltip-namespace_association' ) ) + ); + return array( $nsLabel, "$nsSelect $invert $associated" ); } /** @@ -571,10 +658,10 @@ class SpecialRecentChanges extends IncludableSpecialPage { * @return Array */ protected function categoryFilterForm( FormOptions $opts ) { - list( $label, $input ) = Xml::inputLabelSep( wfMsg('rc_categories'), + list( $label, $input ) = Xml::inputLabelSep( wfMsg( 'rc_categories' ), 'categories', 'mw-categories', false, $opts['categories'] ); - $input .= ' ' . Xml::checkLabel( wfMsg('rc_categories_any'), + $input .= ' ' . Xml::checkLabel( wfMsg( 'rc_categories_any' ), 'categories_any', 'mw-categories_any', $opts['categories_any'] ); return array( $label, $input ); @@ -597,7 +684,9 @@ class SpecialRecentChanges extends IncludableSpecialPage { $cats = array(); foreach( $categories as $cat ) { $cat = trim( $cat ); - if( $cat == '' ) continue; + if( $cat == '' ) { + continue; + } $cats[] = $cat; } @@ -605,10 +694,12 @@ 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 ) continue; # Page might have been deleted... + if( $id == 0 ) { + continue; # Page might have been deleted... + } if( !in_array( $id, $articles ) ) { $articles[] = $id; } @@ -620,18 +711,19 @@ class SpecialRecentChanges extends IncludableSpecialPage { } # Shortcut? - if( !count( $articles ) || !count( $cats ) ) - return ; + if( !count( $articles ) || !count( $cats ) ) { + return; + } # Look up $c = new Categoryfinder; - $c->seed( $articles, $cats, $opts['categories_any'] ? "OR" : "AND" ) ; + $c->seed( $articles, $cats, $opts['categories_any'] ? 'OR' : 'AND' ); $match = $c->run(); # 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]; } @@ -648,15 +740,12 @@ class SpecialRecentChanges extends IncludableSpecialPage { * @param $active Boolean: whether to show the link in bold */ function makeOptionsLink( $title, $override, $options, $active = false ) { - global $wgUser; - $sk = $wgUser->getSkin(); $params = $override + $options; + $text = htmlspecialchars( $title ); if ( $active ) { - return $sk->link( $this->getTitle(), '<strong>' . htmlspecialchars( $title ) . '</strong>', - array(), $params, array( 'known' ) ); - } else { - return $sk->link( $this->getTitle(), htmlspecialchars( $title ), array() , $params, array( 'known' ) ); + $text = '<strong>' . $text . '</strong>'; } + return Linker::linkKnown( $this->getTitle(), $text, array(), $params ); } /** @@ -666,20 +755,21 @@ class SpecialRecentChanges extends IncludableSpecialPage { * @param $nondefaults Array */ function optionsPanel( $defaults, $nondefaults ) { - global $wgLang, $wgUser, $wgRCLinkLimits, $wgRCLinkDays; + global $wgRCLinkLimits, $wgRCLinkDays; $options = $nondefaults + $defaults; $note = ''; - if( !wfEmptyMsg( 'rclegend', wfMsg('rclegend') ) ) { - $note .= '<div class="mw-rclegend">' . wfMsgExt( 'rclegend', array('parseinline') ) . "</div>\n"; + if( !wfEmptyMsg( 'rclegend' ) ) { + $note .= '<div class="mw-rclegend">' . + wfMsgExt( 'rclegend', array( 'parseinline' ) ) . "</div>\n"; } if( $options['from'] ) { $note .= wfMsgExt( 'rcnotefrom', array( 'parseinline' ), - $wgLang->formatNum( $options['limit'] ), - $wgLang->timeanddate( $options['from'], true ), - $wgLang->date( $options['from'], true ), - $wgLang->time( $options['from'], true ) ) . '<br />'; + $this->getLang()->formatNum( $options['limit'] ), + $this->getLang()->timeanddate( $options['from'], true ), + $this->getLang()->date( $options['from'], true ), + $this->getLang()->time( $options['from'], true ) ) . '<br />'; } # Sort data for display and make sure it's unique after we've added user data. @@ -692,50 +782,63 @@ class SpecialRecentChanges extends IncludableSpecialPage { // limit links foreach( $wgRCLinkLimits as $value ) { - $cl[] = $this->makeOptionsLink( $wgLang->formatNum( $value ), - array( 'limit' => $value ), $nondefaults, $value == $options['limit'] ) ; + $cl[] = $this->makeOptionsLink( $this->getLang()->formatNum( $value ), + array( 'limit' => $value ), $nondefaults, $value == $options['limit'] ); } - $cl = $wgLang->pipeList( $cl ); + $cl = $this->getLang()->pipeList( $cl ); // day links, reset 'from' to none foreach( $wgRCLinkDays as $value ) { - $dl[] = $this->makeOptionsLink( $wgLang->formatNum( $value ), - array( 'days' => $value, 'from' => '' ), $nondefaults, $value == $options['days'] ) ; + $dl[] = $this->makeOptionsLink( $this->getLang()->formatNum( $value ), + array( 'days' => $value, 'from' => '' ), $nondefaults, $value == $options['days'] ); } - $dl = $wgLang->pipeList( $dl ); + $dl = $this->getLang()->pipeList( $dl ); // show/hide links $showhide = array( wfMsg( 'show' ), wfMsg( 'hide' ) ); - $minorLink = $this->makeOptionsLink( $showhide[1-$options['hideminor']], - array( 'hideminor' => 1-$options['hideminor'] ), $nondefaults); - $botLink = $this->makeOptionsLink( $showhide[1-$options['hidebots']], - array( 'hidebots' => 1-$options['hidebots'] ), $nondefaults); - $anonsLink = $this->makeOptionsLink( $showhide[ 1 - $options['hideanons'] ], - array( 'hideanons' => 1 - $options['hideanons'] ), $nondefaults ); - $liuLink = $this->makeOptionsLink( $showhide[1-$options['hideliu']], - array( 'hideliu' => 1-$options['hideliu'] ), $nondefaults); - $patrLink = $this->makeOptionsLink( $showhide[1-$options['hidepatrolled']], - array( 'hidepatrolled' => 1-$options['hidepatrolled'] ), $nondefaults); - $myselfLink = $this->makeOptionsLink( $showhide[1-$options['hidemyself']], - array( 'hidemyself' => 1-$options['hidemyself'] ), $nondefaults); - - $links[] = wfMsgHtml( 'rcshowhideminor', $minorLink ); - $links[] = wfMsgHtml( 'rcshowhidebots', $botLink ); - $links[] = wfMsgHtml( 'rcshowhideanons', $anonsLink ); - $links[] = wfMsgHtml( 'rcshowhideliu', $liuLink ); - if( $wgUser->useRCPatrol() ) - $links[] = wfMsgHtml( 'rcshowhidepatr', $patrLink ); - $links[] = wfMsgHtml( 'rcshowhidemine', $myselfLink ); - $hl = $wgLang->pipeList( $links ); + $filters = array( + 'hideminor' => 'rcshowhideminor', + 'hidebots' => 'rcshowhidebots', + 'hideanons' => 'rcshowhideanons', + 'hideliu' => 'rcshowhideliu', + 'hidepatrolled' => 'rcshowhidepatr', + 'hidemyself' => 'rcshowhidemine' + ); + foreach ( $this->customFilters as $key => $params ) { + $filters[$key] = $params['msg']; + } + // Disable some if needed + if ( !$this->getUser()->useRCPatrol() ) { + unset( $filters['hidepatrolled'] ); + } + + $links = array(); + foreach ( $filters as $key => $msg ) { + $link = $this->makeOptionsLink( $showhide[1 - $options[$key]], + array( $key => 1-$options[$key] ), $nondefaults ); + $links[] = wfMsgHtml( $msg, $link ); + } // show from this onward link - $now = $wgLang->timeanddate( wfTimestampNow(), true ); - $tl = $this->makeOptionsLink( $now, array( 'from' => wfTimestampNow() ), $nondefaults ); + $timestamp = wfTimestampNow(); + $now = $this->getLang()->timeanddate( $timestamp, true ); + $tl = $this->makeOptionsLink( + $now, array( 'from' => $timestamp ), $nondefaults + ); $rclinks = wfMsgExt( 'rclinks', array( 'parseinline', 'replaceafter' ), - $cl, $dl, $hl ); + $cl, $dl, $this->getLang()->pipeList( $links ) ); $rclistfrom = wfMsgExt( 'rclistfrom', array( 'parseinline', 'replaceafter' ), $tl ); return "{$note}$rclinks<br />$rclistfrom"; } + + /** + * add javascript specific to the [[Special:RecentChanges]] page + */ + function addRecentChangesJS() { + $this->getOutput()->addModules( array( + 'mediawiki.special.recentchanges', + ) ); + } } diff --git a/includes/specials/SpecialRecentchangeslinked.php b/includes/specials/SpecialRecentchangeslinked.php index db0f554d..8b8369b5 100644 --- a/includes/specials/SpecialRecentchangeslinked.php +++ b/includes/specials/SpecialRecentchangeslinked.php @@ -46,9 +46,8 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { } public function feedSetup() { - global $wgRequest; $opts = parent::feedSetup(); - $opts['target'] = $wgRequest->getVal( 'target' ); + $opts['target'] = $this->getRequest()->getVal( 'target' ); return $opts; } @@ -56,14 +55,13 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { $feed = new ChangesFeed( $feedFormat, false ); $feedObj = $feed->getFeedObject( wfMsgForContent( 'recentchangeslinked-title', $this->getTargetTitle()->getPrefixedText() ), - wfMsgForContent( 'recentchangeslinked-feed' ) + wfMsgForContent( 'recentchangeslinked-feed' ), + $this->getTitle()->getFullUrl() ); return array( $feed, $feedObj ); } public function doMainQuery( $conds, $opts ) { - global $wgUser, $wgOut; - $target = $opts['target']; $showlinkedto = $opts['showlinkedto']; $limit = $opts['limit']; @@ -73,11 +71,11 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { } $title = Title::newFromURL( $target ); if( !$title || $title->getInterwiki() != '' ){ - $wgOut->wrapWikiMsg( "<div class=\"errorbox\">\n$1\n</div><br style=\"clear: both\" />", 'allpagesbadtitle' ); + $this->getOutput()->wrapWikiMsg( "<div class=\"errorbox\">\n$1\n</div><br style=\"clear: both\" />", 'allpagesbadtitle' ); return false; } - $wgOut->setPageTitle( wfMsg( 'recentchangeslinked-title', $title->getPrefixedText() ) ); + $this->getOutput()->setPageTitle( wfMsg( 'recentchangeslinked-title', $title->getPrefixedText() ) ); /* * Ordinary links are in the pagelinks table, while transclusions are @@ -99,13 +97,13 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { $query_options = array(); // left join with watchlist table to highlight watched rows - $uid = $wgUser->getId(); + $uid = $this->getUser()->getId(); if( $uid ) { $tables[] = 'watchlist'; $select[] = 'wl_user'; $join_conds['watchlist'] = array( 'LEFT JOIN', "wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace" ); } - if ( $wgUser->isAllowed( 'rollback' ) ) { + if ( $this->getUser()->isAllowed( 'rollback' ) ) { $tables[] = 'page'; $join_conds['page'] = array('LEFT JOIN', 'rc_cur_id=page_id'); $select[] = 'page_latest'; @@ -143,7 +141,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { // imagelinks and categorylinks tables have no xx_namespace field, and have xx_to instead of xx_title if( $link_table == 'imagelinks' ) $link_ns = NS_FILE; - else if( $link_table == 'categorylinks' ) $link_ns = NS_CATEGORY; + elseif( $link_table == 'categorylinks' ) $link_ns = NS_CATEGORY; else $link_ns = 0; if( $showlinkedto ) { @@ -171,16 +169,16 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { else $order = array(); - - $query = $dbr->selectSQLText( - array_merge( $tables, array( $link_table ) ), - $select, + + $query = $dbr->selectSQLText( + array_merge( $tables, array( $link_table ) ), + $select, $conds + $subconds, - __METHOD__, + __METHOD__, $order + $query_options, $join_conds + array( $link_table => array( 'INNER JOIN', $subjoin ) ) ); - + if( $dbr->unionSupportsOrderAndLimit()) $query = $dbr->limitResult( $query, $limit ); @@ -196,7 +194,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { $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 ) @@ -204,7 +202,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { return $res; } - + function getExtraOptions( $opts ){ $opts->consumeValues( array( 'showlinkedto', 'target', 'tagfilter' ) ); $extraOpts = array(); @@ -219,6 +217,9 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { return $extraOpts; } + /** + * @return Title + */ function getTargetTitle() { if ( $this->rclTargetTitle === null ) { $opts = $this->getOptions(); @@ -231,13 +232,12 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { return $this->rclTargetTitle; } - function setTopText( OutputPage $out, FormOptions $opts ) { - global $wgUser; - $skin = $wgUser->getSkin(); + function setTopText( FormOptions $opts ) { $target = $this->getTargetTitle(); - if( $target ) - $out->setSubtitle( wfMsg( 'recentchangeslinked-backlink', $skin->link( $target, + if( $target ) { + $this->getOutput()->setSubtitle( wfMsg( 'recentchangeslinked-backlink', Linker::link( $target, $target->getPrefixedText(), array(), array( 'redirect' => 'no' ) ) ) ); + } } public function getFeedQuery() { @@ -249,9 +249,9 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges { } } - function setBottomText( OutputPage $out, FormOptions $opts ) { - if( isset( $this->mResultEmpty ) && $this->mResultEmpty ){ - $out->addWikiMsg( 'recentchangeslinked-noresult' ); + function setBottomText( FormOptions $opts ) { + if( isset( $this->mResultEmpty ) && $this->mResultEmpty ) { + $this->getOutput()->addWikiMsg( 'recentchangeslinked-noresult' ); } } } diff --git a/includes/specials/SpecialRevisiondelete.php b/includes/specials/SpecialRevisiondelete.php index f77fc347..3c643253 100644 --- a/includes/specials/SpecialRevisiondelete.php +++ b/includes/specials/SpecialRevisiondelete.php @@ -28,9 +28,6 @@ * @ingroup SpecialPage */ class SpecialRevisionDelete extends UnlistedSpecialPage { - /** Skin object */ - var $skin; - /** True if the submit button was clicked, and the form was posted */ var $submitClicked; @@ -64,39 +61,39 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { */ static $allowedTypes = array( 'revision' => array( - 'check-label' => 'revdelete-hide-text', + 'check-label' => 'revdelete-hide-text', 'deletion-bits' => Revision::DELETED_TEXT, - 'success' => 'revdelete-success', - 'failure' => 'revdelete-failure', - 'list-class' => 'RevDel_RevisionList', + 'success' => 'revdelete-success', + 'failure' => 'revdelete-failure', + 'list-class' => 'RevDel_RevisionList', ), 'archive' => array( - 'check-label' => 'revdelete-hide-text', + 'check-label' => 'revdelete-hide-text', 'deletion-bits' => Revision::DELETED_TEXT, - 'success' => 'revdelete-success', - 'failure' => 'revdelete-failure', - 'list-class' => 'RevDel_ArchiveList', + 'success' => 'revdelete-success', + 'failure' => 'revdelete-failure', + 'list-class' => 'RevDel_ArchiveList', ), 'oldimage'=> array( - 'check-label' => 'revdelete-hide-image', + 'check-label' => 'revdelete-hide-image', 'deletion-bits' => File::DELETED_FILE, - 'success' => 'revdelete-success', - 'failure' => 'revdelete-failure', - 'list-class' => 'RevDel_FileList', + 'success' => 'revdelete-success', + 'failure' => 'revdelete-failure', + 'list-class' => 'RevDel_FileList', ), 'filearchive' => array( - 'check-label' => 'revdelete-hide-image', + 'check-label' => 'revdelete-hide-image', 'deletion-bits' => File::DELETED_FILE, - 'success' => 'revdelete-success', - 'failure' => 'revdelete-failure', - 'list-class' => 'RevDel_ArchivedFileList', + 'success' => 'revdelete-success', + 'failure' => 'revdelete-failure', + 'list-class' => 'RevDel_ArchivedFileList', ), 'logging' => array( - 'check-label' => 'revdelete-hide-name', + 'check-label' => 'revdelete-hide-name', 'deletion-bits' => LogPage::DELETED_ACTION, - 'success' => 'logdelete-success', - 'failure' => 'logdelete-failure', - 'list-class' => 'RevDel_LogList', + 'success' => 'logdelete-success', + 'failure' => 'logdelete-failure', + 'list-class' => 'RevDel_LogList', ), ); @@ -114,43 +111,46 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { } public function execute( $par ) { - global $wgOut, $wgUser, $wgRequest; - if( !$wgUser->isAllowed( 'deletedhistory' ) ) { - $wgOut->permissionRequired( 'deletedhistory' ); + $output = $this->getOutput(); + $user = $this->getUser(); + if( !$user->isAllowed( 'deletedhistory' ) ) { + $output->permissionRequired( 'deletedhistory' ); return; - } else if( wfReadOnly() ) { - $wgOut->readOnlyPage(); + } elseif( wfReadOnly() ) { + $output->readOnlyPage(); return; } - $this->mIsAllowed = $wgUser->isAllowed('deleterevision'); // for changes - $this->skin = $wgUser->getSkin(); + $this->mIsAllowed = $user->isAllowed('deleterevision'); // for changes $this->setHeaders(); $this->outputHeader(); - $this->submitClicked = $wgRequest->wasPosted() && $wgRequest->getBool( 'wpSubmit' ); + $request = $this->getRequest(); + $this->submitClicked = $request->wasPosted() && $request->getBool( 'wpSubmit' ); # Handle our many different possible input types. - $ids = $wgRequest->getVal( 'ids' ); + $ids = $request->getVal( 'ids' ); if ( !is_null( $ids ) ) { # Allow CSV, for backwards compatibility, or a single ID for show/hide links $this->ids = explode( ',', $ids ); } else { # Array input - $this->ids = array_keys( $wgRequest->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 ) ); - if ( $wgRequest->getVal( 'action' ) == 'historysubmit' ) { - # For show/hide form submission from history page - $this->targetObj = $GLOBALS['wgTitle']; + if ( $request->getVal( 'action' ) == 'historysubmit' ) { + // For show/hide form submission from history page + // Since we are access through index.php?title=XXX&action=historysubmit + // getFullTitle() will contain the target title and not our title + $this->targetObj = $this->getFullTitle(); $this->typeName = 'revision'; } else { - $this->typeName = $wgRequest->getVal( 'type' ); - $this->targetObj = Title::newFromText( $wgRequest->getText( 'target' ) ); + $this->typeName = $request->getVal( 'type' ); + $this->targetObj = Title::newFromText( $request->getText( 'target' ) ); } # For reviewing deleted files... - $this->archiveName = $wgRequest->getVal( 'file' ); - $this->token = $wgRequest->getVal( 'token' ); + $this->archiveName = $request->getVal( 'file' ); + $this->token = $request->getVal( 'token' ); if ( $this->archiveName && $this->targetObj ) { $this->tryShowFile( $this->archiveName ); return; @@ -162,23 +162,23 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { # No targets? if( !isset( self::$allowedTypes[$this->typeName] ) || count( $this->ids ) == 0 ) { - $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); + $output->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); return; } $this->typeInfo = self::$allowedTypes[$this->typeName]; # If we have revisions, get the title from the first one - # since they should all be from the same page. This allows + # 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->otherReason = $wgRequest->getVal( 'wpReason' ); + + $this->otherReason = $request->getVal( 'wpReason' ); # We need a target page! if( is_null($this->targetObj) ) { - $wgOut->addWikiMsg( 'undelete-header' ); + $output->addWikiMsg( 'undelete-header' ); return; } # Give a link to the logs/hist for this page @@ -190,27 +190,27 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { array( 'revdelete-hide-comment', 'wpHideComment', Revision::DELETED_COMMENT ), array( 'revdelete-hide-user', 'wpHideUser', Revision::DELETED_USER ) ); - if( $wgUser->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 ) { - $this->submit( $wgRequest ); + $this->submit( $request ); } else { $this->showForm(); } - + $qc = $this->getLogQueryCond(); # Show relevant lines from the deletion log - $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'delete' ) ) . "</h2>\n" ); - LogEventsList::showLogExtract( $wgOut, 'delete', + $output->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'delete' ) ) . "</h2>\n" ); + LogEventsList::showLogExtract( $output, 'delete', $this->targetObj->getPrefixedText(), '', array( 'lim' => 25, 'conds' => $qc ) ); # Show relevant lines from the suppression log - if( $wgUser->isAllowed( 'suppressionlog' ) ) { - $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'suppress' ) ) . "</h2>\n" ); - LogEventsList::showLogExtract( $wgOut, 'suppress', + if( $user->isAllowed( 'suppressionlog' ) ) { + $output->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'suppress' ) ) . "</h2>\n" ); + LogEventsList::showLogExtract( $output, 'suppress', $this->targetObj->getPrefixedText(), '', array( 'lim' => 25, 'conds' => $qc ) ); } } @@ -219,11 +219,10 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { * Show some useful links in the subtitle */ protected function showConvenienceLinks() { - global $wgOut, $wgUser, $wgLang; # Give a link to the logs/hist for this page if( $this->targetObj ) { $links = array(); - $links[] = $this->skin->linkKnown( + $links[] = Linker::linkKnown( SpecialPage::getTitleFor( 'Log' ), wfMsgHtml( 'viewpagelogs' ), array(), @@ -231,16 +230,16 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { ); if ( $this->targetObj->getNamespace() != NS_SPECIAL ) { # Give a link to the page history - $links[] = $this->skin->linkKnown( + $links[] = Linker::linkKnown( $this->targetObj, wfMsgHtml( 'pagehist' ), array(), array( 'action' => 'history' ) ); # Link to deleted edits - if( $wgUser->isAllowed('undelete') ) { + if( $this->getUser()->isAllowed('undelete') ) { $undelete = SpecialPage::getTitleFor( 'Undelete' ); - $links[] = $this->skin->linkKnown( + $links[] = Linker::linkKnown( $undelete, wfMsgHtml( 'deletedhist' ), array(), @@ -249,7 +248,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { } } # Logs themselves don't have histories or archived revisions - $wgOut->setSubtitle( '<p>' . $wgLang->pipeList( $links ) . '</p>' ); + $this->getOutput()->setSubtitle( '<p>' . $this->getLang()->pipeList( $links ) . '</p>' ); } } @@ -259,7 +258,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { protected function getLogQueryCond() { $conds = array(); // Revision delete logs for these item - $conds['log_type'] = array('delete','suppress'); + $conds['log_type'] = array( 'delete', 'suppress' ); $conds['log_action'] = $this->getList()->getLogAction(); $conds['ls_field'] = RevisionDeleter::getRelationType( $this->typeName ); $conds['ls_value'] = $this->ids; @@ -271,36 +270,34 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { * TODO Mostly copied from Special:Undelete. Refactor. */ protected function tryShowFile( $archiveName ) { - global $wgOut, $wgRequest, $wgUser, $wgLang; - $repo = RepoGroup::singleton()->getLocalRepo(); $oimage = $repo->newFromArchiveName( $this->targetObj, $archiveName ); $oimage->load(); // Check if user is allowed to see this file if ( !$oimage->exists() ) { - $wgOut->addWikiMsg( 'revdelete-no-file' ); + $this->getOutput()->addWikiMsg( 'revdelete-no-file' ); return; } if( !$oimage->userCan(File::DELETED_FILE) ) { if( $oimage->isDeleted( File::DELETED_RESTRICTED ) ) { - $wgOut->permissionRequired( 'suppressrevision' ); + $this->getOutput()->permissionRequired( 'suppressrevision' ); } else { - $wgOut->permissionRequired( 'deletedtext' ); + $this->getOutput()->permissionRequired( 'deletedtext' ); } return; } - if ( !$wgUser->matchEditToken( $this->token, $archiveName ) ) { - $wgOut->addWikiMsg( 'revdelete-show-file-confirm', + if ( !$this->getUser()->matchEditToken( $this->token, $archiveName ) ) { + $this->getOutput()->addWikiMsg( 'revdelete-show-file-confirm', $this->targetObj->getText(), - $wgLang->date( $oimage->getTimestamp() ), - $wgLang->time( $oimage->getTimestamp() ) ); - $wgOut->addHTML( + $this->getLang()->date( $oimage->getTimestamp() ), + $this->getLang()->time( $oimage->getTimestamp() ) ); + $this->getOutput()->addHTML( Xml::openElement( 'form', array( 'method' => 'POST', 'action' => $this->getTitle()->getLocalUrl( 'target=' . urlencode( $oimage->getName() ) . '&file=' . urlencode( $archiveName ) . - '&token=' . urlencode( $wgUser->editToken( $archiveName ) ) ) + '&token=' . urlencode( $this->getUser()->editToken( $archiveName ) ) ) ) ) . Xml::submitButton( wfMsg( 'revdelete-show-file-submit' ) ) . @@ -308,14 +305,14 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { ); return; } - $wgOut->disable(); + $this->getOutput()->disable(); # We mustn't allow the output to be Squid cached, otherwise # if an admin previews a deleted image, and it's cached, then # a user without appropriate permissions can toddle off and # nab the image, and Squid will serve it - $wgRequest->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); - $wgRequest->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' ); - $wgRequest->response()->header( 'Pragma: no-cache' ); + $this->getRequest()->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); + $this->getRequest()->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' ); + $this->getRequest()->response()->header( 'Pragma: no-cache' ); # Stream the file to the client global $IP; @@ -331,27 +328,26 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { protected function getList() { if ( is_null( $this->list ) ) { $class = $this->typeInfo['list-class']; - $this->list = new $class( $this, $this->targetObj, $this->ids ); + $this->list = new $class( $this->getContext(), $this->targetObj, $this->ids ); } return $this->list; } /** - * Show a list of items that we will operate on, and show a form with checkboxes + * Show a list of items that we will operate on, and show a form with checkboxes * which will allow the user to choose new visibility settings. */ protected function showForm() { - global $wgOut, $wgUser, $wgLang; $UserAllowed = true; if ( $this->typeName == 'logging' ) { - $wgOut->addWikiMsg( 'logdelete-selected', $wgLang->formatNum( count($this->ids) ) ); + $this->getOutput()->addWikiMsg( 'logdelete-selected', $this->getLang()->formatNum( count($this->ids) ) ); } else { - $wgOut->addWikiMsg( 'revdelete-selected', + $this->getOutput()->addWikiMsg( 'revdelete-selected', $this->targetObj->getPrefixedText(), count( $this->ids ) ); } - $wgOut->addHTML( "<ul>" ); + $this->getOutput()->addHTML( "<ul>" ); $numRevisions = 0; // Live revisions... @@ -360,21 +356,21 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { $item = $list->current(); if ( !$item->canView() ) { if( !$this->submitClicked ) { - $wgOut->permissionRequired( 'suppressrevision' ); + $this->getOutput()->permissionRequired( 'suppressrevision' ); return; } $UserAllowed = false; } $numRevisions++; - $wgOut->addHTML( $item->getHTML() ); + $this->getOutput()->addHTML( $item->getHTML() ); } if( !$numRevisions ) { - $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); + $this->getOutput()->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); return; } - - $wgOut->addHTML( "</ul>" ); + + $this->getOutput()->addHTML( "</ul>" ); // Explanation text $this->addUsageText(); @@ -384,7 +380,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { // Show form if the user can submit if( $this->mIsAllowed ) { $out = Xml::openElement( 'form', array( 'method' => 'post', - 'action' => $this->getTitle()->getLocalUrl( array( 'action' => 'submit' ) ), + 'action' => $this->getTitle()->getLocalUrl( array( 'action' => 'submit' ) ), 'id' => 'mw-revdel-form-revisions' ) ) . Xml::fieldset( wfMsg( 'revdelete-legend' ) ) . $this->buildCheckBoxes() . @@ -404,7 +400,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { Xml::label( wfMsg( 'revdelete-otherreason' ), 'wpReason' ) . '</td>' . '<td class="mw-input">' . - Xml::input( 'wpReason', 60, $this->otherReason, array( 'id' => 'wpReason' ) ) . + Xml::input( 'wpReason', 60, $this->otherReason, array( 'id' => 'wpReason', 'maxlength' => 100 ) ) . '</td>' . "</tr><tr>\n" . '<td></td>' . @@ -414,7 +410,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { '</td>' . "</tr>\n" . Xml::closeElement( 'table' ) . - Html::hidden( 'wpEditToken', $wgUser->editToken() ) . + Html::hidden( 'wpEditToken', $this->getUser()->editToken() ) . Html::hidden( 'target', $this->targetObj->getPrefixedText() ) . Html::hidden( 'type', $this->typeName ) . Html::hidden( 'ids', implode( ',', $this->ids ) ) . @@ -425,9 +421,9 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { if( $this->mIsAllowed ) { $out .= Xml::closeElement( 'form' ) . "\n"; // Show link to edit the dropdown reasons - if( $wgUser->isAllowed( 'editinterface' ) ) { + if( $this->getUser()->isAllowed( 'editinterface' ) ) { $title = Title::makeTitle( NS_MEDIAWIKI, 'revdelete-reason-dropdown' ); - $link = $wgUser->getSkin()->link( + $link = Linker::link( $title, wfMsgHtml( 'revdelete-edit-reasonlist' ), array(), @@ -436,30 +432,27 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { $out .= Xml::tags( 'p', array( 'class' => 'mw-revdel-editreasons' ), $link ) . "\n"; } } - $wgOut->addHTML( $out ); + $this->getOutput()->addHTML( $out ); } /** * Show some introductory text - * FIXME Wikimedia-specific policy text + * @todo FIXME: Wikimedia-specific policy text */ protected function addUsageText() { - global $wgOut, $wgUser; - $wgOut->addWikiMsg( 'revdelete-text' ); - if( $wgUser->isAllowed( 'suppressrevision' ) ) { - $wgOut->addWikiMsg( 'revdelete-suppress-text' ); + $this->getOutput()->addWikiMsg( 'revdelete-text' ); + if( $this->getUser()->isAllowed( 'suppressrevision' ) ) { + $this->getOutput()->addWikiMsg( 'revdelete-suppress-text' ); } if( $this->mIsAllowed ) { - $wgOut->addWikiMsg( 'revdelete-confirm' ); + $this->getOutput()->addWikiMsg( 'revdelete-confirm' ); } } - + /** * @return String: HTML */ protected function buildCheckBoxes() { - global $wgRequest; - $html = '<table>'; // If there is just one item, use checkboxes $list = $this->getList(); @@ -467,7 +460,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { $list->reset(); $bitfield = $list->current()->getBits(); // existing field if( $this->submitClicked ) { - $bitfield = $this->extractBitfield( $this->extractBitParams($wgRequest), $bitfield ); + $bitfield = $this->extractBitfield( $this->extractBitParams(), $bitfield ); } foreach( $this->checks as $item ) { list( $message, $name, $field ) = $item; @@ -488,7 +481,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { list( $message, $name, $field ) = $item; // If there are several items, use third state by default... if( $this->submitClicked ) { - $selected = $wgRequest->getInt( $name, 0 /* unchecked */ ); + $selected = $this->getRequest()->getInt( $name, 0 /* unchecked */ ); } else { $selected = -1; // use existing field } @@ -503,24 +496,23 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { $html .= "<tr>$line</tr>\n"; } } - + $html .= '</table>'; return $html; } /** * UI entry point for form submission. - * @param $request WebRequest */ - protected function submit( $request ) { - global $wgUser, $wgOut; + protected function submit() { # Check edit token on submission - if( $this->submitClicked && !$wgUser->matchEditToken( $request->getVal('wpEditToken') ) ) { - $wgOut->addWikiMsg( 'sessionfailure' ); + $token = $this->getRequest()->getVal('wpEditToken'); + if( $this->submitClicked && !$this->getUser()->matchEditToken( $token ) ) { + $this->getOutput()->addWikiMsg( 'sessionfailure' ); return false; } - $bitParams = $this->extractBitParams( $request ); - $listReason = $request->getText( 'wpRevDeleteReasonList', 'other' ); // from dropdown + $bitParams = $this->extractBitParams(); + $listReason = $this->getRequest()->getText( 'wpRevDeleteReasonList', 'other' ); // from dropdown $comment = $listReason; if( $comment != 'other' && $this->otherReason != '' ) { // Entry from drop down menu + additional comment @@ -529,8 +521,8 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { $comment = $this->otherReason; } # Can the user set this field? - if( $bitParams[Revision::DELETED_RESTRICTED]==1 && !$wgUser->isAllowed('suppressrevision') ) { - $wgOut->permissionRequired( 'suppressrevision' ); + if( $bitParams[Revision::DELETED_RESTRICTED]==1 && !$this->getUser()->isAllowed('suppressrevision') ) { + $this->getOutput()->permissionRequired( 'suppressrevision' ); return false; } # If the save went through, go to success message... @@ -549,9 +541,8 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { * Report that the submit operation succeeded */ protected function success() { - global $wgOut; - $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) ); - $wgOut->wrapWikiMsg( "<span class=\"success\">\n$1\n</span>", $this->typeInfo['success'] ); + $this->getOutput()->setPagetitle( wfMsg( 'actioncomplete' ) ); + $this->getOutput()->wrapWikiMsg( "<span class=\"success\">\n$1\n</span>", $this->typeInfo['success'] ); $this->list->reloadFromMaster(); $this->showForm(); } @@ -560,22 +551,21 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { * Report that the submit operation failed */ protected function failure( $status ) { - global $wgOut; - $wgOut->setPagetitle( wfMsg( 'actionfailed' ) ); - $wgOut->addWikiText( $status->getWikiText( $this->typeInfo['failure'] ) ); + $this->getOutput()->setPagetitle( wfMsg( 'actionfailed' ) ); + $this->getOutput()->addWikiText( $status->getWikiText( $this->typeInfo['failure'] ) ); $this->showForm(); } /** * Put together an array that contains -1, 0, or the *_deleted const for each bit - * @param $request WebRequest + * * @return array */ - protected function extractBitParams( $request ) { + protected function extractBitParams() { $bitfield = array(); foreach( $this->checks as $item ) { list( /* message */ , $name, $field ) = $item; - $val = $request->getInt( $name, 0 /* unchecked */ ); + $val = $this->getRequest()->getInt( $name, 0 /* unchecked */ ); if( $val < -1 || $val > 1) { $val = -1; // -1 for existing value } @@ -586,7 +576,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { } return $bitfield; } - + /** * Put together a rev_deleted bitfield * @param $bitPars array extractBitParams() params @@ -599,7 +589,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { foreach( $bitPars as $const => $val ) { if( $val == 1 ) { $newBits |= $const; // $const is the *_deleted const - } else if( $val == -1 ) { + } elseif( $val == -1 ) { $newBits |= ($oldfield & $const); // use existing } } diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php index fd6e858e..ba9d378a 100644 --- a/includes/specials/SpecialSearch.php +++ b/includes/specials/SpecialSearch.php @@ -24,35 +24,66 @@ */ /** - * Entry point - * - * @param $par String: (default '') - */ -function wfSpecialSearch( $par = '' ) { - global $wgRequest, $wgUser, $wgOut; - $wgOut->allowClickjacking(); - - // Strip underscores from title parameter; most of the time we'll want - // text form here. But don't strip underscores from actual text params! - $titleParam = str_replace( '_', ' ', $par ); - // Fetch the search term - $search = str_replace( "\n", " ", $wgRequest->getText( 'search', $titleParam ) ); - $searchPage = new SpecialSearch( $wgRequest, $wgUser ); - if( $wgRequest->getVal( 'fulltext' ) - || !is_null( $wgRequest->getVal( 'offset' )) - || !is_null( $wgRequest->getVal( 'searchx' )) ) - { - $searchPage->showResults( $search ); - } else { - $searchPage->goResult( $search ); - } -} - -/** * implements Special:Search - Run text & title search and display the output * @ingroup SpecialPage */ -class SpecialSearch { +class SpecialSearch extends SpecialPage { + /** + * Current search profile. Search profile is just a name that identifies + * the active search tab on the search page (content, help, discussions...) + * For users tt replaces the set of enabled namespaces from the query + * string when applicable. Extensions can add new profiles with hooks + * with custom search options just for that profile. + * null|string + */ + protected $profile; + + /// Search engine + protected $searchEngine; + + /// For links + protected $extraParams = array(); + + /// No idea, apparently used by some other classes + protected $mPrefix; + + const NAMESPACES_CURRENT = 'sense'; + + public function __construct() { + parent::__construct( 'Search' ); + } + + /** + * Entry point + * + * @param $par String or null + */ + public function execute( $par ) { + global $wgRequest, $wgUser, $wgOut; + + $this->setHeaders(); + $this->outputHeader(); + $wgOut->allowClickjacking(); + $wgOut->addModuleStyles( 'mediawiki.special' ); + + // Strip underscores from title parameter; most of the time we'll want + // text form here. But don't strip underscores from actual text params! + $titleParam = str_replace( '_', ' ', $par ); + + // Fetch the search term + $search = str_replace( "\n", " ", $wgRequest->getText( 'search', $titleParam ) ); + + $this->load( $wgRequest, $wgUser ); + + if ( $wgRequest->getVal( 'fulltext' ) + || !is_null( $wgRequest->getVal( 'offset' ) ) + || !is_null( $wgRequest->getVal( 'searchx' ) ) ) + { + $this->showResults( $search ); + } else { + $this->goResult( $search ); + } + } /** * Set up basic search parameters from the request and user settings. @@ -61,18 +92,44 @@ class SpecialSearch { * @param $request WebRequest * @param $user User */ - public function __construct( &$request, &$user ) { + public function load( &$request, &$user ) { list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, 'searchlimit' ); - $this->mPrefix = $request->getVal('prefix', ''); - # Extract requested namespaces - $this->namespaces = $this->powerSearch( $request ); - if( empty( $this->namespaces ) ) { - $this->namespaces = SearchEngine::userNamespaces( $user ); + $this->mPrefix = $request->getVal( 'prefix', '' ); + + + # Extract manually requested namespaces + $nslist = $this->powerSearch( $request ); + $this->profile = $profile = $request->getVal( 'profile', null ); + $profiles = $this->getSearchProfiles(); + if ( $profile === null) { + // BC with old request format + $this->profile = 'advanced'; + if ( count( $nslist ) ) { + foreach( $profiles as $key => $data ) { + if ( $nslist === $data['namespaces'] && $key !== 'advanced') { + $this->profile = $key; + } + } + $this->namespaces = $nslist; + } else { + $this->namespaces = SearchEngine::userNamespaces( $user ); + } + } elseif ( $profile === 'advanced' ) { + $this->namespaces = $nslist; + } else { + if ( isset( $profiles[$profile]['namespaces'] ) ) { + $this->namespaces = $profiles[$profile]['namespaces']; + } else { + // Unknown profile requested + $this->profile = 'default'; + $this->namespaces = $profiles['default']['namespaces']; + } } - $this->searchRedirects = $request->getCheck( 'redirs' ); - $this->searchAdvanced = $request->getVal( 'advanced' ); - $this->active = 'advanced'; - $this->sk = $user->getSkin(); + + // Redirects defaults to true, but we don't know whether it was ticked of or just missing + $default = $request->getBool( 'profile' ) ? 0 : 1; + $this->searchRedirects = $request->getBool( 'redirs', $default ) ? 1 : 0; + $this->sk = $this->getSkin(); $this->didYouMeanHtml = ''; # html of did you mean... link $this->fulltext = $request->getVal('fulltext'); } @@ -93,12 +150,12 @@ class SpecialSearch { } # If there's an exact or very near match, jump right there. $t = SearchEngine::getNearMatch( $term ); - + if ( !wfRunHooks( 'SpecialSearchGo', array( &$t, &$term ) ) ) { # Hook requested termination return; } - + if( !is_null( $t ) ) { $wgOut->redirect( $t->getFullURL() ); return; @@ -123,19 +180,21 @@ class SpecialSearch { * @param $term String */ public function showResults( $term ) { - global $wgOut, $wgUser, $wgDisableTextSearch, $wgContLang, $wgScript; + global $wgOut, $wgDisableTextSearch, $wgContLang, $wgScript; wfProfileIn( __METHOD__ ); - $sk = $wgUser->getSkin(); + $sk = $this->getSkin(); - $this->searchEngine = SearchEngine::create(); - $search =& $this->searchEngine; + $search = $this->getSearchEngine(); $search->setLimitOffset( $this->limit, $this->offset ); $search->setNamespaces( $this->namespaces ); - $search->showRedirects = $this->searchRedirects; + $search->showRedirects = $this->searchRedirects; // BC + $search->setFeatureData( 'list-redirects', $this->searchRedirects ); $search->prefix = $this->mPrefix; $term = $search->transformSearchTerm($term); + wfRunHooks( 'SpecialSearchSetupEngine', array( $this, $this->profile, $search ) ); + $this->setupPage( $term ); if( $wgDisableTextSearch ) { @@ -146,14 +205,13 @@ class SpecialSearch { wfProfileOut( __METHOD__ ); return; } - global $wgInputEncoding; $wgOut->addHTML( Xml::openElement( 'fieldset' ) . Xml::element( 'legend', null, wfMsg( 'search-external' ) ) . Xml::element( 'p', array( 'class' => 'mw-searchdisabled' ), wfMsg( 'searchdisabled' ) ) . wfMsg( 'googlesearch', htmlspecialchars( $term ), - htmlspecialchars( $wgInputEncoding ), + htmlspecialchars( 'UTF-8' ), htmlspecialchars( wfMsg( 'searchbutton' ) ) ) . Xml::closeElement( 'fieldset' ) @@ -205,7 +263,7 @@ class SpecialSearch { Xml::openElement( 'form', array( - 'id' => ( $this->searchAdvanced ? 'powersearch' : 'search' ), + 'id' => ( $this->profile === 'advanced' ? 'powersearch' : 'search' ), 'method' => 'get', 'action' => $wgScript ) @@ -214,7 +272,7 @@ class SpecialSearch { $wgOut->addHtml( Xml::openElement( 'table', array( 'id'=>'mw-search-top-table', 'border'=>0, 'cellpadding'=>0, 'cellspacing'=>0 ) ) . Xml::openElement( 'tr' ) . - Xml::openElement( 'td' ) . "\n" . + Xml::openElement( 'td' ) . "\n" . $this->shortDialog( $term ) . Xml::closeElement('td') . Xml::closeElement('tr') . @@ -223,17 +281,15 @@ class SpecialSearch { // Sometimes the search engine knows there are too many hits if( $titleMatches instanceof SearchResultTooMany ) { - $wgOut->addWikiText( '==' . wfMsg( 'toomanymatches' ) . "==\n" ); + $wgOut->wrapWikiMsg( "==$1==\n", 'toomanymatches' ); wfProfileOut( __METHOD__ ); return; } $filePrefix = $wgContLang->getFormattedNsText(NS_FILE).':'; if( trim( $term ) === '' || $filePrefix === trim( $term ) ) { - $wgOut->addHTML( $this->formHeader($term, 0, 0)); - if( $this->searchAdvanced ) { - $wgOut->addHTML( $this->powerSearchBox( $term ) ); - } + $wgOut->addHTML( $this->formHeader( $term, 0, 0 ) ); + $wgOut->addHtml( $this->getProfileForm( $this->profile, $term ) ); $wgOut->addHTML( '</form>' ); // Empty query -- straight view of search form wfProfileOut( __METHOD__ ); @@ -245,7 +301,7 @@ class SpecialSearch { $textMatchesNum = $textMatches ? $textMatches->numRows() : 0; // Total initial query matches (possible false positives) $num = $titleMatchesNum + $textMatchesNum; - + // Get total actual results (after second filtering, if any) $numTitleMatches = $titleMatches && !is_null( $titleMatches->getTotalHits() ) ? $titleMatches->getTotalHits() : $titleMatchesNum; @@ -258,13 +314,12 @@ class SpecialSearch { $totalRes += $titleMatches->getTotalHits(); if($textMatches && !is_null( $textMatches->getTotalHits() )) $totalRes += $textMatches->getTotalHits(); - + // show number of results and current offset - $wgOut->addHTML( $this->formHeader($term, $num, $totalRes)); - if( $this->searchAdvanced ) { - $wgOut->addHTML( $this->powerSearchBox( $term ) ); - } - + $wgOut->addHTML( $this->formHeader( $term, $num, $totalRes ) ); + $wgOut->addHtml( $this->getProfileForm( $this->profile, $term ) ); + + $wgOut->addHtml( Xml::closeElement( 'form' ) ); $wgOut->addHtml( "<div class='searchresults'>" ); @@ -281,8 +336,9 @@ class SpecialSearch { wfRunHooks( 'SpecialSearchResults', array( $term, &$titleMatches, &$textMatches ) ); } else { wfRunHooks( 'SpecialSearchNoResults', array( $term ) ); - } + } + $wgOut->parserOptions()->setEditSection( false ); if( $titleMatches ) { if( $numTitleMatches > 0 ) { $wgOut->wrapWikiMsg( "==$1==\n", 'titlematches' ); @@ -321,23 +377,33 @@ class SpecialSearch { } wfProfileOut( __METHOD__ ); } - + protected function showCreateLink( $t ) { global $wgOut; - + // show direct page/create link if applicable - $messageName = null; - if( !is_null($t) ) { - if( $t->isKnown() ) { - $messageName = 'searchmenu-exists'; - } elseif( $t->userCan( 'create' ) ) { - $messageName = 'searchmenu-new'; - } else { - $messageName = 'searchmenu-new-nocreate'; - } - } + + // Check DBkey !== '' in case of fragment link only. + if( is_null( $t ) || $t->getDBkey() === '' ) { + // invalid title + // preserve the paragraph for margins etc... + $this->getOutput()->addHtml( '<p></p>' ); + return; + } + $messageName = ''; + if( $t->isKnown() ) { + $messageName = 'searchmenu-exists'; + } elseif( $t->userCan( 'create' ) ) { + $messageName = 'searchmenu-new'; + } else { + $messageName = 'searchmenu-new-nocreate'; + } + $params = array( $messageName, wfEscapeWikiText( $t->getPrefixedText() ) ); + wfRunHooks( 'SpecialSearchCreateLink', array( $t, &$params ) ); + + // Extensions using the hook might still return an empty $messageName if( $messageName ) { - $wgOut->wrapWikiMsg( "<p class=\"mw-search-createlink\">\n$1</p>", array( $messageName, wfEscapeWikiText( $t->getPrefixedText() ) ) ); + $this->getOutput()->wrapWikiMsg( "<p class=\"mw-search-createlink\">\n$1</p>", $params ); } else { // preserve the paragraph for margins etc... $wgOut->addHtml( '<p></p>' ); @@ -349,28 +415,14 @@ class SpecialSearch { */ protected function setupPage( $term ) { global $wgOut; - // Figure out the active search profile header - if( $this->searchAdvanced ) { - $this->active = 'advanced'; - } else { - $profiles = $this->getSearchProfiles(); - - foreach( $profiles as $key => $data ) { - if ( $this->namespaces == $data['namespaces'] && $key != 'advanced') - $this->active = $key; - } - - } + # Should advanced UI be used? - $this->searchAdvanced = ($this->active === 'advanced'); - if( !empty( $term ) ) { + $this->searchAdvanced = ($this->profile === 'advanced'); + if( strval( $term ) !== '' ) { $wgOut->setPageTitle( wfMsg( 'searchresults') ); $wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsg( 'searchresults-title', $term ) ) ); } - $wgOut->setArticleRelated( false ); - $wgOut->setRobotPolicy( 'noindex,nofollow' ); // add javascript specific to special:search - $wgOut->addModules( 'mediawiki.legacy.search' ); $wgOut->addModules( 'mediawiki.special.search' ); } @@ -388,6 +440,7 @@ class SpecialSearch { $arr[] = $ns; } } + return $arr; } @@ -398,14 +451,15 @@ class SpecialSearch { */ protected function powerSearchOptions() { $opt = array(); - foreach( $this->namespaces as $n ) { - $opt['ns' . $n] = 1; - } $opt['redirs'] = $this->searchRedirects ? 1 : 0; - if( $this->searchAdvanced ) { - $opt['advanced'] = $this->searchAdvanced; + if( $this->profile !== 'advanced' ) { + $opt['profile'] = $this->profile; + } else { + foreach( $this->namespaces as $n ) { + $opt['ns' . $n] = 1; + } } - return $opt; + return $opt + $this->extraParams; } /** @@ -443,7 +497,7 @@ class SpecialSearch { * @param $terms Array: terms to highlight */ protected function showHit( $result, $terms ) { - global $wgLang, $wgUser; + global $wgLang; wfProfileIn( __METHOD__ ); if( $result->isBrokenTitle() ) { @@ -451,16 +505,16 @@ class SpecialSearch { return "<!-- Broken link in search result -->\n"; } - $sk = $wgUser->getSkin(); + $sk = $this->getSkin(); $t = $result->getTitle(); $titleSnippet = $result->getTitleSnippet($terms); if( $titleSnippet == '' ) $titleSnippet = null; - + $link_t = clone $t; - + wfRunHooks( 'ShowSearchHitTitle', array( &$link_t, &$titleSnippet, $result, $terms, $this ) ); @@ -509,7 +563,6 @@ class SpecialSearch { $section = ''; - if( !is_null($sectionTitle) ) { if( $sectionText == '' ) $sectionText = null; @@ -544,7 +597,7 @@ class SpecialSearch { $size = wfMsgExt( 'search-result-size', array( 'parsemag', 'escape' ), - $this->sk->formatSize( $byteSize ), + $wgLang->formatSize( $byteSize ), $wgLang->formatNum( $wordCount ) ); @@ -706,13 +759,13 @@ class SpecialSearch { $out = ""; // display project name if(is_null($lastInterwiki) || $lastInterwiki != $t->getInterwiki()) { - if( key_exists($t->getInterwiki(),$customCaptions) ) + if( array_key_exists($t->getInterwiki(),$customCaptions) ) { // captions from 'search-interwiki-custom' $caption = $customCaptions[$t->getInterwiki()]; - else{ + } else { // default is to show the hostname of the other wiki which might suck // if there are many wikis on one hostname - $parsed = parse_url($t->getFullURL()); + $parsed = wfParseUrl( $t->getFullURL() ); $caption = wfMsg('search-interwiki-default', $parsed['host']); } // "more results" link (special page stuff could be localized, but we might not know target lang) @@ -735,14 +788,28 @@ class SpecialSearch { return $out; } + protected function getProfileForm( $profile, $term ) { + // Hidden stuff + $opts = array(); + $opts['redirs'] = $this->searchRedirects; + $opts['profile'] = $this->profile; + + if ( $profile === 'advanced' ) { + return $this->powerSearchBox( $term, $opts ); + } else { + $form = ''; + wfRunHooks( 'SpecialSearchProfileForm', array( $this, &$form, $profile, $term, $opts ) ); + return $form; + } + } /** - * Generates the power search box at bottom of [[Special:Search]] + * Generates the power search box at [[Special:Search]] * * @param $term String: search term * @return String: HTML form */ - protected function powerSearchBox( $term ) { + protected function powerSearchBox( $term, $opts ) { // Groups namespaces into rows according to subject $rows = array(); foreach( SearchEngine::searchableNamespaces() as $namespace => $name ) { @@ -768,7 +835,7 @@ class SpecialSearch { } $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 = ''; @@ -782,15 +849,21 @@ class SpecialSearch { } $namespaceTables .= Xml::closeElement( 'table' ); } + + $showSections = array( 'namespaceTables' => $namespaceTables ); + // Show redirects check only if backend supports it - $redirects = ''; - if( $this->searchEngine->acceptListRedirects() ) { - $redirects = - Xml::check( - 'redirs', $this->searchRedirects, array( 'value' => '1', 'id' => 'redirs' ) - ) . - ' ' . - Xml::label( wfMsg( 'powersearch-redir' ), 'redirs' ); + if( $this->getSearchEngine()->supports( 'list-redirects' ) ) { + $showSections['redirects'] = + Xml::checkLabel( wfMsg( 'powersearch-redir' ), 'redirs', 'redirs', $this->searchRedirects ); + } + + wfRunHooks( 'SpecialSearchPowerBox', array( &$showSections, $term, $opts ) ); + + $hidden = ''; + unset( $opts['redirs'] ); + foreach( $opts as $key => $value ) { + $hidden .= Html::hidden( $key, $value ); } // Return final output return @@ -809,7 +882,6 @@ class SpecialSearch { array( 'type'=>'button', 'id' => 'mw-search-toggleall', - 'onclick' => 'mwToggleSearchCheckboxes("all");', 'value' => wfMsg( 'powersearch-toggleall' ) ) ) . @@ -818,25 +890,20 @@ class SpecialSearch { array( 'type'=>'button', 'id' => 'mw-search-togglenone', - 'onclick' => 'mwToggleSearchCheckboxes("none");', 'value' => wfMsg( 'powersearch-togglenone' ) ) ) ) . Xml::element( 'div', array( 'class' => 'divider' ), '', false ) . - $namespaceTables . - Xml::element( 'div', array( 'class' => 'divider' ), '', false ) . - $redirects . - Html::hidden( 'title', SpecialPage::getTitleFor( 'Search' )->getPrefixedText() ) . - Html::hidden( 'advanced', $this->searchAdvanced ) . - Html::hidden( 'fulltext', 'Advanced search' ) . + implode( Xml::element( 'div', array( 'class' => 'divider' ), '', false ), $showSections ) . + $hidden . Xml::closeElement( 'fieldset' ); } - + protected function getSearchProfiles() { // Builds list of Search Types (profiles) $nsAllSet = array_keys( SearchEngine::searchableNamespaces() ); - + $profiles = array( 'default' => array( 'message' => 'searchprofile-articles', @@ -867,25 +934,25 @@ class SpecialSearch { 'advanced' => array( 'message' => 'searchprofile-advanced', 'tooltip' => 'searchprofile-advanced-tooltip', - 'namespaces' => $this->namespaces, - 'parameters' => array( 'advanced' => 1 ), + 'namespaces' => self::NAMESPACES_CURRENT, ) ); - + wfRunHooks( 'SpecialSearchProfiles', array( &$profiles ) ); foreach( $profiles as &$data ) { - sort($data['namespaces']); + if ( !is_array( $data['namespaces'] ) ) continue; + sort( $data['namespaces'] ); } - + return $profiles; } protected function formHeader( $term, $resultsShown, $totalNum ) { global $wgLang; - + $out = Xml::openElement('div', array( 'class' => 'mw-search-formheader' ) ); - + $bareterm = $term; if( $this->startsWithImage( $term ) ) { // Deletes prefixes @@ -893,24 +960,29 @@ class SpecialSearch { } $profiles = $this->getSearchProfiles(); - + // Outputs XML for Search Types $out .= Xml::openElement( 'div', array( 'class' => 'search-types' ) ); $out .= Xml::openElement( 'ul' ); foreach ( $profiles as $id => $profile ) { + if ( !isset( $profile['parameters'] ) ) { + $profile['parameters'] = array(); + } + $profile['parameters']['profile'] = $id; + $tooltipParam = isset( $profile['namespace-messages'] ) ? $wgLang->commaList( $profile['namespace-messages'] ) : null; $out .= Xml::tags( 'li', array( - 'class' => $this->active == $id ? 'current' : 'normal' + 'class' => $this->profile === $id ? 'current' : 'normal' ), $this->makeSearchLink( $bareterm, - $profile['namespaces'], + array(), wfMsg( $profile['message'] ), wfMsg( $profile['tooltip'], $tooltipParam ), - isset( $profile['parameters'] ) ? $profile['parameters'] : array() + $profile['parameters'] ) ); } @@ -930,60 +1002,54 @@ class SpecialSearch { } elseif ( $resultsShown >= $this->limit ) { $top = wfShowingResults( $this->offset, $this->limit ); } else { - $top = wfShowingResultsNum( $this->offset, $this->limit, $resultsShown ); + $top = wfMsgExt( 'showingresultsnum', array( 'parseinline' ), + $wgLang->formatNum( $this->limit ), + $wgLang->formatNum( $this->offset + 1 ), + $wgLang->formatNum( $resultsShown ) + ); } $out .= Xml::tags( 'div', array( 'class' => 'results-info' ), Xml::tags( 'ul', null, Xml::tags( 'li', null, $top ) ) ); } - + $out .= Xml::element( 'div', array( 'style' => 'clear:both' ), '', false ); $out .= Xml::closeElement('div'); - - // Adds hidden namespace fields - if ( !$this->searchAdvanced ) { - foreach( $this->namespaces as $ns ) { - $out .= Html::hidden( "ns{$ns}", '1' ); - } - } - + return $out; } protected function shortDialog( $term ) { - $searchTitle = SpecialPage::getTitleFor( 'Search' ); - $out = Html::hidden( 'title', $searchTitle->getPrefixedText() ) . "\n"; - // Keep redirect setting - $out .= Html::hidden( "redirs", (int)$this->searchRedirects ) . "\n"; + $out = Html::hidden( 'title', $this->getTitle()->getPrefixedText() ); + $out .= Html::hidden( 'profile', $this->profile ) . "\n"; // Term box $out .= Html::input( 'search', $term, 'search', array( - 'id' => $this->searchAdvanced ? 'powerSearchText' : 'searchText', + 'id' => $this->profile === 'advanced' ? 'powerSearchText' : 'searchText', 'size' => '50', 'autofocus' ) ) . "\n"; $out .= Html::hidden( 'fulltext', 'Search' ) . "\n"; $out .= Xml::submitButton( wfMsg( 'searchbutton' ) ) . "\n"; - return $out . $this->didYouMeanHtml; + return $out . $this->didYouMeanHtml; } /** * Make a search link with some target namespaces * * @param $term String - * @param $namespaces Array + * @param $namespaces Array ignored * @param $label String: link's text * @param $tooltip String: link's tooltip * @param $params Array: query string parameters * @return String: HTML fragment */ - protected function makeSearchLink( $term, $namespaces, $label, $tooltip, $params=array() ) { + protected function makeSearchLink( $term, $namespaces, $label, $tooltip, $params = array() ) { $opt = $params; foreach( $namespaces as $n ) { $opt['ns' . $n] = 1; } - $opt['redirs'] = $this->searchRedirects ? 1 : 0; + $opt['redirs'] = $this->searchRedirects; - $st = SpecialPage::getTitleFor( 'Search' ); $stParams = array_merge( array( 'search' => $term, @@ -995,10 +1061,8 @@ class SpecialSearch { return Xml::element( 'a', array( - 'href' => $st->getLocalURL( $stParams ), - 'title' => $tooltip, - 'onmousedown' => 'mwSearchHeaderClick(this);', - 'onkeydown' => 'mwSearchHeaderClick(this);'), + 'href' => $this->getTitle()->getLocalURL( $stParams ), + 'title' => $tooltip), $label ); } @@ -1018,7 +1082,7 @@ class SpecialSearch { } return false; } - + /** * Check if query starts with all: prefix * @@ -1028,12 +1092,32 @@ class SpecialSearch { protected function startsWithAll( $term ) { $allkeyword = wfMsgForContent('searchall'); - + $p = explode( ':', $term ); if( count( $p ) > 1 ) { return $p[0] == $allkeyword; } return false; } -} + /** + * @since 1.18 + */ + public function getSearchEngine() { + if ( $this->searchEngine === null ) { + $this->searchEngine = SearchEngine::create(); + } + return $this->searchEngine; + } + + /** + * Users of hook SpecialSearchSetupEngine can use this to + * add more params to links to not lose selection when + * user navigates search results. + * @since 1.18 + */ + public function setExtraParam( $key, $value ) { + $this->extraParams[$key] = $value; + } + +} diff --git a/includes/specials/SpecialShortpages.php b/includes/specials/SpecialShortpages.php index 989e4c07..3b785018 100644 --- a/includes/specials/SpecialShortpages.php +++ b/includes/specials/SpecialShortpages.php @@ -29,10 +29,11 @@ */ class ShortPagesPage extends QueryPage { - function getName() { - return 'Shortpages'; + function __construct( $name = 'Shortpages' ) { + parent::__construct( $name ); } + // inexpensive? /** * This query is indexed as of 1.5 */ @@ -44,29 +45,27 @@ class ShortPagesPage extends QueryPage { return false; } - function getSQL() { - global $wgContentNamespaces; - - $dbr = wfGetDB( DB_SLAVE ); - $page = $dbr->tableName( 'page' ); - $name = $dbr->addQuotes( $this->getName() ); - - $forceindex = $dbr->useIndexClause("page_len"); - - if ($wgContentNamespaces) - $nsclause = "page_namespace IN (" . $dbr->makeList($wgContentNamespaces) . ")"; - else - $nsclause = "page_namespace = " . NS_MAIN; + function getQueryInfo() { + return array ( + 'tables' => array ( 'page' ), + 'fields' => array ( 'page_namespace AS namespace', + 'page_title AS title', + 'page_len AS value' ), + 'conds' => array ( 'page_namespace' => MWNamespace::getContentNamespaces(), + 'page_is_redirect' => 0 ), + 'options' => array ( 'USE INDEX' => 'page_len' ) + ); + } - return - "SELECT $name as type, - page_namespace as namespace, - page_title as title, - page_len AS value - FROM $page $forceindex - WHERE $nsclause AND page_is_redirect=0"; + function getOrderFields() { + return array( 'page_len' ); } + /** + * @param $db DatabaseBase + * @param $res + * @return void + */ function preprocessResults( $db, $res ) { # There's no point doing a batch check if we aren't caching results; # the page must exist for it to have been pulled out of the table @@ -87,10 +86,10 @@ class ShortPagesPage extends QueryPage { } function formatResult( $skin, $result ) { - global $wgLang, $wgContLang; - $dm = $wgContLang->getDirMark(); + global $wgLang; + $dm = $wgLang->getDirMark(); - $title = Title::makeTitleSafe( $result->namespace, $result->title ); + $title = Title::makeTitle( $result->namespace, $result->title ); if ( !$title ) { return '<!-- Invalid title ' . htmlspecialchars( "{$result->namespace}:{$result->title}" ). '-->'; } @@ -110,14 +109,3 @@ class ShortPagesPage extends QueryPage { : "<del>({$hlink}) {$dm}{$plink} {$dm}[{$size}]</del>"; } } - -/** - * constructor - */ -function wfSpecialShortpages() { - list( $limit, $offset ) = wfCheckLimits(); - - $spp = new ShortPagesPage(); - - return $spp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialSpecialpages.php b/includes/specials/SpecialSpecialpages.php index 19bc6b00..13bc4c2b 100644 --- a/includes/specials/SpecialSpecialpages.php +++ b/includes/specials/SpecialSpecialpages.php @@ -33,10 +33,11 @@ class SpecialSpecialpages extends UnlistedSpecialPage { } function execute( $par ) { - global $wgOut; + $out = $this->getOutput(); $this->setHeaders(); $this->outputHeader(); - $wgOut->allowClickjacking(); + $out->allowClickjacking(); + $out->addModuleStyles( 'mediawiki.special' ); $groups = $this->getPageGroups(); @@ -50,7 +51,7 @@ class SpecialSpecialpages extends UnlistedSpecialPage { private function getPageGroups() { global $wgSortSpecialPages; - $pages = SpecialPage::getUsablePages(); + $pages = SpecialPageFactory::getUsablePages(); if( !count( $pages ) ) { # Yeah, that was pointless. Thanks for coming. @@ -61,11 +62,11 @@ class SpecialSpecialpages extends UnlistedSpecialPage { $groups = array(); foreach ( $pages as $page ) { if ( $page->isListed() ) { - $group = SpecialPage::getGroup( $page ); + $group = SpecialPageFactory::getGroup( $page ); if( !isset( $groups[$group] ) ) { $groups[$group] = array(); } - $groups[$group][$page->getDescription()] = array( $page->getTitle(), $page->isRestricted() ); + $groups[$group][$page->getDescription()] = array( $page->getTitle(), $page->isRestricted(), $page->isExpensive() ); } } @@ -87,52 +88,59 @@ class SpecialSpecialpages extends UnlistedSpecialPage { } private function outputPageList( $groups ) { - global $wgUser, $wgOut; + global $wgMiserMode; + $out = $this->getOutput(); - $sk = $wgUser->getSkin(); $includesRestrictedPages = false; + $includesCachedPages = false; foreach ( $groups as $group => $sortedPages ) { $middle = ceil( count( $sortedPages )/2 ); $total = count( $sortedPages ); $count = 0; - $wgOut->wrapWikiMsg( "<h4 class=\"mw-specialpagesgroup\" id=\"mw-specialpagesgroup-$group\">$1</h4>\n", "specialpages-group-$group" ); - $wgOut->addHTML( + $out->wrapWikiMsg( "<h2 class=\"mw-specialpagesgroup\" id=\"mw-specialpagesgroup-$group\">$1</h2>\n", "specialpages-group-$group" ); + $out->addHTML( Html::openElement( 'table', array( 'style' => 'width:100%;', 'class' => 'mw-specialpages-table' ) ) ."\n" . Html::openElement( 'tr' ) . "\n" . Html::openElement( 'td', array( 'style' => 'width:30%;vertical-align:top' ) ) . "\n" . Html::openElement( 'ul' ) . "\n" ); foreach( $sortedPages as $desc => $specialpage ) { - list( $title, $restricted ) = $specialpage; - $link = $sk->linkKnown( $title , htmlspecialchars( $desc ) ); + list( $title, $restricted, $expensive) = $specialpage; + + $pageClasses = array(); + if ( $expensive && $wgMiserMode ){ + $includesCachedPages = true; + $pageClasses[] = 'mw-specialpagecached'; + } if( $restricted ) { $includesRestrictedPages = true; - $wgOut->addHTML( Html::rawElement( 'li', array( 'class' => 'mw-specialpages-page mw-specialpagerestricted' ), Html::rawElement( 'strong', array(), $link ) ) . "\n" ); - } else { - $wgOut->addHTML( Html::rawElement( 'li', array(), $link ) . "\n" ); + $pageClasses[] = 'mw-specialpagerestricted'; } + $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 ) { - $wgOut->addHTML( + $out->addHTML( Html::closeElement( 'ul' ) . Html::closeElement( 'td' ) . Html::element( 'td', array( 'style' => 'width:10%' ), '' ) . Html::openElement( 'td', array( 'style' => 'width:30%' ) ) . Html::openElement( 'ul' ) . "\n" ); } } - $wgOut->addHTML( + $out->addHTML( Html::closeElement( 'ul' ) . Html::closeElement( 'td' ) . Html::element( 'td', array( 'style' => 'width:30%' ), '' ) . Html::closeElement( 'tr' ) . Html::closeElement( 'table' ) . "\n" ); } - if ( $includesRestrictedPages ) { - $wgOut->wrapWikiMsg( "<div class=\"mw-specialpages-notes\">\n$1\n</div>", 'specialpages-note' ); + if ( $includesRestrictedPages || $includesCachedPages ) { + $out->wrapWikiMsg( "<div class=\"mw-specialpages-notes\">\n$1\n</div>", 'specialpages-note' ); } } } diff --git a/includes/specials/SpecialStatistics.php b/includes/specials/SpecialStatistics.php index b0d0246e..5def4da5 100644 --- a/includes/specials/SpecialStatistics.php +++ b/includes/specials/SpecialStatistics.php @@ -28,20 +28,20 @@ * @ingroup SpecialPage */ class SpecialStatistics extends SpecialPage { - + private $views, $edits, $good, $images, $total, $users, - $activeUsers, $admins = 0; - + $activeUsers = 0; + public function __construct() { parent::__construct( 'Statistics' ); } - + public function execute( $par ) { - global $wgOut, $wgMemc; - global $wgDisableCounters, $wgMiserMode; - + global $wgMemc, $wgDisableCounters, $wgMiserMode; + $this->setHeaders(); - + $this->getOutput()->addModuleStyles( 'mediawiki.special' ); + $this->views = SiteStats::views(); $this->edits = SiteStats::edits(); $this->good = SiteStats::articles(); @@ -49,15 +49,14 @@ class SpecialStatistics extends SpecialPage { $this->total = SiteStats::pages(); $this->users = SiteStats::users(); $this->activeUsers = SiteStats::activeUsers(); - $this->admins = SiteStats::numberingroup('sysop'); $this->hook = ''; - + # Staticic - views $viewsStats = ''; if( !$wgDisableCounters ) { $viewsStats = $this->getViewsStats(); } - + # Set active user count if( !$wgMiserMode ) { $key = wfMemcKey( 'sitestats', 'activeusers-updated' ); @@ -88,7 +87,7 @@ class SpecialStatistics extends SpecialPage { if( !$wgDisableCounters && !$wgMiserMode ) { $text .= $this->getMostViewedPages(); } - + # Statistic - other $extraStats = array(); if( wfRunHooks( 'SpecialStatsAddExtra', array( &$extraStats ) ) ) { @@ -98,12 +97,12 @@ class SpecialStatistics extends SpecialPage { $text .= Xml::closeElement( 'table' ); # Customizable footer - $footer = wfMsgExt( 'statistics-footer', array('parseinline') ); - if( !wfEmptyMsg( 'statistics-footer', $footer ) && $footer != '' ) { - $text .= "\n" . $footer; + $footer = wfMessage( 'statistics-footer' ); + if ( !$footer->isBlank() ) { + $text .= "\n" . $footer->parse(); } - $wgOut->addHTML( $text ); + $this->getOutput()->addHTML( $text ); } /** @@ -117,80 +116,72 @@ class SpecialStatistics extends SpecialPage { */ private function formatRow( $text, $number, $trExtraParams = array(), $descMsg = '', $descMsgParam = '' ) { if( $descMsg ) { - $descriptionText = wfMsgExt( $descMsg, array( 'parseinline' ), $descMsgParam ); - if ( !wfEmptyMsg( $descMsg, $descriptionText ) ) { - $descriptionText = " ($descriptionText)"; - $text .= "<br />" . Xml::element( 'small', array( 'class' => 'mw-statistic-desc'), - $descriptionText ); + $msg = wfMessage( $descMsg, $descMsgParam ); + if ( $msg->exists() ) { + $descriptionText = $msg->parse(); + $text .= "<br />" . Xml::element( 'small', array( 'class' => 'mw-statistic-desc'), + " ($descriptionText)" ); } } - return - Html::rawElement( 'tr', $trExtraParams, + return Html::rawElement( 'tr', $trExtraParams, Html::rawElement( 'td', array(), $text ) . Html::rawElement( 'td', array( 'class' => 'mw-statistics-numbers' ), $number ) ); } - + /** * Each of these methods is pretty self-explanatory, get a particular * row for the table of statistics * @return string */ private function getPageStats() { - global $wgLang; return Xml::openElement( 'tr' ) . Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-header-pages', array( 'parseinline' ) ) ) . Xml::closeElement( 'tr' ) . $this->formatRow( wfMsgExt( 'statistics-articles', array( 'parseinline' ) ), - $wgLang->formatNum( $this->good ), + $this->getLang()->formatNum( $this->good ), array( 'class' => 'mw-statistics-articles' ) ) . $this->formatRow( wfMsgExt( 'statistics-pages', array( 'parseinline' ) ), - $wgLang->formatNum( $this->total ), + $this->getLang()->formatNum( $this->total ), array( 'class' => 'mw-statistics-pages' ), 'statistics-pages-desc' ) . $this->formatRow( wfMsgExt( 'statistics-files', array( 'parseinline' ) ), - $wgLang->formatNum( $this->images ), + $this->getLang()->formatNum( $this->images ), array( 'class' => 'mw-statistics-files' ) ); } private function getEditStats() { - global $wgLang; return Xml::openElement( 'tr' ) . Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-header-edits', array( 'parseinline' ) ) ) . Xml::closeElement( 'tr' ) . $this->formatRow( wfMsgExt( 'statistics-edits', array( 'parseinline' ) ), - $wgLang->formatNum( $this->edits ), + $this->getLang()->formatNum( $this->edits ), array( 'class' => 'mw-statistics-edits' ) ) . $this->formatRow( wfMsgExt( 'statistics-edits-average', array( 'parseinline' ) ), - $wgLang->formatNum( sprintf( '%.2f', $this->total ? $this->edits / $this->total : 0 ) ), + $this->getLang()->formatNum( sprintf( '%.2f', $this->total ? $this->edits / $this->total : 0 ) ), array( 'class' => 'mw-statistics-edits-average' ) ); } private function getUserStats() { - global $wgLang, $wgUser, $wgActiveUserDays; - $sk = $wgUser->getSkin(); + global $wgActiveUserDays; return Xml::openElement( 'tr' ) . Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-header-users', array( 'parseinline' ) ) ) . Xml::closeElement( 'tr' ) . $this->formatRow( wfMsgExt( 'statistics-users', array( 'parseinline' ) ), - $wgLang->formatNum( $this->users ), + $this->getLang()->formatNum( $this->users ), array( 'class' => 'mw-statistics-users' ) ) . $this->formatRow( wfMsgExt( 'statistics-users-active', array( 'parseinline' ) ) . ' ' . - $sk->link( + Linker::linkKnown( SpecialPage::getTitleFor( 'Activeusers' ), - wfMsgHtml( 'listgrouprights-members' ), - array(), - array(), - 'known' + wfMsgHtml( 'listgrouprights-members' ) ), - $wgLang->formatNum( $this->activeUsers ), + $this->getLang()->formatNum( $this->activeUsers ), array( 'class' => 'mw-statistics-users-active' ), 'statistics-users-active-desc', - $wgLang->formatNum( $wgActiveUserDays ) ); + $this->getLang()->formatNum( $wgActiveUserDays ) ); } private function getGroupStats() { - global $wgGroupPermissions, $wgImplicitGroups, $wgLang, $wgUser; - $sk = $wgUser->getSkin(); + global $wgGroupPermissions, $wgImplicitGroups; $text = ''; foreach( $wgGroupPermissions as $group => $permissions ) { # Skip generic * and implicit groups @@ -198,29 +189,28 @@ class SpecialStatistics extends SpecialPage { continue; } $groupname = htmlspecialchars( $group ); - $msg = wfMsg( 'group-' . $groupname ); - if ( wfEmptyMsg( 'group-' . $groupname, $msg ) || $msg == '' ) { + $msg = wfMessage( 'group-' . $groupname ); + if ( $msg->isBlank() ) { $groupnameLocalized = $groupname; } else { - $groupnameLocalized = $msg; + $groupnameLocalized = $msg->text(); } - $msg = wfMsgForContent( 'grouppage-' . $groupname ); - if ( wfEmptyMsg( 'grouppage-' . $groupname, $msg ) || $msg == '' ) { + $msg = wfMessage( 'grouppage-' . $groupname )->inContentLanguage(); + if ( $msg->isBlank() ) { $grouppageLocalized = MWNamespace::getCanonicalName( NS_PROJECT ) . ':' . $groupname; } else { - $grouppageLocalized = $msg; + $grouppageLocalized = $msg->text(); } $linkTarget = Title::newFromText( $grouppageLocalized ); - $grouppage = $sk->link( + $grouppage = Linker::link( $linkTarget, htmlspecialchars( $groupnameLocalized ) ); - $grouplink = $sk->link( + $grouplink = Linker::linkKnown( SpecialPage::getTitleFor( 'Listusers' ), wfMsgHtml( 'listgrouprights-members' ), array(), - array( 'group' => $group ), - 'known' + array( 'group' => $group ) ); # Add a class when a usergroup contains no members to allow hiding these rows $classZero = ''; @@ -229,31 +219,28 @@ class SpecialStatistics extends SpecialPage { $classZero = ' statistics-group-zero'; } $text .= $this->formatRow( $grouppage . ' ' . $grouplink, - $wgLang->formatNum( $countUsers ), + $this->getLang()->formatNum( $countUsers ), array( 'class' => 'statistics-group-' . Sanitizer::escapeClass( $group ) . $classZero ) ); } return $text; } private function getViewsStats() { - global $wgLang; return Xml::openElement( 'tr' ) . Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-header-views', array( 'parseinline' ) ) ) . Xml::closeElement( 'tr' ) . $this->formatRow( wfMsgExt( 'statistics-views-total', array( 'parseinline' ) ), - $wgLang->formatNum( $this->views ), + $this->getLang()->formatNum( $this->views ), array ( 'class' => 'mw-statistics-views-total' ), 'statistics-views-total-desc' ) . $this->formatRow( wfMsgExt( 'statistics-views-peredit', array( 'parseinline' ) ), - $wgLang->formatNum( sprintf( '%.2f', $this->edits ? + $this->getLang()->formatNum( sprintf( '%.2f', $this->edits ? $this->views / $this->edits : 0 ) ), array ( 'class' => 'mw-statistics-views-peredit' ) ); } private function getMostViewedPages() { - global $wgLang, $wgUser; $text = ''; $dbr = wfGetDB( DB_SLAVE ); - $sk = $wgUser->getSkin(); $res = $dbr->select( 'page', array( @@ -278,9 +265,9 @@ class SpecialStatistics extends SpecialPage { foreach ( $res as $row ) { $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title ); if( $title instanceof Title ) { - $text .= $this->formatRow( $sk->link( $title ), - $wgLang->formatNum( $row->page_counter ) ); - + $text .= $this->formatRow( Linker::link( $title ), + $this->getLang()->formatNum( $row->page_counter ) ); + } } $res->free(); @@ -289,22 +276,20 @@ class SpecialStatistics extends SpecialPage { } private function getOtherStats( $stats ) { - global $wgLang; - if ( !count( $stats ) ) return ''; $return = Xml::openElement( 'tr' ) . Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-header-hooks', array( 'parseinline' ) ) ) . Xml::closeElement( 'tr' ); - + foreach( $stats as $name => $number ) { $name = htmlspecialchars( $name ); $number = htmlspecialchars( $number ); - - $return .= $this->formatRow( $name, $wgLang->formatNum( $number ), array( 'class' => 'mw-statistics-hook' ) ); + + $return .= $this->formatRow( $name, $this->getLang()->formatNum( $number ), array( 'class' => 'mw-statistics-hook' ) ); } - + return $return; } } diff --git a/includes/specials/SpecialTags.php b/includes/specials/SpecialTags.php index c2aecf47..66a89e94 100644 --- a/includes/specials/SpecialTags.php +++ b/includes/specials/SpecialTags.php @@ -48,7 +48,8 @@ class SpecialTags extends SpecialPage { Xml::tags( 'th', null, wfMsgExt( 'tags-hitcount-header', 'parseinline' ) ) ); $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'change_tag', array( 'ct_tag', 'count(*) as hitcount' ), array(), __METHOD__, array( 'GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC' ) ); + $res = $dbr->select( 'change_tag', array( 'ct_tag', 'count(*) AS hitcount' ), + array(), __METHOD__, array( 'GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC' ) ); foreach ( $res as $row ) { $html .= $this->doTagRow( $row->ct_tag, $row->hitcount ); @@ -62,10 +63,9 @@ class SpecialTags extends SpecialPage { } function doTagRow( $tag, $hitcount ) { - static $sk=null, $doneTags=array(); - if (!$sk) { - global $wgUser; - $sk = $wgUser->getSkin(); + static $sk = null, $doneTags = array(); + if ( !$sk ) { + $sk = $this->getSkin(); } if ( in_array( $tag, $doneTags ) ) { @@ -73,7 +73,7 @@ class SpecialTags extends SpecialPage { } global $wgLang; - + $newRow = ''; $newRow .= Xml::tags( 'td', null, Xml::element( 'tt', null, $tag ) ); @@ -81,8 +81,8 @@ class SpecialTags extends SpecialPage { $disp .= ' (' . $sk->link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag" ), wfMsgHtml( 'tags-edit' ) ) . ')'; $newRow .= Xml::tags( 'td', null, $disp ); - $desc = wfMsgExt( "tag-$tag-description", 'parseinline' ); - $desc = wfEmptyMsg( "tag-$tag-description", $desc ) ? '' : $desc; + $msg = wfMessage( "tag-$tag-description" ); + $desc = !$msg->exists() ? '' : $msg->parse(); $desc .= ' (' . $sk->link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag-description" ), wfMsgHtml( 'tags-edit' ) ) . ')'; $newRow .= Xml::tags( 'td', null, $desc ); diff --git a/includes/specials/SpecialUnblock.php b/includes/specials/SpecialUnblock.php new file mode 100644 index 00000000..521c1775 --- /dev/null +++ b/includes/specials/SpecialUnblock.php @@ -0,0 +1,209 @@ +<?php +/** + * 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 for unblocking users + * + * @ingroup SpecialPage + */ +class SpecialUnblock extends SpecialPage { + + protected $target; + protected $type; + protected $block; + + public function __construct(){ + parent::__construct( 'Unblock', 'block' ); + } + + public function execute( $par ){ + global $wgUser, $wgOut, $wgRequest; + + # Check permissions + if( !$this->userCanExecute( $wgUser ) ) { + $this->displayRestrictionError(); + return; + } + + # Check for database lock + if( wfReadOnly() ) { + throw new ReadOnlyError; + } + + list( $this->target, $this->type ) = SpecialBlock::getTargetAndType( $par, $wgRequest ); + $this->block = Block::newFromTarget( $this->target ); + + # bug 15810: blocked admins should have limited access here. This won't allow sysops + # to remove autoblocks on themselves, but they should have ipblock-exempt anyway + $status = SpecialBlock::checkUnblockSelf( $this->target ); + if ( $status !== true ) { + throw new ErrorPageError( 'badaccess', $status ); + } + + $wgOut->setPageTitle( wfMsg( 'unblockip' ) ); + $wgOut->addModules( 'mediawiki.special' ); + + $form = new HTMLForm( $this->getFields(), $this->getContext() ); + $form->setWrapperLegend( wfMsg( 'unblockip' ) ); + $form->setSubmitCallback( array( __CLASS__, 'processUnblock' ) ); + $form->setSubmitText( wfMsg( 'ipusubmit' ) ); + $form->addPreText( wfMsgExt( 'unblockiptext', 'parse' ) ); + + if( $form->show() ){ + switch( $this->type ){ + case Block::TYPE_USER: + case Block::TYPE_IP: + $wgOut->addWikiMsg( 'unblocked', $this->target ); + break; + case Block::TYPE_RANGE: + $wgOut->addWikiMsg( 'unblocked-range', $this->target ); + break; + case Block::TYPE_ID: + case Block::TYPE_AUTO: + $wgOut->addWikiMsg( 'unblocked-id', $this->target ); + break; + } + } + } + + protected function getFields(){ + $fields = array( + 'Target' => array( + 'type' => 'text', + 'label-message' => 'ipadressorusername', + 'tabindex' => '1', + 'size' => '45', + 'required' => true, + ), + 'Name' => array( + 'type' => 'info', + 'label-message' => 'ipadressorusername', + ), + 'Reason' => array( + 'type' => 'text', + 'label-message' => 'ipbreason', + ) + ); + + 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 ){ + $fields['Target']['default'] = $this->target; + unset( $fields['Name'] ); + + } else { + $fields['Target']['default'] = $target; + $fields['Target']['type'] = 'hidden'; + switch( $type ){ + case Block::TYPE_USER: + case Block::TYPE_IP: + $skin = $this->getSkin(); + $fields['Name']['default'] = $skin->link( + $target->getUserPage(), + $target->getName() + ); + $fields['Name']['raw'] = true; + break; + + case Block::TYPE_RANGE: + $fields['Name']['default'] = $target; + break; + + case Block::TYPE_AUTO: + $fields['Name']['default'] = $this->block->getRedactedName(); + $fields['Name']['raw'] = true; + # Don't expose the real target of the autoblock + $fields['Target']['default'] = "#{$this->target}"; + break; + } + } + + } else { + $fields['Target']['default'] = $this->target; + unset( $fields['Name'] ); + } + return $fields; + } + + /** + * Process the form + * @return Array( Array(message key, parameters) ) on failure, True on success + */ + public static function processUnblock( array $data ){ + global $wgUser; + + $target = $data['Target']; + $block = Block::newFromTarget( $data['Target'] ); + + if( !$block instanceof Block ){ + return array( array( 'ipb_cant_unblock', $target ) ); + } + + # 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 the name was hidden and the blocking user cannot hide + # names, then don't allow any block removals... + if( !$wgUser->isAllowed( 'hideuser' ) && $block->mHideName ) { + return array( 'unblock-hideuser' ); + } + + # Delete block + if ( !$block->delete() ) { + return array( 'ipb_cant_unblock', htmlspecialchars( $block->getTarget() ) ); + } + + # Unset _deleted fields as needed + 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() + : User::idFromName( $block->getTarget() ); + + RevisionDeleteUser::unsuppressUserName( $block->getTarget(), $id ); + } + + # Redact the name (IP address) for autoblocks + if ( $block->getType() == Block::TYPE_AUTO ) { + $page = Title::makeTitle( NS_USER, '#' . $block->getId() ); + } else { + $page = $block->getTarget() instanceof User + ? $block->getTarget()->getUserpage() + : Title::makeTitle( NS_USER, $block->getTarget() ); + } + + # Make log entry + $log = new LogPage( 'block' ); + $log->addEntry( 'unblock', $page, $data['Reason'] ); + + return true; + } +} diff --git a/includes/specials/SpecialUncategorizedcategories.php b/includes/specials/SpecialUncategorizedcategories.php index 9574af70..70d98df9 100644 --- a/includes/specials/SpecialUncategorizedcategories.php +++ b/includes/specials/SpecialUncategorizedcategories.php @@ -27,22 +27,8 @@ * @ingroup SpecialPage */ class UncategorizedCategoriesPage extends UncategorizedPagesPage { - function __construct() { + function __construct( $name = 'Uncategorizedcategories' ) { + parent::__construct( $name ); $this->requestedNamespace = NS_CATEGORY; } - - function getName() { - return "Uncategorizedcategories"; - } -} - -/** - * constructor - */ -function wfSpecialUncategorizedcategories() { - list( $limit, $offset ) = wfCheckLimits(); - - $lpp = new UncategorizedCategoriesPage(); - - return $lpp->doQuery( $offset, $limit ); } diff --git a/includes/specials/SpecialUncategorizedimages.php b/includes/specials/SpecialUncategorizedimages.php index c4254039..3efed747 100644 --- a/includes/specials/SpecialUncategorizedimages.php +++ b/includes/specials/SpecialUncategorizedimages.php @@ -27,10 +27,11 @@ * * @ingroup SpecialPage */ +// @todo FIXME: Use an instance of UncategorizedPagesPage or something class UncategorizedImagesPage extends ImageQueryPage { - function getName() { - return 'Uncategorizedimages'; + function __construct( $name = 'Uncategorizedimages' ) { + parent::__construct( $name ); } function sortDescending() { @@ -45,21 +46,18 @@ class UncategorizedImagesPage extends ImageQueryPage { return false; } - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $page, $categorylinks ) = $dbr->tableNamesN( 'page', 'categorylinks' ); - $ns = NS_FILE; - - return "SELECT 'Uncategorizedimages' AS type, page_namespace AS namespace, - page_title AS title, page_title AS value - FROM {$page} LEFT JOIN {$categorylinks} ON page_id = cl_from - WHERE cl_from IS NULL AND page_namespace = {$ns} AND page_is_redirect = 0"; + function getQueryInfo() { + return array ( + 'tables' => array( 'page', 'categorylinks' ), + 'fields' => array( 'page_namespace AS namespace', + 'page_title AS title', + 'page_title AS value' ), + 'conds' => array( 'cl_from IS NULL', + 'page_namespace' => NS_FILE, + 'page_is_redirect' => 0 ), + 'join_conds' => array( 'categorylinks' => array( + 'LEFT JOIN', 'cl_from=page_id' ) ) + ); } } - -function wfSpecialUncategorizedimages() { - $uip = new UncategorizedImagesPage(); - list( $limit, $offset ) = wfCheckLimits(); - return $uip->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialUncategorizedpages.php b/includes/specials/SpecialUncategorizedpages.php index c7fef5d2..08a69448 100644 --- a/includes/specials/SpecialUncategorizedpages.php +++ b/includes/specials/SpecialUncategorizedpages.php @@ -26,11 +26,12 @@ * * @ingroup SpecialPage */ +// @todo FIXME: Make $requestedNamespace selectable, unify all subclasses into one class UncategorizedPagesPage extends PageQueryPage { - var $requestedNamespace = NS_MAIN; + protected $requestedNamespace = false; - function getName() { - return "Uncategorizedpages"; + function __construct( $name = 'Uncategorizedpages' ) { + parent::__construct( $name ); } function sortDescending() { @@ -42,32 +43,27 @@ class UncategorizedPagesPage extends PageQueryPage { } function isSyndicated() { return false; } - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $page, $categorylinks ) = $dbr->tableNamesN( 'page', 'categorylinks' ); - $name = $dbr->addQuotes( $this->getName() ); - - return - " - SELECT - $name as type, - page_namespace AS namespace, - page_title AS title, - page_title AS value - FROM $page - LEFT JOIN $categorylinks ON page_id=cl_from - WHERE cl_from IS NULL AND page_namespace={$this->requestedNamespace} AND page_is_redirect=0 - "; + function getQueryInfo() { + return array ( + 'tables' => array ( 'page', 'categorylinks' ), + 'fields' => array ( 'page_namespace AS namespace', + 'page_title AS title', + 'page_title AS value' ), + // default for page_namespace is all content namespaces (if requestedNamespace is false) + // otherwise, page_namespace is requestedNamespace + 'conds' => array ( 'cl_from IS NULL', + 'page_namespace' => ( $this->requestedNamespace!==false ? $this->requestedNamespace : MWNamespace::getContentNamespaces() ), + 'page_is_redirect' => 0 ), + 'join_conds' => array ( 'categorylinks' => array ( + 'LEFT JOIN', 'cl_from = page_id' ) ) + ); } -} -/** - * constructor - */ -function wfSpecialUncategorizedpages() { - list( $limit, $offset ) = wfCheckLimits(); - - $lpp = new UncategorizedPagesPage(); - - return $lpp->doQuery( $offset, $limit ); + function getOrderFields() { + // For some crazy reason ordering by a constant + // causes a filesort + if( $this->requestedNamespace === false && count( MWNamespace::getContentNamespaces() ) > 1 ) + return array( 'page_namespace', 'page_title' ); + return array( 'page_title' ); + } } diff --git a/includes/specials/SpecialUncategorizedtemplates.php b/includes/specials/SpecialUncategorizedtemplates.php index aa4e979d..af038fa8 100644 --- a/includes/specials/SpecialUncategorizedtemplates.php +++ b/includes/specials/SpecialUncategorizedtemplates.php @@ -29,20 +29,8 @@ * @ingroup SpecialPage */ class UncategorizedTemplatesPage extends UncategorizedPagesPage { - - var $requestedNamespace = NS_TEMPLATE; - - public function getName() { - return 'Uncategorizedtemplates'; + public function __construct( $name = 'Uncategorizedtemplates' ) { + parent::__construct( $name ); + $this->requestedNamespace = NS_TEMPLATE; } - -} - -/** - * Main execution point - */ -function wfSpecialUncategorizedtemplates() { - list( $limit, $offset ) = wfCheckLimits(); - $utp = new UncategorizedTemplatesPage(); - $utp->doQuery( $offset, $limit ); } diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php index 1cf61d26..d4636e74 100644 --- a/includes/specials/SpecialUndelete.php +++ b/includes/specials/SpecialUndelete.php @@ -27,6 +27,10 @@ * @ingroup SpecialPage */ class PageArchive { + + /** + * @var Title + */ protected $title; var $fileStatus; @@ -76,6 +80,11 @@ class PageArchive { return self::listPages( $dbr, $conds ); } + /** + * @param $dbr DatabaseBase + * @param $condition + * @return bool|ResultWrapper + */ protected static function listPages( $dbr, $condition ) { return $dbr->resultObject( $dbr->select( @@ -105,9 +114,12 @@ class PageArchive { function listRevisions() { $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( 'archive', - array( 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text', 'ar_comment', 'ar_len', 'ar_deleted' ), + array( + 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text', + 'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id' + ), array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey() ), + 'ar_title' => $this->title->getDBkey() ), 'PageArchive::listRevisions', array( 'ORDER BY' => 'ar_timestamp DESC' ) ); $ret = $dbr->resultObject( $res ); @@ -155,19 +167,6 @@ class PageArchive { } /** - * Fetch (and decompress if necessary) the stored text for the deleted - * revision of the page with the given timestamp. - * - * @param $timestamp String - * @return String - * @deprecated Use getRevision() for more flexible information - */ - function getRevisionText( $timestamp ) { - $rev = $this->getRevision( $timestamp ); - return $rev ? $rev->getText() : null; - } - - /** * Return a Revision object containing data for the deleted revision. * Note that the result *may* or *may not* have a null page ID. * @@ -190,8 +189,8 @@ class PageArchive { 'ar_deleted', 'ar_len' ), 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() ) ); @@ -217,8 +216,8 @@ class PageArchive { $row = $dbr->selectRow( 'archive', 'ar_timestamp', array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey(), - 'ar_timestamp < ' . + 'ar_title' => $this->title->getDBkey(), + 'ar_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ), __METHOD__, array( @@ -263,7 +262,7 @@ class PageArchive { 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_" ); + return Revision::getRevisionText( $row, 'ar_' ); } else { // New-style: keyed to the text storage backend. $dbr = wfGetDB( DB_SLAVE ); @@ -275,7 +274,6 @@ class PageArchive { } } - /** * Fetch (and decompress if necessary) the stored text of the most * recently edited deleted revision of the page. @@ -289,7 +287,7 @@ class PageArchive { $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 ) { @@ -308,8 +306,8 @@ class PageArchive { $dbr = wfGetDB( DB_SLAVE ); $n = $dbr->selectField( 'archive', 'COUNT(ar_title)', array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey() ) ); - return ($n > 0); + 'ar_title' => $this->title->getDBkey() ) ); + return ( $n > 0 ); } /** @@ -336,6 +334,9 @@ class PageArchive { if( $restoreFiles && $this->title->getNamespace() == NS_FILE ) { $img = wfLocalFile( $this->title ); $this->fileStatus = $img->restore( $fileVersions, $unsuppress ); + if ( !$this->fileStatus->isOk() ) { + return false; + } $filesRestored = $this->fileStatus->successCount; } else { $filesRestored = 0; @@ -343,8 +344,9 @@ class PageArchive { if( $restoreText ) { $textRestored = $this->undeleteRevisions( $timestamps, $unsuppress, $comment ); - if($textRestored === false) // It must be one of UNDELETE_* + if( $textRestored === false ) { // It must be one of UNDELETE_* return false; + } } else { $textRestored = 0; } @@ -368,11 +370,12 @@ class PageArchive { return false; } - if( trim( $comment ) != '' ) + if( trim( $comment ) != '' ) { $reason .= wfMsgForContent( 'colon-separator' ) . $comment; + } $log->addEntry( 'restore', $this->title, $reason ); - return array($textRestored, $filesRestored, $reason); + return array( $textRestored, $filesRestored, $reason ); } /** @@ -387,19 +390,24 @@ class PageArchive { * @return Mixed: number of revisions restored or false on failure */ private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) { - if ( wfReadOnly() ) + if ( wfReadOnly() ) { return false; + } $restoreAll = empty( $timestamps ); $dbw = wfGetDB( DB_MASTER ); # Does this page already exist? We'll have to update it... $article = new Article( $this->title ); + # Load latest data for the current page (bug 31179) + $article->loadPageData( 'fromdbmaster' ); + $oldcountable = $article->isCountable(); + $options = 'FOR UPDATE'; // lock page $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__, $options ); @@ -463,7 +471,7 @@ class PageArchive { $ret = $dbw->resultObject( $result ); $rev_count = $dbw->numRows( $result ); if( !$rev_count ) { - wfDebug( __METHOD__.": no revisions to restore\n" ); + wfDebug( __METHOD__ . ": no revisions to restore\n" ); return false; // ??? } @@ -473,7 +481,7 @@ class PageArchive { if( $makepage ) { // Check the state of the newest to-be version... - if( !$unsuppress && ($row->ar_deleted & Revision::DELETED_TEXT) ) { + if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) { return false; // we can't leave the current revision like this! } // Safe to insert now... @@ -483,7 +491,7 @@ class PageArchive { // Check if a deleted revision will become the current revision... if( $row->ar_timestamp > $previousTimestamp ) { // Check the state of the newest to-be version... - if( !$unsuppress && ($row->ar_deleted & Revision::DELETED_TEXT) ) { + if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) { return false; // we can't leave the current revision like this! } } @@ -495,8 +503,11 @@ class PageArchive { foreach ( $ret as $row ) { // Check for key dupes due to shitty archive integrity. if( $row->ar_rev_id ) { - $exists = $dbw->selectField( 'revision', '1', array('rev_id' => $row->ar_rev_id), __METHOD__ ); - if( $exists ) continue; // don't throw DB errors + $exists = $dbw->selectField( 'revision', '1', + array( 'rev_id' => $row->ar_rev_id ), __METHOD__ ); + if( $exists ) { + continue; // don't throw DB errors + } } // Insert one revision at a time...maintaining deletion status // unless we are specifically removing all restrictions... @@ -520,38 +531,33 @@ class PageArchive { __METHOD__ ); // Was anything restored at all? - if( $restored == 0 ) + if ( $restored == 0 ) { return 0; + } - if( $revision ) { - // Attach the latest revision to the page... - $wasnew = $article->updateIfNewerOn( $dbw, $revision, $previousRevId ); - if( $newid || $wasnew ) { - // Update site stats, link tables, etc - $article->createUpdates( $revision ); - } + $created = (bool)$newid; - if( $newid ) { - wfRunHooks( 'ArticleUndelete', array( &$this->title, true, $comment ) ); - Article::onArticleCreate( $this->title ); - } else { - wfRunHooks( 'ArticleUndelete', array( &$this->title, false, $comment ) ); - Article::onArticleEdit( $this->title ); - } + // Attach the latest revision to the page... + $wasnew = $article->updateIfNewerOn( $dbw, $revision, $previousRevId ); + if ( $created || $wasnew ) { + // Update site stats, link tables, etc + $user = User::newFromName( $revision->getRawUserText(), false ); + $article->doEditUpdates( $revision, $user, array( 'created' => $created, 'oldcountable' => $oldcountable ) ); + } - if( $this->title->getNamespace() == NS_FILE ) { - $update = new HTMLCacheUpdate( $this->title, 'imagelinks' ); - $update->doUpdate(); - } - } else { - // Revision couldn't be created. This is very weird - wfDebug( "Undelete: unknown error...\n" ); - return false; + wfRunHooks( 'ArticleUndelete', array( &$this->title, $created, $comment ) ); + + if( $this->title->getNamespace() == NS_FILE ) { + $update = new HTMLCacheUpdate( $this->title, 'imagelinks' ); + $update->doUpdate(); } return $restored; } + /** + * @return Status + */ function getFileStatus() { return $this->fileStatus; } } @@ -561,47 +567,47 @@ class PageArchive { * * @ingroup SpecialPage */ -class UndeleteForm extends SpecialPage { - var $mAction, $mTarget, $mTimestamp, $mRestore, $mInvert, $mTargetObj; - var $mTargetTimestamp, $mAllowed, $mCanView, $mComment, $mToken, $mRequest; +class SpecialUndelete extends SpecialPage { + var $mAction, $mTarget, $mTimestamp, $mRestore, $mInvert, $mFilename; + var $mTargetTimestamp, $mAllowed, $mCanView, $mComment, $mToken; - function __construct( $request = null ) { - parent::__construct( 'Undelete', 'deletedhistory' ); + /** + * @var Title + */ + var $mTargetObj; - if ( $request === null ) { - global $wgRequest; - $this->mRequest = $wgRequest; - } else { - $this->mRequest = $request; - } + function __construct() { + parent::__construct( 'Undelete', 'deletedhistory' ); } function loadRequest() { - global $wgUser; - $this->mAction = $this->mRequest->getVal( 'action' ); - $this->mTarget = $this->mRequest->getVal( 'target' ); - $this->mSearchPrefix = $this->mRequest->getText( 'prefix' ); - $time = $this->mRequest->getVal( 'timestamp' ); + $request = $this->getRequest(); + $user = $this->getUser(); + + $this->mAction = $request->getVal( 'action' ); + $this->mTarget = $request->getVal( 'target' ); + $this->mSearchPrefix = $request->getText( 'prefix' ); + $time = $request->getVal( 'timestamp' ); $this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : ''; - $this->mFile = $this->mRequest->getVal( 'file' ); - - $posted = $this->mRequest->wasPosted() && - $wgUser->matchEditToken( $this->mRequest->getVal( 'wpEditToken' ) ); - $this->mRestore = $this->mRequest->getCheck( 'restore' ) && $posted; - $this->mInvert = $this->mRequest->getCheck( 'invert' ) && $posted; - $this->mPreview = $this->mRequest->getCheck( 'preview' ) && $posted; - $this->mDiff = $this->mRequest->getCheck( 'diff' ); - $this->mComment = $this->mRequest->getText( 'wpComment' ); - $this->mUnsuppress = $this->mRequest->getVal( 'wpUnsuppress' ) && $wgUser->isAllowed( 'suppressrevision' ); - $this->mToken = $this->mRequest->getVal( 'token' ); - - if ( $wgUser->isAllowed( 'undelete' ) && !$wgUser->isBlocked() ) { + $this->mFilename = $request->getVal( 'file' ); + + $posted = $request->wasPosted() && + $user->matchEditToken( $request->getVal( 'wpEditToken' ) ); + $this->mRestore = $request->getCheck( 'restore' ) && $posted; + $this->mInvert = $request->getCheck( 'invert' ) && $posted; + $this->mPreview = $request->getCheck( 'preview' ) && $posted; + $this->mDiff = $request->getCheck( 'diff' ); + $this->mComment = $request->getText( 'wpComment' ); + $this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && $user->isAllowed( 'suppressrevision' ); + $this->mToken = $request->getVal( 'token' ); + + if ( $user->isAllowed( 'undelete' ) && !$user->isBlocked() ) { $this->mAllowed = true; // user can restore $this->mCanView = true; // user can view content - } elseif ( $wgUser->isAllowed( 'deletedtext' ) ) { + } elseif ( $user->isAllowed( 'deletedtext' ) ) { $this->mAllowed = false; // user cannot restore $this->mCanView = true; // user can view content - } else { // user can only view the list of revisions + } else { // user can only view the list of revisions $this->mAllowed = false; $this->mCanView = false; $this->mTimestamp = ''; @@ -611,7 +617,7 @@ class UndeleteForm extends SpecialPage { if( $this->mRestore || $this->mInvert ) { $timestamps = array(); $this->mFileVersions = array(); - foreach( $_REQUEST as $key => $val ) { + foreach( $request->getValues() as $key => $val ) { $matches = array(); if( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) { array_push( $timestamps, $matches[1] ); @@ -627,10 +633,8 @@ class UndeleteForm extends SpecialPage { } function execute( $par ) { - global $wgOut, $wgUser; - $this->setHeaders(); - if ( !$this->userCanExecute( $wgUser ) ) { + if ( !$this->userCanExecute( $this->getUser() ) ) { $this->displayRestrictionError(); return; } @@ -638,10 +642,12 @@ class UndeleteForm extends SpecialPage { $this->loadRequest(); + $out = $this->getOutput(); + if ( $this->mAllowed ) { - $wgOut->setPagetitle( wfMsg( "undeletepage" ) ); + $out->setPageTitle( wfMsg( 'undeletepage' ) ); } else { - $wgOut->setPagetitle( wfMsg( "viewdeletedpage" ) ); + $out->setPageTitle( wfMsg( 'viewdeletedpage' ) ); } if( $par != '' ) { @@ -649,13 +655,14 @@ class UndeleteForm extends SpecialPage { } if ( $this->mTarget !== '' ) { $this->mTargetObj = Title::newFromURL( $this->mTarget ); + $this->getSkin()->setRelevantTitle( $this->mTargetObj ); } else { $this->mTargetObj = null; } if( is_null( $this->mTargetObj ) ) { - # Not all users can just browse every deleted page from the list - if( $wgUser->isAllowed( 'browsearchive' ) ) { + # Not all users can just browse every deleted page from the list + if( $this->getUser()->isAllowed( 'browsearchive' ) ) { $this->showSearchForm(); # List undeletable articles @@ -664,52 +671,53 @@ class UndeleteForm extends SpecialPage { $this->showList( $result ); } } else { - $wgOut->addWikiMsg( 'undelete-header' ); + $out->addWikiMsg( 'undelete-header' ); } return; } if( $this->mTimestamp !== '' ) { return $this->showRevision( $this->mTimestamp ); } - if( $this->mFile !== null ) { - $file = new ArchivedFile( $this->mTargetObj, '', $this->mFile ); + if( $this->mFilename !== null ) { + $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename ); // Check if user is allowed to see this file if ( !$file->exists() ) { - $wgOut->addWikiMsg( 'filedelete-nofile', $this->mFile ); + $out->addWikiMsg( 'filedelete-nofile', $this->mFilename ); return; - } else if( !$file->userCan( File::DELETED_FILE ) ) { + } elseif( !$file->userCan( File::DELETED_FILE ) ) { if( $file->isDeleted( File::DELETED_RESTRICTED ) ) { - $wgOut->permissionRequired( 'suppressrevision' ); + $out->permissionRequired( 'suppressrevision' ); } else { - $wgOut->permissionRequired( 'deletedtext' ); + $out->permissionRequired( 'deletedtext' ); } return false; - } elseif ( !$wgUser->matchEditToken( $this->mToken, $this->mFile ) ) { - $this->showFileConfirmationForm( $this->mFile ); + } elseif ( !$this->getUser()->matchEditToken( $this->mToken, $this->mFilename ) ) { + $this->showFileConfirmationForm( $this->mFilename ); return false; } else { - return $this->showFile( $this->mFile ); + return $this->showFile( $this->mFilename ); } } - if( $this->mRestore && $this->mAction == "submit" ) { + if( $this->mRestore && $this->mAction == 'submit' ) { global $wgUploadMaintenance; if( $wgUploadMaintenance && $this->mTargetObj && $this->mTargetObj->getNamespace() == NS_FILE ) { - $wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n", array( 'filedelete-maintenance' ) ); + $out->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n", array( 'filedelete-maintenance' ) ); return; } return $this->undelete(); } - if( $this->mInvert && $this->mAction == "submit" ) { - return $this->showHistory( ); + if( $this->mInvert && $this->mAction == 'submit' ) { + return $this->showHistory(); } return $this->showHistory(); } function showSearchForm() { - global $wgOut, $wgScript; - $wgOut->addWikiMsg( 'undelete-header' ); + global $wgScript; + + $this->getOutput()->addWikiMsg( 'undelete-header' ); - $wgOut->addHTML( + $this->getOutput()->addHTML( Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . @@ -725,23 +733,27 @@ class UndeleteForm extends SpecialPage { ); } - // Generic list of deleted pages + /** + * Generic list of deleted pages + * + * @param $result ResultWrapper + * @return bool + */ private function showList( $result ) { - global $wgLang, $wgUser, $wgOut; + $out = $this->getOutput(); if( $result->numRows() == 0 ) { - $wgOut->addWikiMsg( 'undelete-no-results' ); + $out->addWikiMsg( 'undelete-no-results' ); return; } - $wgOut->addWikiMsg( 'undeletepagetext', $wgLang->formatNum( $result->numRows() ) ); + $out->addWikiMsg( 'undeletepagetext', $this->getLang()->formatNum( $result->numRows() ) ); - $sk = $wgUser->getSkin(); $undelete = $this->getTitle(); - $wgOut->addHTML( "<ul>\n" ); + $out->addHTML( "<ul>\n" ); foreach ( $result as $row ) { $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title ); - $link = $sk->linkKnown( + $link = Linker::linkKnown( $undelete, htmlspecialchars( $title->getPrefixedText() ), array(), @@ -749,111 +761,102 @@ class UndeleteForm extends SpecialPage { ); $revs = wfMsgExt( 'undeleterevisions', array( 'parseinline' ), - $wgLang->formatNum( $row->count ) ); - $wgOut->addHTML( "<li>{$link} ({$revs})</li>\n" ); + $this->getLang()->formatNum( $row->count ) ); + $out->addHTML( "<li>{$link} ({$revs})</li>\n" ); } $result->free(); - $wgOut->addHTML( "</ul>\n" ); + $out->addHTML( "</ul>\n" ); return true; } private function showRevision( $timestamp ) { - global $wgLang, $wgUser, $wgOut; - - $skin = $wgUser->getSkin(); + $out = $this->getOutput(); - if(!preg_match("/[0-9]{14}/",$timestamp)) return 0; + if( !preg_match( '/[0-9]{14}/', $timestamp ) ) { + return 0; + } $archive = new PageArchive( $this->mTargetObj ); + wfRunHooks( 'UndeleteForm::showRevision', array( &$archive, $this->mTargetObj ) ); $rev = $archive->getRevision( $timestamp ); if( !$rev ) { - $wgOut->addWikiMsg( 'undeleterevision-missing' ); + $out->addWikiMsg( 'undeleterevision-missing' ); return; } - if( $rev->isDeleted(Revision::DELETED_TEXT) ) { - if( !$rev->userCan(Revision::DELETED_TEXT) ) { - $wgOut->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 ) ) { + $out->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' ); return; } else { - $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' ); - $wgOut->addHTML( '<br />' ); + $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... } } - $wgOut->setPageTitle( wfMsg( 'undeletepage' ) ); - - $link = $skin->linkKnown( - $this->getTitle( $this->mTargetObj->getPrefixedDBkey() ), - htmlspecialchars( $this->mTargetObj->getPrefixedText() ) - ); + $out->setPageTitle( wfMsg( 'undeletepage' ) ); if( $this->mDiff ) { $previousRev = $archive->getPreviousRevision( $timestamp ); if( $previousRev ) { $this->showDiff( $previousRev, $rev ); - if( $wgUser->getOption( 'diffonly' ) ) { + if( $this->getUser()->getOption( 'diffonly' ) ) { return; } else { - $wgOut->addHTML( '<hr />' ); + $out->addHTML( '<hr />' ); } } else { - $wgOut->addWikiMsg( 'undelete-nodiff' ); + $out->addWikiMsg( 'undelete-nodiff' ); } } + $link = Linker::linkKnown( + $this->getTitle( $this->mTargetObj->getPrefixedDBkey() ), + htmlspecialchars( $this->mTargetObj->getPrefixedText() ) + ); + // date and time are separate parameters to facilitate localisation. // $time is kept for backward compat reasons. - $time = htmlspecialchars( $wgLang->timeAndDate( $timestamp, true ) ); - $d = htmlspecialchars( $wgLang->date( $timestamp, true ) ); - $t = htmlspecialchars( $wgLang->time( $timestamp, true ) ); - $user = $skin->revUserTools( $rev ); + $time = $this->getLang()->timeAndDate( $timestamp, true ); + $d = $this->getLang()->date( $timestamp, true ); + $t = $this->getLang()->time( $timestamp, true ); + $user = Linker::revUserTools( $rev ); if( $this->mPreview ) { $openDiv = '<div id="mw-undelete-revision" class="mw-warning">'; } else { $openDiv = '<div id="mw-undelete-revision">'; } + $out->addHTML( $openDiv ); // Revision delete links - $canHide = $wgUser->isAllowed( 'deleterevision' ); - if( $this->mDiff ) { - $revdlink = ''; // diffs already have revision delete links - } else if( $canHide || ($rev->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) { - if( !$rev->userCan(Revision::DELETED_RESTRICTED ) ) { - $revdlink = $skin->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops - } else { - $query = array( - 'type' => 'archive', - 'target' => $this->mTargetObj->getPrefixedDBkey(), - 'ids' => $rev->getTimestamp() - ); - $revdlink = $skin->revDeleteLink( $query, - $rev->isDeleted( File::DELETED_RESTRICTED ), $canHide ); + if ( !$this->mDiff ) { + $revdel = $this->revDeleteLink( $rev ); + if ( $revdel ) { + $out->addHTML( $revdel ); } - } else { - $revdlink = ''; } - $wgOut->addHTML( $openDiv . $revdlink . wfMsgWikiHtml( 'undelete-revision', $link, $time, $user, $d, $t ) . '</div>' ); + $out->addHTML( wfMessage( 'undelete-revision' )->rawParams( $link )->params( + $time )->rawParams( $user )->params( $d, $t )->parse() . '</div>' ); wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) ); if( $this->mPreview ) { - //Hide [edit]s - $popts = $wgOut->parserOptions(); + // Hide [edit]s + $popts = $out->parserOptions(); $popts->setEditSection( false ); - $wgOut->parserOptions( $popts ); - $wgOut->addWikiTextTitleTidy( $rev->getText( Revision::FOR_THIS_USER ), $this->mTargetObj, true ); + $out->parserOptions( $popts ); + $out->addWikiTextTitleTidy( $rev->getText( Revision::FOR_THIS_USER ), $this->mTargetObj, true ); } - $wgOut->addHTML( + $out->addHTML( Xml::element( 'textarea', array( 'readonly' => 'readonly', - 'cols' => intval( $wgUser->getOption( 'cols' ) ), - 'rows' => intval( $wgUser->getOption( 'rows' ) ) ), + 'cols' => intval( $this->getUser()->getOption( 'cols' ) ), + 'rows' => intval( $this->getUser()->getOption( 'rows' ) ) ), $rev->getText( Revision::FOR_THIS_USER ) . "\n" ) . Xml::openElement( 'div' ) . Xml::openElement( 'form', array( @@ -870,7 +873,7 @@ class UndeleteForm extends SpecialPage { Xml::element( 'input', array( 'type' => 'hidden', 'name' => 'wpEditToken', - 'value' => $wgUser->editToken() ) ) . + 'value' => $this->getUser()->editToken() ) ) . Xml::element( 'input', array( 'type' => 'submit', 'name' => 'preview', @@ -884,6 +887,48 @@ class UndeleteForm extends SpecialPage { } /** + * Get a revision-deletion link, or disabled link, or nothing, depending + * on user permissions & the settings on the revision. + * + * Will use forward-compatible revision ID in the Special:RevDelete link + * if possible, otherwise the timestamp-based ID which may break after + * undeletion. + * + * @param Revision $rev + * @return string HTML fragment + */ + function revDeleteLink( $rev ) { + $canHide = $this->getUser()->isAllowed( 'deleterevision' ); + if( $canHide || ( $rev->getVisibility() && $this->getUser()->isAllowed( 'deletedhistory' ) ) ) { + if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) { + $revdlink = Linker::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops + } else { + if ( $rev->getId() ) { + // RevDelete links using revision ID are stable across + // page deletion and undeletion; use when possible. + $query = array( + 'type' => 'revision', + 'target' => $this->mTargetObj->getPrefixedDBkey(), + 'ids' => $rev->getId() + ); + } else { + // Older deleted entries didn't save a revision ID. + // We have to refer to these by timestamp, ick! + $query = array( + 'type' => 'archive', + 'target' => $this->mTargetObj->getPrefixedDBkey(), + 'ids' => $rev->getTimestamp() + ); + } + return Linker::revDeleteLink( $query, + $rev->isDeleted( File::DELETED_RESTRICTED ), $canHide ); + } + } else { + return ''; + } + } + + /** * Build a diff display between this and the previous either deleted * or non-deleted edit. * @@ -892,11 +937,9 @@ class UndeleteForm extends SpecialPage { * @return String: HTML */ function showDiff( $previousRev, $currentRev ) { - global $wgOut; - $diffEngine = new DifferenceEngine( $previousRev->getTitle() ); $diffEngine->showDiffStyle(); - $wgOut->addHTML( + $this->getOutput()->addHTML( "<div>" . "<table border='0' width='98%' cellpadding='0' cellspacing='4' class='diff'>" . "<col class='diff-marker' />" . @@ -918,59 +961,47 @@ class UndeleteForm extends SpecialPage { ); } + /** + * @param $rev Revision + * @param $prefix + * @return string + */ private function diffHeader( $rev, $prefix ) { - global $wgUser, $wgLang; - $sk = $wgUser->getSkin(); $isDeleted = !( $rev->getId() && $rev->getTitle() ); if( $isDeleted ) { - /// @todo Fixme: $rev->getTitle() is null for deleted revs...? + /// @todo FIXME: $rev->getTitle() is null for deleted revs...? $targetPage = $this->getTitle(); $targetQuery = array( 'target' => $this->mTargetObj->getPrefixedText(), 'timestamp' => wfTimestamp( TS_MW, $rev->getTimestamp() ) ); } else { - /// @todo Fixme getId() may return non-zero for deleted revs... + /// @todo FIXME: getId() may return non-zero for deleted revs... $targetPage = $rev->getTitle(); $targetQuery = array( 'oldid' => $rev->getId() ); } // Add show/hide deletion links if available - $canHide = $wgUser->isAllowed( 'deleterevision' ); - if( $canHide || ($rev->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) { - $del = ' '; - if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) { - $del .= $sk->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops - } else { - $query = array( - 'type' => 'archive', - 'target' => $this->mTargetObj->getPrefixedDbkey(), - 'ids' => $rev->getTimestamp() - ); - $del .= $sk->revDeleteLink( $query, - $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide ); - } - } else { - $del = ''; - } + $del = $this->revDeleteLink( $rev ); return - '<div id="mw-diff-'.$prefix.'title1"><strong>' . - $sk->link( + '<div id="mw-diff-' . $prefix . 'title1"><strong>' . + Linker::link( $targetPage, - wfMsgHtml( + wfMsgExt( 'revisionasof', - htmlspecialchars( $wgLang->timeanddate( $rev->getTimestamp(), true ) ), - htmlspecialchars( $wgLang->date( $rev->getTimestamp(), true ) ), - htmlspecialchars( $wgLang->time( $rev->getTimestamp(), true ) ) + array( 'escape' ), + $this->getLang()->timeanddate( $rev->getTimestamp(), true ), + $this->getLang()->date( $rev->getTimestamp(), true ), + $this->getLang()->time( $rev->getTimestamp(), true ) ), array(), $targetQuery ) . '</strong></div>' . '<div id="mw-diff-'.$prefix.'title2">' . - $sk->revUserTools( $rev ) . '<br />' . + Linker::revUserTools( $rev ) . '<br />' . '</div>' . '<div id="mw-diff-'.$prefix.'title3">' . - $sk->revComment( $rev ) . $del . '<br />' . + Linker::revComment( $rev ) . $del . '<br />' . '</div>'; } @@ -978,19 +1009,18 @@ class UndeleteForm extends SpecialPage { * Show a form confirming whether a tokenless user really wants to see a file */ private function showFileConfirmationForm( $key ) { - global $wgOut, $wgUser, $wgLang; - $file = new ArchivedFile( $this->mTargetObj, '', $this->mFile ); - $wgOut->addWikiMsg( 'undelete-show-file-confirm', + $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename ); + $this->getOutput()->addWikiMsg( 'undelete-show-file-confirm', $this->mTargetObj->getText(), - $wgLang->date( $file->getTimestamp() ), - $wgLang->time( $file->getTimestamp() ) ); - $wgOut->addHTML( + $this->getLang()->date( $file->getTimestamp() ), + $this->getLang()->time( $file->getTimestamp() ) ); + $this->getOutput()->addHTML( Xml::openElement( 'form', array( 'method' => 'POST', - 'action' => $this->getTitle()->getLocalUrl( + 'action' => $this->getTitle()->getLocalURL( 'target=' . urlencode( $this->mTarget ) . '&file=' . urlencode( $key ) . - '&token=' . urlencode( $wgUser->editToken( $key ) ) ) + '&token=' . urlencode( $this->getUser()->editToken( $key ) ) ) ) ) . Xml::submitButton( wfMsg( 'undelete-show-file-submit' ) ) . @@ -1002,16 +1032,16 @@ class UndeleteForm extends SpecialPage { * Show a deleted file version requested by the visitor. */ private function showFile( $key ) { - global $wgOut, $wgRequest; - $wgOut->disable(); + $this->getOutput()->disable(); # We mustn't allow the output to be Squid cached, otherwise # if an admin previews a deleted image, and it's cached, then # a user without appropriate permissions can toddle off and # nab the image, and Squid will serve it - $wgRequest->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); - $wgRequest->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' ); - $wgRequest->response()->header( 'Pragma: no-cache' ); + $response = $this->getRequest()->response(); + $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); + $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' ); + $response->header( 'Pragma: no-cache' ); global $IP; require_once( "$IP/includes/StreamFile.php" ); @@ -1020,34 +1050,36 @@ class UndeleteForm extends SpecialPage { wfStreamFile( $path ); } - private function showHistory( ) { - global $wgUser, $wgOut; - - $sk = $wgUser->getSkin(); + private function showHistory() { + $out = $this->getOutput(); if( $this->mAllowed ) { - $wgOut->setPagetitle( wfMsg( "undeletepage" ) ); + $out->addModules( 'mediawiki.special.undelete' ); + $out->setPageTitle( wfMsg( 'undeletepage' ) ); } else { - $wgOut->setPagetitle( wfMsg( 'viewdeletedpage' ) ); + $out->setPageTitle( wfMsg( 'viewdeletedpage' ) ); } - - $wgOut->wrapWikiMsg( "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n", array ( 'undeletepagetitle', $this->mTargetObj->getPrefixedText() ) ); + $out->wrapWikiMsg( + "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n", + array( 'undeletepagetitle', $this->mTargetObj->getPrefixedText() ) + ); $archive = new PageArchive( $this->mTargetObj ); + wfRunHooks( 'UndeleteForm::showHistory', array( &$archive, $this->mTargetObj ) ); /* $text = $archive->getLastRevisionText(); if( is_null( $text ) ) { - $wgOut->addWikiMsg( "nohistory" ); + $out->addWikiMsg( 'nohistory' ); return; } */ - $wgOut->addHTML( '<div class="mw-undelete-history">' ); + $out->addHTML( '<div class="mw-undelete-history">' ); if ( $this->mAllowed ) { - $wgOut->addWikiMsg( "undeletehistory" ); - $wgOut->addWikiMsg( "undeleterevdel" ); + $out->addWikiMsg( 'undeletehistory' ); + $out->addWikiMsg( 'undeleterevdel' ); } else { - $wgOut->addWikiMsg( "undeletehistorynoadmin" ); + $out->addWikiMsg( 'undeletehistorynoadmin' ); } - $wgOut->addHTML( '</div>' ); + $out->addHTML( '</div>' ); # List all stored revisions $revisions = $archive->listRevisions(); @@ -1080,39 +1112,39 @@ class UndeleteForm extends SpecialPage { $action = $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) ); # Start the form here $top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'undelete' ) ); - $wgOut->addHTML( $top ); + $out->addHTML( $top ); } # Show relevant lines from the deletion log: - $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) . "\n" ); - LogEventsList::showLogExtract( $wgOut, 'delete', $this->mTargetObj->getPrefixedText() ); + $out->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) . "\n" ); + LogEventsList::showLogExtract( $out, 'delete', $this->mTargetObj->getPrefixedText() ); # Show relevant lines from the suppression log: - if( $wgUser->isAllowed( 'suppressionlog' ) ) { - $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'suppress' ) ) . "\n" ); - LogEventsList::showLogExtract( $wgOut, 'suppress', $this->mTargetObj->getPrefixedText() ); + if( $this->getUser()->isAllowed( 'suppressionlog' ) ) { + $out->addHTML( Xml::element( 'h2', null, LogPage::logName( 'suppress' ) ) . "\n" ); + LogEventsList::showLogExtract( $out, 'suppress', $this->mTargetObj->getPrefixedText() ); } if( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) { # Format the user-visible controls (comment field, submission button) # in a nice little table - if( $wgUser->isAllowed( 'suppressrevision' ) ) { + if( $this->getUser()->isAllowed( 'suppressrevision' ) ) { $unsuppressBox = "<tr> <td> </td> <td class='mw-input'>" . - Xml::checkLabel( wfMsg('revdelete-unsuppress'), 'wpUnsuppress', + Xml::checkLabel( wfMsg( 'revdelete-unsuppress' ), 'wpUnsuppress', 'mw-undelete-unsuppress', $this->mUnsuppress ). "</td> </tr>"; } else { - $unsuppressBox = ""; + $unsuppressBox = ''; } $table = Xml::fieldset( wfMsg( 'undelete-fieldset-title' ) ) . Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) . "<tr> <td colspan='2' class='mw-undelete-extrahelp'>" . - wfMsgWikiHtml( 'undeleteextrahelp' ) . + wfMsgExt( 'undeleteextrahelp', 'parse' ) . "</td> </tr> <tr> @@ -1127,7 +1159,6 @@ class UndeleteForm extends SpecialPage { <td> </td> <td class='mw-submit'>" . Xml::submitButton( wfMsg( 'undeletebtn' ), array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) ) . ' ' . - Xml::element( 'input', array( 'type' => 'reset', 'value' => wfMsg( 'undeletereset' ), 'id' => 'mw-undelete-reset' ) ) . ' ' . Xml::submitButton( wfMsg( 'undeleteinvert' ), array( 'name' => 'invert', 'id' => 'mw-undelete-invert' ) ) . "</td> </tr>" . @@ -1135,51 +1166,49 @@ class UndeleteForm extends SpecialPage { Xml::closeElement( 'table' ) . Xml::closeElement( 'fieldset' ); - $wgOut->addHTML( $table ); + $out->addHTML( $table ); } - $wgOut->addHTML( Xml::element( 'h2', null, wfMsg( 'history' ) ) . "\n" ); + $out->addHTML( Xml::element( 'h2', null, wfMsg( 'history' ) ) . "\n" ); if( $haveRevisions ) { # The page's stored (deleted) history: - $wgOut->addHTML("<ul>"); + $out->addHTML( '<ul>' ); $remaining = $revisions->numRows(); $earliestLiveTime = $this->mTargetObj->getEarliestRevTime(); foreach ( $revisions as $row ) { $remaining--; - $wgOut->addHTML( $this->formatRevisionRow( $row, $earliestLiveTime, $remaining, $sk ) ); + $out->addHTML( $this->formatRevisionRow( $row, $earliestLiveTime, $remaining ) ); } $revisions->free(); - $wgOut->addHTML("</ul>"); + $out->addHTML( '</ul>' ); } else { - $wgOut->addWikiMsg( "nohistory" ); + $out->addWikiMsg( 'nohistory' ); } if( $haveFiles ) { - $wgOut->addHTML( Xml::element( 'h2', null, wfMsg( 'filehist' ) ) . "\n" ); - $wgOut->addHTML( "<ul>" ); + $out->addHTML( Xml::element( 'h2', null, wfMsg( 'filehist' ) ) . "\n" ); + $out->addHTML( '<ul>' ); foreach ( $files as $row ) { - $wgOut->addHTML( $this->formatFileRow( $row, $sk ) ); + $out->addHTML( $this->formatFileRow( $row ) ); } $files->free(); - $wgOut->addHTML( "</ul>" ); + $out->addHTML( '</ul>' ); } if ( $this->mAllowed ) { # Slip in the hidden controls here $misc = Html::hidden( 'target', $this->mTarget ); - $misc .= Html::hidden( 'wpEditToken', $wgUser->editToken() ); + $misc .= Html::hidden( 'wpEditToken', $this->getUser()->editToken() ); $misc .= Xml::closeElement( 'form' ); - $wgOut->addHTML( $misc ); + $out->addHTML( $misc ); } return true; } - private function formatRevisionRow( $row, $earliestLiveTime, $remaining, $sk ) { - global $wgUser, $wgLang; - + private function formatRevisionRow( $row, $earliestLiveTime, $remaining ) { $rev = Revision::newFromArchiveRow( $row, array( 'page' => $this->mTargetObj->getArticleId() ) ); $stxt = ''; @@ -1188,7 +1217,7 @@ class UndeleteForm extends SpecialPage { if( $this->mAllowed ) { if( $this->mInvert ) { if( in_array( $ts, $this->mTargetTimestamp ) ) { - $checkBox = Xml::check( "ts$ts"); + $checkBox = Xml::check( "ts$ts" ); } else { $checkBox = Xml::check( "ts$ts", true ); } @@ -1203,13 +1232,13 @@ class UndeleteForm extends SpecialPage { $titleObj = $this->getTitle(); # Last link if( !$rev->userCan( Revision::DELETED_TEXT ) ) { - $pageLink = htmlspecialchars( $wgLang->timeanddate( $ts, true ) ); - $last = wfMsgHtml('diff'); - } else if( $remaining > 0 || ($earliestLiveTime && $ts > $earliestLiveTime) ) { - $pageLink = $this->getPageLink( $rev, $titleObj, $ts, $sk ); - $last = $sk->linkKnown( + $pageLink = htmlspecialchars( $this->getLang()->timeanddate( $ts, true ) ); + $last = wfMsgHtml( 'diff' ); + } elseif( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) { + $pageLink = $this->getPageLink( $rev, $titleObj, $ts ); + $last = Linker::linkKnown( $titleObj, - wfMsgHtml('diff'), + wfMsgHtml( 'diff' ), array(), array( 'target' => $this->mTargetObj->getPrefixedText(), @@ -1218,77 +1247,61 @@ class UndeleteForm extends SpecialPage { ) ); } else { - $pageLink = $this->getPageLink( $rev, $titleObj, $ts, $sk ); - $last = wfMsgHtml('diff'); + $pageLink = $this->getPageLink( $rev, $titleObj, $ts ); + $last = wfMsgHtml( 'diff' ); } } else { - $pageLink = htmlspecialchars( $wgLang->timeanddate( $ts, true ) ); - $last = wfMsgHtml('diff'); + $pageLink = htmlspecialchars( $this->getLang()->timeanddate( $ts, true ) ); + $last = wfMsgHtml( 'diff' ); } // User links - $userLink = $sk->revUserTools( $rev ); + $userLink = Linker::revUserTools( $rev ); // Revision text size - if( !is_null($size = $row->ar_len) ) { - $stxt = $sk->formatRevisionSize( $size ); + $size = $row->ar_len; + if( !is_null( $size ) ) { + $stxt = Linker::formatRevisionSize( $size ); } // Edit summary - $comment = $sk->revComment( $rev ); + $comment = Linker::revComment( $rev ); // Revision delete links - $canHide = $wgUser->isAllowed( 'deleterevision' ); - if( $canHide || ($rev->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) { - if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) { - $revdlink = $sk->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops - } else { - $query = array( - 'type' => 'archive', - 'target' => $this->mTargetObj->getPrefixedDBkey(), - 'ids' => $ts - ); - $revdlink = $sk->revDeleteLink( $query, - $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide ); - } - } else { - $revdlink = ''; - } + $revdlink = $this->revDeleteLink( $rev ); return "<li>$checkBox $revdlink ($last) $pageLink . . $userLink $stxt $comment</li>"; } - private function formatFileRow( $row, $sk ) { - global $wgUser, $wgLang; - + private function formatFileRow( $row ) { $file = ArchivedFile::newFromRow( $row ); $ts = wfTimestamp( TS_MW, $row->fa_timestamp ); if( $this->mAllowed && $row->fa_storage_key ) { - $checkBox = Xml::check( "fileid" . $row->fa_id ); + $checkBox = Xml::check( 'fileid' . $row->fa_id ); $key = urlencode( $row->fa_storage_key ); - $pageLink = $this->getFileLink( $file, $this->getTitle(), $ts, $key, $sk ); + $pageLink = $this->getFileLink( $file, $this->getTitle(), $ts, $key ); } else { $checkBox = ''; - $pageLink = $wgLang->timeanddate( $ts, true ); + $pageLink = $this->getLang()->timeanddate( $ts, true ); } - $userLink = $this->getFileUser( $file, $sk ); + $userLink = $this->getFileUser( $file ); $data = wfMsg( 'widthheight', - $wgLang->formatNum( $row->fa_width ), - $wgLang->formatNum( $row->fa_height ) ) . + $this->getLang()->formatNum( $row->fa_width ), + $this->getLang()->formatNum( $row->fa_height ) ) . ' (' . - wfMsg( 'nbytes', $wgLang->formatNum( $row->fa_size ) ) . + wfMsg( 'nbytes', $this->getLang()->formatNum( $row->fa_size ) ) . ')'; $data = htmlspecialchars( $data ); - $comment = $this->getFileComment( $file, $sk ); + $comment = $this->getFileComment( $file ); // Add show/hide deletion links if available - $canHide = $wgUser->isAllowed( 'deleterevision' ); - if( $canHide || ($file->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) { - if( !$file->userCan(File::DELETED_RESTRICTED ) ) { - $revdlink = $sk->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops + $canHide = $this->getUser()->isAllowed( 'deleterevision' ); + if( $canHide || ( $file->getVisibility() && $this->getUser()->isAllowed( 'deletedhistory' ) ) ) { + if( !$file->userCan( File::DELETED_RESTRICTED ) ) { + $revdlink = Linker::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops } else { $query = array( 'type' => 'filearchive', 'target' => $this->mTargetObj->getPrefixedDBkey(), 'ids' => $row->fa_id ); - $revdlink = $sk->revDeleteLink( $query, + $revdlink = Linker::revDeleteLink( $query, $file->isDeleted( File::DELETED_RESTRICTED ), $canHide ); } } else { @@ -1299,17 +1312,17 @@ class UndeleteForm extends SpecialPage { /** * Fetch revision text link if it's available to all users + * + * @param $rev Revision * @return string */ - function getPageLink( $rev, $titleObj, $ts, $sk ) { - global $wgLang; - - $time = htmlspecialchars( $wgLang->timeanddate( $ts, true ) ); + function getPageLink( $rev, $titleObj, $ts ) { + $time = htmlspecialchars( $this->getLang()->timeanddate( $ts, true ) ); - if( !$rev->userCan(Revision::DELETED_TEXT) ) { + if( !$rev->userCan( Revision::DELETED_TEXT ) ) { return '<span class="history-deleted">' . $time . '</span>'; } else { - $link = $sk->linkKnown( + $link = Linker::linkKnown( $titleObj, $time, array(), @@ -1318,8 +1331,9 @@ class UndeleteForm extends SpecialPage { 'timestamp' => $ts ) ); - if( $rev->isDeleted(Revision::DELETED_TEXT) ) + if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { $link = '<span class="history-deleted">' . $link . '</span>'; + } return $link; } } @@ -1327,26 +1341,26 @@ class UndeleteForm extends SpecialPage { /** * Fetch image view link if it's available to all users * + * @param $file File * @return String: HTML fragment */ - function getFileLink( $file, $titleObj, $ts, $key, $sk ) { - global $wgLang, $wgUser; - - if( !$file->userCan(File::DELETED_FILE) ) { - return '<span class="history-deleted">' . $wgLang->timeanddate( $ts, true ) . '</span>'; + function getFileLink( $file, $titleObj, $ts, $key ) { + if( !$file->userCan( File::DELETED_FILE ) ) { + return '<span class="history-deleted">' . $this->getLang()->timeanddate( $ts, true ) . '</span>'; } else { - $link = $sk->linkKnown( + $link = Linker::linkKnown( $titleObj, - $wgLang->timeanddate( $ts, true ), + $this->getLang()->timeanddate( $ts, true ), array(), array( 'target' => $this->mTargetObj->getPrefixedText(), 'file' => $key, - 'token' => $wgUser->editToken( $key ) + 'token' => $this->getUser()->editToken( $key ) ) ); - if( $file->isDeleted(File::DELETED_FILE) ) + if( $file->isDeleted( File::DELETED_FILE ) ) { $link = '<span class="history-deleted">' . $link . '</span>'; + } return $link; } } @@ -1354,16 +1368,18 @@ class UndeleteForm extends SpecialPage { /** * Fetch file's user id if it's available to this user * + * @param $file File * @return String: HTML fragment */ - function getFileUser( $file, $sk ) { - if( !$file->userCan(File::DELETED_USER) ) { + function getFileUser( $file ) { + if( !$file->userCan( File::DELETED_USER ) ) { return '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>'; } else { - $link = $sk->userLink( $file->getRawUser(), $file->getRawUserText() ) . - $sk->userToolLinks( $file->getRawUser(), $file->getRawUserText() ); - if( $file->isDeleted(File::DELETED_USER) ) + $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; } } @@ -1371,54 +1387,57 @@ class UndeleteForm extends SpecialPage { /** * Fetch file upload comment if it's available to this user * + * @param $file File * @return String: HTML fragment */ - function getFileComment( $file, $sk ) { - if( !$file->userCan(File::DELETED_COMMENT) ) { - return '<span class="history-deleted"><span class="comment">' . wfMsgHtml( 'rev-deleted-comment' ) . '</span></span>'; + function getFileComment( $file ) { + if( !$file->userCan( File::DELETED_COMMENT ) ) { + return '<span class="history-deleted"><span class="comment">' . + wfMsgHtml( 'rev-deleted-comment' ) . '</span></span>'; } else { - $link = $sk->commentBlock( $file->getRawDescription() ); - if( $file->isDeleted(File::DELETED_COMMENT) ) + $link = Linker::commentBlock( $file->getRawDescription() ); + if( $file->isDeleted( File::DELETED_COMMENT ) ) { $link = '<span class="history-deleted">' . $link . '</span>'; + } return $link; } } function undelete() { - global $wgOut, $wgUser; if ( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; + throw new ReadOnlyError; } + if( !is_null( $this->mTargetObj ) ) { $archive = new PageArchive( $this->mTargetObj ); + wfRunHooks( 'UndeleteForm::undelete', array( &$archive, $this->mTargetObj ) ); $ok = $archive->undelete( $this->mTargetTimestamp, $this->mComment, $this->mFileVersions, $this->mUnsuppress ); - if( is_array($ok) ) { - if ( $ok[1] ) // Undeleted file count + if( is_array( $ok ) ) { + if ( $ok[1] ) { // Undeleted file count wfRunHooks( 'FileUndeleteComplete', array( $this->mTargetObj, $this->mFileVersions, - $wgUser, $this->mComment) ); + $this->getUser(), $this->mComment ) ); + } - $skin = $wgUser->getSkin(); - $link = $skin->linkKnown( $this->mTargetObj ); - $wgOut->addHTML( wfMsgWikiHtml( 'undeletedpage', $link ) ); + $link = Linker::linkKnown( $this->mTargetObj ); + $this->getOutput()->addHTML( wfMessage( 'undeletedpage' )->rawParams( $link )->parse() ); } else { - $wgOut->showFatalError( wfMsg( "cannotundelete" ) ); - $wgOut->addHTML( '<p>' . wfMsgHtml( "undeleterevdel" ) . '</p>' ); + $this->getOutput()->showFatalError( wfMsg( 'cannotundelete' ) ); + $this->getOutput()->addWikiMsg( 'undeleterevdel' ); } // Show file deletion warnings and errors $status = $archive->getFileStatus(); if( $status && !$status->isGood() ) { - $wgOut->addWikiText( $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) ); + $this->getOutput()->addWikiText( $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) ); } } else { - $wgOut->showFatalError( wfMsg( "cannotundelete" ) ); + $this->getOutput()->showFatalError( wfMsg( 'cannotundelete' ) ); } return false; } diff --git a/includes/specials/SpecialUnlockdb.php b/includes/specials/SpecialUnlockdb.php index c71b554b..95ad0bf5 100644 --- a/includes/specials/SpecialUnlockdb.php +++ b/includes/specials/SpecialUnlockdb.php @@ -33,12 +33,13 @@ class SpecialUnlockdb extends SpecialPage { } public function execute( $par ) { - global $wgUser, $wgOut, $wgRequest; + global $wgUser, $wgRequest; $this->setHeaders(); - if( !$wgUser->isAllowed( 'siteadmin' ) ) { - $wgOut->permissionRequired( 'siteadmin' ); + # Permission check + if( !$this->userCanExecute( $wgUser ) ) { + $this->displayRestrictionError(); return; } @@ -48,7 +49,7 @@ class SpecialUnlockdb extends SpecialPage { if ( $action == 'success' ) { $this->showSuccess(); - } else if ( $action == 'submit' && $wgRequest->wasPosted() && + } elseif ( $action == 'submit' && $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { $this->doSubmit(); } else { @@ -104,7 +105,12 @@ class SpecialUnlockdb extends SpecialPage { $this->showForm( wfMsg( 'locknoconfirm' ) ); return; } - if ( @!unlink( $wgReadOnlyFile ) ) { + + wfSuppressWarnings(); + $res = unlink( $wgReadOnlyFile ); + wfRestoreWarnings(); + + if ( !$res ) { $wgOut->showFileDeleteError( $wgReadOnlyFile ); return; } diff --git a/includes/specials/SpecialUnusedcategories.php b/includes/specials/SpecialUnusedcategories.php index ff2a66e1..e4b8e544 100644 --- a/includes/specials/SpecialUnusedcategories.php +++ b/includes/specials/SpecialUnusedcategories.php @@ -28,25 +28,26 @@ class UnusedCategoriesPage extends QueryPage { function isExpensive() { return true; } - function getName() { - return 'Unusedcategories'; + function __construct( $name = 'Unusedcategories' ) { + parent::__construct( $name ); } function getPageHeader() { return wfMsgExt( 'unusedcategoriestext', array( 'parse' ) ); } - function getSQL() { - $NScat = NS_CATEGORY; - $dbr = wfGetDB( DB_SLAVE ); - list( $categorylinks, $page ) = $dbr->tableNamesN( 'categorylinks', 'page' ); - return "SELECT 'Unusedcategories' as type, - {$NScat} as namespace, page_title as title, page_title as value - FROM $page - LEFT JOIN $categorylinks ON page_title=cl_to - WHERE cl_from IS NULL - AND page_namespace = {$NScat} - AND page_is_redirect = 0"; + function getQueryInfo() { + return array ( + 'tables' => array ( 'page', 'categorylinks' ), + 'fields' => array ( 'page_namespace AS namespace', + 'page_title AS title', + 'page_title AS value' ), + 'conds' => array ( 'cl_from IS NULL', + 'page_namespace' => NS_CATEGORY, + 'page_is_redirect' => 0 ), + 'join_conds' => array ( 'categorylinks' => array ( + 'LEFT JOIN', 'cl_to = page_title' ) ) + ); } /** @@ -61,10 +62,3 @@ class UnusedCategoriesPage extends QueryPage { return $skin->link( $title, $title->getText() ); } } - -/** constructor */ -function wfSpecialUnusedCategories() { - list( $limit, $offset ) = wfCheckLimits(); - $uc = new UnusedCategoriesPage(); - return $uc->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialUnusedimages.php b/includes/specials/SpecialUnusedimages.php index 091ec3a3..6407de44 100644 --- a/includes/specials/SpecialUnusedimages.php +++ b/includes/specials/SpecialUnusedimages.php @@ -27,41 +27,53 @@ * @ingroup SpecialPage */ class UnusedimagesPage extends ImageQueryPage { + function __construct( $name = 'Unusedimages' ) { + parent::__construct( $name ); + } - function isExpensive() { return true; } - - function getName() { - return 'Unusedimages'; + function isExpensive() { + return true; } function sortDescending() { return false; } - function isSyndicated() { return false; } - - function getSQL() { - global $wgCountCategorizedImagesAsUsed; - $dbr = wfGetDB( DB_SLAVE ); + function isSyndicated() { + return false; + } - $epoch = $dbr->unixTimestamp( 'img_timestamp' ); + function getQueryInfo() { + global $wgCountCategorizedImagesAsUsed; + $retval = array ( + 'tables' => array ( 'image', 'imagelinks' ), + 'fields' => array ( "'" . NS_FILE . "' AS namespace", + 'img_name AS title', + 'img_timestamp AS value', + 'img_user', 'img_user_text', + 'img_description' ), + 'conds' => array ( 'il_to IS NULL' ), + 'join_conds' => array ( 'imagelinks' => array ( + 'LEFT JOIN', 'il_to = img_name' ) ) + ); if ( $wgCountCategorizedImagesAsUsed ) { - list( $page, $image, $imagelinks, $categorylinks ) = $dbr->tableNamesN( 'page', 'image', 'imagelinks', 'categorylinks' ); - - return "SELECT 'Unusedimages' as type, 6 as namespace, img_name as title, $epoch as value, - img_user, img_user_text, img_description - FROM ((($page AS I LEFT JOIN $categorylinks AS L ON I.page_id = L.cl_from) - LEFT JOIN $imagelinks AS P ON I.page_title = P.il_to) - INNER JOIN $image AS G ON I.page_title = G.img_name) - WHERE I.page_namespace = ".NS_FILE." AND L.cl_from IS NULL AND P.il_to IS NULL"; - } else { - list( $image, $imagelinks ) = $dbr->tableNamesN( 'image','imagelinks' ); - - return "SELECT 'Unusedimages' as type, 6 as namespace, img_name as title, $epoch as value, - img_user, img_user_text, img_description - FROM $image LEFT JOIN $imagelinks ON img_name=il_to WHERE il_to IS NULL "; + // Order is significant + $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 ( + 'LEFT JOIN', 'cl_from = page_id' ); + $retval['join_conds']['imagelinks'] = array ( + 'LEFT JOIN', 'il_to = page_title' ); } + return $retval; + } + + function usesTimestamps() { + return true; } function getPageHeader() { @@ -69,13 +81,3 @@ class UnusedimagesPage extends ImageQueryPage { } } - -/** - * Entry point - */ -function wfSpecialUnusedimages() { - list( $limit, $offset ) = wfCheckLimits(); - $uip = new UnusedimagesPage(); - - return $uip->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialUnusedtemplates.php b/includes/specials/SpecialUnusedtemplates.php index 68bf95a2..da501605 100644 --- a/includes/specials/SpecialUnusedtemplates.php +++ b/includes/specials/SpecialUnusedtemplates.php @@ -31,24 +31,34 @@ */ class UnusedtemplatesPage extends QueryPage { - function getName() { return( 'Unusedtemplates' ); } + function __construct( $name = 'Unusedtemplates' ) { + parent::__construct( $name ); + } + function isExpensive() { return true; } function isSyndicated() { return false; } function sortDescending() { return false; } - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $page, $templatelinks) = $dbr->tableNamesN( 'page', 'templatelinks' ); - $sql = "SELECT 'Unusedtemplates' AS type, page_title AS title, - page_namespace AS namespace, 0 AS value - FROM $page - LEFT JOIN $templatelinks - ON page_namespace = tl_namespace AND page_title = tl_title - WHERE page_namespace = 10 AND tl_from IS NULL - AND page_is_redirect = 0"; - return $sql; + function getQueryInfo() { + return array ( + 'tables' => array ( 'page', 'templatelinks' ), + 'fields' => array ( 'page_namespace AS namespace', + 'page_title AS title', + 'page_title AS value' ), + 'conds' => array ( 'page_namespace' => NS_TEMPLATE, + 'tl_from IS NULL', + 'page_is_redirect' => 0 ), + 'join_conds' => array ( 'templatelinks' => array ( + 'LEFT JOIN', array ( 'tl_title = page_title', + 'tl_namespace = page_namespace' ) ) ) + ); } + /** + * @param $skin Skin + * @param $result + * @return string + */ function formatResult( $skin, $result ) { $title = Title::makeTitle( NS_TEMPLATE, $result->title ); $pageLink = $skin->linkKnown( @@ -72,8 +82,3 @@ class UnusedtemplatesPage extends QueryPage { } -function wfSpecialUnusedtemplates() { - list( $limit, $offset ) = wfCheckLimits(); - $utp = new UnusedtemplatesPage(); - $utp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialUnwatchedpages.php b/includes/specials/SpecialUnwatchedpages.php index ecd62cb7..0f11140b 100644 --- a/includes/specials/SpecialUnwatchedpages.php +++ b/includes/specials/SpecialUnwatchedpages.php @@ -31,62 +31,58 @@ */ class UnwatchedpagesPage extends QueryPage { - function getName() { return 'Unwatchedpages'; } + function __construct( $name = 'Unwatchedpages' ) { + parent::__construct( $name, 'unwatchedpages' ); + } + function isExpensive() { return true; } function isSyndicated() { return false; } - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $page, $watchlist ) = $dbr->tableNamesN( 'page', 'watchlist' ); - $mwns = NS_MEDIAWIKI; - return - " - SELECT - 'Unwatchedpages' as type, - page_namespace as namespace, - page_title as title, - page_namespace as value - FROM $page - LEFT JOIN $watchlist ON wl_namespace = page_namespace AND page_title = wl_title - WHERE wl_title IS NULL AND page_is_redirect = 0 AND page_namespace<>$mwns - "; + function getQueryInfo() { + return array ( + 'tables' => array ( 'page', 'watchlist' ), + 'fields' => array ( 'page_namespace AS namespace', + 'page_title AS title', + 'page_namespace AS value' ), + 'conds' => array ( 'wl_title IS NULL', + 'page_is_redirect' => 0, + "page_namespace != '" . NS_MEDIAWIKI . + "'" ), + 'join_conds' => array ( 'watchlist' => array ( + 'LEFT JOIN', array ( 'wl_title = page_title', + 'wl_namespace = page_namespace' ) ) ) + ); } function sortDescending() { return false; } + function getOrderFields() { + return array( 'page_namespace', 'page_title' ); + } + + /** + * @param $skin Skin + * @param $result + * @return string + */ function formatResult( $skin, $result ) { global $wgContLang; $nt = Title::makeTitle( $result->namespace, $result->title ); $text = $wgContLang->convert( $nt->getPrefixedText() ); - $plink = $skin->linkKnown( + $plink = Linker::linkKnown( $nt, htmlspecialchars( $text ) ); - $wlink = $skin->linkKnown( + $token = WatchAction::getWatchToken( $nt, $this->getUser() ); + $wlink = Linker::linkKnown( $nt, wfMsgHtml( 'watch' ), array(), - array( 'action' => 'watch' ) + array( 'action' => 'watch', 'token' => $token ) ); return wfSpecialList( $plink, $wlink ); } } - -/** - * constructor - */ -function wfSpecialUnwatchedpages() { - global $wgUser, $wgOut; - - if ( ! $wgUser->isAllowed( 'unwatchedpages' ) ) - return $wgOut->permissionRequired( 'unwatchedpages' ); - - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new UnwatchedpagesPage(); - - $wpp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php index 893e4be2..8eeca5d5 100644 --- a/includes/specials/SpecialUpload.php +++ b/includes/specials/SpecialUpload.php @@ -45,7 +45,15 @@ class SpecialUpload extends SpecialPage { /** Misc variables **/ public $mRequest; // The WebRequest or FauxRequest this form is supposed to handle public $mSourceType; + + /** + * @var UploadBase + */ public $mUpload; + + /** + * @var LocalFile + */ public $mLocalFile; public $mUploadClicked; @@ -71,6 +79,8 @@ class SpecialUpload extends SpecialPage { public $uploadFormTextTop; public $uploadFormTextAfterSummary; + public $mWatchthis; + /** * Initialize instance variables from request and create an Upload handler * @@ -105,7 +115,7 @@ class SpecialUpload extends SpecialPage { $this->mForReUpload = $request->getBool( 'wpForReUpload' ); // updating a file $this->mCancelUpload = $request->getCheck( 'wpCancelUpload' ) - || $request->getCheck( 'wpReUpload' ); // b/w compat + || $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' ); @@ -130,7 +140,7 @@ class SpecialUpload extends SpecialPage { * @param $user User object * @return Boolean */ - public function userCanExecute( $user ) { + public function userCanExecute( User $user ) { return UploadBase::isEnabled() && parent::userCanExecute( $user ); } @@ -196,7 +206,7 @@ class SpecialUpload extends SpecialPage { wfDebug( "Hook 'UploadForm:initial' broke output of the upload form" ); return; } - + $this->showUploadForm( $this->getUploadForm() ); } @@ -214,7 +224,7 @@ class SpecialUpload extends SpecialPage { */ protected function showUploadForm( $form ) { # Add links if file was previously deleted - if ( !$this->mDesiredDestName ) { + if ( $this->mDesiredDestName ) { $this->showViewDeletedLinks(); } @@ -267,7 +277,7 @@ class SpecialUpload extends SpecialPage { $desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName ); $delNotice = ''; // empty by default if ( $desiredTitleObj instanceof Title && !$desiredTitleObj->exists() ) { - LogEventsList::showLogExtract( $delNotice, array( 'delete', 'move' ), + LogEventsList::showLogExtract( $delNotice, array( 'delete', 'move' ), $desiredTitleObj->getPrefixedText(), '', array( 'lim' => 10, 'conds' => array( "log_action != 'revision'" ), @@ -278,17 +288,17 @@ class SpecialUpload extends SpecialPage { $form->addPreText( $delNotice ); # Add text to form - $form->addPreText( '<div id="uploadtext">' . - wfMsgExt( 'uploadtext', 'parse', array( $this->mDesiredDestName ) ) . + $form->addPreText( '<div id="uploadtext">' . + wfMsgExt( 'uploadtext', 'parse', array( $this->mDesiredDestName ) ) . '</div>' ); # Add upload error message $form->addPreText( $message ); # Add footer to form - $uploadFooter = wfMsgNoTrans( 'uploadfooter' ); - if ( $uploadFooter != '-' && !wfEmptyMsg( 'uploadfooter', $uploadFooter ) ) { + $uploadFooter = wfMessage( 'uploadfooter' ); + if ( !$uploadFooter->isDisabled() ) { $form->addPostText( '<div id="mw-upload-footer-message">' - . $wgOut->parse( $uploadFooter ) . "</div>\n" ); + . $wgOut->parse( $uploadFooter->plain() ) . "</div>\n" ); } return $form; @@ -309,7 +319,7 @@ class SpecialUpload extends SpecialPage { $link = wfMsgExt( $wgUser->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted', array( 'parse', 'replaceafter' ), - $wgUser->getSkin()->linkKnown( + $this->getSkin()->linkKnown( SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedText() ), wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $count ) ) @@ -317,11 +327,6 @@ class SpecialUpload extends SpecialPage { $wgOut->addHTML( "<div id=\"contentSub2\">{$link}</div>" ); } } - - // Show the relevant lines from deletion log (for still deleted files only) - if( $title instanceof Title && $title->isDeletedQuick() && !$title->exists() ) { - $this->showDeletionLog( $wgOut, $title->getPrefixedText() ); - } } /** @@ -337,7 +342,7 @@ class SpecialUpload extends SpecialPage { */ protected function showRecoverableUploadError( $message ) { $sessionKey = $this->mUpload->stashSession(); - $message = '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" . + $message = '<h2>' . wfMsgHtml( 'uploaderror' ) . "</h2>\n" . '<div class="error">' . $message . "</div>\n"; $form = $this->getUploadForm( $message, $sessionKey ); @@ -357,8 +362,8 @@ class SpecialUpload extends SpecialPage { # mDestWarningAck is set when some javascript has shown the warning # to the user. mForReUpload is set when the user clicks the "upload a # new version" link. - if ( !$warnings || ( count( $warnings ) == 1 && - isset( $warnings['exists'] ) && + if ( !$warnings || ( count( $warnings ) == 1 && + isset( $warnings['exists'] ) && ( $this->mDestWarningAck || $this->mForReUpload ) ) ) { return false; @@ -436,16 +441,15 @@ class SpecialUpload extends SpecialPage { return; } - // Upload verification $details = $this->mUpload->verifyUpload(); if ( $details['status'] != UploadBase::OK ) { $this->processVerificationError( $details ); return; } - + // Verify permissions for this title - $permErrors = $this->mUpload->verifyPermissions( $wgUser ); + $permErrors = $this->mUpload->verifyTitlePermissions( $wgUser ); if( $permErrors !== true ) { $code = array_shift( $permErrors[0] ); $this->showRecoverableUploadError( wfMsgExt( $code, @@ -574,6 +578,10 @@ class SpecialUpload extends SpecialPage { $this->showRecoverableUploadError( wfMsgExt( 'filetype-missing', 'parseinline' ) ); break; + case UploadBase::WINDOWS_NONASCII_FILENAME: + $this->showRecoverableUploadError( wfMsgExt( 'windows-nonascii-filename', + 'parseinline' ) ); + break; /** Statuses that require reuploading **/ case UploadBase::EMPTY_FILE: @@ -583,18 +591,25 @@ class SpecialUpload extends SpecialPage { $this->showUploadError( wfMsgHtml( 'largefileserver' ) ); break; case UploadBase::FILETYPE_BADTYPE: - $finalExt = $details['finalExt']; - $this->showUploadError( - wfMsgExt( 'filetype-banned-type', - array( 'parseinline' ), - htmlspecialchars( $finalExt ), - implode( - wfMsgExt( 'comma-separator', array( 'escapenoentities' ) ), - $wgFileExtensions - ), - $wgLang->formatNum( count( $wgFileExtensions ) ) - ) - ); + $msg = wfMessage( 'filetype-banned-type' ); + if ( isset( $details['blacklistedExt'] ) ) { + $msg->params( $wgLang->commaList( $details['blacklistedExt'] ) ); + } else { + $msg->params( $details['finalExt'] ); + } + $msg->params( $wgLang->commaList( $wgFileExtensions ), + count( $wgFileExtensions ) ); + + // Add PLURAL support for the first parameter. This results + // in a bit unlogical parameter sequence, but does not break + // old translations + if ( isset( $details['blacklistedExt'] ) ) { + $msg->params( count( $details['blacklistedExt'] ) ); + } else { + $msg->params( 1 ); + } + + $this->showUploadError( $msg->parse() ); break; case UploadBase::VERIFICATION_ERROR: unset( $details['status'] ); @@ -690,7 +705,7 @@ class SpecialUpload extends SpecialPage { 'page' => $filename ) ); - $warning = wfMsgWikiHtml( 'filewasdeleted', $llink ); + $warning = wfMsgExt( 'filewasdeleted', array( 'parse', 'replaceafter' ), $llink ); } return $warning; @@ -761,6 +776,8 @@ class UploadForm extends HTMLForm { protected $mSourceIds; + protected $mMaxFileSize = array(); + public function __construct( $options = array() ) { $this->mWatch = !empty( $options['watch'] ); $this->mForReUpload = !empty( $options['forreupload'] ); @@ -777,7 +794,7 @@ class UploadForm extends HTMLForm { ? $options['texttop'] : ''; $this->mTextAfterSummary = isset( $options['textaftersummary'] ) - ? $options['textaftersummary'] : ''; + ? $options['textaftersummary'] : ''; $sourceDescriptor = $this->getSourceSection(); $descriptor = $sourceDescriptor @@ -812,7 +829,6 @@ class UploadForm extends HTMLForm { */ protected function getSourceSection() { global $wgLang, $wgUser, $wgRequest; - global $wgMaxUploadSize; if ( $this->mSessionKey ) { return array( @@ -841,6 +857,14 @@ class UploadForm extends HTMLForm { ); } + $this->mMaxUploadSize['file'] = UploadBase::getMaxUploadSize( 'file' ); + # Limit to upload_max_filesize unless we are running under HipHop and + # that setting doesn't exist + if ( !wfIsHipHop() ) { + $this->mMaxUploadSize['file'] = min( $this->mMaxUploadSize['file'], + wfShorthandToInteger( ini_get( 'upload_max_filesize' ) ) ); + } + $descriptor['UploadFile'] = array( 'class' => 'UploadSourceField', 'section' => 'source', @@ -851,17 +875,12 @@ class UploadForm extends HTMLForm { 'radio' => &$radio, 'help' => wfMsgExt( 'upload-maxfilesize', array( 'parseinline', 'escapenoentities' ), - $wgLang->formatSize( - wfShorthandToInteger( min( - wfShorthandToInteger( - ini_get( 'upload_max_filesize' ) - ), $wgMaxUploadSize - ) ) - ) + $wgLang->formatSize( $this->mMaxUploadSize['file'] ) ) . ' ' . wfMsgHtml( 'upload_source_file' ), 'checked' => $selectedSourceType == 'file', ); if ( $canUploadByUrl ) { + $this->mMaxUploadSize['url'] = UploadBase::getMaxUploadSize( 'url' ); $descriptor['UploadFileURL'] = array( 'class' => 'UploadSourceField', 'section' => 'source', @@ -871,7 +890,7 @@ class UploadForm extends HTMLForm { 'radio' => &$radio, 'help' => wfMsgExt( 'upload-maxfilesize', array( 'parseinline', 'escapenoentities' ), - $wgLang->formatSize( $wgMaxUploadSize ) + $wgLang->formatSize( $this->mMaxUploadSize['url'] ) ) . ' ' . wfMsgHtml( 'upload_source_url' ), 'checked' => $selectedSourceType == 'url', ); @@ -903,16 +922,16 @@ class UploadForm extends HTMLForm { # Everything not permitted is banned $extensionsList = '<div id="mw-upload-permitted">' . - wfMsgWikiHtml( 'upload-permitted', $wgLang->commaList( $wgFileExtensions ) ) . + wfMsgExt( 'upload-permitted', 'parse', $wgLang->commaList( $wgFileExtensions ) ) . "</div>\n"; } else { # We have to list both preferred and prohibited $extensionsList = '<div id="mw-upload-preferred">' . - wfMsgWikiHtml( 'upload-preferred', $wgLang->commaList( $wgFileExtensions ) ) . + wfMsgExt( 'upload-preferred', 'parse', $wgLang->commaList( $wgFileExtensions ) ) . "</div>\n" . '<div id="mw-upload-prohibited">' . - wfMsgWikiHtml( 'upload-prohibited', $wgLang->commaList( $wgFileBlacklist ) ) . + wfMsgExt( 'upload-prohibited', 'parse', $wgLang->commaList( $wgFileBlacklist ) ) . "</div>\n"; } } else { @@ -931,6 +950,26 @@ class UploadForm extends HTMLForm { protected function getDescriptionSection() { global $wgUser; + if ( $this->mSessionKey ) { + $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash(); + try { + $file = $stash->getFile( $this->mSessionKey ); + } catch ( MWException $e ) { + $file = null; + } + if ( $file ) { + global $wgContLang; + + $mto = $file->transform( array( 'width' => 120 ) ); + $this->addHeaderText( + '<div class="thumb t' . $wgContLang->alignEnd() . '">' . + Html::element( 'img', array( + 'src' => $mto->getUrl(), + 'class' => 'thumbimage', + ) ) . '</div>', 'description' ); + } + } + $descriptor = array( 'DestFile' => array( 'type' => 'text', @@ -939,7 +978,7 @@ class UploadForm extends HTMLForm { 'label-message' => 'destfilename', 'size' => 60, 'default' => $this->mDestFile, - # FIXME: hack to work around poor handling of the 'default' option in HTMLForm + # @todo FIXME: Hack to work around poor handling of the 'default' option in HTMLForm 'nodata' => strval( $this->mDestFile ) !== '', ), 'UploadDescription' => array( @@ -967,6 +1006,7 @@ class UploadForm extends HTMLForm { 'EditTools' => array( 'type' => 'edittools', 'section' => 'description', + 'message' => 'edittools-upload', ) ); @@ -1035,7 +1075,7 @@ class UploadForm extends HTMLForm { 'id' => 'wpDestFileWarningAck', 'default' => $this->mDestWarningAck ? '1' : '', ); - + if ( $this->mForReUpload ) { $descriptor['ForReUpload'] = array( 'type' => 'hidden', @@ -1064,6 +1104,7 @@ class UploadForm extends HTMLForm { $useAjaxDestCheck = $wgUseAjax && $wgAjaxUploadDestCheck; $useAjaxLicensePreview = $wgUseAjax && $wgAjaxLicensePreview && $wgEnableAPI; + $this->mMaxUploadSize['*'] = UploadBase::getMaxUploadSize(); $scriptVars = array( 'wgAjaxUploadDestCheck' => $useAjaxDestCheck, @@ -1075,12 +1116,17 @@ class UploadForm extends HTMLForm { 'wgUploadSourceIds' => $this->mSourceIds, 'wgStrictFileExtensions' => $wgStrictFileExtensions, 'wgCapitalizeUploads' => MWNamespace::isCapitalized( NS_FILE ), + 'wgMaxUploadSize' => $this->mMaxUploadSize, ); $wgOut->addScript( Skin::makeVariablesScript( $scriptVars ) ); - // For <charinsert> support - $wgOut->addModules( array( 'mediawiki.legacy.edit', 'mediawiki.legacy.upload' ) ); + + $wgOut->addModules( array( + 'mediawiki.action.edit', // For <charinsert> support + 'mediawiki.legacy.upload', // Old form stuff... + 'mediawiki.special.upload', // Newer extras for thumbnail preview. + ) ); } /** diff --git a/includes/specials/SpecialUploadStash.php b/includes/specials/SpecialUploadStash.php index 48a41a5e..20a37f0b 100644 --- a/includes/specials/SpecialUploadStash.php +++ b/includes/specials/SpecialUploadStash.php @@ -20,13 +20,7 @@ class SpecialUploadStash extends UnlistedSpecialPage { // UploadStash private $stash; - // is the edit request authorized? boolean - private $isEditAuthorized; - - // did the user request us to clear the stash? boolean - private $requestedClear; - - // Since we are directly writing the file to STDOUT, + // Since we are directly writing the file to STDOUT, // we should not be reading in really big files and serving them out. // // We also don't want people using this as a file drop, even if they @@ -34,19 +28,15 @@ class SpecialUploadStash extends UnlistedSpecialPage { // // This service is really for thumbnails and other such previews while // uploading. - const MAX_SERVE_BYTES = 262144; // 256K - - public function __construct( $request = null ) { - global $wgRequest; + const MAX_SERVE_BYTES = 1048576; // 1MB + public function __construct() { parent::__construct( 'UploadStash', 'upload' ); try { $this->stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash(); } catch ( UploadStashNotAvailableException $e ) { return null; } - - $this->loadRequest( is_null( $request ) ? $wgRequest : $request ); } /** @@ -91,7 +81,7 @@ class SpecialUploadStash extends UnlistedSpecialPage { return $this->outputLocalFile( $params['file'] ); } } catch( UploadStashFileNotFoundException $e ) { - $code = 404; + $code = 404; $message = $e->getMessage(); } catch( UploadStashZeroLengthFileException $e ) { $code = 500; @@ -107,15 +97,15 @@ class SpecialUploadStash extends UnlistedSpecialPage { $message = $e->getMessage(); } - wfHttpError( $code, OutputPage::getStatusMessage( $code ), $message ); + wfHttpError( $code, HttpStatus::getMessage( $code ), $message ); return false; } - + /** - * Parse the key passed to the SpecialPage. Returns an array containing - * the associated file object, the type ('file' or 'thumb') and if + * Parse the key passed to the SpecialPage. Returns an array containing + * the associated file object, the type ('file' or 'thumb') and if * application the transform parameters - * + * * @param string $key * @return array */ @@ -132,28 +122,27 @@ class SpecialUploadStash extends UnlistedSpecialPage { $srcNamePos = strrpos( $thumbPart, $fileName ); if ( $srcNamePos === false || $srcNamePos < 1 ) { throw new UploadStashBadPathException( 'Unrecognized thumb name' ); - } + } $paramString = substr( $thumbPart, 0, $srcNamePos - 1 ); - + $handler = $file->getHandler(); - $params = $handler->parseParamString( $paramString ); - return array( 'file' => $file, 'type' => $type, 'params' => $params ); + $params = $handler->parseParamString( $paramString ); + return array( 'file' => $file, 'type' => $type, 'params' => $params ); } - + return array( 'file' => $file, 'type' => $type ); } - - - /** * Get a thumbnail for file, either generated locally or remotely, and stream it out - * @param String $key: key for the file in the stash - * @param int $width: width of desired thumbnail - * @return boolean success - */ + * + * @param $file + * @param $params array + * + * @return boolean success + */ private function outputThumbFromStash( $file, $params ) { - + // this global, if it exists, points to a "scaler", as you might find in the Wikimedia Foundation cluster. See outputRemoteScaledThumb() // this is part of our horrible NFS-based system, we create a file on a mount point here, but fetch the scaled file from somewhere else that // happens to share it over NFS @@ -165,16 +154,13 @@ class SpecialUploadStash extends UnlistedSpecialPage { } else { $this->outputLocallyScaledThumb( $file, $params, $flags ); } - - } - /** * Scale a file (probably with a locally installed imagemagick, or similar) and output it to STDOUT. - * @param $file: File object + * @param $file: File object * @param $params: scaling parameters ( e.g. array( width => '50' ) ); - * @param $flags: scaling flags ( see File:: constants ) + * @param $flags: scaling flags ( see File:: constants ) * @throws MWException * @return boolean success */ @@ -182,7 +168,7 @@ class SpecialUploadStash extends UnlistedSpecialPage { // n.b. this is stupid, we insist on re-transforming the file every time we are invoked. We rely // on HTTP caching to ensure this doesn't happen. - + $flags |= File::RENDER_NOW; $thumbnailImage = $file->transform( $params, $flags ); @@ -203,41 +189,47 @@ class SpecialUploadStash extends UnlistedSpecialPage { } return $this->outputLocalFile( $thumbFile ); - + } - + /** * Scale a file with a remote "scaler", as exists on the Wikimedia Foundation cluster, and output it to STDOUT. - * Note: unlike the usual thumbnail process, the web client never sees the cluster URL; we do the whole HTTP transaction to the scaler ourselves + * Note: unlike the usual thumbnail process, the web client never sees the cluster URL; we do the whole HTTP transaction to the scaler ourselves * and cat the results out. - * Note: We rely on NFS to have propagated the file contents to the scaler. However, we do not rely on the thumbnail being created in NFS and then - * propagated back to our filesystem. Instead we take the results of the HTTP request instead. + * Note: We rely on NFS to have propagated the file contents to the scaler. However, we do not rely on the thumbnail being created in NFS and then + * propagated back to our filesystem. Instead we take the results of the HTTP request instead. * Note: no caching is being done here, although we are instructing the client to cache it forever. - * @param $file: File object + * @param $file: File object * @param $params: scaling parameters ( e.g. array( width => '50' ) ); - * @param $flags: scaling flags ( see File:: constants ) + * @param $flags: scaling flags ( see File:: constants ) * @throws MWException * @return boolean success */ private function outputRemoteScaledThumb( $file, $params, $flags ) { - + // this global probably looks something like 'http://upload.wikimedia.org/wikipedia/test/thumb/temp' // do not use trailing slash global $wgUploadStashScalerBaseUrl; - $scalerThumbName = $file->getParamThumbName( $file->name, $params ); - $scalerThumbUrl = $wgUploadStashScalerBaseUrl . '/' . $file->getRel() . '/' . $scalerThumbName; - + // We need to use generateThumbName() instead of thumbName(), because + // the suffix needs to match the file name for the remote thumbnailer + // to work + $scalerThumbName = $file->generateThumbName( $file->getName(), $params ); + $scalerThumbUrl = $wgUploadStashScalerBaseUrl . '/' . $file->getUrlRel() . + '/' . rawurlencode( $scalerThumbName ); + // make a curl call to the scaler to create a thumbnail - $httpOptions = array( + $httpOptions = array( 'method' => 'GET', 'timeout' => 'default' ); $req = MWHttpRequest::factory( $scalerThumbUrl, $httpOptions ); $status = $req->execute(); if ( ! $status->isOK() ) { - $errors = $status->getErrorsArray(); - throw new MWException( "Fetching thumbnail failed: " . join( ", ", $errors ) ); + $errors = $status->getErrorsArray(); + $errorStr = "Fetching thumbnail failed: " . print_r( $errors, 1 ); + $errorStr .= "\nurl = $scalerThumbUrl\n"; + throw new MWException( $errorStr ); } $contentType = $req->getResponseHeader( "content-type" ); if ( ! $contentType ) { @@ -257,13 +249,13 @@ class SpecialUploadStash extends UnlistedSpecialPage { private function outputLocalFile( $file ) { if ( $file->getSize() > self::MAX_SERVE_BYTES ) { throw new SpecialUploadStashTooLargeException(); - } + } self::outputFileHeaders( $file->getMimeType(), $file->getSize() ); readfile( $file->getPath() ); return true; } - /** + /** * Output HTTP response of raw content * Side effect: writes HTTP response to STDOUT. * @param String $content: content @@ -275,11 +267,11 @@ class SpecialUploadStash extends UnlistedSpecialPage { throw new SpecialUploadStashTooLargeException(); } self::outputFileHeaders( $contentType, $size ); - print $content; + print $content; return true; } - /** + /** * Output headers for streaming * XXX unsure about encoding as binary; if we received from HTTP perhaps we should use that encoding, concatted with semicolon to mimeType as it usually is. * Side effect: preps PHP to write headers to STDOUT. @@ -290,38 +282,20 @@ class SpecialUploadStash extends UnlistedSpecialPage { header( "Content-Type: $contentType", true ); header( 'Content-Transfer-Encoding: binary', true ); header( 'Expires: Sun, 17-Jan-2038 19:14:07 GMT', true ); - header( "Content-Length: $size", true ); - } - - - /** - * Initialize authorization & actions to take, from the request - * @param $request: WebRequest - */ - private function loadRequest( $request ) { - global $wgUser; - if ( $request->wasPosted() ) { - - $token = $request->getVal( 'wpEditToken' ); - $this->isEditAuthorized = $wgUser->matchEditToken( $token ); - - $this->requestedClear = $request->getBool( 'clear' ); - - } + header( "Content-Length: $size", true ); } /** - * Static callback for the HTMLForm in showUploads, to process + * Static callback for the HTMLForm in showUploads, to process * 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. * * @return Status - */ + */ public static function tryClearStashedUploads( $formData ) { - wfDebug( __METHOD__ . " form data : " . print_r( $formData, 1 ) ); - if ( isset( $formData['clear'] ) and $formData['clear'] ) { - $stash = new UploadStash(); - wfDebug( "stash has: " . print_r( $stash->listFiles(), 1 ) ); + if ( isset( $formData['Clear'] ) ) { + $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash(); + wfDebug( "stash has: " . print_r( $stash->listFiles(), true ) ); if ( ! $stash->clear() ) { return Status::newFatal( 'uploadstash-errclear' ); } @@ -333,7 +307,7 @@ 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 : $status - the result of processRequest - */ + */ private function showUploads( $status = null ) { global $wgOut; if ( $status === null ) { @@ -344,49 +318,49 @@ class SpecialUploadStash extends UnlistedSpecialPage { $this->setHeaders(); $this->outputHeader(); - // 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? - $form = new HTMLForm( array( - 'Clear' => array( - 'type' => 'hidden', + $form = new HTMLForm( array( + 'Clear' => array( + 'type' => 'hidden', 'default' => true, 'name' => 'clear', - ) + ) ), 'clearStashedUploads' ); - $form->setSubmitCallback( array( __CLASS__, 'tryClearStashedUploads' ) ); + $form->setSubmitCallback( array( __CLASS__ , 'tryClearStashedUploads' ) ); $form->setTitle( $this->getTitle() ); - $form->addHiddenField( 'clear', true, array( 'type' => 'boolean' ) ); $form->setSubmitText( wfMsg( 'uploadstash-clear' ) ); - $form->prepareForm(); - $formResult = $form->tryAuthorizedSubmit(); - + $form->prepareForm(); + $formResult = $form->tryAuthorizedSubmit(); // show the files + form, if there are any, or just say there are none - $refreshHtml = Html::element( 'a', array( 'href' => $this->getTitle()->getLocalURL() ), wfMsg( 'uploadstash-refresh' ) ); + $refreshHtml = Html::element( 'a', + array( 'href' => $this->getTitle()->getLocalURL() ), + wfMsg( 'uploadstash-refresh' ) ); $files = $this->stash->listFiles(); if ( count( $files ) ) { sort( $files ); $fileListItemsHtml = ''; foreach ( $files as $file ) { + // TODO: Use Linker::link or even construct the list in plain wikitext $fileListItemsHtml .= Html::rawElement( 'li', array(), - Html::element( 'a', array( 'href' => + Html::element( 'a', array( 'href' => $this->getTitle( "file/$file" )->getLocalURL() ), $file ) ); } $wgOut->addHtml( Html::rawElement( 'ul', array(), $fileListItemsHtml ) ); - $form->displayForm( $formResult ); + $form->displayForm( $formResult ); $wgOut->addHtml( Html::rawElement( 'p', array(), $refreshHtml ) ); } else { - $wgOut->addHtml( Html::rawElement( 'p', array(), + $wgOut->addHtml( Html::rawElement( 'p', array(), Html::element( 'span', array(), wfMsg( 'uploadstash-nofiles' ) ) - . ' ' + . ' ' . $refreshHtml ) ); } - + return true; } } diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php index ccace79d..01dc9a1c 100644 --- a/includes/specials/SpecialUserlogin.php +++ b/includes/specials/SpecialUserlogin.php @@ -22,24 +22,11 @@ */ /** - * Constructor - */ -function wfSpecialUserlogin( $par = '' ) { - global $wgRequest; - if( session_id() == '' ) { - wfSetupSession(); - } - - $form = new LoginForm( $wgRequest, $par ); - $form->execute(); -} - -/** * Implements Special:UserLogin * * @ingroup SpecialPage */ -class LoginForm { +class LoginForm extends SpecialPage { const SUCCESS = 0; const NO_NAME = 1; @@ -56,23 +43,42 @@ class LoginForm { const NEED_TOKEN = 12; const WRONG_TOKEN = 13; - var $mName, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted; - var $mAction, $mCreateaccount, $mCreateaccountMail, $mMailmypassword; + var $mUsername, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted; + var $mAction, $mCreateaccount, $mCreateaccountMail; var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage; var $mSkipCookieCheck, $mReturnToQuery, $mToken, $mStickHTTPS; + var $mType, $mReason, $mRealName; + var $mAbortLoginErrorMsg = 'login-abort-generic'; + /** + * @var ExternalUser + */ private $mExtUser = null; /** - * Constructor - * @param $request WebRequest: a WebRequest object passed by reference - * @param $par String: subpage parameter + * @param WebRequest $request + */ + public function __construct( $request = null ) { + parent::__construct( 'Userlogin' ); + + if ( $request === null ) { + global $wgRequest; + $this->load( $wgRequest ); + } else { + $this->load( $request ); + } + } + + /** + * Loader + * + * @param $request WebRequest object */ - function __construct( &$request, $par = '' ) { + function load( $request ) { global $wgAuth, $wgHiddenPrefs, $wgEnableEmail, $wgRedirectOnLogin; - $this->mType = ( $par == 'signup' ) ? $par : $request->getText( 'type' ); # Check for [[Special:Userlogin/signup]] - $this->mName = $request->getText( 'wpName' ); + $this->mType = $request->getText( 'type' ); + $this->mUsername = $request->getText( 'wpName' ); $this->mPassword = $request->getText( 'wpPassword' ); $this->mRetype = $request->getText( 'wpRetype' ); $this->mDomain = $request->getText( 'wpDomain' ); @@ -83,9 +89,7 @@ class LoginForm { $this->mPosted = $request->wasPosted(); $this->mCreateaccount = $request->getCheck( 'wpCreateaccount' ); $this->mCreateaccountMail = $request->getCheck( 'wpCreateaccountMail' ) - && $wgEnableEmail; - $this->mMailmypassword = $request->getCheck( 'wpMailmypassword' ) - && $wgEnableEmail; + && $wgEnableEmail; $this->mLoginattempt = $request->getCheck( 'wpLoginattempt' ); $this->mAction = $request->getVal( 'action' ); $this->mRemember = $request->getCheck( 'wpRemember' ); @@ -105,9 +109,9 @@ class LoginForm { $this->mEmail = ''; } if( !in_array( 'realname', $wgHiddenPrefs ) ) { - $this->mRealName = $request->getText( 'wpRealName' ); + $this->mRealName = $request->getText( 'wpRealName' ); } else { - $this->mRealName = ''; + $this->mRealName = ''; } if( !$wgAuth->validDomain( $this->mDomain ) ) { @@ -123,7 +127,15 @@ class LoginForm { } } - function execute() { + public function execute( $par ) { + if ( session_id() == '' ) { + wfSetupSession(); + } + + if ( $par == 'signup' ) { # Check for [[Special:Userlogin/signup]] + $this->mType = 'signup'; + } + if ( !is_null( $this->mCookieCheck ) ) { $this->onCookieRedirectCheck( $this->mCookieCheck ); return; @@ -132,8 +144,6 @@ class LoginForm { return $this->addNewAccount(); } elseif ( $this->mCreateaccountMail ) { return $this->addNewAccountMailPassword(); - } elseif ( $this->mMailmypassword ) { - return $this->mailPassword(); } elseif ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) { return $this->processLogin(); } @@ -167,8 +177,6 @@ class LoginForm { $u->addNewUserLogEntry( true, $this->mReason ); $wgOut->setPageTitle( wfMsg( 'accmailtitle' ) ); - $wgOut->setRobotPolicy( 'noindex,nofollow' ); - $wgOut->setArticleRelated( false ); if( !$result->isGood() ) { $this->mainLoginForm( wfMsg( 'mailerror', $result->getWikiText() ) ); @@ -198,7 +206,7 @@ class LoginForm { } # Send out an email authentication message if needed - if( $wgEmailAuthentication && User::isValidEmailAddr( $u->getEmail() ) ) { + if( $wgEmailAuthentication && Sanitizer::validateEmail( $u->getEmail() ) ) { $status = $u->sendConfirmationMail(); if( $status->isGood() ) { $wgOut->addWikiMsg( 'confirmemail_oncreate' ); @@ -216,6 +224,10 @@ class LoginForm { if( $wgUser->isAnon() ) { $wgUser = $u; $wgUser->setCookies(); + // This should set it for OutputPage and the Skin + // which is needed or the personal links will be + // wrong. + RequestContext::getMain()->setUser( $u ); wfRunHooks( 'AddNewAccount', array( $wgUser, false ) ); $wgUser->addNewUserLogEntry(); if( $this->hasSessionCookie() ) { @@ -227,9 +239,7 @@ class LoginForm { # Confirm that the account was created $self = SpecialPage::getTitleFor( 'Userlogin' ); $wgOut->setPageTitle( wfMsgHtml( 'accountcreated' ) ); - $wgOut->setArticleRelated( false ); - $wgOut->setRobotPolicy( 'noindex,nofollow' ); - $wgOut->addHTML( wfMsgWikiHtml( 'accountcreatedtext', $u->getName() ) ); + $wgOut->addWikiMsg( 'accountcreatedtext', $u->getName() ); $wgOut->returnToMain( false, $self ); wfRunHooks( 'AddNewAccount', array( $u, false ) ); $u->addNewUserLogEntry( false, $this->mReason ); @@ -258,7 +268,8 @@ class LoginForm { // 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->mName ) || !$wgAuth->authenticate( $this->mName, $this->mPassword ) ) ) { + if( !$wgAuth->canCreateAccounts() && ( !$wgAuth->userExists( $this->mUsername ) + || !$wgAuth->authenticate( $this->mUsername, $this->mPassword ) ) ) { $this->mainLoginForm( wfMsg( 'wrongpassword' ) ); return false; } @@ -272,7 +283,7 @@ class LoginForm { # Request forgery checks. if ( !self::getCreateaccountToken() ) { self::setCreateaccountToken(); - $this->mainLoginForm( wfMsgExt( 'nocookiesnew', array( 'parseinline' ) ) ); + $this->mainLoginForm( wfMsgExt( 'nocookiesfornew', array( 'parseinline' ) ) ); return false; } @@ -293,7 +304,7 @@ class LoginForm { $wgOut->permissionRequired( 'createaccount' ); return false; } elseif ( $wgUser->isBlockedFromCreateAccount() ) { - $this->userBlockedMessage(); + $this->userBlockedMessage( $wgUser->isBlockedFromCreateAccount() ); return false; } @@ -304,7 +315,7 @@ class LoginForm { } # Now create a dummy user ($u) and check if it is valid - $name = trim( $this->mName ); + $name = trim( $this->mUsername ); $u = User::newFromName( $name, 'creatable' ); if ( !is_object( $u ) ) { $this->mainLoginForm( wfMsg( 'noname' ) ); @@ -325,7 +336,14 @@ class LoginForm { $valid = $u->getPasswordValidity( $this->mPassword ); if ( $valid !== true ) { if ( !$this->mCreateaccountMail ) { - $this->mainLoginForm( wfMsgExt( $valid, array( 'parsemag' ), $wgMinimalPasswordLength ) ); + if ( is_array( $valid ) ) { + $message = array_shift( $valid ); + $params = $valid; + } else { + $message = $valid; + $params = array( $wgMinimalPasswordLength ); + } + $this->mainLoginForm( wfMsgExt( $message, array( 'parsemag' ), $params ) ); return false; } else { # do not force a password for account creation by email @@ -341,7 +359,7 @@ class LoginForm { return false; } - if( !empty( $this->mEmail ) && !User::isValidEmailAddr( $this->mEmail ) ) { + if( !empty( $this->mEmail ) && !Sanitizer::validateEmail( $this->mEmail ) ) { $this->mainLoginForm( wfMsg( 'invalidemailaddress' ) ); return false; } @@ -431,9 +449,9 @@ class LoginForm { * creation. */ public function authenticateUserData() { - global $wgUser, $wgAuth, $wgMemc; + global $wgUser, $wgAuth; - if ( $this->mName == '' ) { + if ( $this->mUsername == '' ) { return self::NO_NAME; } @@ -452,22 +470,9 @@ class LoginForm { return self::NEED_TOKEN; } - global $wgPasswordAttemptThrottle; - - $throttleCount = 0; - if ( is_array( $wgPasswordAttemptThrottle ) ) { - $throttleKey = wfMemcKey( 'password-throttle', wfGetIP(), md5( $this->mName ) ); - $count = $wgPasswordAttemptThrottle['count']; - $period = $wgPasswordAttemptThrottle['seconds']; - - $throttleCount = $wgMemc->get( $throttleKey ); - if ( !$throttleCount ) { - $wgMemc->add( $throttleKey, 1, $period ); // start counter - } elseif ( $throttleCount < $count ) { - $wgMemc->incr( $throttleKey ); - } elseif ( $throttleCount >= $count ) { - return self::THROTTLED; - } + $throttleCount = self::incLoginThrottle( $this->mUsername ); + if ( $throttleCount === true ) { + return self::THROTTLED; } // Validate the login token @@ -481,16 +486,16 @@ class LoginForm { // creates the user in the database. Until we load $wgUser, checking // for user existence using User::newFromName($name)->getId() below // will effectively be using stale data. - if ( $wgUser->getName() === $this->mName ) { - wfDebug( __METHOD__ . ": already logged in as {$this->mName}\n" ); + if ( $wgUser->getName() === $this->mUsername ) { + wfDebug( __METHOD__ . ": already logged in as {$this->mUsername}\n" ); return self::SUCCESS; } - $this->mExtUser = ExternalUser::newFromName( $this->mName ); + $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->mName ); + $u = User::newFromName( $this->mUsername ); if( !( $u instanceof User ) || !User::isUsableName( $u->getName() ) ) { return self::ILLEGAL; } @@ -518,7 +523,7 @@ class LoginForm { // Give general extensions, such as a captcha, a chance to abort logins $abort = self::ABORTED; - if( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort ) ) ) { + if( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$this->mAbortLoginErrorMsg ) ) ) { return $abort; } @@ -559,10 +564,14 @@ class LoginForm { } else { $wgAuth->updateUser( $u ); $wgUser = $u; + // This should set it for OutputPage and the Skin + // which is needed or the personal links will be + // wrong. + RequestContext::getMain()->setUser( $u ); // Please reset throttle for successful logins, thanks! - if( $throttleCount ) { - $wgMemc->delete( $throttleKey ); + if ( $throttleCount ) { + self::clearLoginThrottle( $this->mUsername ); } if ( $isAutoCreated ) { @@ -576,9 +585,52 @@ class LoginForm { return $retval; } + /* + * 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 + * @return Bool|Integer The integer hit count or True if it is already at the limit + */ + public static function incLoginThrottle( $username ) { + global $wgPasswordAttemptThrottle, $wgMemc; + + $throttleCount = 0; + if ( is_array( $wgPasswordAttemptThrottle ) ) { + $throttleKey = wfMemcKey( 'password-throttle', wfGetIP(), md5( $username ) ); + $count = $wgPasswordAttemptThrottle['count']; + $period = $wgPasswordAttemptThrottle['seconds']; + + $throttleCount = $wgMemc->get( $throttleKey ); + if ( !$throttleCount ) { + $wgMemc->add( $throttleKey, 1, $period ); // start counter + } elseif ( $throttleCount < $count ) { + $wgMemc->incr( $throttleKey ); + } elseif ( $throttleCount >= $count ) { + return true; + } + } + + return $throttleCount; + } + + /* + * Clear the login attempt throttle hit count for the (username,current IP) tuple. + * @param $username string The user name + * @return void + */ + public static function clearLoginThrottle( $username ) { + global $wgMemc; + + $throttleKey = wfMemcKey( 'password-throttle', wfGetIP(), md5( $username ) ); + $wgMemc->delete( $throttleKey ); + } + /** * Attempt to automatically create a user on login. Only succeeds if there * is an external authentication method which allows it. + * + * @param $user User + * * @return integer Status code */ function attemptAutoCreate( $user ) { @@ -618,6 +670,14 @@ class LoginForm { } } + $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; + return self::ABORTED; + } + wfDebug( __METHOD__ . ": creating account\n" ); $this->initUser( $user, true ); return self::SUCCESS; @@ -639,7 +699,7 @@ class LoginForm { self::clearLoginToken(); // Reset the throttle - $key = wfMemcKey( 'password-throttle', wfGetIP(), md5( $this->mName ) ); + $key = wfMemcKey( 'password-throttle', wfGetIP(), md5( $this->mUsername ) ); global $wgMemc; $wgMemc->delete( $key ); @@ -657,7 +717,7 @@ class LoginForm { break; case self::NEED_TOKEN: - $this->mainLoginForm( wfMsgExt( 'nocookieslogin', array( 'parseinline' ) ) ); + $this->mainLoginForm( wfMsgExt( 'nocookiesforlogin', array( 'parseinline' ) ) ); break; case self::WRONG_TOKEN: $this->mainLoginForm( wfMsg( 'sessionfailure' ) ); @@ -671,9 +731,11 @@ class LoginForm { break; case self::NOT_EXISTS: if( $wgUser->isAllowed( 'createaccount' ) ) { - $this->mainLoginForm( wfMsgWikiHtml( 'nosuchuser', htmlspecialchars( $this->mName ) ) ); + $this->mainLoginForm( wfMsgExt( 'nosuchuser', 'parseinline', + wfEscapeWikiText( $this->mUsername ) ) ); } else { - $this->mainLoginForm( wfMsg( 'nosuchusershort', htmlspecialchars( $this->mName ) ) ); + $this->mainLoginForm( wfMsg( 'nosuchusershort', + wfEscapeWikiText( $this->mUsername ) ) ); } break; case self::WRONG_PASS: @@ -686,14 +748,17 @@ class LoginForm { $this->resetLoginForm( wfMsg( 'resetpass_announce' ) ); break; case self::CREATE_BLOCKED: - $this->userBlockedMessage(); + $this->userBlockedMessage( $wgUser->mBlock ); break; case self::THROTTLED: $this->mainLoginForm( wfMsg( 'login-throttled' ) ); break; case self::USER_BLOCKED: $this->mainLoginForm( wfMsgExt( 'login-userblocked', - array( 'parsemag', 'escape' ), $this->mName ) ); + array( 'parsemag', 'escape' ), $this->mUsername ) ); + break; + case self::ABORTED: + $this->mainLoginForm( wfMsg( $this->mAbortLoginErrorMsg ) ); break; default: throw new MWException( 'Unhandled case value' ); @@ -703,100 +768,11 @@ class LoginForm { function resetLoginForm( $error ) { global $wgOut; $wgOut->addHTML( Xml::element('p', array( 'class' => 'error' ), $error ) ); - $reset = new SpecialResetpass(); + $reset = new SpecialChangePassword(); $reset->execute( null ); } /** - * @private - */ - function mailPassword() { - global $wgUser, $wgOut, $wgAuth; - - if ( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return false; - } - - if( !$wgAuth->allowPasswordChange() ) { - $this->mainLoginForm( wfMsg( 'resetpass_forbidden' ) ); - return; - } - - # Check against blocked IPs so blocked users can't flood admins - # with password resets - if( $wgUser->isBlocked() ) { - $this->mainLoginForm( wfMsg( 'blocked-mailpassword' ) ); - return; - } - - # Check for hooks - $error = null; - if ( !wfRunHooks( 'UserLoginMailPassword', array( $this->mName, &$error ) ) ) { - $this->mainLoginForm( $error ); - return; - } - - # If the user doesn't have a login token yet, set one. - if ( !self::getLoginToken() ) { - self::setLoginToken(); - $this->mainLoginForm( wfMsg( 'sessionfailure' ) ); - return; - } - - # If the user didn't pass a login token, tell them we need one - if ( !$this->mToken ) { - $this->mainLoginForm( wfMsg( 'sessionfailure' ) ); - return; - } - - # Check against the rate limiter - if( $wgUser->pingLimiter( 'mailpassword' ) ) { - $wgOut->rateLimited(); - return; - } - - if ( $this->mName == '' ) { - $this->mainLoginForm( wfMsg( 'noname' ) ); - return; - } - $u = User::newFromName( $this->mName ); - if( !$u instanceof User ) { - $this->mainLoginForm( wfMsg( 'noname' ) ); - return; - } - if ( 0 == $u->getID() ) { - $this->mainLoginForm( wfMsgWikiHtml( 'nosuchuser', htmlspecialchars( $u->getName() ) ) ); - return; - } - - # Validate the login token - if ( $this->mToken !== self::getLoginToken() ) { - $this->mainLoginForm( wfMsg( 'sessionfailure' ) ); - return; - } - - # Check against password throttle - if ( $u->isPasswordReminderThrottled() ) { - global $wgPasswordReminderResendTime; - # Round the time in hours to 3 d.p., in case someone is specifying - # minutes or seconds. - $this->mainLoginForm( wfMsgExt( 'throttled-mailpassword', array( 'parsemag' ), - round( $wgPasswordReminderResendTime, 3 ) ) ); - return; - } - - $result = $this->mailPasswordInternal( $u, true, 'passwordremindertitle', 'passwordremindertext' ); - if( $result->isGood() ) { - $this->mainLoginForm( wfMsg( 'passwordsent', $u->getName() ), 'success' ); - self::clearLoginToken(); - } else { - $this->mainLoginForm( $result->getWikiText( 'mailerror' ) ); - } - } - - - /** * @param $u User object * @param $throttle Boolean * @param $emailTitle String: message name of email title @@ -872,9 +848,14 @@ class LoginForm { global $wgUser; # Run any hooks; display injected HTML $injected_html = ''; + $welcome_creation_msg = 'welcomecreation'; + wfRunHooks( 'UserLoginComplete', array( &$wgUser, &$injected_html ) ); - $this->displaySuccessfulLogin( 'welcomecreation', $injected_html ); + //let any extensions change what message is shown + wfRunHooks( 'BeforeWelcomeCreation', array( &$welcome_creation_msg, &$injected_html ) ); + + $this->displaySuccessfulLogin( $welcome_creation_msg, $injected_html ); } /** @@ -884,9 +865,10 @@ class LoginForm { global $wgOut, $wgUser; $wgOut->setPageTitle( wfMsg( 'loginsuccesstitle' ) ); - $wgOut->setRobotPolicy( 'noindex,nofollow' ); - $wgOut->setArticleRelated( false ); - $wgOut->addWikiMsg( $msgname, $wgUser->getName() ); + if( $msgname ){ + $wgOut->addWikiMsg( $msgname, wfEscapeWikiText( $wgUser->getName() ) ); + } + $wgOut->addHTML( $injected_html ); if ( !empty( $this->mReturnTo ) ) { @@ -896,9 +878,15 @@ class LoginForm { } } - /** */ - function userBlockedMessage() { - global $wgOut, $wgUser; + /** + * Output a message that informs the user that they cannot create an account because + * there is a block on them or their IP which prevents account creation. Note that + * User::isBlockedFromCreateAccount(), which gets this block, ignores the 'hardblock' + * setting on blocks (bug 13611). + * @param $block Block the block causing this error + */ + function userBlockedMessage( Block $block ) { + global $wgOut; # Let's be nice about this, it's likely that this feature will be used # for blocking large numbers of innocent people, e.g. range blocks on @@ -909,17 +897,19 @@ class LoginForm { # out. $wgOut->setPageTitle( wfMsg( 'cantcreateaccounttitle' ) ); - $wgOut->setRobotPolicy( 'noindex,nofollow' ); - $wgOut->setArticleRelated( false ); - - $ip = wfGetIP(); - $blocker = User::whoIs( $wgUser->mBlock->mBy ); - $block_reason = $wgUser->mBlock->mReason; + $block_reason = $block->mReason; if ( strval( $block_reason ) === '' ) { $block_reason = wfMsg( 'blockednoreason' ); } - $wgOut->addWikiMsg( 'cantcreateaccount-text', $ip, $block_reason, $blocker ); + + $wgOut->addWikiMsg( + 'cantcreateaccount-text', + $block->getTarget(), + $block_reason, + $block->getBlocker()->getName() + ); + $wgOut->returnToMain( false ); } @@ -927,10 +917,11 @@ class LoginForm { * @private */ function mainLoginForm( $msg, $msgtype = 'error' ) { - global $wgUser, $wgOut, $wgHiddenPrefs, $wgEnableEmail; + global $wgUser, $wgOut, $wgHiddenPrefs; + global $wgEnableEmail, $wgEnableUserEmail; global $wgRequest, $wgLoginLanguageSelector; global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration; - global $wgSecureLogin; + global $wgSecureLogin, $wgPasswordResetRoutes; $titleObj = SpecialPage::getTitleFor( 'Userlogin' ); @@ -942,7 +933,7 @@ class LoginForm { $wgOut->readOnlyPage(); return; } elseif ( $wgUser->isBlockedFromCreateAccount() ) { - $this->userBlockedMessage(); + $this->userBlockedMessage( $wgUser->isBlockedFromCreateAccount() ); return; } elseif ( count( $permErrors = $titleObj->getUserPermissionsErrors( 'createaccount', $wgUser, true ) )>0 ) { $wgOut->showPermissionsErrorPage( $permErrors, 'createaccount' ); @@ -950,11 +941,11 @@ class LoginForm { } } - if ( $this->mName == '' ) { + if ( $this->mUsername == '' ) { if ( $wgUser->isLoggedIn() ) { - $this->mName = $wgUser->getName(); + $this->mUsername = $wgUser->getName(); } else { - $this->mName = $wgRequest->getCookie( 'UserName' ); + $this->mUsername = $wgRequest->getCookie( 'UserName' ); } } @@ -996,8 +987,12 @@ class LoginForm { $template->set( 'link', '' ); } + $resetLink = $this->mType == 'signup' + ? null + : is_array( $wgPasswordResetRoutes ) && in_array( true, array_values( $wgPasswordResetRoutes ) ); + $template->set( 'header', '' ); - $template->set( 'name', $this->mName ); + $template->set( 'name', $this->mUsername ); $template->set( 'password', $this->mPassword ); $template->set( 'retype', $this->mRetype ); $template->set( 'email', $this->mEmail ); @@ -1012,7 +1007,9 @@ class LoginForm { $template->set( 'userealname', !in_array( 'realname', $wgHiddenPrefs ) ); $template->set( 'useemail', $wgEnableEmail ); $template->set( 'emailrequired', $wgEmailConfirmToEdit ); + $template->set( 'emailothers', $wgEnableUserEmail ); $template->set( 'canreset', $wgAuth->allowPasswordChange() ); + $template->set( 'resetlink', $resetLink ); $template->set( 'canremember', ( $wgCookieExpiration > 0 ) ); $template->set( 'usereason', $wgUser->isLoggedIn() ); $template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) || $this->mRemember ); @@ -1037,6 +1034,22 @@ class LoginForm { if( $this->mLanguage ) $template->set( 'uselang', $this->mLanguage ); } + + // Use loginend-https for HTTPS requests if it's not blank, loginend otherwise + // Ditto for signupend + $usingHTTPS = WebRequest::detectProtocol() == 'https'; + $loginendHTTPS = wfMessage( 'loginend-https' ); + $signupendHTTPS = wfMessage( 'signupend-https' ); + if ( $usingHTTPS && !$loginendHTTPS->isBlank() ) { + $template->set( 'loginend', $loginendHTTPS->parse() ); + } else { + $template->set( 'loginend', wfMessage( 'loginend' )->parse() ); + } + if ( $usingHTTPS && !$signupendHTTPS->isBlank() ) { + $template->set( 'signupend', $signupendHTTPS->parse() ); + } else { + $template->set( 'signupend', wfMessage( 'signupend' )->parse() ); + } // Give authentication and captcha plugins a chance to modify the form $wgAuth->modifyUITemplate( $template, $this->mType ); @@ -1053,22 +1066,24 @@ class LoginForm { $wgOut->setPageTitle( wfMsg( 'userloginnocreate' ) ); } - $wgOut->setRobotPolicy( 'noindex,nofollow' ); - $wgOut->setArticleRelated( false ); $wgOut->disallowUserJs(); // just in case... $wgOut->addTemplate( $template ); } /** * @private + * + * @param $user User + * + * @return Boolean */ function showCreateOrLoginLink( &$user ) { if( $this->mType == 'signup' ) { - return( true ); + return true; } elseif( $user->isAllowed( 'createaccount' ) ) { - return( true ); + return true; } else { - return( false ); + return false; } } @@ -1186,15 +1201,15 @@ class LoginForm { function makeLanguageSelector() { global $wgLang; - $msg = wfMsgForContent( 'loginlanguagelinks' ); - if( $msg != '' && !wfEmptyMsg( 'loginlanguagelinks', $msg ) ) { - $langs = explode( "\n", $msg ); + $msg = wfMessage( 'loginlanguagelinks' )->inContentLanguage(); + if( !$msg->isBlank() ) { + $langs = explode( "\n", $msg->text() ); $links = array(); foreach( $langs as $lang ) { $lang = trim( $lang, '* ' ); $parts = explode( '|', $lang ); if ( count( $parts ) >= 2 ) { - $links[] = $this->makeLanguageSelectorLink( $parts[0], $parts[1] ); + $links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) ); } } return count( $links ) > 0 ? wfMsgHtml( 'loginlanguagelabel', $wgLang->pipeList( $links ) ) : ''; @@ -1211,7 +1226,6 @@ class LoginForm { * @param $lang Language code */ function makeLanguageSelectorLink( $text, $lang ) { - global $wgUser; $self = SpecialPage::getTitleFor( 'Userlogin' ); $attr = array( 'uselang' => $lang ); if( $this->mType == 'signup' ) { @@ -1220,8 +1234,7 @@ class LoginForm { if( $this->mReturnTo ) { $attr['returnto'] = $this->mReturnTo; } - $skin = $wgUser->getSkin(); - return $skin->linkKnown( + return Linker::linkKnown( $self, htmlspecialchars( $text ), array(), diff --git a/includes/specials/SpecialUserrights.php b/includes/specials/SpecialUserrights.php index 6ea8668b..4de048c0 100644 --- a/includes/specials/SpecialUserrights.php +++ b/includes/specials/SpecialUserrights.php @@ -41,7 +41,7 @@ class UserrightsPage extends SpecialPage { return true; } - public function userCanExecute( $user ) { + public function userCanExecute( User $user ) { return $this->userCanChangeRights( $user, false ); } @@ -98,7 +98,7 @@ class UserrightsPage extends SpecialPage { } if( !$this->userCanChangeRights( $wgUser, true ) ) { - // fixme... there may be intermediate groups we can mention. + // @todo FIXME: There may be intermediate groups we can mention. $wgOut->showPermissionsErrorPage( array( array( $wgUser->isAnon() ? 'userrights-nologin' @@ -112,7 +112,7 @@ class UserrightsPage extends SpecialPage { } $this->outputHeader(); - + $wgOut->addModuleStyles( 'mediawiki.special' ); $this->setHeaders(); // show the general form @@ -222,7 +222,7 @@ class UserrightsPage extends SpecialPage { $user->removeGroup( $group ); } } - if( $add ) { + if( $add ) { $newGroups = array_merge( $newGroups, $add ); foreach( $add as $group ) { $user->addGroup( $group ); @@ -317,7 +317,7 @@ class UserrightsPage extends SpecialPage { 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 ) ); @@ -414,7 +414,7 @@ class UserrightsPage extends SpecialPage { * @param $groups Array: Array of groups the user is in */ protected function showEditUserGroupsForm( $user, $groups ) { - global $wgOut, $wgUser, $wgLang; + global $wgOut, $wgUser, $wgLang, $wgRequest; $list = array(); foreach( $groups as $group ) { @@ -429,12 +429,14 @@ class UserrightsPage extends SpecialPage { } $grouplist = ''; - if( count( $list ) > 0 ) { - $grouplist = wfMsgHtml( 'userrights-groupsmember' ); + $count = count( $list ); + if( $count > 0 ) { + $grouplist = wfMessage( 'userrights-groupsmember', $count)->parse(); $grouplist = '<p>' . $grouplist . ' ' . $wgLang->listToText( $list ) . "</p>\n"; } - if( count( $autolist ) > 0 ) { - $autogrouplistintro = wfMsgHtml( 'userrights-groupsmember-auto' ); + $count = count( $autolist ); + if( $count > 0 ) { + $autogrouplistintro = wfMessage( 'userrights-groupsmember-auto', $count)->parse(); $grouplist .= '<p>' . $autogrouplistintro . ' ' . $wgLang->listToText( $autolist ) . "</p>\n"; } $wgOut->addHTML( @@ -453,14 +455,15 @@ class UserrightsPage extends SpecialPage { Xml::label( wfMsg( 'userrights-reason' ), 'wpReason' ) . "</td> <td class='mw-input'>" . - Xml::input( 'user-reason', 60, false, array( 'id' => 'wpReason', 'maxlength' => 255 ) ) . + Xml::input( 'user-reason', 60, $wgRequest->getVal( 'user-reason', false ), + array( 'id' => 'wpReason', 'maxlength' => 255 ) ) . "</td> </tr> <tr> <td></td> <td class='mw-submit'>" . Xml::submitButton( wfMsg( 'saveusergroups' ), - array( 'name' => 'saveusergroups' ) + $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'userrights-set' ) ) . + array( 'name' => 'saveusergroups' ) + Linker::tooltipAndAccesskeyAttribs( 'userrights-set' ) ) . "</td> </tr>" . Xml::closeElement( 'table' ) . "\n" . @@ -534,7 +537,7 @@ class UserrightsPage extends SpecialPage { foreach( $columns as $name => $column ) { if( $column === array() ) continue; - $ret .= Xml::element( 'th', null, wfMsg( 'userrights-' . $name . '-col' ) ); + $ret .= Xml::element( 'th', null, wfMessage( 'userrights-' . $name . '-col', count( $column ) )->text() ); } $ret.= "</tr>\n<tr>\n"; foreach( $columns as $column ) { diff --git a/includes/specials/SpecialVersion.php b/includes/specials/SpecialVersion.php index 101823db..0331f056 100644 --- a/includes/specials/SpecialVersion.php +++ b/includes/specials/SpecialVersion.php @@ -29,15 +29,15 @@ * @ingroup SpecialPage */ class SpecialVersion extends SpecialPage { - + protected $firstExtOpened = false; protected static $extensionTypes = false; - + protected static $viewvcUrls = array( 'svn+ssh://svn.wikimedia.org/svnroot/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki', 'http://svn.wikimedia.org/svnroot/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki', - # Doesn't work at the time of writing but maybe some day: + # Doesn't work at the time of writing but maybe some day: 'https://svn.wikimedia.org/viewvc/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki', ); @@ -49,30 +49,33 @@ class SpecialVersion extends SpecialPage { * main() */ public function execute( $par ) { - global $wgOut, $wgSpecialVersionShowHooks, $wgContLang; - + global $wgOut, $wgSpecialVersionShowHooks, $wgRequest; + $this->setHeaders(); $this->outputHeader(); $wgOut->allowClickjacking(); - $wgOut->addHTML( Xml::openElement( 'div', - array( 'dir' => $wgContLang->getDir() ) ) ); - $text = + $text = $this->getMediaWikiCredits() . $this->softwareInformation() . $this->getExtensionCredits(); if ( $wgSpecialVersionShowHooks ) { $text .= $this->getWgHooks(); } - + $wgOut->addWikiText( $text ); $wgOut->addHTML( $this->IPInfo() ); - $wgOut->addHTML( '</div>' ); + + if ( $wgRequest->getVal( 'easteregg' ) ) { + if ( $this->showEasterEgg() ) { + // TODO: put something interesting here + } + } } /** * Returns wiki text showing the license information. - * + * * @return string */ private static function getMediaWikiCredits() { @@ -113,7 +116,7 @@ class SpecialVersion extends SpecialPage { /** * Returns wiki text showing the third party software versions (apache, php, mysql). - * + * * @return string */ static function softwareInformation() { @@ -136,14 +139,14 @@ class SpecialVersion extends SpecialPage { <th>" . wfMsg( 'version-software-product' ) . "</th> <th>" . wfMsg( 'version-software-version' ) . "</th> </tr>\n"; - + foreach( $software as $name => $version ) { $out .= "<tr> <td>" . $name . "</td> - <td>" . $version . "</td> + <td class=\"ltr\">" . $version . "</td> </tr>\n"; } - + return $out . Xml::closeElement( 'table' ); } @@ -163,8 +166,8 @@ class SpecialVersion extends SpecialPage { $version = "$wgVersion (r{$info['checkout-rev']})"; } else { $version = $wgVersion . ' ' . - wfMsg( - 'version-svn-revision', + wfMsg( + 'version-svn-revision', isset( $info['directory-rev'] ) ? $info['directory-rev'] : '', $info['checkout-rev'] ); @@ -173,7 +176,7 @@ class SpecialVersion extends SpecialPage { wfProfileOut( __METHOD__ ); return $version; } - + /** * Return a wikitext-formatted string of the MediaWiki version with a link to * the SVN revision if available. @@ -183,16 +186,16 @@ class SpecialVersion extends SpecialPage { public static function getVersionLinked() { global $wgVersion, $IP; wfProfileIn( __METHOD__ ); - + $info = self::getSvnInfo( $IP ); - + if ( isset( $info['checkout-rev'] ) ) { $linkText = wfMsg( 'version-svn-revision', isset( $info['directory-rev'] ) ? $info['directory-rev'] : '', $info['checkout-rev'] ); - + if ( isset( $info['viewvc-url'] ) ) { $version = "$wgVersion [{$info['viewvc-url']} $linkText]"; } else { @@ -201,7 +204,7 @@ class SpecialVersion extends SpecialPage { } else { $version = $wgVersion; } - + wfProfileOut( __METHOD__ ); return $version; } @@ -209,13 +212,13 @@ class SpecialVersion extends SpecialPage { /** * Returns an array with the base extension types. * Type is stored as array key, the message as array value. - * + * * TODO: ideally this would return all extension types, including * those added by SpecialVersionExtensionTypes. This is not possible * since this hook is passing along $this though. - * + * * @since 1.17 - * + * * @return array */ public static function getExtensionTypes() { @@ -225,44 +228,46 @@ class SpecialVersion extends SpecialPage { 'parserhook' => wfMsg( 'version-parserhooks' ), 'variable' => wfMsg( 'version-variables' ), 'media' => wfMsg( 'version-mediahandlers' ), + 'antispam' => wfMsg( 'version-antispam' ), 'skin' => wfMsg( 'version-skins' ), + 'api' => wfMsg( 'version-api' ), 'other' => wfMsg( 'version-other' ), ); - + wfRunHooks( 'ExtensionTypes', array( &self::$extensionTypes ) ); } - + return self::$extensionTypes; } - + /** * Returns the internationalized name for an extension type. - * + * * @since 1.17 - * + * * @param $type String - * + * * @return string */ public static function getExtensionTypeName( $type ) { $types = self::getExtensionTypes(); return isset( $types[$type] ) ? $types[$type] : $types['other']; } - + /** * Generate wikitext showing extensions name, URL, author and description. * * @return String: Wikitext */ function getExtensionCredits() { - global $wgExtensionCredits, $wgExtensionFunctions, $wgParser, $wgSkinExtensionFunctions; + global $wgExtensionCredits, $wgExtensionFunctions, $wgParser; - if ( !count( $wgExtensionCredits ) && !count( $wgExtensionFunctions ) && !count( $wgSkinExtensionFunctions ) ) { + if ( !count( $wgExtensionCredits ) && !count( $wgExtensionFunctions ) ) { return ''; } $extensionTypes = self::getExtensionTypes(); - + /** * @deprecated as of 1.17, use hook ExtensionTypes instead. */ @@ -271,25 +276,25 @@ class SpecialVersion extends SpecialPage { $out = Xml::element( 'h2', array( 'id' => 'mw-version-ext' ), wfMsg( 'version-extensions' ) ) . Xml::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-ext' ) ); - // Make sure the 'other' type is set to an array. + // Make sure the 'other' type is set to an array. if ( !array_key_exists( 'other', $wgExtensionCredits ) ) { $wgExtensionCredits['other'] = array(); } - + // Find all extensions that do not have a valid type and give them the type 'other'. foreach ( $wgExtensionCredits as $type => $extensions ) { if ( !array_key_exists( $type, $extensionTypes ) ) { $wgExtensionCredits['other'] = array_merge( $wgExtensionCredits['other'], $extensions ); } } - + // Loop through the extension categories to display their extensions in the list. foreach ( $extensionTypes as $type => $message ) { if ( $type != 'other' ) { $out .= $this->getExtensionCategory( $type, $message ); } } - + // We want the 'other' type to be last in the list. $out .= $this->getExtensionCategory( 'other', $extensionTypes['other'] ); @@ -309,36 +314,32 @@ class SpecialVersion extends SpecialPage { $out .= '<tr><td colspan="4">' . $this->listToText( $tags ). "</td></tr>\n"; } - if( count( $fhooks = $wgParser->getFunctionHooks() ) ) { + $fhooks = $wgParser->getFunctionHooks(); + if( count( $fhooks ) ) { $out .= $this->openExtType( wfMsg( 'version-parser-function-hooks' ), 'parser-function-hooks' ); $out .= '<tr><td colspan="4">' . $this->listToText( $fhooks ) . "</td></tr>\n"; } - if ( count( $wgSkinExtensionFunctions ) ) { - $out .= $this->openExtType( wfMsg( 'version-skin-extension-functions' ), 'skin-extension-functions' ); - $out .= '<tr><td colspan="4">' . $this->listToText( $wgSkinExtensionFunctions ) . "</td></tr>\n"; - } - $out .= Xml::closeElement( 'table' ); - + return $out; } - + /** * Creates and returns the HTML for a single extension category. - * + * * @since 1.17 - * + * * @param $type String * @param $message String - * + * * @return string */ protected function getExtensionCategory( $type, $message ) { - global $wgExtensionCredits; - + global $wgExtensionCredits; + $out = ''; - + if ( array_key_exists( $type, $wgExtensionCredits ) && count( $wgExtensionCredits[$type] ) > 0 ) { $out .= $this->openExtType( $message, 'credits-' . $type ); @@ -350,7 +351,7 @@ class SpecialVersion extends SpecialPage { } return $out; - } + } /** * Callback to sort extensions by type. @@ -368,14 +369,14 @@ class SpecialVersion extends SpecialPage { /** * Creates and formats the creidts for a single extension and returns this. - * + * * @param $extension Array - * + * * @return string */ function getCreditsForExtension( array $extension ) { $name = isset( $extension['name'] ) ? $extension['name'] : '[no name]'; - + if ( isset( $extension['path'] ) ) { $svnInfo = self::getSvnInfo( dirname($extension['path']) ); $directoryRev = isset( $svnInfo['directory-rev'] ) ? $svnInfo['directory-rev'] : null; @@ -393,10 +394,10 @@ class SpecialVersion extends SpecialPage { } else { $mainLink = $name; } - + if ( isset( $extension['version'] ) ) { - $versionText = '<span class="mw-version-ext-version">' . - wfMsg( 'version-version', $extension['version'] ) . + $versionText = '<span class="mw-version-ext-version">' . + wfMsg( 'version-version', $extension['version'] ) . '</span>'; } else { $versionText = ''; @@ -412,22 +413,19 @@ class SpecialVersion extends SpecialPage { # Make description text. $description = isset ( $extension['description'] ) ? $extension['description'] : ''; - + if( isset ( $extension['descriptionmsg'] ) ) { # Look for a localized description. $descriptionMsg = $extension['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 - $msg = wfMsg( $descriptionMsgKey, $descriptionMsg ); + $description = wfMsg( $descriptionMsgKey, $descriptionMsg ); } else { - $msg = wfMsg( $descriptionMsg ); + $description = wfMsg( $descriptionMsg ); } - if ( !wfEmptyMsg( $descriptionMsg, $msg ) && $msg != '' ) { - $description = $msg; - } } if ( $svnText !== false ) { @@ -438,12 +436,12 @@ class SpecialVersion extends SpecialPage { $extNameVer = "<tr> <td colspan=\"2\"><em>$mainLink $versionText</em></td>"; } - + $author = isset ( $extension['author'] ) ? $extension['author'] : array(); $extDescAuthor = "<td>$description</td> - <td>" . $this->listToText( (array)$author, false ) . "</td> + <td>" . $this->listAuthors( $author, false ) . "</td> </tr>\n"; - + return $extNameVer . $extDescAuthor; } @@ -466,11 +464,12 @@ class SpecialVersion extends SpecialPage { <th>" . wfMsg( 'version-hook-subscribedby' ) . "</th> </tr>\n"; - foreach ( $myWgHooks as $hook => $hooks ) + foreach ( $myWgHooks as $hook => $hooks ) { $ret .= "<tr> <td>$hook</td> <td>" . $this->listToText( $hooks ) . "</td> </tr>\n"; + } $ret .= Xml::closeElement( 'table' ); return $ret; @@ -487,13 +486,13 @@ class SpecialVersion extends SpecialPage { $out .= '<tr class="sv-space">' . Html::element( 'td', $opt ) . "</tr>\n"; } $this->firstExtOpened = true; - + if( $name ) { $opt['id'] = "sv-$name"; } $out .= "<tr>" . Xml::element( 'th', $opt, $text ) . "</tr>\n"; - + return $out; } @@ -509,11 +508,29 @@ class SpecialVersion extends SpecialPage { } /** + * Return a formatted unsorted list of authors + * + * @param $authors mixed: string or array of strings + * @return String: HTML fragment + */ + function listAuthors( $authors ) { + $list = array(); + foreach( (array)$authors as $item ) { + if( $item == '...' ) { + $list[] = wfMsg( 'version-poweredby-others' ); + } else { + $list[] = $item; + } + } + return $this->listToText( $list, false ); + } + + /** * Convert an array of items into a list for display. * * @param $list Array of elements to display * @param $sort Boolean: whether to sort the items in $list - * + * * @return String */ function listToText( $list, $sort = true ) { @@ -538,29 +555,31 @@ class SpecialVersion extends SpecialPage { * * @param $list Mixed: will convert an array to string if given and return * the paramater unaltered otherwise - * + * * @return Mixed */ - static function arrayToString( $list ) { - if( is_array( $list ) && count( $list ) == 1 ) + public static function arrayToString( $list ) { + if( is_array( $list ) && count( $list ) == 1 ) { $list = $list[0]; + } if( is_object( $list ) ) { $class = get_class( $list ); 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 + } else { $class = $list[0]; + } return "($class, {$list[1]})"; } } /** - * Get an associative array of information about a given path, from its .svn - * subdirectory. Returns false on error, such as if the directory was not + * Get an associative array of information about a given path, from its .svn + * subdirectory. Returns false on error, such as if the directory was not * checked out with subversion. * * Returned keys are: @@ -608,7 +627,7 @@ class SpecialVersion extends SpecialPage { } } } - + return false; } @@ -616,26 +635,26 @@ class SpecialVersion extends SpecialPage { if ( count( $lines ) < 11 ) { return false; } - + $info = array( 'checkout-rev' => intval( trim( $lines[3] ) ), 'url' => trim( $lines[4] ), 'repo-url' => trim( $lines[5] ), 'directory-rev' => intval( trim( $lines[10] ) ) ); - + if ( isset( self::$viewvcUrls[$info['repo-url']] ) ) { - $viewvc = str_replace( - $info['repo-url'], + $viewvc = str_replace( + $info['repo-url'], self::$viewvcUrls[$info['repo-url']], $info['url'] ); - + $viewvc .= '/?pathrev='; $viewvc .= urlencode( $info['checkout-rev'] ); $info['viewvc-url'] = $viewvc; } - + return $info; } @@ -643,12 +662,12 @@ class SpecialVersion extends SpecialPage { * Retrieve the revision number of a Subversion working directory. * * @param $dir String: directory of the svn checkout - * + * * @return Integer: revision number as int */ public static function getSvnRevision( $dir ) { $info = self::getSvnInfo( $dir ); - + if ( $info === false ) { return false; } elseif ( isset( $info['checkout-rev'] ) ) { @@ -658,4 +677,108 @@ class SpecialVersion extends SpecialPage { } } + 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 ); + } } diff --git a/includes/specials/SpecialWantedcategories.php b/includes/specials/SpecialWantedcategories.php index b588dbf0..800e940a 100644 --- a/includes/specials/SpecialWantedcategories.php +++ b/includes/specials/SpecialWantedcategories.php @@ -30,28 +30,29 @@ */ class WantedCategoriesPage extends WantedQueryPage { - function getName() { - return 'Wantedcategories'; + function __construct( $name = 'Wantedcategories' ) { + parent::__construct( $name ); } - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $categorylinks, $page ) = $dbr->tableNamesN( 'categorylinks', 'page' ); - $name = $dbr->addQuotes( $this->getName() ); - return - " - SELECT - $name as type, - " . NS_CATEGORY . " as namespace, - cl_to as title, - COUNT(*) as value - FROM $categorylinks - LEFT JOIN $page ON cl_to = page_title AND page_namespace = ". NS_CATEGORY ." - WHERE page_title IS NULL - GROUP BY cl_to - "; + function getQueryInfo() { + return array ( + 'tables' => array ( 'categorylinks', 'page' ), + 'fields' => array ( "'" . NS_CATEGORY . "' AS namespace", + 'cl_to AS title', + 'COUNT(*) AS value' ), + '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 + * @return string + */ function formatResult( $skin, $result ) { global $wgLang, $wgContLang; @@ -73,14 +74,3 @@ class WantedCategoriesPage extends WantedQueryPage { return wfSpecialList($plink, $nlinks); } } - -/** - * constructor - */ -function wfSpecialWantedCategories() { - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new WantedCategoriesPage(); - - $wpp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialWantedfiles.php b/includes/specials/SpecialWantedfiles.php index d6c1157b..8a4fe56f 100644 --- a/includes/specials/SpecialWantedfiles.php +++ b/includes/specials/SpecialWantedfiles.php @@ -31,8 +31,8 @@ */ class WantedFilesPage extends WantedQueryPage { - function getName() { - return 'Wantedfiles'; + function __construct( $name = 'Wantedfiles' ) { + parent::__construct( $name ); } /** @@ -45,32 +45,19 @@ class WantedFilesPage extends WantedQueryPage { return true; } - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $imagelinks, $image ) = $dbr->tableNamesN( 'imagelinks', 'image' ); - $name = $dbr->addQuotes( $this->getName() ); - return - " - SELECT - $name as type, - " . NS_FILE . " as namespace, - il_to as title, - COUNT(*) as value - FROM $imagelinks - LEFT JOIN $image ON il_to = img_name - WHERE img_name IS NULL - GROUP BY il_to - "; + function getQueryInfo() { + return array ( + 'tables' => array ( 'imagelinks', 'image' ), + 'fields' => array ( "'" . NS_FILE . "' AS namespace", + 'il_to AS title', + 'COUNT(*) AS value' ), + 'conds' => array ( 'img_name IS NULL' ), + 'options' => array ( 'GROUP BY' => 'il_to' ), + 'join_conds' => array ( 'image' => + array ( 'LEFT JOIN', + array ( 'il_to = img_name' ) + ) + ) + ); } } - -/** - * constructor - */ -function wfSpecialWantedFiles() { - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new WantedFilesPage(); - - $wpp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialWantedpages.php b/includes/specials/SpecialWantedpages.php index 4e1611bc..a4233155 100644 --- a/includes/specials/SpecialWantedpages.php +++ b/includes/specials/SpecialWantedpages.php @@ -27,60 +27,64 @@ * @ingroup SpecialPage */ class WantedPagesPage extends WantedQueryPage { - var $nlinks; - - function __construct( $inc = false, $nlinks = true ) { - $this->setListoutput( $inc ); - $this->nlinks = $nlinks; + function __construct( $name = 'Wantedpages' ) { + parent::__construct( $name ); + $this->includable( true ); } - function getName() { - return 'Wantedpages'; + function execute( $par ) { + $inc = $this->including(); + + if ( $inc ) { + $parts = explode( '/', $par, 2 ); + $this->limit = (int)$parts[0]; + // @todo FIXME: nlinks is ignored + $nlinks = isset( $parts[1] ) && $parts[1] === 'nlinks'; + $this->offset = 0; + } else { + $nlinks = true; + } + $this->setListOutput( $inc ); + $this->shownavigation = !$inc; + parent::execute( $par ); } - function getSQL() { + function getQueryInfo() { global $wgWantedPagesThreshold; $count = $wgWantedPagesThreshold - 1; - $dbr = wfGetDB( DB_SLAVE ); - $pagelinks = $dbr->tableName( 'pagelinks' ); - $page = $dbr->tableName( 'page' ); - $sql = "SELECT 'Wantedpages' AS type, - pl_namespace AS namespace, - pl_title AS title, - COUNT(*) AS value - FROM $pagelinks - LEFT JOIN $page AS pg1 - ON pl_namespace = pg1.page_namespace AND pl_title = pg1.page_title - LEFT JOIN $page AS pg2 - ON pl_from = pg2.page_id - WHERE pg1.page_namespace IS NULL - AND pl_namespace NOT IN ( " . NS_USER . ", ". NS_USER_TALK . ") - AND pg2.page_namespace != " . NS_MEDIAWIKI . " - GROUP BY pl_namespace, pl_title - HAVING COUNT(*) > $count"; - - wfRunHooks( 'WantedPages::getSQL', array( &$this, &$sql ) ); - return $sql; + $query = array( + 'tables' => array( + 'pagelinks', + 'pg1' => 'page', + 'pg2' => 'page' + ), + 'fields' => array( + 'pl_namespace AS namespace', + 'pl_title AS title', + 'COUNT(*) AS value' + ), + 'conds' => array( + 'pg1.page_namespace IS NULL', + "pl_namespace NOT IN ( '" . NS_USER . + "', '" . NS_USER_TALK . "' )", + "pg2.page_namespace != '" . NS_MEDIAWIKI . "'" + ), + 'options' => array( + 'HAVING' => "COUNT(*) > $count", + 'GROUP BY' => 'pl_namespace, pl_title' + ), + 'join_conds' => array( + 'pg1' => array( + 'LEFT JOIN', array( + 'pg1.page_namespace = pl_namespace', + 'pg1.page_title = pl_title' + ) + ), + 'pg2' => array( 'LEFT JOIN', 'pg2.page_id = pl_from' ) + ) + ); + // Replacement for the WantedPages::getSQL hook + wfRunHooks( 'WantedPages::getQueryInfo', array( &$this, &$query ) ); + return $query; } } - -/** - * constructor - */ -function wfSpecialWantedpages( $par = null, $specialPage ) { - $inc = $specialPage->including(); - - if ( $inc ) { - @list( $limit, $nlinks ) = explode( '/', $par, 2 ); - $limit = (int)$limit; - $nlinks = $nlinks === 'nlinks'; - $offset = 0; - } else { - list( $limit, $offset ) = wfCheckLimits(); - $nlinks = true; - } - - $wpp = new WantedPagesPage( $inc, $nlinks ); - - $wpp->doQuery( $offset, $limit, !$inc ); -} diff --git a/includes/specials/SpecialWantedtemplates.php b/includes/specials/SpecialWantedtemplates.php index ae43c237..ab9d6046 100644 --- a/includes/specials/SpecialWantedtemplates.php +++ b/includes/specials/SpecialWantedtemplates.php @@ -33,35 +33,23 @@ */ class WantedTemplatesPage extends WantedQueryPage { - function getName() { - return 'Wantedtemplates'; + function __construct( $name = 'Wantedtemplates' ) { + parent::__construct( $name ); } - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $templatelinks, $page ) = $dbr->tableNamesN( 'templatelinks', 'page' ); - $name = $dbr->addQuotes( $this->getName() ); - return - " - SELECT $name as type, - tl_namespace as namespace, - tl_title as title, - COUNT(*) as value - FROM $templatelinks LEFT JOIN - $page ON tl_title = page_title AND tl_namespace = page_namespace - WHERE page_title IS NULL AND tl_namespace = ". NS_TEMPLATE ." - GROUP BY tl_namespace, tl_title - "; + function getQueryInfo() { + return array ( + 'tables' => array ( 'templatelinks', 'page' ), + 'fields' => array ( 'tl_namespace AS namespace', + 'tl_title AS title', + 'COUNT(*) AS value' ), + 'conds' => array ( 'page_title IS NULL', + 'tl_namespace' => NS_TEMPLATE ), + 'options' => array ( + 'GROUP BY' => 'tl_namespace, tl_title' ), + 'join_conds' => array ( 'page' => array ( 'LEFT JOIN', + array ( 'page_namespace = tl_namespace', + 'page_title = tl_title' ) ) ) + ); } } - -/** - * constructor - */ -function wfSpecialWantedTemplates() { - list( $limit, $offset ) = wfCheckLimits(); - - $wpp = new WantedTemplatesPage(); - - $wpp->doQuery( $offset, $limit ); -} diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php index bb1c194d..51086bb1 100644 --- a/includes/specials/SpecialWatchlist.php +++ b/includes/specials/SpecialWatchlist.php @@ -20,464 +20,471 @@ * @file * @ingroup SpecialPage Watchlist */ - -/** - * Constructor - * - * @param $par Parameter passed to the page - */ -function wfSpecialWatchlist( $par ) { - global $wgUser, $wgOut, $wgLang, $wgRequest; - global $wgRCShowWatchingUsers, $wgEnotifWatchlist, $wgShowUpdatedMarker; - - // Add feed links - $wlToken = $wgUser->getOption( 'watchlisttoken' ); - if (!$wlToken) { - $wlToken = sha1( mt_rand() . microtime( true ) ); - $wgUser->setOption( 'watchlisttoken', $wlToken ); - $wgUser->saveSettings(); +class SpecialWatchlist extends SpecialPage { + protected $customFilters; + + /** + * Constructor + */ + public function __construct( $page = 'Watchlist' ){ + parent::__construct( $page ); } - - global $wgFeedClasses; - $apiParams = array( 'action' => 'feedwatchlist', 'allrev' => 'allrev', - 'wlowner' => $wgUser->getName(), 'wltoken' => $wlToken ); - $feedTemplate = wfScript('api').'?'; - - foreach( $wgFeedClasses as $format => $class ) { - $theseParams = $apiParams + array( 'feedformat' => $format ); - $url = $feedTemplate . wfArrayToCGI( $theseParams ); - $wgOut->addFeedLink( $format, $url ); - } - - $skin = $wgUser->getSkin(); - $specialTitle = SpecialPage::getTitleFor( 'Watchlist' ); - $wgOut->setRobotPolicy( 'noindex,nofollow' ); - # Anons don't get a watchlist - if( $wgUser->isAnon() ) { - $wgOut->setPageTitle( wfMsg( 'watchnologin' ) ); - $llink = $skin->linkKnown( - SpecialPage::getTitleFor( 'Userlogin' ), - wfMsgHtml( 'loginreqlink' ), - array(), - array( 'returnto' => $specialTitle->getPrefixedText() ) - ); - $wgOut->addHTML( wfMsgWikiHtml( 'watchlistanontext', $llink ) ); - return; - } + /** + * Execute + * @param $par Parameter passed to the page + */ + function execute( $par ) { + global $wgRCShowWatchingUsers, $wgEnotifWatchlist, $wgShowUpdatedMarker; + + $user = $this->getUser(); + $output = $this->getOutput(); + + // Add feed links + $wlToken = $user->getOption( 'watchlisttoken' ); + if ( !$wlToken ) { + $wlToken = sha1( mt_rand() . microtime( true ) ); + $user->setOption( 'watchlisttoken', $wlToken ); + $user->saveSettings(); + } - $wgOut->setPageTitle( wfMsg( 'watchlist' ) ); + $this->addFeedLinks( array( 'action' => 'feedwatchlist', 'allrev' => 'allrev', + 'wlowner' => $user->getName(), 'wltoken' => $wlToken ) ); + + $output->setRobotPolicy( 'noindex,nofollow' ); + + # Anons don't get a watchlist + if( $user->isAnon() ) { + $output->setPageTitle( wfMsg( 'watchnologin' ) ); + $llink = Linker::linkKnown( + SpecialPage::getTitleFor( 'Userlogin' ), + wfMsgHtml( 'loginreqlink' ), + array(), + array( 'returnto' => $this->getTitle()->getPrefixedText() ) + ); + $output->addHTML( wfMessage( 'watchlistanontext' )->rawParams( $llink )->parse() ); + return; + } - $sub = wfMsgExt( 'watchlistfor2', array( 'parseinline', 'replaceafter' ), $wgUser->getName(), WatchlistEditor::buildTools( $wgUser->getSkin() ) ); - $wgOut->setSubtitle( $sub ); + $this->setHeaders(); + $this->outputHeader(); - if( ( $mode = WatchlistEditor::getMode( $wgRequest, $par ) ) !== false ) { - $editor = new WatchlistEditor(); - $editor->execute( $wgUser, $wgOut, $wgRequest, $mode ); - return; - } + $sub = wfMsgExt( + 'watchlistfor2', + array( 'parseinline', 'replaceafter' ), + $user->getName(), + SpecialEditWatchlist::buildTools( $this->getSkin() ) + ); + $output->setSubtitle( $sub ); + + $request = $this->getRequest(); + + $mode = SpecialEditWatchlist::getMode( $request, $par ); + if( $mode !== false ) { + # TODO: localise? + switch( $mode ){ + case SpecialEditWatchlist::EDIT_CLEAR: + $mode = 'clear'; + break; + case SpecialEditWatchlist::EDIT_RAW: + $mode = 'raw'; + break; + default: + $mode = null; + } + $title = SpecialPage::getTitleFor( 'EditWatchlist', $mode ); + $output->redirect( $title->getLocalUrl() ); + return; + } - $uid = $wgUser->getId(); - if( ($wgEnotifWatchlist || $wgShowUpdatedMarker) && $wgRequest->getVal( 'reset' ) && - $wgRequest->wasPosted() ) - { - $wgUser->clearAllNotifications( $uid ); - $wgOut->redirect( $specialTitle->getFullUrl() ); - return; - } + if( ( $wgEnotifWatchlist || $wgShowUpdatedMarker ) && $request->getVal( 'reset' ) && + $request->wasPosted() ) + { + $user->clearAllNotifications(); + $output->redirect( $this->getTitle()->getFullUrl() ); + return; + } - $defaults = array( - /* float */ 'days' => floatval( $wgUser->getOption( 'watchlistdays' ) ), /* 3.0 or 0.5, watch further below */ - /* bool */ 'hideMinor' => (int)$wgUser->getBoolOption( 'watchlisthideminor' ), - /* bool */ 'hideBots' => (int)$wgUser->getBoolOption( 'watchlisthidebots' ), - /* bool */ 'hideAnons' => (int)$wgUser->getBoolOption( 'watchlisthideanons' ), - /* bool */ 'hideLiu' => (int)$wgUser->getBoolOption( 'watchlisthideliu' ), - /* bool */ 'hidePatrolled' => (int)$wgUser->getBoolOption( 'watchlisthidepatrolled' ), - /* bool */ 'hideOwn' => (int)$wgUser->getBoolOption( 'watchlisthideown' ), - /* ? */ 'namespace' => 'all', - /* ? */ 'invert' => false, - ); - - extract($defaults); - - # Extract variables from the request, falling back to user preferences or - # other default values if these don't exist - $prefs['days'] = floatval( $wgUser->getOption( 'watchlistdays' ) ); - $prefs['hideminor'] = $wgUser->getBoolOption( 'watchlisthideminor' ); - $prefs['hidebots'] = $wgUser->getBoolOption( 'watchlisthidebots' ); - $prefs['hideanons'] = $wgUser->getBoolOption( 'watchlisthideanons' ); - $prefs['hideliu'] = $wgUser->getBoolOption( 'watchlisthideliu' ); - $prefs['hideown' ] = $wgUser->getBoolOption( 'watchlisthideown' ); - $prefs['hidepatrolled' ] = $wgUser->getBoolOption( 'watchlisthidepatrolled' ); - - # Get query variables - $days = $wgRequest->getVal( 'days' , $prefs['days'] ); - $hideMinor = $wgRequest->getBool( 'hideMinor', $prefs['hideminor'] ); - $hideBots = $wgRequest->getBool( 'hideBots' , $prefs['hidebots'] ); - $hideAnons = $wgRequest->getBool( 'hideAnons', $prefs['hideanons'] ); - $hideLiu = $wgRequest->getBool( 'hideLiu' , $prefs['hideliu'] ); - $hideOwn = $wgRequest->getBool( 'hideOwn' , $prefs['hideown'] ); - $hidePatrolled = $wgRequest->getBool( 'hidePatrolled' , $prefs['hidepatrolled'] ); - - # Get namespace value, if supplied, and prepare a WHERE fragment - $nameSpace = $wgRequest->getIntOrNull( 'namespace' ); - $invert = $wgRequest->getIntOrNull( 'invert' ); - if( !is_null( $nameSpace ) ) { - $nameSpace = intval( $nameSpace ); - if( $invert && $nameSpace !== 'all' ) - $nameSpaceClause = "rc_namespace != $nameSpace"; - else - $nameSpaceClause = "rc_namespace = $nameSpace"; - } else { - $nameSpace = ''; - $nameSpaceClause = ''; - } + $nitems = $this->countItems(); + if ( $nitems == 0 ) { + $output->addWikiMsg( 'nowatchlist' ); + return; + } - $dbr = wfGetDB( DB_SLAVE, 'watchlist' ); - $recentchanges = $dbr->tableName( 'recentchanges' ); + // @TODO: use FormOptions! + $defaults = array( + /* float */ 'days' => floatval( $user->getOption( 'watchlistdays' ) ), /* 3.0 or 0.5, watch further below */ + /* bool */ 'hideMinor' => (int)$user->getBoolOption( 'watchlisthideminor' ), + /* bool */ 'hideBots' => (int)$user->getBoolOption( 'watchlisthidebots' ), + /* bool */ 'hideAnons' => (int)$user->getBoolOption( 'watchlisthideanons' ), + /* bool */ 'hideLiu' => (int)$user->getBoolOption( 'watchlisthideliu' ), + /* bool */ 'hidePatrolled' => (int)$user->getBoolOption( 'watchlisthidepatrolled' ), + /* bool */ 'hideOwn' => (int)$user->getBoolOption( 'watchlisthideown' ), + /* ? */ 'namespace' => 'all', + /* ? */ 'invert' => false, + ); + $this->customFilters = array(); + wfRunHooks( 'SpecialWatchlistFilters', array( $this, &$this->customFilters ) ); + foreach( $this->customFilters as $key => $params ) { + $defaults[$key] = $params['msg']; + } - $watchlistCount = $dbr->selectField( 'watchlist', 'COUNT(*)', - array( 'wl_user' => $uid ), __METHOD__ ); - // Adjust for page X, talk:page X, which are both stored separately, - // but treated together - $nitems = floor($watchlistCount / 2); + # 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 ); + } - if( is_null($days) || !is_numeric($days) ) { - $big = 1000; /* The magical big */ - if($nitems > $big) { - # Set default cutoff shorter - $days = $defaults['days'] = (12.0 / 24.0); # 12 hours... + # Get namespace value, if supplied, and prepare a WHERE fragment + $nameSpace = $request->getIntOrNull( 'namespace' ); + $invert = $request->getIntOrNull( 'invert' ); + if ( !is_null( $nameSpace ) ) { + $nameSpace = intval( $nameSpace ); // paranioa + if ( $invert ) { + $nameSpaceClause = "rc_namespace != $nameSpace"; + } else { + $nameSpaceClause = "rc_namespace = $nameSpace"; + } } else { - $days = $defaults['days']; # default cutoff for shortlisters + $nameSpace = ''; + $nameSpaceClause = ''; + } + $values['namespace'] = $nameSpace; + $values['invert'] = $invert; + + 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'] ); } - } else { - $days = floatval($days); - } - - // Dump everything here - $nondefaults = array(); - - wfAppendToArrayIfNotDefault( 'days' , $days , $defaults, $nondefaults); - wfAppendToArrayIfNotDefault( 'hideMinor', (int)$hideMinor, $defaults, $nondefaults ); - wfAppendToArrayIfNotDefault( 'hideBots' , (int)$hideBots , $defaults, $nondefaults); - wfAppendToArrayIfNotDefault( 'hideAnons', (int)$hideAnons, $defaults, $nondefaults ); - wfAppendToArrayIfNotDefault( 'hideLiu' , (int)$hideLiu , $defaults, $nondefaults ); - wfAppendToArrayIfNotDefault( 'hideOwn' , (int)$hideOwn , $defaults, $nondefaults); - wfAppendToArrayIfNotDefault( 'namespace', $nameSpace , $defaults, $nondefaults); - wfAppendToArrayIfNotDefault( 'hidePatrolled', (int)$hidePatrolled, $defaults, $nondefaults ); - - if( $nitems == 0 ) { - $wgOut->addWikiMsg( 'nowatchlist' ); - return; - } - - # Possible where conditions - $conds = array(); - - if( $days > 0 ) { - $conds[] = "rc_timestamp > '".$dbr->timestamp( time() - intval( $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( $hideOwn ) { - $conds[] = "rc_user != $uid"; - } - if( $hideBots ) { - $conds[] = 'rc_bot = 0'; - } - if( $hideMinor ) { - $conds[] = 'rc_minor = 0'; - } - if( $hideLiu ) { - $conds[] = 'rc_user = 0'; - } - if( $hideAnons ) { - $conds[] = 'rc_user != 0'; - } - if ( $wgUser->useRCPatrol() && $hidePatrolled ) { - $conds[] = 'rc_patrolled != 1'; - } - if( $nameSpaceClause ) { - $conds[] = $nameSpaceClause; - } + // Dump everything here + $nondefaults = array(); + foreach ( $defaults as $name => $defValue ) { + wfAppendToArrayIfNotDefault( $name, $values[$name], $defaults, $nondefaults ); + } - # Toggle watchlist content (all recent edits or just the latest) - if( $wgUser->getOption( 'extendwatchlist' )) { - $limitWatchlist = intval( $wgUser->getOption( 'wllimit' ) ); - $usePage = false; - } else { - # Top log Ids for a page are not stored - $conds[] = 'rc_this_oldid=page_latest OR rc_type=' . RC_LOG; - $limitWatchlist = 0; - $usePage = true; - } + $dbr = wfGetDB( DB_SLAVE, 'watchlist' ); - # Show a message about slave lag, if applicable - if( ( $lag = $dbr->getLag() ) > 0 ) - $wgOut->showLagWarning( $lag ); + # Possible where conditions + $conds = array(); - # Create output form - $form = Xml::fieldset( wfMsg( 'watchlist-options' ), false, array( 'id' => 'mw-watchlist-options' ) ); + if( $values['days'] > 0 ) { + $conds[] = "rc_timestamp > '".$dbr->timestamp( time() - intval( $values['days'] * 86400 ) )."'"; + } - # Show watchlist header - $form .= wfMsgExt( 'watchlist-details', array( 'parseinline' ), $wgLang->formatNum( $nitems ) ); + # If the watchlist is relatively short, it's simplest to zip + # down its entirety and then sort the results. - if( $wgUser->getOption( 'enotifwatchlistpages' ) && $wgEnotifWatchlist) { - $form .= wfMsgExt( 'wlheader-enotif', 'parse' ) . "\n"; - } - if( $wgShowUpdatedMarker ) { - $form .= Xml::openElement( 'form', array( 'method' => 'post', - 'action' => $specialTitle->getLocalUrl(), - 'id' => 'mw-watchlist-resetbutton' ) ) . - wfMsgExt( 'wlheader-showupdated', array( 'parseinline' ) ) . ' ' . - Xml::submitButton( wfMsg( 'enotif_reset' ), array( 'name' => 'dummy' ) ) . - Html::hidden( 'reset', 'all' ) . - Xml::closeElement( 'form' ); - } - $form .= '<hr />'; - - $tables = array( 'recentchanges', 'watchlist' ); - $fields = array( "{$recentchanges}.*" ); - $join_conds = array( - 'watchlist' => array('INNER JOIN',"wl_user='{$uid}' AND wl_namespace=rc_namespace AND wl_title=rc_title"), - ); - $options = array( 'ORDER BY' => 'rc_timestamp DESC' ); - if( $wgShowUpdatedMarker ) { - $fields[] = 'wl_notificationtimestamp'; - } - if( $limitWatchlist ) { - $options['LIMIT'] = $limitWatchlist; - } + # If it's relatively long, it may be worth our while to zip + # through the time-sorted page list checking for watched items. - $rollbacker = $wgUser->isAllowed('rollback'); - if ( $usePage || $rollbacker ) { - $tables[] = 'page'; - $join_conds['page'] = array('LEFT JOIN','rc_cur_id=page_id'); - if ($rollbacker) - $fields[] = 'page_latest'; - } + # Up estimate of watched items by 15% to compensate for talk pages... - ChangeTags::modifyDisplayQuery( $tables, $fields, $conds, $join_conds, $options, '' ); - wfRunHooks('SpecialWatchlistQuery', array(&$conds,&$tables,&$join_conds,&$fields) ); - - $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $join_conds ); - $numRows = $dbr->numRows( $res ); - - /* Start bottom header */ - - $wlInfo = ''; - if( $days >= 1 ) { - $wlInfo = wfMsgExt( 'rcnote', 'parseinline', - $wgLang->formatNum( $numRows ), - $wgLang->formatNum( $days ), - $wgLang->timeAndDate( wfTimestampNow(), true ), - $wgLang->date( wfTimestampNow(), true ), - $wgLang->time( wfTimestampNow(), true ) - ) . '<br />'; - } elseif( $days > 0 ) { - $wlInfo = wfMsgExt( 'wlnote', 'parseinline', - $wgLang->formatNum( $numRows ), - $wgLang->formatNum( round($days*24) ) - ) . '<br />'; - } - $cutofflinks = "\n" . wlCutoffLinks( $days, 'Watchlist', $nondefaults ) . "<br />\n"; + # Toggles + if( $values['hideOwn'] ) { + $conds[] = 'rc_user != ' . $user->getId(); + } + if( $values['hideBots'] ) { + $conds[] = 'rc_bot = 0'; + } + if( $values['hideMinor'] ) { + $conds[] = 'rc_minor = 0'; + } + if( $values['hideLiu'] ) { + $conds[] = 'rc_user = 0'; + } + if( $values['hideAnons'] ) { + $conds[] = 'rc_user != 0'; + } + if ( $user->useRCPatrol() && $values['hidePatrolled'] ) { + $conds[] = 'rc_patrolled != 1'; + } + if ( $nameSpaceClause ) { + $conds[] = $nameSpaceClause; + } - $thisTitle = SpecialPage::getTitleFor( 'Watchlist' ); + # Toggle watchlist content (all recent edits or just the latest) + if( $user->getOption( 'extendwatchlist' ) ) { + $limitWatchlist = intval( $user->getOption( 'wllimit' ) ); + $usePage = false; + } else { + # Top log Ids for a page are not stored + $conds[] = 'rc_this_oldid=page_latest OR rc_type=' . RC_LOG; + $limitWatchlist = 0; + $usePage = true; + } - # Spit out some control panel links - $links[] = wlShowHideLink( $nondefaults, 'rcshowhideminor', 'hideMinor', $hideMinor ); - $links[] = wlShowHideLink( $nondefaults, 'rcshowhidebots', 'hideBots', $hideBots ); - $links[] = wlShowHideLink( $nondefaults, 'rcshowhideanons', 'hideAnons', $hideAnons ); - $links[] = wlShowHideLink( $nondefaults, 'rcshowhideliu', 'hideLiu', $hideLiu ); - $links[] = wlShowHideLink( $nondefaults, 'rcshowhidemine', 'hideOwn', $hideOwn ); + # Show a message about slave lag, if applicable + $lag = wfGetLB()->safeGetLag( $dbr ); + if( $lag > 0 ) { + $output->showLagWarning( $lag ); + } - if( $wgUser->useRCPatrol() ) { - $links[] = wlShowHideLink( $nondefaults, 'rcshowhidepatr', 'hidePatrolled', $hidePatrolled ); - } + $lang = $this->getLang(); - # Namespace filter and put the whole form together. - $form .= $wlInfo; - $form .= $cutofflinks; - $form .= $wgLang->pipeList( $links ); - $form .= Xml::openElement( 'form', array( 'method' => 'post', 'action' => $thisTitle->getLocalUrl(), 'id' => 'mw-watchlist-form-namespaceselector' ) ); - $form .= '<hr /><p>'; - $form .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' '; - $form .= Xml::namespaceSelector( $nameSpace, '' ) . ' '; - $form .= Xml::checkLabel( wfMsg('invert'), 'invert', 'nsinvert', $invert ) . ' '; - $form .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . '</p>'; - $form .= Html::hidden( 'days', $days ); - if( $hideMinor ) - $form .= Html::hidden( 'hideMinor', 1 ); - if( $hideBots ) - $form .= Html::hidden( 'hideBots', 1 ); - if( $hideAnons ) - $form .= Html::hidden( 'hideAnons', 1 ); - if( $hideLiu ) - $form .= Html::hidden( 'hideLiu', 1 ); - if( $hideOwn ) - $form .= Html::hidden( 'hideOwn', 1 ); - $form .= Xml::closeElement( 'form' ); - $form .= Xml::closeElement( 'fieldset' ); - $wgOut->addHTML( $form ); - - # If there's nothing to show, stop here - if( $numRows == 0 ) { - $wgOut->addWikiMsg( 'watchnochange' ); - return; - } + # Create output form + $form = Xml::fieldset( wfMsg( 'watchlist-options' ), false, array( 'id' => 'mw-watchlist-options' ) ); - /* End bottom header */ + # Show watchlist header + $form .= wfMsgExt( 'watchlist-details', array( 'parseinline' ), $lang->formatNum( $nitems ) ); - /* Do link batch query */ - $linkBatch = new LinkBatch; - foreach ( $res as $row ) { - $userNameUnderscored = str_replace( ' ', '_', $row->rc_user_text ); - if ( $row->rc_user != 0 ) { - $linkBatch->add( NS_USER, $userNameUnderscored ); + if( $user->getOption( 'enotifwatchlistpages' ) && $wgEnotifWatchlist) { + $form .= wfMsgExt( 'wlheader-enotif', 'parse' ) . "\n"; } - $linkBatch->add( NS_USER_TALK, $userNameUnderscored ); - - $linkBatch->add( $row->rc_namespace, $row->rc_title ); - } - $linkBatch->execute(); - $dbr->dataSeek( $res, 0 ); - - $list = ChangesList::newFromUser( $wgUser ); - $list->setWatchlistDivs(); - - $s = $list->beginRecentChangesList(); - $counter = 1; - foreach ( $res as $obj ) { - # Make RC entry - $rc = RecentChange::newFromRow( $obj ); - $rc->counter = $counter++; - - if ( $wgShowUpdatedMarker ) { - $updated = $obj->wl_notificationtimestamp; - } else { - $updated = false; + if( $wgShowUpdatedMarker ) { + $form .= Xml::openElement( 'form', array( 'method' => 'post', + 'action' => $this->getTitle()->getLocalUrl(), + 'id' => 'mw-watchlist-resetbutton' ) ) . + wfMsgExt( 'wlheader-showupdated', array( 'parseinline' ) ) . ' ' . + Xml::submitButton( wfMsg( 'enotif_reset' ), array( 'name' => 'dummy' ) ) . + Html::hidden( 'reset', 'all' ) . + Xml::closeElement( 'form' ); } + $form .= '<hr />'; - if ($wgRCShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' )) { - $rc->numberofWatchingusers = $dbr->selectField( 'watchlist', - 'COUNT(*)', - array( - 'wl_namespace' => $obj->rc_namespace, - 'wl_title' => $obj->rc_title, - ), - __METHOD__ ); - } else { - $rc->numberofWatchingusers = 0; + $tables = array( 'recentchanges', 'watchlist' ); + $fields = array( $dbr->tableName( 'recentchanges' ) . '.*' ); + $join_conds = array( + 'watchlist' => array('INNER JOIN',"wl_user='{$user->getId()}' AND wl_namespace=rc_namespace AND wl_title=rc_title"), + ); + $options = array( 'ORDER BY' => 'rc_timestamp DESC' ); + if( $wgShowUpdatedMarker ) { + $fields[] = 'wl_notificationtimestamp'; + } + if( $limitWatchlist ) { + $options['LIMIT'] = $limitWatchlist; } - $s .= $list->recentChangesLine( $rc, $updated, $counter ); - } - $s .= $list->endRecentChangesList(); - - $wgOut->addHTML( $s ); -} + $rollbacker = $user->isAllowed('rollback'); + if ( $usePage || $rollbacker ) { + $tables[] = 'page'; + $join_conds['page'] = array('LEFT JOIN','rc_cur_id=page_id'); + if ( $rollbacker ) { + $fields[] = 'page_latest'; + } + } -function wlShowHideLink( $options, $message, $name, $value ) { - global $wgUser; + ChangeTags::modifyDisplayQuery( $tables, $fields, $conds, $join_conds, $options, '' ); + wfRunHooks('SpecialWatchlistQuery', array(&$conds,&$tables,&$join_conds,&$fields) ); + + $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $join_conds ); + $numRows = $dbr->numRows( $res ); + + /* Start bottom header */ + + $wlInfo = ''; + if( $values['days'] >= 1 ) { + $timestamp = wfTimestampNow(); + $wlInfo = wfMsgExt( 'rcnote', 'parseinline', + $lang->formatNum( $numRows ), + $lang->formatNum( $values['days'] ), + $lang->timeAndDate( $timestamp, true ), + $lang->date( $timestamp, true ), + $lang->time( $timestamp, true ) + ) . '<br />'; + } elseif( $values['days'] > 0 ) { + $wlInfo = wfMsgExt( 'wlnote', 'parseinline', + $lang->formatNum( $numRows ), + $lang->formatNum( round( $values['days'] * 24 ) ) + ) . '<br />'; + } - $showLinktext = wfMsgHtml( 'show' ); - $hideLinktext = wfMsgHtml( 'hide' ); - $title = SpecialPage::getTitleFor( 'Watchlist' ); - $skin = $wgUser->getSkin(); + $cutofflinks = "\n" . $this->cutoffLinks( $values['days'], $nondefaults ) . "<br />\n"; - $label = $value ? $showLinktext : $hideLinktext; - $options[$name] = 1 - (int) $value; + # Spit out some control panel links + $filters = array( + 'hideMinor' => 'rcshowhideminor', + 'hideBots' => 'rcshowhidebots', + 'hideAnons' => 'rcshowhideanons', + 'hideLiu' => 'rcshowhideliu', + 'hideOwn' => 'rcshowhidemine', + 'hidePatrolled' => 'rcshowhidepatr' + ); + foreach ( $this->customFilters as $key => $params ) { + $filters[$key] = $params['msg']; + } + // Disable some if needed + if ( !$user->useNPPatrol() ) { + unset( $filters['hidePatrolled'] ); + } - return wfMsgHtml( $message, $skin->linkKnown( $title, $label, array(), $options ) ); -} + $links = array(); + foreach( $filters as $name => $msg ) { + $links[] = $this->showHideLink( $nondefaults, $msg, $name, $values[$name] ); + } + # 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 .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' '; + $form .= Xml::namespaceSelector( $nameSpace, '' ) . ' '; + $form .= Xml::checkLabel( wfMsg('invert'), 'invert', 'nsinvert', $invert ) . ' '; + $form .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . '</p>'; + $form .= Html::hidden( 'days', $values['days'] ); + foreach ( $filters as $key => $msg ) { + if ( $values[$key] ) { + $form .= Html::hidden( $key, 1 ); + } + } + $form .= Xml::closeElement( 'form' ); + $form .= Xml::closeElement( 'fieldset' ); + $output->addHTML( $form ); + + # If there's nothing to show, stop here + if( $numRows == 0 ) { + $output->addWikiMsg( 'watchnochange' ); + return; + } -function wlHoursLink( $h, $page, $options = array() ) { - global $wgUser, $wgLang, $wgContLang; + /* End bottom header */ - $sk = $wgUser->getSkin(); - $title = Title::newFromText( $wgContLang->specialPage( $page ) ); - $options['days'] = ($h / 24.0); + /* Do link batch query */ + $linkBatch = new LinkBatch; + foreach ( $res as $row ) { + $userNameUnderscored = str_replace( ' ', '_', $row->rc_user_text ); + if ( $row->rc_user != 0 ) { + $linkBatch->add( NS_USER, $userNameUnderscored ); + } + $linkBatch->add( NS_USER_TALK, $userNameUnderscored ); - $s = $sk->linkKnown( - $title, - $wgLang->formatNum( $h ), - array(), - $options - ); + $linkBatch->add( $row->rc_namespace, $row->rc_title ); + } + $linkBatch->execute(); + $dbr->dataSeek( $res, 0 ); + + $list = ChangesList::newFromContext( $this->getContext() ); + $list->setWatchlistDivs(); + + $s = $list->beginRecentChangesList(); + $counter = 1; + foreach ( $res as $obj ) { + # Make RC entry + $rc = RecentChange::newFromRow( $obj ); + $rc->counter = $counter++; + + if ( $wgShowUpdatedMarker ) { + $updated = $obj->wl_notificationtimestamp; + } else { + $updated = false; + } + + if ( $wgRCShowWatchingUsers && $user->getOption( 'shownumberswatching' ) ) { + $rc->numberofWatchingusers = $dbr->selectField( 'watchlist', + 'COUNT(*)', + array( + 'wl_namespace' => $obj->rc_namespace, + 'wl_title' => $obj->rc_title, + ), + __METHOD__ ); + } else { + $rc->numberofWatchingusers = 0; + } + + $s .= $list->recentChangesLine( $rc, $updated, $counter ); + } + $s .= $list->endRecentChangesList(); - return $s; -} + $output->addHTML( $s ); + } -function wlDaysLink( $d, $page, $options = array() ) { - global $wgUser, $wgLang, $wgContLang; + protected function showHideLink( $options, $message, $name, $value ) { + $showLinktext = wfMsgHtml( 'show' ); + $hideLinktext = wfMsgHtml( 'hide' ); - $sk = $wgUser->getSkin(); - $title = Title::newFromText( $wgContLang->specialPage( $page ) ); - $options['days'] = $d; - $message = ($d ? $wgLang->formatNum( $d ) : wfMsgHtml( 'watchlistall2' ) ); + $label = $value ? $showLinktext : $hideLinktext; + $options[$name] = 1 - (int) $value; - $s = $sk->linkKnown( - $title, - $message, - array(), - $options - ); + return wfMsgHtml( $message, Linker::linkKnown( $this->getTitle(), $label, array(), $options ) ); + } - return $s; -} + protected function hoursLink( $h, $options = array() ) { + $options['days'] = ( $h / 24.0 ); -/** - * Returns html - */ -function wlCutoffLinks( $days, $page = 'Watchlist', $options = array() ) { - global $wgLang; - - $hours = array( 1, 2, 6, 12 ); - $days = array( 1, 3, 7 ); - $i = 0; - foreach( $hours as $h ) { - $hours[$i++] = wlHoursLink( $h, $page, $options ); - } - $i = 0; - foreach( $days as $d ) { - $days[$i++] = wlDaysLink( $d, $page, $options ); + return Linker::linkKnown( + $this->getTitle(), + $this->getLang()->formatNum( $h ), + array(), + $options + ); } - return wfMsgExt('wlshowlast', - array('parseinline', 'replaceafter'), - $wgLang->pipeList( $hours ), - $wgLang->pipeList( $days ), - wlDaysLink( 0, $page, $options ) ); -} -/** - * Count the number of items on a user's watchlist - * - * @param $user User object - * @param $talk Boolean: include talk pages - * @return Integer - */ -function wlCountItems( &$user, $talk = true ) { - $dbr = wfGetDB( DB_SLAVE, 'watchlist' ); + protected function daysLink( $d, $options = array() ) { + $options['days'] = $d; + $message = ( $d ? $this->getLang()->formatNum( $d ) : wfMsgHtml( 'watchlistall2' ) ); - # Fetch the raw count - $res = $dbr->select( 'watchlist', 'COUNT(*) AS count', - array( 'wl_user' => $user->mId ), 'wlCountItems' ); - $row = $dbr->fetchObject( $res ); - $count = $row->count; + return Linker::linkKnown( + $this->getTitle(), + $message, + array(), + $options + ); + } - # Halve to remove talk pages if needed - if( !$talk ) - $count = floor( $count / 2 ); + /** + * Returns html + * + * @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 ) { + $hours[$i++] = $this->hoursLink( $h, $options ); + } + $i = 0; + foreach( $days as $d ) { + $days[$i++] = $this->daysLink( $d, $options ); + } + return wfMsgExt('wlshowlast', + array('parseinline', 'replaceafter'), + $this->getLang()->pipeList( $hours ), + $this->getLang()->pipeList( $days ), + $this->daysLink( 0, $options ) ); + } - return( $count ); + /** + * Count the number of items on a user's watchlist + * + * @return Integer + */ + protected function countItems() { + $dbr = wfGetDB( DB_SLAVE, 'watchlist' ); + + # Fetch the raw count + $res = $dbr->select( 'watchlist', 'COUNT(*) AS count', + array( 'wl_user' => $this->getUser()->getId() ), __METHOD__ ); + $row = $dbr->fetchObject( $res ); + $count = $row->count; + + return floor( $count / 2 ); + } } diff --git a/includes/specials/SpecialWhatlinkshere.php b/includes/specials/SpecialWhatlinkshere.php index 360f3f68..5cdaad6a 100644 --- a/includes/specials/SpecialWhatlinkshere.php +++ b/includes/specials/SpecialWhatlinkshere.php @@ -28,23 +28,27 @@ */ class SpecialWhatLinksHere extends SpecialPage { - // Stored objects - protected $opts, $target, $selfTitle; + /** + * @var FormOptions + */ + protected $opts; - // Stored globals - protected $skin; + protected $selfTitle; + + /** + * @var Title + */ + protected $target; protected $limits = array( 20, 50, 100, 250, 500 ); public function __construct() { parent::__construct( 'Whatlinkshere' ); - global $wgUser; - $this->skin = $wgUser->getSkin(); } function execute( $par ) { - global $wgOut, $wgRequest; - + $out = $this->getOutput(); + $this->setHeaders(); $opts = new FormOptions(); @@ -59,7 +63,7 @@ class SpecialWhatLinksHere extends SpecialPage { $opts->add( 'hidelinks', false ); $opts->add( 'hideimages', false ); - $opts->fetchValuesFromRequest( $wgRequest ); + $opts->fetchValuesFromRequest( $this->getRequest() ); $opts->validateIntBounds( 'limit', 0, 5000 ); // Give precedence to subpage syntax @@ -72,29 +76,32 @@ class SpecialWhatLinksHere extends SpecialPage { $this->target = Title::newFromURL( $opts->getValue( 'target' ) ); if( !$this->target ) { - $wgOut->addHTML( $this->whatlinkshereForm() ); + $out->addHTML( $this->whatlinkshereForm() ); return; } + $this->getSkin()->setRelevantTitle( $this->target ); + + $this->selfTitle = $this->getTitle( $this->target->getPrefixedDBkey() ); - $wgOut->setPageTitle( wfMsg( 'whatlinkshere-title', $this->target->getPrefixedText() ) ); - $wgOut->setSubtitle( wfMsg( 'whatlinkshere-backlink', $this->skin->link( $this->target, $this->target->getPrefixedText(), array(), array( 'redirect' => 'no' ) ) ) ); + $out->setPageTitle( wfMsg( 'whatlinkshere-title', $this->target->getPrefixedText() ) ); + $out->setSubtitle( wfMsg( 'whatlinkshere-backlink', Linker::link( $this->target, $this->target->getPrefixedText(), array(), array( 'redirect' => 'no' ) ) ) ); $this->showIndirectLinks( 0, $this->target, $opts->getValue( 'limit' ), $opts->getValue( 'from' ), $opts->getValue( 'back' ) ); } /** - * @param $level int Recursion level + * @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 - * @private + * @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 */ function showIndirectLinks( $level, $target, $limit, $from = 0, $back = 0 ) { - global $wgOut, $wgMaxRedirectLinksRetrieved; + global $wgMaxRedirectLinksRetrieved; + $out = $this->getOutput(); $dbr = wfGetDB( DB_SLAVE ); $options = array(); @@ -171,14 +178,14 @@ class SpecialWhatLinksHere extends SpecialPage { if( ( !$fetchlinks || !$dbr->numRows($plRes) ) && ( $hidetrans || !$dbr->numRows($tlRes) ) && ( $hideimages || !$dbr->numRows($ilRes) ) ) { if ( 0 == $level ) { - $wgOut->addHTML( $this->whatlinkshereForm() ); + $out->addHTML( $this->whatlinkshereForm() ); // Show filters only if there are links if( $hidelinks || $hidetrans || $hideredirs || $hideimages ) - $wgOut->addHTML( $this->getFilterPanel() ); + $out->addHTML( $this->getFilterPanel() ); $errMsg = is_int($namespace) ? 'nolinkshere-ns' : 'nolinkshere'; - $wgOut->addWikiMsg( $errMsg, $this->target->getPrefixedText() ); + $out->addWikiMsg( $errMsg, $this->target->getPrefixedText() ); } return; } @@ -228,31 +235,31 @@ class SpecialWhatLinksHere extends SpecialPage { $prevId = $from; if ( $level == 0 ) { - $wgOut->addHTML( $this->whatlinkshereForm() ); - $wgOut->addHTML( $this->getFilterPanel() ); - $wgOut->addWikiMsg( 'linkshere', $this->target->getPrefixedText() ); + $out->addHTML( $this->whatlinkshereForm() ); + $out->addHTML( $this->getFilterPanel() ); + $out->addWikiMsg( 'linkshere', $this->target->getPrefixedText() ); $prevnext = $this->getPrevNext( $prevId, $nextId ); - $wgOut->addHTML( $prevnext ); + $out->addHTML( $prevnext ); } - $wgOut->addHTML( $this->listStart( $level ) ); + $out->addHTML( $this->listStart( $level ) ); foreach ( $rows as $row ) { $nt = Title::makeTitle( $row->page_namespace, $row->page_title ); if ( $row->page_is_redirect && $level < 2 ) { - $wgOut->addHTML( $this->listItem( $row, $nt, true ) ); + $out->addHTML( $this->listItem( $row, $nt, true ) ); $this->showIndirectLinks( $level + 1, $nt, $wgMaxRedirectLinksRetrieved ); - $wgOut->addHTML( Xml::closeElement( 'li' ) ); + $out->addHTML( Xml::closeElement( 'li' ) ); } else { - $wgOut->addHTML( $this->listItem( $row, $nt ) ); + $out->addHTML( $this->listItem( $row, $nt ) ); } } - $wgOut->addHTML( $this->listEnd() ); + $out->addHTML( $this->listEnd() ); if( $level == 0 ) { - $wgOut->addHTML( $prevnext ); + $out->addHTML( $prevnext ); } } @@ -261,6 +268,9 @@ class SpecialWhatLinksHere extends SpecialPage { } protected function listItem( $row, $nt, $notClose = false ) { + global $wgLang; + $dirmark = $wgLang->getDirMark(); + # local message cache static $msgcache = null; if ( $msgcache === null ) { @@ -278,7 +288,7 @@ class SpecialWhatLinksHere extends SpecialPage { $query = array(); } - $link = $this->skin->linkKnown( + $link = Linker::linkKnown( $nt, null, array(), @@ -304,8 +314,8 @@ class SpecialWhatLinksHere extends SpecialPage { $wlh = Xml::wrapClass( "($wlhLink)", 'mw-whatlinkshere-tools' ); return $notClose ? - Xml::openElement( 'li' ) . "$link $propsText $wlh\n" : - Xml::tags( 'li', null, "$link $propsText $wlh" ) . "\n"; + Xml::openElement( 'li' ) . "$link $propsText $dirmark $wlh\n" : + Xml::tags( 'li', null, "$link $propsText $dirmark $wlh" ) . "\n"; } protected function listEnd() { @@ -317,7 +327,7 @@ class SpecialWhatLinksHere extends SpecialPage { if ( $title === null ) $title = $this->getTitle(); - return $this->skin->linkKnown( + return Linker::linkKnown( $title, $text, array(), @@ -326,7 +336,7 @@ class SpecialWhatLinksHere extends SpecialPage { } function makeSelfLink( $text, $query ) { - return $this->skin->linkKnown( + return Linker::linkKnown( $this->selfTitle, $text, array(), @@ -378,7 +388,7 @@ class SpecialWhatLinksHere extends SpecialPage { # Build up the form $f = Xml::openElement( 'form', array( 'action' => $wgScript ) ); - + # Values that should not be forgotten $f .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ); foreach ( $this->opts->getUnconsumedValues() as $name => $value ) { @@ -410,7 +420,7 @@ class SpecialWhatLinksHere extends SpecialPage { /** * Create filter panel - * + * * @return string HTML fieldset and filter panel with the show/hide links */ function getFilterPanel() { diff --git a/includes/specials/SpecialWithoutinterwiki.php b/includes/specials/SpecialWithoutinterwiki.php index 90c1f441..9d91b833 100644 --- a/includes/specials/SpecialWithoutinterwiki.php +++ b/includes/specials/SpecialWithoutinterwiki.php @@ -30,8 +30,14 @@ class WithoutInterwikiPage extends PageQueryPage { private $prefix = ''; - function getName() { - return 'Withoutinterwiki'; + function __construct( $name = 'Withoutinterwiki' ) { + parent::__construct( $name ); + } + + function execute( $par ) { + global $wgRequest; + $this->prefix = Title::capitalize( $wgRequest->getVal( 'prefix', $par ), NS_MAIN ); + parent::execute( $par ); } function getPageHeader() { @@ -43,9 +49,9 @@ class WithoutInterwikiPage extends PageQueryPage { } $prefix = $this->prefix; - $t = SpecialPage::getTitleFor( $this->getName() ); + $t = $this->getTitle(); - return Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . + return Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . Xml::openElement( 'fieldset' ) . Xml::element( 'legend', null, wfMsg( 'withoutinterwiki-legend' ) ) . Html::hidden( 'title', $t->getPrefixedText() ) . @@ -59,6 +65,10 @@ class WithoutInterwikiPage extends PageQueryPage { return false; } + function getOrderFields() { + return array( 'page_namespace', 'page_title' ); + } + function isExpensive() { return true; } @@ -67,36 +77,22 @@ class WithoutInterwikiPage extends PageQueryPage { return false; } - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); - list( $page, $langlinks ) = $dbr->tableNamesN( 'page', 'langlinks' ); - $prefix = $this->prefix ? 'AND page_title' . $dbr->buildLike( $this->prefix , $dbr->anyString() ) : ''; - return - "SELECT 'Withoutinterwiki' AS type, - page_namespace AS namespace, - page_title AS title, - page_title AS value - FROM $page - LEFT JOIN $langlinks - ON ll_from = page_id - WHERE ll_title IS NULL - AND page_namespace=" . NS_MAIN . " - AND page_is_redirect = 0 - {$prefix}"; - } - - function setPrefix( $prefix = '' ) { - $this->prefix = $prefix; + function getQueryInfo() { + $query = array ( + 'tables' => array ( 'page', 'langlinks' ), + 'fields' => array ( 'page_namespace AS namespace', + 'page_title AS title', + 'page_title AS value' ), + 'conds' => array ( 'll_title IS NULL', + 'page_namespace' => NS_MAIN, + 'page_is_redirect' => 0 ), + 'join_conds' => array ( 'langlinks' => array ( + 'LEFT JOIN', 'll_from = page_id' ) ) + ); + if ( $this->prefix ) { + $dbr = wfGetDB( DB_SLAVE ); + $query['conds'][] = 'page_title ' . $dbr->buildLike( $this->prefix, $dbr->anyString() ); + } + return $query; } - -} - -function wfSpecialWithoutinterwiki() { - global $wgRequest; - list( $limit, $offset ) = wfCheckLimits(); - // Only searching the mainspace anyway - $prefix = Title::capitalize( $wgRequest->getVal( 'prefix' ), NS_MAIN ); - $wip = new WithoutInterwikiPage(); - $wip->setPrefix( $prefix ); - $wip->doQuery( $offset, $limit ); } |